提交 0f6df630 编写于 作者: J Johannes Rieken

highlight future replace range, add setting to disable, #10266

上级 6be5444b
...@@ -2354,6 +2354,10 @@ export interface ISuggestOptions { ...@@ -2354,6 +2354,10 @@ export interface ISuggestOptions {
* Overwrite word ends on accept. Default to false. * Overwrite word ends on accept. Default to false.
*/ */
overwriteOnAccept?: boolean; overwriteOnAccept?: boolean;
/**
* Should the editor highlight what text suggest will replace.
*/
highlightReplaceRange?: boolean;
/** /**
* Enable graceful matching. Defaults to true. * Enable graceful matching. Defaults to true.
*/ */
...@@ -2487,6 +2491,7 @@ class EditorSuggest extends BaseEditorOption<EditorOption.suggest, InternalSugge ...@@ -2487,6 +2491,7 @@ class EditorSuggest extends BaseEditorOption<EditorOption.suggest, InternalSugge
constructor() { constructor() {
const defaults: InternalSuggestOptions = { const defaults: InternalSuggestOptions = {
overwriteOnAccept: false, overwriteOnAccept: false,
highlightReplaceRange: true,
filterGraceful: true, filterGraceful: true,
snippetsPreventQuickSuggestions: true, snippetsPreventQuickSuggestions: true,
localityBonus: false, localityBonus: false,
...@@ -2527,6 +2532,11 @@ class EditorSuggest extends BaseEditorOption<EditorOption.suggest, InternalSugge ...@@ -2527,6 +2532,11 @@ class EditorSuggest extends BaseEditorOption<EditorOption.suggest, InternalSugge
default: defaults.overwriteOnAccept, default: defaults.overwriteOnAccept,
description: nls.localize('suggest.overwriteOnAccept', "Controls whether words are overwritten when accepting completions.") 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': { 'editor.suggest.filterGraceful': {
type: 'boolean', type: 'boolean',
default: defaults.filterGraceful, default: defaults.filterGraceful,
...@@ -2704,6 +2714,7 @@ class EditorSuggest extends BaseEditorOption<EditorOption.suggest, InternalSugge ...@@ -2704,6 +2714,7 @@ class EditorSuggest extends BaseEditorOption<EditorOption.suggest, InternalSugge
const input = _input as ISuggestOptions; const input = _input as ISuggestOptions;
return { return {
overwriteOnAccept: EditorBooleanOption.boolean(input.overwriteOnAccept, this.defaultValue.overwriteOnAccept), overwriteOnAccept: EditorBooleanOption.boolean(input.overwriteOnAccept, this.defaultValue.overwriteOnAccept),
highlightReplaceRange: EditorBooleanOption.boolean(input.highlightReplaceRange, this.defaultValue.highlightReplaceRange),
filterGraceful: EditorBooleanOption.boolean(input.filterGraceful, this.defaultValue.filterGraceful), filterGraceful: EditorBooleanOption.boolean(input.filterGraceful, this.defaultValue.filterGraceful),
snippetsPreventQuickSuggestions: EditorBooleanOption.boolean(input.snippetsPreventQuickSuggestions, this.defaultValue.filterGraceful), snippetsPreventQuickSuggestions: EditorBooleanOption.boolean(input.snippetsPreventQuickSuggestions, this.defaultValue.filterGraceful),
localityBonus: EditorBooleanOption.boolean(input.localityBonus, this.defaultValue.localityBonus), localityBonus: EditorBooleanOption.boolean(input.localityBonus, this.defaultValue.localityBonus),
......
...@@ -31,12 +31,13 @@ import { WordContextKey } from 'vs/editor/contrib/suggest/wordContextKey'; ...@@ -31,12 +31,13 @@ import { WordContextKey } from 'vs/editor/contrib/suggest/wordContextKey';
import { Event } from 'vs/base/common/event'; import { Event } from 'vs/base/common/event';
import { IEditorWorkerService } from 'vs/editor/common/services/editorWorkerService'; import { IEditorWorkerService } from 'vs/editor/common/services/editorWorkerService';
import { IdleValue } from 'vs/base/common/async'; 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 { CommitCharacterController } from './suggestCommitCharacters';
import { IPosition } from 'vs/editor/common/core/position'; import { IPosition } from 'vs/editor/common/core/position';
import { TrackedRangeStickiness, ITextModel } from 'vs/editor/common/model'; import { TrackedRangeStickiness, ITextModel } from 'vs/editor/common/model';
import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { EditorOption } from 'vs/editor/common/config/editorOptions';
import * as platform from 'vs/base/common/platform'; 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 * Stop suggest widget from disappearing when clicking into other areas
...@@ -101,33 +102,36 @@ export class SuggestController implements IEditorContribution { ...@@ -101,33 +102,36 @@ export class SuggestController implements IEditorContribution {
return editor.getContribution<SuggestController>(SuggestController.ID); return editor.getContribution<SuggestController>(SuggestController.ID);
} }
private readonly _model: SuggestModel; readonly editor: ICodeEditor;
private readonly _widget: IdleValue<SuggestWidget>; readonly model: SuggestModel;
readonly widget: IdleValue<SuggestWidget>;
private readonly _alternatives: IdleValue<SuggestAlternatives>; private readonly _alternatives: IdleValue<SuggestAlternatives>;
private readonly _lineSuffix = new MutableDisposable<LineSuffix>(); private readonly _lineSuffix = new MutableDisposable<LineSuffix>();
private readonly _toDispose = new DisposableStore(); private readonly _toDispose = new DisposableStore();
constructor( constructor(
private _editor: ICodeEditor, editor: ICodeEditor,
@IEditorWorkerService editorWorker: IEditorWorkerService, @IEditorWorkerService editorWorker: IEditorWorkerService,
@ISuggestMemoryService private readonly _memoryService: ISuggestMemoryService, @ISuggestMemoryService private readonly _memoryService: ISuggestMemoryService,
@ICommandService private readonly _commandService: ICommandService, @ICommandService private readonly _commandService: ICommandService,
@IContextKeyService private readonly _contextKeyService: IContextKeyService, @IContextKeyService private readonly _contextKeyService: IContextKeyService,
@IInstantiationService private readonly _instantiationService: IInstantiationService, @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);
this._toDispose.add(widget.onDidSelect(item => this._insertSuggestion(item, 0), this)); this._toDispose.add(widget.onDidSelect(item => this._insertSuggestion(item, 0), this));
// Wire up logic to accept a suggestion on certain characters // 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(commitCharacterController);
this._toDispose.add(this._model.onDidSuggest(e => { this._toDispose.add(this.model.onDidSuggest(e => {
if (e.completionModel.items.length === 0) { if (e.completionModel.items.length === 0) {
commitCharacterController.reset(); commitCharacterController.reset();
} }
...@@ -137,19 +141,19 @@ export class SuggestController implements IEditorContribution { ...@@ -137,19 +141,19 @@ export class SuggestController implements IEditorContribution {
let makesTextEdit = SuggestContext.MakesTextEdit.bindTo(this._contextKeyService); let makesTextEdit = SuggestContext.MakesTextEdit.bindTo(this._contextKeyService);
this._toDispose.add(widget.onDidFocus(({ item }) => { this._toDispose.add(widget.onDidFocus(({ item }) => {
const position = this._editor.getPosition()!; const position = this.editor.getPosition()!;
const startColumn = item.editStart.column; const startColumn = item.editStart.column;
const endColumn = position.column; const endColumn = position.column;
let value = true; let value = true;
if ( if (
this._editor.getOption(EditorOption.acceptSuggestionOnEnter) === 'smart' this.editor.getOption(EditorOption.acceptSuggestionOnEnter) === 'smart'
&& this._model.state === State.Auto && this.model.state === State.Auto
&& !item.completion.command && !item.completion.command
&& !item.completion.additionalTextEdits && !item.completion.additionalTextEdits
&& !(item.completion.insertTextRules! & CompletionItemInsertTextRule.InsertAsSnippet) && !(item.completion.insertTextRules! & CompletionItemInsertTextRule.InsertAsSnippet)
&& endColumn - startColumn === item.completion.insertText.length && endColumn - startColumn === item.completion.insertText.length
) { ) {
const oldText = this._editor.getModel()!.getValueInRange({ const oldText = this.editor.getModel()!.getValueInRange({
startLineNumber: position.lineNumber, startLineNumber: position.lineNumber,
startColumn, startColumn,
endLineNumber: position.lineNumber, endLineNumber: position.lineNumber,
...@@ -161,38 +165,40 @@ export class SuggestController implements IEditorContribution { ...@@ -161,38 +165,40 @@ export class SuggestController implements IEditorContribution {
})); }));
this._toDispose.add(toDisposable(() => makesTextEdit.reset())); this._toDispose.add(toDisposable(() => makesTextEdit.reset()));
return widget; return widget;
}); });
this._alternatives = new IdleValue(() => { 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._toDispose.add(this.model.onDidTrigger(e => {
this._widget.getValue().showTriggered(e.auto, e.shy ? 250 : 50); this.widget.getValue().showTriggered(e.auto, e.shy ? 250 : 50);
this._lineSuffix.value = new LineSuffix(this._editor.getModel()!, e.position); 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) { if (!e.shy) {
let index = this._memoryService.select(this._editor.getModel()!, this._editor.getPosition()!, e.completionModel.items); 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.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) { 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) { if (!_sticky) {
this._model.cancel(); this.model.cancel();
this._model.clear(); 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 // cmd + c on macOS, ctrl + c on Win / Linux
if ( if (
e.toKeybinding().equals(new SimpleKeybinding(true, false, false, false, KeyCode.KEY_C)) || e.toKeybinding().equals(new SimpleKeybinding(true, false, false, false, KeyCode.KEY_C)) ||
...@@ -203,25 +209,28 @@ export class SuggestController implements IEditorContribution { ...@@ -203,25 +209,28 @@ export class SuggestController implements IEditorContribution {
} }
if (!e.toKeybinding().isModifierKey()) { if (!e.toKeybinding().isModifierKey()) {
this._editor.focus(); this.editor.focus();
} }
})); }));
// Manage the acceptSuggestionsOnEnter context key // Manage the acceptSuggestionsOnEnter context key
let acceptSuggestionsOnEnter = SuggestContext.AcceptSuggestionsOnEnter.bindTo(_contextKeyService); let acceptSuggestionsOnEnter = SuggestContext.AcceptSuggestionsOnEnter.bindTo(_contextKeyService);
let updateFromConfig = () => { let updateFromConfig = () => {
const acceptSuggestionOnEnter = this._editor.getOption(EditorOption.acceptSuggestionOnEnter); const acceptSuggestionOnEnter = this.editor.getOption(EditorOption.acceptSuggestionOnEnter);
acceptSuggestionsOnEnter.set(acceptSuggestionOnEnter === 'on' || acceptSuggestionOnEnter === 'smart'); acceptSuggestionsOnEnter.set(acceptSuggestionOnEnter === 'on' || acceptSuggestionOnEnter === 'smart');
}; };
this._toDispose.add(this._editor.onDidChangeConfiguration(() => updateFromConfig())); this._toDispose.add(this.editor.onDidChangeConfiguration(() => updateFromConfig()));
updateFromConfig(); updateFromConfig();
// create range highlighter
this._toDispose.add(new SuggestRangeHighlighter(this));
} }
dispose(): void { dispose(): void {
this._alternatives.dispose(); this._alternatives.dispose();
this._toDispose.dispose(); this._toDispose.dispose();
this._widget.dispose(); this.widget.dispose();
this._model.dispose(); this.model.dispose();
this._lineSuffix.dispose(); this._lineSuffix.dispose();
} }
...@@ -231,32 +240,31 @@ export class SuggestController implements IEditorContribution { ...@@ -231,32 +240,31 @@ export class SuggestController implements IEditorContribution {
): void { ): void {
if (!event || !event.item) { if (!event || !event.item) {
this._alternatives.getValue().reset(); this._alternatives.getValue().reset();
this._model.cancel(); this.model.cancel();
this._model.clear(); this.model.clear();
return; return;
} }
if (!this._editor.hasModel()) { if (!this.editor.hasModel()) {
return; return;
} }
const model = this._editor.getModel(); const model = this.editor.getModel();
const modelVersionNow = model.getAlternativeVersionId(); const modelVersionNow = model.getAlternativeVersionId();
const { item } = event; const { item } = event;
const { completion: suggestion, position } = item; const { completion: suggestion } = item;
const columnDelta = this._editor.getPosition().column - position.column;
// pushing undo stops *before* additional text edits and // pushing undo stops *before* additional text edits and
// *after* the main edit // *after* the main edit
if (!(flags & InsertFlags.NoBeforeUndoStop)) { if (!(flags & InsertFlags.NoBeforeUndoStop)) {
this._editor.pushUndoStop(); this.editor.pushUndoStop();
} }
if (Array.isArray(suggestion.additionalTextEdits)) { 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 // keep item in memory
this._memoryService.memorize(model, this._editor.getPosition(), item); this._memoryService.memorize(model, this.editor.getPosition(), item);
let { insertText } = suggestion; let { insertText } = suggestion;
if (!(suggestion.insertTextRules! & CompletionItemInsertTextRule.InsertAsSnippet)) { if (!(suggestion.insertTextRules! & CompletionItemInsertTextRule.InsertAsSnippet)) {
...@@ -264,40 +272,38 @@ export class SuggestController implements IEditorContribution { ...@@ -264,40 +272,38 @@ export class SuggestController implements IEditorContribution {
} }
const overwriteConfig = flags & InsertFlags.AlternativeOverwriteConfig 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 info = this.getOverwriteInfo(item, overwriteConfig);
const overwriteAfter = (overwriteConfig ? item.editReplaceEnd.column : item.editInsertEnd.column) - position.column;
const suffixDelta = this._lineSuffix.value ? this._lineSuffix.value.delta(this._editor.getPosition()) : 0;
SnippetController2.get(this._editor).insert(insertText, { SnippetController2.get(this.editor).insert(insertText, {
overwriteBefore: overwriteBefore + columnDelta, overwriteBefore: info.overwriteBefore,
overwriteAfter: overwriteAfter + suffixDelta, overwriteAfter: info.overwriteAfter,
undoStopBefore: false, undoStopBefore: false,
undoStopAfter: false, undoStopAfter: false,
adjustWhitespace: !(suggestion.insertTextRules! & CompletionItemInsertTextRule.KeepWhitespace) adjustWhitespace: !(suggestion.insertTextRules! & CompletionItemInsertTextRule.KeepWhitespace)
}); });
if (!(flags & InsertFlags.NoAfterUndoStop)) { if (!(flags & InsertFlags.NoAfterUndoStop)) {
this._editor.pushUndoStop(); this.editor.pushUndoStop();
} }
if (!suggestion.command) { if (!suggestion.command) {
// done // done
this._model.cancel(); this.model.cancel();
this._model.clear(); this.model.clear();
} else if (suggestion.command.id === TriggerSuggestAction.id) { } else if (suggestion.command.id === TriggerSuggestAction.id) {
// retigger // retigger
this._model.trigger({ auto: true, shy: false }, true); this.model.trigger({ auto: true, shy: false }, true);
} else { } else {
// exec command, done // exec command, done
this._commandService.executeCommand(suggestion.command.id, ...(suggestion.command.arguments ? [...suggestion.command.arguments] : [])) this._commandService.executeCommand(suggestion.command.id, ...(suggestion.command.arguments ? [...suggestion.command.arguments] : []))
.catch(onUnexpectedError) .catch(onUnexpectedError)
.finally(() => this._model.clear()); // <- clear only now, keep commands alive .finally(() => this.model.clear()); // <- clear only now, keep commands alive
this._model.cancel(); this.model.cancel();
} }
if (flags & InsertFlags.KeepAlternativeSuggestions) { if (flags & InsertFlags.KeepAlternativeSuggestions) {
...@@ -321,6 +327,20 @@ export class SuggestController implements IEditorContribution { ...@@ -321,6 +327,20 @@ export class SuggestController implements IEditorContribution {
this._alertCompletionItem(event.item); 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 { private _alertCompletionItem({ completion: suggestion }: CompletionItem): void {
if (isNonEmptyArray(suggestion.additionalTextEdits)) { if (isNonEmptyArray(suggestion.additionalTextEdits)) {
let msg = nls.localize('arai.alert.snippet', "Accepting '{0}' made {1} additional edits", suggestion.label, suggestion.additionalTextEdits.length); 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 { ...@@ -329,22 +349,22 @@ export class SuggestController implements IEditorContribution {
} }
triggerSuggest(onlyFrom?: Set<CompletionItemProvider>): void { triggerSuggest(onlyFrom?: Set<CompletionItemProvider>): void {
if (this._editor.hasModel()) { if (this.editor.hasModel()) {
this._model.trigger({ auto: false, shy: false }, false, onlyFrom); this.model.trigger({ auto: false, shy: false }, false, onlyFrom);
this._editor.revealLine(this._editor.getPosition().lineNumber, ScrollType.Smooth); this.editor.revealLine(this.editor.getPosition().lineNumber, ScrollType.Smooth);
this._editor.focus(); this.editor.focus();
} }
} }
triggerSuggestAndAcceptBest(arg: { fallback: string }): void { triggerSuggestAndAcceptBest(arg: { fallback: string }): void {
if (!this._editor.hasModel()) { if (!this.editor.hasModel()) {
return; return;
} }
const positionNow = this._editor.getPosition(); const positionNow = this.editor.getPosition();
const fallback = () => { const fallback = () => {
if (positionNow.equals(this._editor.getPosition()!)) { if (positionNow.equals(this.editor.getPosition()!)) {
this._commandService.executeCommand(arg.fallback); this._commandService.executeCommand(arg.fallback);
} }
}; };
...@@ -354,14 +374,14 @@ export class SuggestController implements IEditorContribution { ...@@ -354,14 +374,14 @@ export class SuggestController implements IEditorContribution {
// snippet, other editor -> makes edit // snippet, other editor -> makes edit
return true; return true;
} }
const position = this._editor.getPosition()!; const position = this.editor.getPosition()!;
const startColumn = item.editStart.column; const startColumn = item.editStart.column;
const endColumn = position.column; const endColumn = position.column;
if (endColumn - startColumn !== item.completion.insertText.length) { if (endColumn - startColumn !== item.completion.insertText.length) {
// unequal lengths -> makes edit // unequal lengths -> makes edit
return true; return true;
} }
const textNow = this._editor.getModel()!.getValueInRange({ const textNow = this.editor.getModel()!.getValueInRange({
startLineNumber: position.lineNumber, startLineNumber: position.lineNumber,
startColumn, startColumn,
endLineNumber: position.lineNumber, endLineNumber: position.lineNumber,
...@@ -371,41 +391,41 @@ export class SuggestController implements IEditorContribution { ...@@ -371,41 +391,41 @@ export class SuggestController implements IEditorContribution {
return textNow !== item.completion.insertText; 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 // wait for trigger because only then the cancel-event is trustworthy
let listener: IDisposable[] = []; 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 // retrigger or cancel -> try to type default text
dispose(listener); dispose(listener);
fallback(); fallback();
}, undefined, listener); }, undefined, listener);
this._model.onDidSuggest(({ completionModel }) => { this.model.onDidSuggest(({ completionModel }) => {
dispose(listener); dispose(listener);
if (completionModel.items.length === 0) { if (completionModel.items.length === 0) {
fallback(); fallback();
return; 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]; const item = completionModel.items[index];
if (!makesTextEdit(item)) { if (!makesTextEdit(item)) {
fallback(); fallback();
return; return;
} }
this._editor.pushUndoStop(); this.editor.pushUndoStop();
this._insertSuggestion({ index, item, model: completionModel }, InsertFlags.KeepAlternativeSuggestions | InsertFlags.NoBeforeUndoStop | InsertFlags.NoAfterUndoStop); this._insertSuggestion({ index, item, model: completionModel }, InsertFlags.KeepAlternativeSuggestions | InsertFlags.NoBeforeUndoStop | InsertFlags.NoAfterUndoStop);
}, undefined, listener); }, undefined, listener);
}); });
this._model.trigger({ auto: false, shy: true }); this.model.trigger({ auto: false, shy: true });
this._editor.revealLine(positionNow.lineNumber, ScrollType.Smooth); this.editor.revealLine(positionNow.lineNumber, ScrollType.Smooth);
this._editor.focus(); this.editor.focus();
} }
acceptSelectedSuggestion(keepAlternativeSuggestions: boolean, alternativeOverwriteConfig: boolean): void { acceptSelectedSuggestion(keepAlternativeSuggestions: boolean, alternativeOverwriteConfig: boolean): void {
const item = this._widget.getValue().getFocusedItem(); const item = this.widget.getValue().getFocusedItem();
let flags = 0; let flags = 0;
if (keepAlternativeSuggestions) { if (keepAlternativeSuggestions) {
flags |= InsertFlags.KeepAlternativeSuggestions; flags |= InsertFlags.KeepAlternativeSuggestions;
...@@ -425,45 +445,45 @@ export class SuggestController implements IEditorContribution { ...@@ -425,45 +445,45 @@ export class SuggestController implements IEditorContribution {
} }
cancelSuggestWidget(): void { cancelSuggestWidget(): void {
this._model.cancel(); this.model.cancel();
this._model.clear(); this.model.clear();
this._widget.getValue().hideWidget(); this.widget.getValue().hideWidget();
} }
selectNextSuggestion(): void { selectNextSuggestion(): void {
this._widget.getValue().selectNext(); this.widget.getValue().selectNext();
} }
selectNextPageSuggestion(): void { selectNextPageSuggestion(): void {
this._widget.getValue().selectNextPage(); this.widget.getValue().selectNextPage();
} }
selectLastSuggestion(): void { selectLastSuggestion(): void {
this._widget.getValue().selectLast(); this.widget.getValue().selectLast();
} }
selectPrevSuggestion(): void { selectPrevSuggestion(): void {
this._widget.getValue().selectPrevious(); this.widget.getValue().selectPrevious();
} }
selectPrevPageSuggestion(): void { selectPrevPageSuggestion(): void {
this._widget.getValue().selectPreviousPage(); this.widget.getValue().selectPreviousPage();
} }
selectFirstSuggestion(): void { selectFirstSuggestion(): void {
this._widget.getValue().selectFirst(); this.widget.getValue().selectFirst();
} }
toggleSuggestionDetails(): void { toggleSuggestionDetails(): void {
this._widget.getValue().toggleDetails(); this.widget.getValue().toggleDetails();
} }
toggleExplainMode(): void { toggleExplainMode(): void {
this._widget.getValue().toggleExplainMode(); this.widget.getValue().toggleExplainMode();
} }
toggleSuggestionFocus(): void { toggleSuggestionFocus(): void {
this._widget.getValue().toggleDetailsFocus(); this.widget.getValue().toggleDetailsFocus();
} }
} }
......
/*---------------------------------------------------------------------------------------------
* 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 () { ...@@ -35,6 +35,7 @@ suite('CompletionModel', function () {
let defaultOptions = { let defaultOptions = {
overwriteOnAccept: false, overwriteOnAccept: false,
highlightReplaceRange: true,
snippetsPreventQuickSuggestions: true, snippetsPreventQuickSuggestions: true,
filterGraceful: true, filterGraceful: true,
localityBonus: false, localityBonus: false,
......
...@@ -3397,6 +3397,10 @@ declare namespace monaco.editor { ...@@ -3397,6 +3397,10 @@ declare namespace monaco.editor {
* Overwrite word ends on accept. Default to false. * Overwrite word ends on accept. Default to false.
*/ */
overwriteOnAccept?: boolean; overwriteOnAccept?: boolean;
/**
* Should the editor highlight what text suggest will replace.
*/
highlightReplaceRange?: boolean;
/** /**
* Enable graceful matching. Defaults to true. * Enable graceful matching. Defaults to true.
*/ */
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册