mirror of
https://github.com/microsoft/vscode.git
synced 2026-07-11 05:30:03 -05:00
highlight future replace range, add setting to disable, #10266
This commit is contained in:
@@ -2354,6 +2354,10 @@ export interface ISuggestOptions {
|
||||
* Overwrite word ends on accept. Default to false.
|
||||
*/
|
||||
overwriteOnAccept?: boolean;
|
||||
/**
|
||||
* Should the editor highlight what text suggest will replace.
|
||||
*/
|
||||
highlightReplaceRange?: boolean;
|
||||
/**
|
||||
* Enable graceful matching. Defaults to true.
|
||||
*/
|
||||
@@ -2487,6 +2491,7 @@ class EditorSuggest extends BaseEditorOption<EditorOption.suggest, InternalSugge
|
||||
constructor() {
|
||||
const defaults: InternalSuggestOptions = {
|
||||
overwriteOnAccept: false,
|
||||
highlightReplaceRange: true,
|
||||
filterGraceful: true,
|
||||
snippetsPreventQuickSuggestions: true,
|
||||
localityBonus: false,
|
||||
@@ -2527,6 +2532,11 @@ class EditorSuggest extends BaseEditorOption<EditorOption.suggest, InternalSugge
|
||||
default: defaults.overwriteOnAccept,
|
||||
description: nls.localize('suggest.overwriteOnAccept', "Controls whether words are overwritten when accepting completions.")
|
||||
},
|
||||
'editor.suggest.highlightReplaceRange': {
|
||||
type: 'boolean',
|
||||
default: defaults.highlightReplaceRange,
|
||||
description: nls.localize('suggest.highlightReplaceRange', "Controls whether the editor highlights what text suggestions will replace.")
|
||||
},
|
||||
'editor.suggest.filterGraceful': {
|
||||
type: 'boolean',
|
||||
default: defaults.filterGraceful,
|
||||
@@ -2704,6 +2714,7 @@ class EditorSuggest extends BaseEditorOption<EditorOption.suggest, InternalSugge
|
||||
const input = _input as ISuggestOptions;
|
||||
return {
|
||||
overwriteOnAccept: EditorBooleanOption.boolean(input.overwriteOnAccept, this.defaultValue.overwriteOnAccept),
|
||||
highlightReplaceRange: EditorBooleanOption.boolean(input.highlightReplaceRange, this.defaultValue.highlightReplaceRange),
|
||||
filterGraceful: EditorBooleanOption.boolean(input.filterGraceful, this.defaultValue.filterGraceful),
|
||||
snippetsPreventQuickSuggestions: EditorBooleanOption.boolean(input.snippetsPreventQuickSuggestions, this.defaultValue.filterGraceful),
|
||||
localityBonus: EditorBooleanOption.boolean(input.localityBonus, this.defaultValue.localityBonus),
|
||||
|
||||
@@ -31,12 +31,13 @@ import { WordContextKey } from 'vs/editor/contrib/suggest/wordContextKey';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { IEditorWorkerService } from 'vs/editor/common/services/editorWorkerService';
|
||||
import { IdleValue } from 'vs/base/common/async';
|
||||
import { isObject } from 'vs/base/common/types';
|
||||
import { isObject, assertType } from 'vs/base/common/types';
|
||||
import { CommitCharacterController } from './suggestCommitCharacters';
|
||||
import { IPosition } from 'vs/editor/common/core/position';
|
||||
import { TrackedRangeStickiness, ITextModel } from 'vs/editor/common/model';
|
||||
import { EditorOption } from 'vs/editor/common/config/editorOptions';
|
||||
import * as platform from 'vs/base/common/platform';
|
||||
import { SuggestRangeHighlighter } from 'vs/editor/contrib/suggest/suggestRangeHighlighter';
|
||||
|
||||
/**
|
||||
* Stop suggest widget from disappearing when clicking into other areas
|
||||
@@ -101,33 +102,36 @@ export class SuggestController implements IEditorContribution {
|
||||
return editor.getContribution<SuggestController>(SuggestController.ID);
|
||||
}
|
||||
|
||||
private readonly _model: SuggestModel;
|
||||
private readonly _widget: IdleValue<SuggestWidget>;
|
||||
readonly editor: ICodeEditor;
|
||||
readonly model: SuggestModel;
|
||||
readonly widget: IdleValue<SuggestWidget>;
|
||||
|
||||
private readonly _alternatives: IdleValue<SuggestAlternatives>;
|
||||
private readonly _lineSuffix = new MutableDisposable<LineSuffix>();
|
||||
private readonly _toDispose = new DisposableStore();
|
||||
|
||||
constructor(
|
||||
private _editor: ICodeEditor,
|
||||
editor: ICodeEditor,
|
||||
@IEditorWorkerService editorWorker: IEditorWorkerService,
|
||||
@ISuggestMemoryService private readonly _memoryService: ISuggestMemoryService,
|
||||
@ICommandService private readonly _commandService: ICommandService,
|
||||
@IContextKeyService private readonly _contextKeyService: IContextKeyService,
|
||||
@IInstantiationService private readonly _instantiationService: IInstantiationService,
|
||||
) {
|
||||
this._model = new SuggestModel(this._editor, editorWorker);
|
||||
this.editor = editor;
|
||||
this.model = new SuggestModel(this.editor, editorWorker);
|
||||
|
||||
this._widget = new IdleValue(() => {
|
||||
this.widget = new IdleValue(() => {
|
||||
|
||||
const widget = this._instantiationService.createInstance(SuggestWidget, this._editor);
|
||||
const widget = this._instantiationService.createInstance(SuggestWidget, this.editor);
|
||||
|
||||
this._toDispose.add(widget);
|
||||
this._toDispose.add(widget.onDidSelect(item => this._insertSuggestion(item, 0), this));
|
||||
|
||||
// Wire up logic to accept a suggestion on certain characters
|
||||
const commitCharacterController = new CommitCharacterController(this._editor, widget, item => this._insertSuggestion(item, InsertFlags.NoAfterUndoStop));
|
||||
const commitCharacterController = new CommitCharacterController(this.editor, widget, item => this._insertSuggestion(item, InsertFlags.NoAfterUndoStop));
|
||||
this._toDispose.add(commitCharacterController);
|
||||
this._toDispose.add(this._model.onDidSuggest(e => {
|
||||
this._toDispose.add(this.model.onDidSuggest(e => {
|
||||
if (e.completionModel.items.length === 0) {
|
||||
commitCharacterController.reset();
|
||||
}
|
||||
@@ -137,19 +141,19 @@ export class SuggestController implements IEditorContribution {
|
||||
let makesTextEdit = SuggestContext.MakesTextEdit.bindTo(this._contextKeyService);
|
||||
this._toDispose.add(widget.onDidFocus(({ item }) => {
|
||||
|
||||
const position = this._editor.getPosition()!;
|
||||
const position = this.editor.getPosition()!;
|
||||
const startColumn = item.editStart.column;
|
||||
const endColumn = position.column;
|
||||
let value = true;
|
||||
if (
|
||||
this._editor.getOption(EditorOption.acceptSuggestionOnEnter) === 'smart'
|
||||
&& this._model.state === State.Auto
|
||||
this.editor.getOption(EditorOption.acceptSuggestionOnEnter) === 'smart'
|
||||
&& this.model.state === State.Auto
|
||||
&& !item.completion.command
|
||||
&& !item.completion.additionalTextEdits
|
||||
&& !(item.completion.insertTextRules! & CompletionItemInsertTextRule.InsertAsSnippet)
|
||||
&& endColumn - startColumn === item.completion.insertText.length
|
||||
) {
|
||||
const oldText = this._editor.getModel()!.getValueInRange({
|
||||
const oldText = this.editor.getModel()!.getValueInRange({
|
||||
startLineNumber: position.lineNumber,
|
||||
startColumn,
|
||||
endLineNumber: position.lineNumber,
|
||||
@@ -161,38 +165,40 @@ export class SuggestController implements IEditorContribution {
|
||||
}));
|
||||
this._toDispose.add(toDisposable(() => makesTextEdit.reset()));
|
||||
|
||||
|
||||
|
||||
return widget;
|
||||
});
|
||||
|
||||
this._alternatives = new IdleValue(() => {
|
||||
return this._toDispose.add(new SuggestAlternatives(this._editor, this._contextKeyService));
|
||||
return this._toDispose.add(new SuggestAlternatives(this.editor, this._contextKeyService));
|
||||
});
|
||||
|
||||
this._toDispose.add(_instantiationService.createInstance(WordContextKey, _editor));
|
||||
this._toDispose.add(_instantiationService.createInstance(WordContextKey, editor));
|
||||
|
||||
this._toDispose.add(this._model.onDidTrigger(e => {
|
||||
this._widget.getValue().showTriggered(e.auto, e.shy ? 250 : 50);
|
||||
this._lineSuffix.value = new LineSuffix(this._editor.getModel()!, e.position);
|
||||
this._toDispose.add(this.model.onDidTrigger(e => {
|
||||
this.widget.getValue().showTriggered(e.auto, e.shy ? 250 : 50);
|
||||
this._lineSuffix.value = new LineSuffix(this.editor.getModel()!, e.position);
|
||||
}));
|
||||
this._toDispose.add(this._model.onDidSuggest(e => {
|
||||
this._toDispose.add(this.model.onDidSuggest(e => {
|
||||
if (!e.shy) {
|
||||
let index = this._memoryService.select(this._editor.getModel()!, this._editor.getPosition()!, e.completionModel.items);
|
||||
this._widget.getValue().showSuggestions(e.completionModel, index, e.isFrozen, e.auto);
|
||||
let index = this._memoryService.select(this.editor.getModel()!, this.editor.getPosition()!, e.completionModel.items);
|
||||
this.widget.getValue().showSuggestions(e.completionModel, index, e.isFrozen, e.auto);
|
||||
}
|
||||
}));
|
||||
this._toDispose.add(this._model.onDidCancel(e => {
|
||||
this._toDispose.add(this.model.onDidCancel(e => {
|
||||
if (!e.retrigger) {
|
||||
this._widget.getValue().hideWidget();
|
||||
this.widget.getValue().hideWidget();
|
||||
}
|
||||
}));
|
||||
this._toDispose.add(this._editor.onDidBlurEditorWidget(() => {
|
||||
this._toDispose.add(this.editor.onDidBlurEditorWidget(() => {
|
||||
if (!_sticky) {
|
||||
this._model.cancel();
|
||||
this._model.clear();
|
||||
this.model.cancel();
|
||||
this.model.clear();
|
||||
}
|
||||
}));
|
||||
|
||||
this._toDispose.add(this._widget.getValue().onDetailsKeyDown(e => {
|
||||
this._toDispose.add(this.widget.getValue().onDetailsKeyDown(e => {
|
||||
// cmd + c on macOS, ctrl + c on Win / Linux
|
||||
if (
|
||||
e.toKeybinding().equals(new SimpleKeybinding(true, false, false, false, KeyCode.KEY_C)) ||
|
||||
@@ -203,25 +209,28 @@ export class SuggestController implements IEditorContribution {
|
||||
}
|
||||
|
||||
if (!e.toKeybinding().isModifierKey()) {
|
||||
this._editor.focus();
|
||||
this.editor.focus();
|
||||
}
|
||||
}));
|
||||
|
||||
// Manage the acceptSuggestionsOnEnter context key
|
||||
let acceptSuggestionsOnEnter = SuggestContext.AcceptSuggestionsOnEnter.bindTo(_contextKeyService);
|
||||
let updateFromConfig = () => {
|
||||
const acceptSuggestionOnEnter = this._editor.getOption(EditorOption.acceptSuggestionOnEnter);
|
||||
const acceptSuggestionOnEnter = this.editor.getOption(EditorOption.acceptSuggestionOnEnter);
|
||||
acceptSuggestionsOnEnter.set(acceptSuggestionOnEnter === 'on' || acceptSuggestionOnEnter === 'smart');
|
||||
};
|
||||
this._toDispose.add(this._editor.onDidChangeConfiguration(() => updateFromConfig()));
|
||||
this._toDispose.add(this.editor.onDidChangeConfiguration(() => updateFromConfig()));
|
||||
updateFromConfig();
|
||||
|
||||
// create range highlighter
|
||||
this._toDispose.add(new SuggestRangeHighlighter(this));
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this._alternatives.dispose();
|
||||
this._toDispose.dispose();
|
||||
this._widget.dispose();
|
||||
this._model.dispose();
|
||||
this.widget.dispose();
|
||||
this.model.dispose();
|
||||
this._lineSuffix.dispose();
|
||||
}
|
||||
|
||||
@@ -231,32 +240,31 @@ export class SuggestController implements IEditorContribution {
|
||||
): void {
|
||||
if (!event || !event.item) {
|
||||
this._alternatives.getValue().reset();
|
||||
this._model.cancel();
|
||||
this._model.clear();
|
||||
this.model.cancel();
|
||||
this.model.clear();
|
||||
return;
|
||||
}
|
||||
if (!this._editor.hasModel()) {
|
||||
if (!this.editor.hasModel()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const model = this._editor.getModel();
|
||||
const model = this.editor.getModel();
|
||||
const modelVersionNow = model.getAlternativeVersionId();
|
||||
const { item } = event;
|
||||
const { completion: suggestion, position } = item;
|
||||
const columnDelta = this._editor.getPosition().column - position.column;
|
||||
const { completion: suggestion } = item;
|
||||
|
||||
// pushing undo stops *before* additional text edits and
|
||||
// *after* the main edit
|
||||
if (!(flags & InsertFlags.NoBeforeUndoStop)) {
|
||||
this._editor.pushUndoStop();
|
||||
this.editor.pushUndoStop();
|
||||
}
|
||||
|
||||
if (Array.isArray(suggestion.additionalTextEdits)) {
|
||||
this._editor.executeEdits('suggestController.additionalTextEdits', suggestion.additionalTextEdits.map(edit => EditOperation.replace(Range.lift(edit.range), edit.text)));
|
||||
this.editor.executeEdits('suggestController.additionalTextEdits', suggestion.additionalTextEdits.map(edit => EditOperation.replace(Range.lift(edit.range), edit.text)));
|
||||
}
|
||||
|
||||
// keep item in memory
|
||||
this._memoryService.memorize(model, this._editor.getPosition(), item);
|
||||
this._memoryService.memorize(model, this.editor.getPosition(), item);
|
||||
|
||||
let { insertText } = suggestion;
|
||||
if (!(suggestion.insertTextRules! & CompletionItemInsertTextRule.InsertAsSnippet)) {
|
||||
@@ -264,40 +272,38 @@ export class SuggestController implements IEditorContribution {
|
||||
}
|
||||
|
||||
const overwriteConfig = flags & InsertFlags.AlternativeOverwriteConfig
|
||||
? !this._editor.getOption(EditorOption.suggest).overwriteOnAccept
|
||||
: this._editor.getOption(EditorOption.suggest).overwriteOnAccept;
|
||||
? !this.editor.getOption(EditorOption.suggest).overwriteOnAccept
|
||||
: this.editor.getOption(EditorOption.suggest).overwriteOnAccept;
|
||||
|
||||
const overwriteBefore = position.column - item.editStart.column;
|
||||
const overwriteAfter = (overwriteConfig ? item.editReplaceEnd.column : item.editInsertEnd.column) - position.column;
|
||||
const suffixDelta = this._lineSuffix.value ? this._lineSuffix.value.delta(this._editor.getPosition()) : 0;
|
||||
const info = this.getOverwriteInfo(item, overwriteConfig);
|
||||
|
||||
SnippetController2.get(this._editor).insert(insertText, {
|
||||
overwriteBefore: overwriteBefore + columnDelta,
|
||||
overwriteAfter: overwriteAfter + suffixDelta,
|
||||
SnippetController2.get(this.editor).insert(insertText, {
|
||||
overwriteBefore: info.overwriteBefore,
|
||||
overwriteAfter: info.overwriteAfter,
|
||||
undoStopBefore: false,
|
||||
undoStopAfter: false,
|
||||
adjustWhitespace: !(suggestion.insertTextRules! & CompletionItemInsertTextRule.KeepWhitespace)
|
||||
});
|
||||
|
||||
if (!(flags & InsertFlags.NoAfterUndoStop)) {
|
||||
this._editor.pushUndoStop();
|
||||
this.editor.pushUndoStop();
|
||||
}
|
||||
|
||||
if (!suggestion.command) {
|
||||
// done
|
||||
this._model.cancel();
|
||||
this._model.clear();
|
||||
this.model.cancel();
|
||||
this.model.clear();
|
||||
|
||||
} else if (suggestion.command.id === TriggerSuggestAction.id) {
|
||||
// retigger
|
||||
this._model.trigger({ auto: true, shy: false }, true);
|
||||
this.model.trigger({ auto: true, shy: false }, true);
|
||||
|
||||
} else {
|
||||
// exec command, done
|
||||
this._commandService.executeCommand(suggestion.command.id, ...(suggestion.command.arguments ? [...suggestion.command.arguments] : []))
|
||||
.catch(onUnexpectedError)
|
||||
.finally(() => this._model.clear()); // <- clear only now, keep commands alive
|
||||
this._model.cancel();
|
||||
.finally(() => this.model.clear()); // <- clear only now, keep commands alive
|
||||
this.model.cancel();
|
||||
}
|
||||
|
||||
if (flags & InsertFlags.KeepAlternativeSuggestions) {
|
||||
@@ -321,6 +327,20 @@ export class SuggestController implements IEditorContribution {
|
||||
this._alertCompletionItem(event.item);
|
||||
}
|
||||
|
||||
getOverwriteInfo(item: CompletionItem, overwriteOnAccept: boolean): { overwriteBefore: number, overwriteAfter: number } {
|
||||
assertType(this.editor.hasModel());
|
||||
|
||||
const overwriteBefore = item.position.column - item.editStart.column;
|
||||
const overwriteAfter = (overwriteOnAccept ? item.editReplaceEnd.column : item.editInsertEnd.column) - item.position.column;
|
||||
const columnDelta = this.editor.getPosition().column - item.position.column;
|
||||
const suffixDelta = this._lineSuffix.value ? this._lineSuffix.value.delta(this.editor.getPosition()) : 0;
|
||||
|
||||
return {
|
||||
overwriteBefore: overwriteBefore + columnDelta,
|
||||
overwriteAfter: overwriteAfter + suffixDelta
|
||||
};
|
||||
}
|
||||
|
||||
private _alertCompletionItem({ completion: suggestion }: CompletionItem): void {
|
||||
if (isNonEmptyArray(suggestion.additionalTextEdits)) {
|
||||
let msg = nls.localize('arai.alert.snippet', "Accepting '{0}' made {1} additional edits", suggestion.label, suggestion.additionalTextEdits.length);
|
||||
@@ -329,22 +349,22 @@ export class SuggestController implements IEditorContribution {
|
||||
}
|
||||
|
||||
triggerSuggest(onlyFrom?: Set<CompletionItemProvider>): void {
|
||||
if (this._editor.hasModel()) {
|
||||
this._model.trigger({ auto: false, shy: false }, false, onlyFrom);
|
||||
this._editor.revealLine(this._editor.getPosition().lineNumber, ScrollType.Smooth);
|
||||
this._editor.focus();
|
||||
if (this.editor.hasModel()) {
|
||||
this.model.trigger({ auto: false, shy: false }, false, onlyFrom);
|
||||
this.editor.revealLine(this.editor.getPosition().lineNumber, ScrollType.Smooth);
|
||||
this.editor.focus();
|
||||
}
|
||||
}
|
||||
|
||||
triggerSuggestAndAcceptBest(arg: { fallback: string }): void {
|
||||
if (!this._editor.hasModel()) {
|
||||
if (!this.editor.hasModel()) {
|
||||
return;
|
||||
|
||||
}
|
||||
const positionNow = this._editor.getPosition();
|
||||
const positionNow = this.editor.getPosition();
|
||||
|
||||
const fallback = () => {
|
||||
if (positionNow.equals(this._editor.getPosition()!)) {
|
||||
if (positionNow.equals(this.editor.getPosition()!)) {
|
||||
this._commandService.executeCommand(arg.fallback);
|
||||
}
|
||||
};
|
||||
@@ -354,14 +374,14 @@ export class SuggestController implements IEditorContribution {
|
||||
// snippet, other editor -> makes edit
|
||||
return true;
|
||||
}
|
||||
const position = this._editor.getPosition()!;
|
||||
const position = this.editor.getPosition()!;
|
||||
const startColumn = item.editStart.column;
|
||||
const endColumn = position.column;
|
||||
if (endColumn - startColumn !== item.completion.insertText.length) {
|
||||
// unequal lengths -> makes edit
|
||||
return true;
|
||||
}
|
||||
const textNow = this._editor.getModel()!.getValueInRange({
|
||||
const textNow = this.editor.getModel()!.getValueInRange({
|
||||
startLineNumber: position.lineNumber,
|
||||
startColumn,
|
||||
endLineNumber: position.lineNumber,
|
||||
@@ -371,41 +391,41 @@ export class SuggestController implements IEditorContribution {
|
||||
return textNow !== item.completion.insertText;
|
||||
};
|
||||
|
||||
Event.once(this._model.onDidTrigger)(_ => {
|
||||
Event.once(this.model.onDidTrigger)(_ => {
|
||||
// wait for trigger because only then the cancel-event is trustworthy
|
||||
let listener: IDisposable[] = [];
|
||||
|
||||
Event.any<any>(this._model.onDidTrigger, this._model.onDidCancel)(() => {
|
||||
Event.any<any>(this.model.onDidTrigger, this.model.onDidCancel)(() => {
|
||||
// retrigger or cancel -> try to type default text
|
||||
dispose(listener);
|
||||
fallback();
|
||||
}, undefined, listener);
|
||||
|
||||
this._model.onDidSuggest(({ completionModel }) => {
|
||||
this.model.onDidSuggest(({ completionModel }) => {
|
||||
dispose(listener);
|
||||
if (completionModel.items.length === 0) {
|
||||
fallback();
|
||||
return;
|
||||
}
|
||||
const index = this._memoryService.select(this._editor.getModel()!, this._editor.getPosition()!, completionModel.items);
|
||||
const index = this._memoryService.select(this.editor.getModel()!, this.editor.getPosition()!, completionModel.items);
|
||||
const item = completionModel.items[index];
|
||||
if (!makesTextEdit(item)) {
|
||||
fallback();
|
||||
return;
|
||||
}
|
||||
this._editor.pushUndoStop();
|
||||
this.editor.pushUndoStop();
|
||||
this._insertSuggestion({ index, item, model: completionModel }, InsertFlags.KeepAlternativeSuggestions | InsertFlags.NoBeforeUndoStop | InsertFlags.NoAfterUndoStop);
|
||||
|
||||
}, undefined, listener);
|
||||
});
|
||||
|
||||
this._model.trigger({ auto: false, shy: true });
|
||||
this._editor.revealLine(positionNow.lineNumber, ScrollType.Smooth);
|
||||
this._editor.focus();
|
||||
this.model.trigger({ auto: false, shy: true });
|
||||
this.editor.revealLine(positionNow.lineNumber, ScrollType.Smooth);
|
||||
this.editor.focus();
|
||||
}
|
||||
|
||||
acceptSelectedSuggestion(keepAlternativeSuggestions: boolean, alternativeOverwriteConfig: boolean): void {
|
||||
const item = this._widget.getValue().getFocusedItem();
|
||||
const item = this.widget.getValue().getFocusedItem();
|
||||
let flags = 0;
|
||||
if (keepAlternativeSuggestions) {
|
||||
flags |= InsertFlags.KeepAlternativeSuggestions;
|
||||
@@ -425,45 +445,45 @@ export class SuggestController implements IEditorContribution {
|
||||
}
|
||||
|
||||
cancelSuggestWidget(): void {
|
||||
this._model.cancel();
|
||||
this._model.clear();
|
||||
this._widget.getValue().hideWidget();
|
||||
this.model.cancel();
|
||||
this.model.clear();
|
||||
this.widget.getValue().hideWidget();
|
||||
}
|
||||
|
||||
selectNextSuggestion(): void {
|
||||
this._widget.getValue().selectNext();
|
||||
this.widget.getValue().selectNext();
|
||||
}
|
||||
|
||||
selectNextPageSuggestion(): void {
|
||||
this._widget.getValue().selectNextPage();
|
||||
this.widget.getValue().selectNextPage();
|
||||
}
|
||||
|
||||
selectLastSuggestion(): void {
|
||||
this._widget.getValue().selectLast();
|
||||
this.widget.getValue().selectLast();
|
||||
}
|
||||
|
||||
selectPrevSuggestion(): void {
|
||||
this._widget.getValue().selectPrevious();
|
||||
this.widget.getValue().selectPrevious();
|
||||
}
|
||||
|
||||
selectPrevPageSuggestion(): void {
|
||||
this._widget.getValue().selectPreviousPage();
|
||||
this.widget.getValue().selectPreviousPage();
|
||||
}
|
||||
|
||||
selectFirstSuggestion(): void {
|
||||
this._widget.getValue().selectFirst();
|
||||
this.widget.getValue().selectFirst();
|
||||
}
|
||||
|
||||
toggleSuggestionDetails(): void {
|
||||
this._widget.getValue().toggleDetails();
|
||||
this.widget.getValue().toggleDetails();
|
||||
}
|
||||
|
||||
toggleExplainMode(): void {
|
||||
this._widget.getValue().toggleExplainMode();
|
||||
this.widget.getValue().toggleExplainMode();
|
||||
}
|
||||
|
||||
toggleSuggestionFocus(): void {
|
||||
this._widget.getValue().toggleDetailsFocus();
|
||||
this.widget.getValue().toggleDetailsFocus();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
98
src/vs/editor/contrib/suggest/suggestRangeHighlighter.ts
Normal file
98
src/vs/editor/contrib/suggest/suggestRangeHighlighter.ts
Normal file
@@ -0,0 +1,98 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { registerThemingParticipant } from 'vs/platform/theme/common/themeService';
|
||||
import { editorSelectionBackground, registerColor, editorSelectionHighlightBorder } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { EditorOption } from 'vs/editor/common/config/editorOptions';
|
||||
import { CompletionItem } from 'vs/editor/contrib/suggest/suggest';
|
||||
import { TrackedRangeStickiness } from 'vs/editor/common/model';
|
||||
import { SuggestController } from 'vs/editor/contrib/suggest/suggestController';
|
||||
import { localize } from 'vs/nls';
|
||||
|
||||
|
||||
const suggestReplaceBackgroundColor = registerColor(
|
||||
'editor.suggestReplaceBackground',
|
||||
{ light: editorSelectionBackground, dark: editorSelectionBackground, hc: editorSelectionBackground },
|
||||
localize('suggestReplaceBackground', "Background color of text that suggest will replace.")
|
||||
);
|
||||
|
||||
const suggestReplaceBorderColor = registerColor(
|
||||
'editor.suggestReplaceBorder',
|
||||
{ light: null, dark: null, hc: editorSelectionHighlightBorder },
|
||||
localize('suggestReplaceBorder', "Border color of text that suggest will replace.")
|
||||
);
|
||||
|
||||
registerThemingParticipant((theme, collector) => {
|
||||
const suggestReplaceBackground = theme.getColor(suggestReplaceBackgroundColor);
|
||||
if (suggestReplaceBackground) {
|
||||
collector.addRule(`.monaco-editor .suggestReplace { background-color: ${suggestReplaceBackground}; }`);
|
||||
}
|
||||
const suggestReplaceBorder = theme.getColor(suggestReplaceBorderColor);
|
||||
if (suggestReplaceBorder) {
|
||||
collector.addRule(`.monaco-editor .suggestReplace { border: 1px ${theme.type === 'hc' ? 'dotted' : 'solid'} ${suggestReplaceBorder}; }`);
|
||||
}
|
||||
});
|
||||
|
||||
export class SuggestRangeHighlighter {
|
||||
|
||||
private readonly _disposables = new DisposableStore();
|
||||
|
||||
private _decorations: string[] = [];
|
||||
private _hasWidgetListener: boolean = false;
|
||||
|
||||
constructor(private readonly _controller: SuggestController) {
|
||||
|
||||
this._disposables.add(_controller.model.onDidSuggest(e => {
|
||||
if (!e.shy) {
|
||||
const widget = this._controller.widget.getValue();
|
||||
const focused = widget.getFocusedItem();
|
||||
if (focused) {
|
||||
this._highlight(focused.item);
|
||||
}
|
||||
|
||||
if (!this._hasWidgetListener) {
|
||||
this._hasWidgetListener = true;
|
||||
widget.onDidFocus(e => this._highlight(e.item), undefined, this._disposables);
|
||||
}
|
||||
}
|
||||
}));
|
||||
this._disposables.add(_controller.model.onDidCancel(() => {
|
||||
this._reset();
|
||||
}));
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this._reset();
|
||||
this._disposables.dispose();
|
||||
}
|
||||
|
||||
private _reset(): void {
|
||||
this._decorations = this._controller.editor.deltaDecorations(this._decorations, []);
|
||||
}
|
||||
|
||||
private _highlight(item: CompletionItem) {
|
||||
|
||||
const { overwriteOnAccept, highlightReplaceRange } = this._controller.editor.getOption(EditorOption.suggest);
|
||||
|
||||
if (highlightReplaceRange) {
|
||||
const info = this._controller.getOverwriteInfo(item, overwriteOnAccept);
|
||||
const position = this._controller.editor.getPosition()!;
|
||||
const range = new Range(
|
||||
position.lineNumber, position.column - info.overwriteBefore,
|
||||
position.lineNumber, position.column + info.overwriteAfter
|
||||
);
|
||||
|
||||
this._decorations = this._controller.editor.deltaDecorations(this._decorations, [{
|
||||
range,
|
||||
options: {
|
||||
className: 'suggestReplace',
|
||||
stickiness: TrackedRangeStickiness.AlwaysGrowsWhenTypingAtEdges
|
||||
}
|
||||
}]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -35,6 +35,7 @@ suite('CompletionModel', function () {
|
||||
|
||||
let defaultOptions = {
|
||||
overwriteOnAccept: false,
|
||||
highlightReplaceRange: true,
|
||||
snippetsPreventQuickSuggestions: true,
|
||||
filterGraceful: true,
|
||||
localityBonus: false,
|
||||
|
||||
4
src/vs/monaco.d.ts
vendored
4
src/vs/monaco.d.ts
vendored
@@ -3397,6 +3397,10 @@ declare namespace monaco.editor {
|
||||
* Overwrite word ends on accept. Default to false.
|
||||
*/
|
||||
overwriteOnAccept?: boolean;
|
||||
/**
|
||||
* Should the editor highlight what text suggest will replace.
|
||||
*/
|
||||
highlightReplaceRange?: boolean;
|
||||
/**
|
||||
* Enable graceful matching. Defaults to true.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user