first cut of CompletionItem#commitCharacters, #7326

This commit is contained in:
Johannes Rieken
2017-01-10 17:21:30 +01:00
parent 706cfcef28
commit 61bd582998
5 changed files with 71 additions and 9 deletions

View File

@@ -214,6 +214,7 @@ export interface ISuggestion {
filterText?: string;
sortText?: string;
noAutoAccept?: boolean;
commitCharacters?: string[];
overwriteBefore?: number;
overwriteAfter?: number;
additionalTextEdits?: editorCommon.ISingleEditOperation[];

View File

@@ -7,6 +7,7 @@
import * as nls from 'vs/nls';
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
import { onUnexpectedError } from 'vs/base/common/errors';
import { isFalsyOrEmpty } from 'vs/base/common/arrays';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
@@ -25,6 +26,47 @@ import { SuggestModel } from '../common/suggestModel';
import { ICompletionItem } from '../common/completionModel';
import { SuggestWidget } from './suggestWidget';
class AcceptOnCharacterOracle {
private _disposables: IDisposable[] = [];
private _activeAcceptCharacters = new Set<string>();
private _activeItem: ICompletionItem;
constructor(editor: ICodeEditor, widget: SuggestWidget, accept: (item: ICompletionItem) => any) {
this._disposables.push(widget.onDidFocus(item => {
if (!item || isFalsyOrEmpty(item.suggestion.commitCharacters)) {
this._activeItem = undefined;
return;
}
this._activeItem = item;
this._activeAcceptCharacters.clear();
for (const ch of item.suggestion.commitCharacters) {
this._activeAcceptCharacters.add(ch[0]);
}
}));
this._disposables.push(editor.onWillType(text => {
if (this._activeItem) {
const ch = text[text.length - 1];
if (this._activeAcceptCharacters.has(ch)) {
accept(this._activeItem);
}
}
}));
}
reset(): void {
this._activeItem = undefined;
}
dispose() {
dispose(this._disposables);
}
}
@editorContribution
export class SuggestController implements IEditorContribution {
private static ID: string = 'editor.contrib.suggestController';
@@ -59,6 +101,14 @@ export class SuggestController implements IEditorContribution {
this.widget = instantiationService.createInstance(SuggestWidget, this.editor);
this.toDispose.push(this.widget.onDidSelect(this.onDidSelectItem, this));
// Wire up logic to accept a suggestion on certain characters
const autoAcceptOracle = new AcceptOnCharacterOracle(editor, this.widget, item => this.onDidSelectItem(item));
this.toDispose.push(
this.model.onDidCancel(autoAcceptOracle.reset, autoAcceptOracle),
this.model.onDidTrigger(autoAcceptOracle.reset, autoAcceptOracle),
autoAcceptOracle
);
}
getId(): string {

View File

@@ -293,7 +293,7 @@ export class SuggestWidget implements IContentWidget, IDelegate<ICompletionItem>
static NO_SUGGESTIONS_MESSAGE: string = nls.localize('suggestWidget.noSuggestions', "No suggestions.");
// Editor.IContentWidget.allowEditorOverflow
allowEditorOverflow = true;
readonly allowEditorOverflow = true;
private state: State;
private isAuto: boolean;
@@ -312,12 +312,16 @@ export class SuggestWidget implements IContentWidget, IDelegate<ICompletionItem>
private suggestWidgetMultipleSuggestions: IContextKey<boolean>;
private suggestionSupportsAutoAccept: IContextKey<boolean>;
private onDidSelectEmitter = new Emitter<ICompletionItem>();
private editorBlurTimeout: TPromise<void>;
private showTimeout: TPromise<void>;
private toDispose: IDisposable[];
private onDidSelectEmitter = new Emitter<ICompletionItem>();
private onDidFocusEmitter = new Emitter<ICompletionItem>();
readonly onDidSelect: Event<ICompletionItem> = this.onDidSelectEmitter.event;
readonly onDidFocus: Event<ICompletionItem> = this.onDidFocusEmitter.event;
constructor(
private editor: ICodeEditor,
@ITelemetryService private telemetryService: ITelemetryService,
@@ -472,6 +476,9 @@ export class SuggestWidget implements IContentWidget, IDelegate<ICompletionItem>
})
.then(null, err => !isPromiseCanceledError(err) && onUnexpectedError(err))
.then(() => this.currentSuggestionDetails = null);
// emit an event
this.onDidFocusEmitter.fire(item);
}
private setState(state: State): void {
@@ -528,10 +535,6 @@ export class SuggestWidget implements IContentWidget, IDelegate<ICompletionItem>
}
}
get onDidSelect(): Event<ICompletionItem> {
return this.onDidSelectEmitter.event;
}
showTriggered(auto: boolean) {
if (this.state !== State.Hidden) {
return;

9
src/vs/vscode.d.ts vendored
View File

@@ -2396,6 +2396,13 @@ declare module 'vscode' {
*/
range?: Range;
/**
* An optional set of characters that when pressed while this completion is active will accept it first and
* then insert type that character. *Note* that all commit characters should have `length=1` and that superfluous
* characters will be ignored.
*/
commitCharacters?: string[];
/**
* @deprecated **Deprecated** in favor of `CompletionItem.insertText` and `CompletionItem.range`.
*
@@ -2814,7 +2821,7 @@ declare module 'vscode' {
/**
* Readable dictionary that backs this configuration.
*/
readonly [key: string]: any;
readonly[key: string]: any;
}
/**

View File

@@ -514,7 +514,8 @@ class SuggestAdapter {
//
insertText: undefined,
additionalTextEdits: item.additionalTextEdits && item.additionalTextEdits.map(TypeConverters.TextEdit.from),
command: this._commands.toInternal(item.command)
command: this._commands.toInternal(item.command),
commitCharacters: item.commitCharacters
};
// 'insertText'-logic