Skip to content

Commit

Permalink
change setting to editor.suggest.insertMode, highlight in editor wh…
Browse files Browse the repository at this point in the history
…en suggestion doesn't what you expect, #10266
  • Loading branch information
jrieken committed Nov 20, 2019
1 parent 0f6df63 commit 0ea4167
Show file tree
Hide file tree
Showing 6 changed files with 110 additions and 85 deletions.
30 changes: 12 additions & 18 deletions src/vs/editor/common/config/editorOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2353,11 +2353,7 @@ export interface ISuggestOptions {
/**
* Overwrite word ends on accept. Default to false.
*/
overwriteOnAccept?: boolean;
/**
* Should the editor highlight what text suggest will replace.
*/
highlightReplaceRange?: boolean;
insertMode?: 'insert' | 'replace';
/**
* Enable graceful matching. Defaults to true.
*/
Expand Down Expand Up @@ -2490,8 +2486,7 @@ class EditorSuggest extends BaseEditorOption<EditorOption.suggest, InternalSugge

constructor() {
const defaults: InternalSuggestOptions = {
overwriteOnAccept: false,
highlightReplaceRange: true,
insertMode: 'insert',
filterGraceful: true,
snippetsPreventQuickSuggestions: true,
localityBonus: false,
Expand Down Expand Up @@ -2527,15 +2522,15 @@ class EditorSuggest extends BaseEditorOption<EditorOption.suggest, InternalSugge
super(
EditorOption.suggest, 'suggest', defaults,
{
'editor.suggest.overwriteOnAccept': {
type: 'boolean',
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.insertMode': {
type: 'string',
enum: ['insert', 'replace'],
enumDescriptions: [
nls.localize('suggest.insertMode.insert', "Insert suggestion without overwriting text right of the cursor."),
nls.localize('suggest.insertMode.replace', "Insert suggestion and overwrite text right of the cursor."),
],
default: defaults.insertMode,
description: nls.localize('suggest.insertMode', "Controls whether words are overwritten when accepting completions. Note that this depends on extensions opting into this feature.")
},
'editor.suggest.filterGraceful': {
type: 'boolean',
Expand Down Expand Up @@ -2713,8 +2708,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),
insertMode: EditorStringEnumOption.stringSet(input.insertMode, this.defaultValue.insertMode, ['insert', 'replace']),
filterGraceful: EditorBooleanOption.boolean(input.filterGraceful, this.defaultValue.filterGraceful),
snippetsPreventQuickSuggestions: EditorBooleanOption.boolean(input.snippetsPreventQuickSuggestions, this.defaultValue.filterGraceful),
localityBonus: EditorBooleanOption.boolean(input.localityBonus, this.defaultValue.localityBonus),
Expand Down
11 changes: 11 additions & 0 deletions src/vs/editor/contrib/suggest/media/suggest.css
Original file line number Diff line number Diff line change
Expand Up @@ -276,3 +276,14 @@
border-radius: 3px;
padding: 0 0.4em;
}


/* replace/insert decorations */

.monaco-editor .suggest-insertMode-goes {
text-decoration-line: line-through;
}

.monaco-editor .suggest-insertMode-stays {
font-style: italic;
}
14 changes: 7 additions & 7 deletions src/vs/editor/contrib/suggest/suggestController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -271,11 +271,7 @@ export class SuggestController implements IEditorContribution {
insertText = SnippetParser.escape(insertText);
}

const overwriteConfig = flags & InsertFlags.AlternativeOverwriteConfig
? !this.editor.getOption(EditorOption.suggest).overwriteOnAccept
: this.editor.getOption(EditorOption.suggest).overwriteOnAccept;

const info = this.getOverwriteInfo(item, overwriteConfig);
const info = this.getOverwriteInfo(item, Boolean(flags & InsertFlags.AlternativeOverwriteConfig));

SnippetController2.get(this.editor).insert(insertText, {
overwriteBefore: info.overwriteBefore,
Expand Down Expand Up @@ -327,11 +323,15 @@ export class SuggestController implements IEditorContribution {
this._alertCompletionItem(event.item);
}

getOverwriteInfo(item: CompletionItem, overwriteOnAccept: boolean): { overwriteBefore: number, overwriteAfter: number } {
getOverwriteInfo(item: CompletionItem, toggleMode: boolean): { overwriteBefore: number, overwriteAfter: number } {
assertType(this.editor.hasModel());

let replace = this.editor.getOption(EditorOption.suggest).insertMode === 'replace';
if (toggleMode) {
replace = !replace;
}
const overwriteBefore = item.position.column - item.editStart.column;
const overwriteAfter = (overwriteOnAccept ? item.editReplaceEnd.column : item.editInsertEnd.column) - item.position.column;
const overwriteAfter = (replace ? 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;

Expand Down
127 changes: 76 additions & 51 deletions src/vs/editor/contrib/suggest/suggestRangeHighlighter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,46 +3,23 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { DisposableStore } from 'vs/base/common/lifecycle';
import { DisposableStore, dispose, IDisposable } 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 { IModelDeltaDecoration } 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}; }`);
}
});
import { Emitter } from 'vs/base/common/event';
import { domEvent } from 'vs/base/browser/event';

export class SuggestRangeHighlighter {

private readonly _disposables = new DisposableStore();

private _decorations: string[] = [];
private _hasWidgetListener: boolean = false;
private _widgetListener?: IDisposable;
private _shiftKeyListener?: IDisposable;
private _currentItem?: CompletionItem;

constructor(private readonly _controller: SuggestController) {

Expand All @@ -53,13 +30,12 @@ export class SuggestRangeHighlighter {
if (focused) {
this._highlight(focused.item);
}

if (!this._hasWidgetListener) {
this._hasWidgetListener = true;
widget.onDidFocus(e => this._highlight(e.item), undefined, this._disposables);
if (!this._widgetListener) {
this._widgetListener = widget.onDidFocus(e => this._highlight(e.item));
}
}
}));

this._disposables.add(_controller.model.onDidCancel(() => {
this._reset();
}));
Expand All @@ -68,31 +44,80 @@ export class SuggestRangeHighlighter {
dispose(): void {
this._reset();
this._disposables.dispose();
dispose(this._widgetListener);
dispose(this._shiftKeyListener);
}

private _reset(): void {
this._decorations = this._controller.editor.deltaDecorations(this._decorations, []);
if (this._shiftKeyListener) {
this._shiftKeyListener.dispose();
this._shiftKeyListener = undefined;
}
}

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
}
}]);
this._currentItem = item;
const opts = this._controller.editor.getOption(EditorOption.suggest);
let newDeco: IModelDeltaDecoration[] = [];

if (!this._shiftKeyListener) {
this._shiftKeyListener = shiftKey.event(() => this._highlight(this._currentItem!));
}

const info = this._controller.getOverwriteInfo(item, shiftKey.isPressed);
const position = this._controller.editor.getPosition()!;

if (opts.insertMode === 'insert' && info.overwriteAfter > 0) {
// wants inserts but got replace-mode -> highlight AFTER range
newDeco = [{
range: new Range(position.lineNumber, position.column, position.lineNumber, position.column + info.overwriteAfter),
options: { inlineClassName: 'suggest-insertMode-goes' }
}];

} else if (opts.insertMode === 'replace' && info.overwriteAfter === 0) {
// want replace but likely got insert -> highlight AFTER range
const wordInfo = this._controller.editor.getModel()?.getWordAtPosition(position);
if (wordInfo && wordInfo.endColumn > position.column) {
newDeco = [{
range: new Range(position.lineNumber, position.column, position.lineNumber, wordInfo.endColumn),
options: { inlineClassName: 'suggest-insertMode-stays' }
}];
}
}

// update editor decorations
this._decorations = this._controller.editor.deltaDecorations(this._decorations, newDeco);
}
}

const shiftKey = new class ShiftKey extends Emitter<boolean> {

private readonly _subscriptions = new DisposableStore();
private _isPressed: boolean = false;

constructor() {
super();
this._subscriptions.add(domEvent(document.body, 'keydown')(e => this.isPressed = e.shiftKey));
this._subscriptions.add(domEvent(document.body, 'keyup')(() => this.isPressed = false));
this._subscriptions.add(domEvent(document.body, 'mouseleave')(() => this.isPressed = false));
this._subscriptions.add(domEvent(document.body, 'blur')(() => this.isPressed = false));
}

get isPressed(): boolean {
return this._isPressed;
}

set isPressed(value: boolean) {
if (this._isPressed !== value) {
this._isPressed = value;
this.fire(value);
}
}

dispose() {
this._subscriptions.dispose();
super.dispose();
}
};
7 changes: 3 additions & 4 deletions src/vs/editor/contrib/suggest/test/completionModel.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import * as modes from 'vs/editor/common/modes';
import { CompletionModel } from 'vs/editor/contrib/suggest/completionModel';
import { CompletionItem, getSuggestionComparator, SnippetSortOrder } from 'vs/editor/contrib/suggest/suggest';
import { WordDistance } from 'vs/editor/contrib/suggest/wordDistance';
import { EditorOptions } from 'vs/editor/common/config/editorOptions';
import { EditorOptions, InternalSuggestOptions } from 'vs/editor/common/config/editorOptions';

export function createSuggestItem(label: string, overwriteBefore: number, kind = modes.CompletionItemKind.Property, incomplete: boolean = false, position: IPosition = { lineNumber: 1, column: 1 }, sortText?: string, filterText?: string): CompletionItem {
const suggestion: modes.CompletionItem = {
Expand All @@ -33,9 +33,8 @@ export function createSuggestItem(label: string, overwriteBefore: number, kind =
}
suite('CompletionModel', function () {

let defaultOptions = {
overwriteOnAccept: false,
highlightReplaceRange: true,
let defaultOptions = <InternalSuggestOptions>{
insertMode: 'insert',
snippetsPreventQuickSuggestions: true,
filterGraceful: true,
localityBonus: false,
Expand Down
6 changes: 1 addition & 5 deletions src/vs/monaco.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3396,11 +3396,7 @@ 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;
insertMode?: 'insert' | 'replace';
/**
* Enable graceful matching. Defaults to true.
*/
Expand Down

0 comments on commit 0ea4167

Please sign in to comment.