提交 377eabe6 编写于 作者: M Matt Bierner

Add optional CompletionContext to provideCompletionItems

Fixes #752

Adds a new overload of `provideCompletionItems` that takes a context argument. This context is currently used to provide the trigger character of the suggestion
上级 b86108e5
......@@ -247,6 +247,13 @@ export interface ISuggestResult {
dispose?(): void;
}
/**
* @internal
*/
export interface SuggestContext {
triggerCharacter?: string;
}
/**
* @internal
*/
......@@ -254,7 +261,7 @@ export interface ISuggestSupport {
triggerCharacters?: string[];
provideCompletionItems(model: editorCommon.IModel, position: Position, token: CancellationToken): ISuggestResult | Thenable<ISuggestResult>;
provideCompletionItems(model: editorCommon.IModel, position: Position, context: SuggestContext, token: CancellationToken): ISuggestResult | Thenable<ISuggestResult>;
resolveCompletionItem?(model: editorCommon.IModel, position: Position, item: ISuggestion, token: CancellationToken): ISuggestion | Thenable<ISuggestion>;
}
......
......@@ -12,7 +12,7 @@ import { onUnexpectedExternalError } from 'vs/base/common/errors';
import { TPromise } from 'vs/base/common/winjs.base';
import { IModel, IEditorContribution, ICommonCodeEditor } from 'vs/editor/common/editorCommon';
import { CommonEditorRegistry } from 'vs/editor/common/editorCommonExtensions';
import { ISuggestResult, ISuggestSupport, ISuggestion, SuggestRegistry } from 'vs/editor/common/modes';
import { ISuggestResult, ISuggestSupport, ISuggestion, SuggestRegistry, SuggestContext } from 'vs/editor/common/modes';
import { Position, IPosition } from 'vs/editor/common/core/position';
import { RawContextKey } from 'vs/platform/contextkey/common/contextkey';
......@@ -42,7 +42,7 @@ export function setSnippetSuggestSupport(support: ISuggestSupport): ISuggestSupp
return old;
}
export function provideSuggestionItems(model: IModel, position: Position, snippetConfig: SnippetConfig = 'bottom', onlyFrom?: ISuggestSupport[]): TPromise<ISuggestionItem[]> {
export function provideSuggestionItems(model: IModel, position: Position, snippetConfig: SnippetConfig = 'bottom', onlyFrom?: ISuggestSupport[], context?: SuggestContext): TPromise<ISuggestionItem[]> {
const allSuggestions: ISuggestionItem[] = [];
const acceptSuggestion = createSuggesionFilter(snippetConfig);
......@@ -73,7 +73,7 @@ export function provideSuggestionItems(model: IModel, position: Position, snippe
return undefined;
}
return asWinJsPromise(token => support.provideCompletionItems(model, position, token)).then(container => {
return asWinJsPromise(token => support.provideCompletionItems(model, position, context || {}, token)).then(container => {
const len = allSuggestions.length;
......
......@@ -209,7 +209,7 @@ export class SuggestController implements IEditorContribution {
}
triggerSuggest(onlyFrom?: ISuggestSupport[]): void {
this._model.trigger(false, false, onlyFrom);
this._model.trigger({ auto: false }, false, onlyFrom);
this._editor.revealLine(this._editor.getPosition().lineNumber, ScrollType.Smooth);
this._editor.focus();
}
......
......@@ -31,6 +31,11 @@ export interface ISuggestEvent {
auto: boolean;
}
export interface SuggestTriggerContext {
auto: boolean;
triggerCharacter?: string;
}
export class LineContext {
static shouldAutoTrigger(editor: ICommonCodeEditor): boolean {
......@@ -201,7 +206,7 @@ export class SuggestModel implements IDisposable {
}
}
}
this.trigger(true, Boolean(this.completionModel), supports, items);
this.trigger({ auto: true, triggerCharacter: lastChar }, Boolean(this.completionModel), supports, items);
}
});
}
......@@ -237,7 +242,7 @@ export class SuggestModel implements IDisposable {
if (!SuggestRegistry.has(this.editor.getModel())) {
this.cancel();
} else {
this.trigger(this._state === State.Auto, true);
this.trigger({ auto: this._state === State.Auto }, true);
}
}
}
......@@ -311,7 +316,7 @@ export class SuggestModel implements IDisposable {
}
this.triggerAutoSuggestPromise = null;
this.trigger(true);
this.trigger({ auto: true });
});
}
}
......@@ -326,14 +331,14 @@ export class SuggestModel implements IDisposable {
}
}
public trigger(auto: boolean, retrigger: boolean = false, onlyFrom?: ISuggestSupport[], existingItems?: ISuggestionItem[]): void {
public trigger(context: SuggestTriggerContext, retrigger: boolean = false, onlyFrom?: ISuggestSupport[], existingItems?: ISuggestionItem[]): void {
const model = this.editor.getModel();
if (!model) {
return;
}
const auto = context.auto;
const ctx = new LineContext(model, this.editor.getPosition(), auto);
if (!LineContext.isInEditableRange(this.editor)) {
......@@ -350,7 +355,8 @@ export class SuggestModel implements IDisposable {
this.requestPromise = provideSuggestionItems(model, this.editor.getPosition(),
this.editor.getConfiguration().contribInfo.snippetSuggestions,
onlyFrom
onlyFrom,
context
).then(items => {
this.requestPromise = null;
......@@ -394,7 +400,7 @@ export class SuggestModel implements IDisposable {
if (ctx.column < this.context.column) {
// typed -> moved cursor LEFT -> retrigger if still on a word
if (ctx.leadingWord.word) {
this.trigger(this.context.auto, true);
this.trigger({ auto: this.context.auto }, true);
} else {
this.cancel();
}
......@@ -409,7 +415,7 @@ export class SuggestModel implements IDisposable {
if (ctx.column > this.context.column && this.completionModel.incomplete && ctx.leadingWord.word.length !== 0) {
// typed -> moved cursor RIGHT & incomple model & still on a word -> retrigger
const { complete, incomplete } = this.completionModel.resolveIncompleteInfo();
this.trigger(this._state === State.Auto, true, incomplete, complete);
this.trigger({ auto: this._state === State.Auto }, true, incomplete, complete);
} else {
// typed -> moved cursor RIGHT -> update UI
......@@ -425,7 +431,7 @@ export class SuggestModel implements IDisposable {
if (LineContext.shouldAutoTrigger(this.editor) && this.context.leadingWord.endColumn < ctx.leadingWord.startColumn) {
// retrigger when heading into a new word
this.trigger(this.context.auto, true);
this.trigger({ auto: this.context.auto }, true);
return;
}
......
......@@ -149,25 +149,25 @@ suite('SuggestModel - TriggerAndCancelOracle', function () {
// cancel on trigger
assertEvent(model.onDidCancel, function () {
model.trigger(false);
model.trigger({ auto: false });
}, function (event) {
assert.equal(event.retrigger, false);
}),
assertEvent(model.onDidCancel, function () {
model.trigger(false, true);
model.trigger({ auto: false }, true);
}, function (event) {
assert.equal(event.retrigger, true);
}),
assertEvent(model.onDidTrigger, function () {
model.trigger(true);
model.trigger({ auto: true });
}, function (event) {
assert.equal(event.auto, true);
}),
assertEvent(model.onDidTrigger, function () {
model.trigger(false);
model.trigger({ auto: false });
}, function (event) {
assert.equal(event.auto, false);
})
......@@ -183,12 +183,12 @@ suite('SuggestModel - TriggerAndCancelOracle', function () {
return withOracle(model => {
return TPromise.join([
assertEvent(model.onDidCancel, function () {
model.trigger(true);
model.trigger({ auto: true });
}, function (event) {
assert.equal(event.retrigger, false);
}),
assertEvent(model.onDidSuggest, function () {
model.trigger(false);
model.trigger({ auto: false });
}, function (event) {
assert.equal(event.auto, false);
assert.equal(event.isFrozen, false);
......@@ -239,7 +239,7 @@ suite('SuggestModel - TriggerAndCancelOracle', function () {
return assertEvent(model.onDidSuggest, () => {
// make sure completionModel starts here!
model.trigger(true);
model.trigger({ auto: true });
}, event => {
return assertEvent(model.onDidSuggest, () => {
......@@ -338,7 +338,7 @@ suite('SuggestModel - TriggerAndCancelOracle', function () {
editor.setPosition({ lineNumber: 1, column: 3 });
return assertEvent(model.onDidSuggest, () => {
model.trigger(false);
model.trigger({ auto: false });
}, event => {
assert.equal(event.auto, false);
assert.equal(event.isFrozen, false);
......@@ -363,7 +363,7 @@ suite('SuggestModel - TriggerAndCancelOracle', function () {
editor.setPosition({ lineNumber: 1, column: 3 });
return assertEvent(model.onDidSuggest, () => {
model.trigger(false);
model.trigger({ auto: false });
}, event => {
assert.equal(event.auto, false);
assert.equal(event.isFrozen, false);
......@@ -400,7 +400,7 @@ suite('SuggestModel - TriggerAndCancelOracle', function () {
editor.setPosition({ lineNumber: 1, column: 4 });
return assertEvent(model.onDidSuggest, () => {
model.trigger(false);
model.trigger({ auto: false });
}, event => {
assert.equal(event.auto, false);
assert.equal(event.completionModel.incomplete, true);
......@@ -437,7 +437,7 @@ suite('SuggestModel - TriggerAndCancelOracle', function () {
editor.setPosition({ lineNumber: 1, column: 4 });
return assertEvent(model.onDidSuggest, () => {
model.trigger(false);
model.trigger({ auto: false });
}, event => {
assert.equal(event.auto, false);
assert.equal(event.completionModel.incomplete, true);
......@@ -457,4 +457,38 @@ suite('SuggestModel - TriggerAndCancelOracle', function () {
});
});
});
test('Trigger characters is provided in suggest context', function () {
let triggerCharacter = '';
disposables.push(SuggestRegistry.register({ scheme: 'test' }, {
triggerCharacters: ['.'],
provideCompletionItems(doc, pos, context) {
triggerCharacter = context.triggerCharacter;
return <ISuggestResult>{
currentWord: '',
incomplete: false,
suggestions: [
{
label: 'foo.bar',
type: 'property',
insertText: 'foo.bar',
overwriteBefore: pos.column - 1
}
]
};
}
}));
model.setValue('');
return withOracle((model, editor) => {
return assertEvent(model.onDidSuggest, () => {
editor.setPosition({ lineNumber: 1, column: 1 });
editor.trigger('keyboard', Handler.Type, { text: 'foo.' });
}, event => {
assert.equal(triggerCharacter, '.');
});
});
});
});
......@@ -373,8 +373,8 @@ export function registerCompletionItemProvider(languageId: string, provider: Com
let adapter = new SuggestAdapter(provider);
return modes.SuggestRegistry.register(languageId, {
triggerCharacters: provider.triggerCharacters,
provideCompletionItems: (model: editorCommon.IReadOnlyModel, position: Position, token: CancellationToken): Thenable<modes.ISuggestResult> => {
return adapter.provideCompletionItems(model, position, token);
provideCompletionItems: (model: editorCommon.IReadOnlyModel, position: Position, context: modes.SuggestContext, token: CancellationToken): Thenable<modes.ISuggestResult> => {
return adapter.provideCompletionItems(model, position, context, token);
},
resolveCompletionItem: (model: editorCommon.IReadOnlyModel, position: Position, suggestion: modes.ISuggestion, token: CancellationToken): Thenable<modes.ISuggestion> => {
return adapter.resolveCompletionItem(model, position, suggestion, token);
......@@ -537,6 +537,23 @@ export interface CompletionList {
*/
items: CompletionItem[];
}
/**
* Contains additional information about the context in which
* [completion provider](#CompletionItemProvider.provideCompletionItems) is triggered.
*/
export interface CompletionContext {
/**
* Character that triggered the completion item provider.
*
* Undefined if provider was not triggered by a character.
*/
triggerCharacter?: string;
}
export type ProviderCompletionItems = (document: editorCommon.IReadOnlyModel, position: Position, token: CancellationToken) => CompletionItem[] | Thenable<CompletionItem[]> | CompletionList | Thenable<CompletionList>;
export type ProviderCompletionItemsForContext = (document: editorCommon.IReadOnlyModel, position: Position, context: CompletionContext, token: CancellationToken) => CompletionItem[] | Thenable<CompletionItem[]> | CompletionList | Thenable<CompletionList>;
/**
* The completion item provider interface defines the contract between extensions and
* the [IntelliSense](https://code.visualstudio.com/docs/editor/intellisense).
......@@ -553,7 +570,7 @@ export interface CompletionItemProvider {
/**
* Provide completion items for the given position and document.
*/
provideCompletionItems(model: editorCommon.IReadOnlyModel, position: Position, token: CancellationToken): CompletionItem[] | Thenable<CompletionItem[]> | CompletionList | Thenable<CompletionList>;
provideCompletionItems: ProviderCompletionItems | ProviderCompletionItemsForContext;
/**
* Given a completion item fill in more data, like [doc-comment](#CompletionItem.documentation)
* or [details](#CompletionItem.detail).
......@@ -639,9 +656,14 @@ class SuggestAdapter {
return suggestion;
}
provideCompletionItems(model: editorCommon.IReadOnlyModel, position: Position, token: CancellationToken): Thenable<modes.ISuggestResult> {
return toThenable<CompletionItem[] | CompletionList>(this._provider.provideCompletionItems(model, position, token)).then(value => {
provideCompletionItems(model: editorCommon.IReadOnlyModel, position: Position, context: modes.SuggestContext, token: CancellationToken): Thenable<modes.ISuggestResult> {
let request: any;
if (this._provider.provideCompletionItems.length <= 3) {
request = (this._provider.provideCompletionItems as ProviderCompletionItems)(model, position, token);
} else {
request = (this._provider.provideCompletionItems as ProviderCompletionItemsForContext)(model, position, context, token);
}
return toThenable<CompletionItem[] | CompletionList>(request).then(value => {
const result: modes.ISuggestResult = {
suggestions: []
};
......
......@@ -4229,6 +4229,23 @@ declare module monaco.languages {
items: CompletionItem[];
}
/**
* Contains additional information about the context in which
* [completion provider](#CompletionItemProvider.provideCompletionItems) is triggered.
*/
export interface CompletionContext {
/**
* Character that triggered the completion item provider.
*
* Undefined if provider was not triggered by a character.
*/
triggerCharacter?: string;
}
export type ProviderCompletionItems = (document: editor.IReadOnlyModel, position: Position, token: CancellationToken) => CompletionItem[] | Thenable<CompletionItem[]> | CompletionList | Thenable<CompletionList>;
export type ProviderCompletionItemsForContext = (document: editor.IReadOnlyModel, position: Position, context: CompletionContext, token: CancellationToken) => CompletionItem[] | Thenable<CompletionItem[]> | CompletionList | Thenable<CompletionList>;
/**
* The completion item provider interface defines the contract between extensions and
* the [IntelliSense](https://code.visualstudio.com/docs/editor/intellisense).
......@@ -4245,7 +4262,7 @@ declare module monaco.languages {
/**
* Provide completion items for the given position and document.
*/
provideCompletionItems(model: editor.IReadOnlyModel, position: Position, token: CancellationToken): CompletionItem[] | Thenable<CompletionItem[]> | CompletionList | Thenable<CompletionList>;
provideCompletionItems: ProviderCompletionItems | ProviderCompletionItemsForContext;
/**
* Given a completion item fill in more data, like [doc-comment](#CompletionItem.documentation)
* or [details](#CompletionItem.detail).
......
......@@ -2718,6 +2718,22 @@ declare module 'vscode' {
constructor(items?: CompletionItem[], isIncomplete?: boolean);
}
/**
* Contains additional information about the context in which
* [completion provider](#CompletionItemProvider.provideCompletionItems) is triggered.
*/
export interface CompletionContext {
/**
* Character that triggered the completion item provider.
*
* Undefined if provider was not triggered by a character.
*/
readonly triggerCharacter?: string;
}
export type ProviderCompletionItems = (document: TextDocument, position: Position, token: CancellationToken) => ProviderResult<CompletionItem[] | CompletionList>;
export type ProviderCompletionItemsForContext = (document: TextDocument, position: Position, context: CompletionContext, token: CancellationToken) => ProviderResult<CompletionItem[] | CompletionList>;
/**
* The completion item provider interface defines the contract between extensions and
* [IntelliSense](https://code.visualstudio.com/docs/editor/intellisense).
......@@ -2743,7 +2759,7 @@ declare module 'vscode' {
* @return An array of completions, a [completion list](#CompletionList), or a thenable that resolves to either.
* The lack of a result can be signaled by returning `undefined`, `null`, or an empty array.
*/
provideCompletionItems(document: TextDocument, position: Position, token: CancellationToken): ProviderResult<CompletionItem[] | CompletionList>;
provideCompletionItems: ProviderCompletionItems | ProviderCompletionItemsForContext;
/**
* Given a completion item fill in more data, like [doc-comment](#CompletionItem.documentation)
......
......@@ -233,8 +233,8 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha
this._registrations[handle] = modes.SuggestRegistry.register(selector, <modes.ISuggestSupport>{
triggerCharacters,
provideCompletionItems: (model: IReadOnlyModel, position: EditorPosition, token: CancellationToken): Thenable<modes.ISuggestResult> => {
return wireCancellationToken(token, this._proxy.$provideCompletionItems(handle, model.uri, position)).then(result => {
provideCompletionItems: (model: IReadOnlyModel, position: EditorPosition, context: modes.SuggestContext, token: CancellationToken): Thenable<modes.ISuggestResult> => {
return wireCancellationToken(token, this._proxy.$provideCompletionItems(handle, model.uri, position, context)).then(result => {
if (!result) {
return result;
}
......
......@@ -552,7 +552,7 @@ export interface ExtHostLanguageFeaturesShape {
$provideWorkspaceSymbols(handle: number, search: string): TPromise<modes.SymbolInformation[]>;
$resolveWorkspaceSymbol(handle: number, symbol: modes.SymbolInformation): TPromise<modes.SymbolInformation>;
$provideRenameEdits(handle: number, resource: URI, position: IPosition, newName: string): TPromise<modes.WorkspaceEdit>;
$provideCompletionItems(handle: number, resource: URI, position: IPosition): TPromise<IExtHostSuggestResult>;
$provideCompletionItems(handle: number, resource: URI, position: IPosition, context: modes.SuggestContext): TPromise<IExtHostSuggestResult>;
$resolveCompletionItem(handle: number, resource: URI, position: IPosition, suggestion: modes.ISuggestion): TPromise<modes.ISuggestion>;
$releaseCompletionItems(handle: number, id: number): void;
$provideSignatureHelp(handle: number, resource: URI, position: IPosition): TPromise<modes.SignatureHelp>;
......
......@@ -486,12 +486,17 @@ class SuggestAdapter {
this._provider = provider;
}
provideCompletionItems(resource: URI, position: IPosition): TPromise<IExtHostSuggestResult> {
provideCompletionItems(resource: URI, position: IPosition, context: modes.SuggestContext): TPromise<IExtHostSuggestResult> {
const doc = this._documents.getDocumentData(resource).document;
const pos = TypeConverters.toPosition(position);
return asWinJsPromise<vscode.CompletionItem[] | vscode.CompletionList>(token => this._provider.provideCompletionItems(doc, pos, token)).then(value => {
return asWinJsPromise<vscode.CompletionItem[] | vscode.CompletionList>(token => {
if (this._provider.provideCompletionItems.length <= 3) {
return (this._provider.provideCompletionItems as vscode.ProviderCompletionItems)(doc, pos, token);
}
return (this._provider.provideCompletionItems as vscode.ProviderCompletionItemsForContext)(doc, pos, context, token);
}).then(value => {
const _id = this._idPool++;
......@@ -1001,8 +1006,8 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape {
return this._createDisposable(handle);
}
$provideCompletionItems(handle: number, resource: URI, position: IPosition): TPromise<IExtHostSuggestResult> {
return this._withAdapter(handle, SuggestAdapter, adapter => adapter.provideCompletionItems(resource, position));
$provideCompletionItems(handle: number, resource: URI, position: IPosition, context: modes.SuggestContext): TPromise<IExtHostSuggestResult> {
return this._withAdapter(handle, SuggestAdapter, adapter => adapter.provideCompletionItems(resource, position, context));
}
$resolveCompletionItem(handle: number, resource: URI, position: IPosition, suggestion: modes.ISuggestion): TPromise<modes.ISuggestion> {
......
......@@ -180,7 +180,7 @@ export class Repl extends Panel implements IPrivateReplService {
modes.SuggestRegistry.register({ scheme: debug.DEBUG_SCHEME }, {
triggerCharacters: ['.'],
provideCompletionItems: (model: IReadOnlyModel, position: Position, token: CancellationToken): Thenable<modes.ISuggestResult> => {
provideCompletionItems: (model: IReadOnlyModel, position: Position, _context: modes.SuggestContext, token: CancellationToken): Thenable<modes.ISuggestResult> => {
const word = this.replInput.getModel().getWordAtPosition(position);
const overwriteBefore = word ? word.word.length : 0;
const text = this.replInput.getModel().getLineContent(position.lineNumber);
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册