Skip to content

Commit

Permalink
add CompletionOptions with support to filter by completion kind. Will…
Browse files Browse the repository at this point in the history
… make it simple to implement #45039
  • Loading branch information
jrieken committed Mar 7, 2019
1 parent 96a2e5e commit bb750da
Show file tree
Hide file tree
Showing 10 changed files with 88 additions and 71 deletions.
8 changes: 4 additions & 4 deletions src/vs/editor/common/modes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -324,11 +324,11 @@ export const completionKindToCssClass = (function () {
/**
* @internal
*/
export let completionKindFromLegacyString = (function () {
let data = Object.create(null);
export let completionKindFromString = (function () {
let data: Record<string, CompletionItemKind> = Object.create(null);
data['method'] = CompletionItemKind.Method;
data['function'] = CompletionItemKind.Function;
data['constructor'] = CompletionItemKind.Constructor;
data['constructor'] = <any>CompletionItemKind.Constructor;
data['field'] = CompletionItemKind.Field;
data['variable'] = CompletionItemKind.Variable;
data['class'] = CompletionItemKind.Class;
Expand All @@ -354,7 +354,7 @@ export let completionKindFromLegacyString = (function () {
data['type-parameter'] = CompletionItemKind.TypeParameter;

return function (value: string) {
return data[value] || 'property';
return data[value] || CompletionItemKind.Property;
};
})();

Expand Down
66 changes: 33 additions & 33 deletions src/vs/editor/contrib/suggest/suggest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
*--------------------------------------------------------------------------------------------*/

import { first } from 'vs/base/common/async';
import { isNonEmptyArray } from 'vs/base/common/arrays';
import { assign } from 'vs/base/common/objects';
import { onUnexpectedExternalError, canceled, isPromiseCanceledError } from 'vs/base/common/errors';
import { IEditorContribution } from 'vs/editor/common/editorCommon';
Expand Down Expand Up @@ -87,7 +86,20 @@ export class CompletionItem {
}
}

export type SnippetConfig = 'top' | 'bottom' | 'inline' | 'none';
export const enum SnippetSortOrder {
Top, Inline, Bottom
}

export class CompletionOptions {

static readonly default = new CompletionOptions();

constructor(
readonly snippetSortOrder = SnippetSortOrder.Bottom,
readonly kindFilter = new Set<modes.CompletionItemKind>(),
readonly providerFilter = new Set<modes.CompletionItemProvider>(),
) { }
}

let _snippetSuggestSupport: modes.CompletionItemProvider;

Expand All @@ -104,15 +116,12 @@ export function setSnippetSuggestSupport(support: modes.CompletionItemProvider):
export function provideSuggestionItems(
model: ITextModel,
position: Position,
snippetConfig: SnippetConfig = 'bottom',
onlyFrom?: modes.CompletionItemProvider[],
context?: modes.CompletionContext,
options: CompletionOptions = CompletionOptions.default,
context: modes.CompletionContext = { triggerKind: modes.CompletionTriggerKind.Invoke },
token: CancellationToken = CancellationToken.None
): Promise<CompletionItem[]> {

const allSuggestions: CompletionItem[] = [];
const acceptSuggestion = createSuggesionFilter(snippetConfig);

const wordUntil = model.getWordUntilPosition(position);
const defaultRange = new Range(position.lineNumber, wordUntil.startColumn, position.lineNumber, wordUntil.endColumn);

Expand All @@ -122,30 +131,28 @@ export function provideSuggestionItems(
const supports = modes.CompletionProviderRegistry.orderedGroups(model);

// add snippets provider unless turned off
if (snippetConfig !== 'none' && _snippetSuggestSupport) {
if (!options.kindFilter.has(modes.CompletionItemKind.Snippet) && _snippetSuggestSupport) {
supports.unshift([_snippetSuggestSupport]);
}

const suggestConext = context || { triggerKind: modes.CompletionTriggerKind.Invoke };

// add suggestions from contributed providers - providers are ordered in groups of
// equal score and once a group produces a result the process stops
let hasResult = false;
const factory = supports.map(supports => () => {
// for each support in the group ask for suggestions
return Promise.all(supports.map(provider => {

if (isNonEmptyArray(onlyFrom) && onlyFrom.indexOf(provider) < 0) {
if (options.providerFilter.size > 0 && !options.providerFilter.has(provider)) {
return undefined;
}

return Promise.resolve(provider.provideCompletionItems(model, position, suggestConext, token)).then(container => {
return Promise.resolve(provider.provideCompletionItems(model, position, context, token)).then(container => {

const len = allSuggestions.length;

if (container) {
for (let suggestion of container.suggestions || []) {
if (acceptSuggestion(suggestion)) {
if (!options.kindFilter.has(suggestion.kind)) {

// fill in default range when missing
if (!suggestion.range) {
Expand All @@ -172,7 +179,7 @@ export function provideSuggestionItems(
if (token.isCancellationRequested) {
return Promise.reject<any>(canceled());
}
return allSuggestions.sort(getSuggestionComparator(snippetConfig));
return allSuggestions.sort(getSuggestionComparator(options.snippetSortOrder));
});

// result.then(items => {
Expand All @@ -185,13 +192,7 @@ export function provideSuggestionItems(
return result;
}

function createSuggesionFilter(snippetConfig: SnippetConfig): (candidate: modes.CompletionItem) => boolean {
if (snippetConfig === 'none') {
return suggestion => suggestion.kind !== modes.CompletionItemKind.Snippet;
} else {
return () => true;
}
}

function defaultComparator(a: CompletionItem, b: CompletionItem): number {
// check with 'sortText'
if (a.sortTextLow && b.sortTextLow) {
Expand Down Expand Up @@ -233,14 +234,14 @@ function snippetDownComparator(a: CompletionItem, b: CompletionItem): number {
return defaultComparator(a, b);
}

export function getSuggestionComparator(snippetConfig: SnippetConfig): (a: CompletionItem, b: CompletionItem) => number {
if (snippetConfig === 'top') {
return snippetUpComparator;
} else if (snippetConfig === 'bottom') {
return snippetDownComparator;
} else {
return defaultComparator;
}
interface Comparator<T> { (a: T, b: T): number; }
const _snippetComparators = new Map<SnippetSortOrder, Comparator<CompletionItem>>();
_snippetComparators.set(SnippetSortOrder.Top, snippetUpComparator);
_snippetComparators.set(SnippetSortOrder.Bottom, snippetDownComparator);
_snippetComparators.set(SnippetSortOrder.Inline, defaultComparator);

export function getSuggestionComparator(snippetConfig: SnippetSortOrder): (a: CompletionItem, b: CompletionItem) => number {
return _snippetComparators.get(snippetConfig)!;
}

registerDefaultLanguageCommand('_executeCompletionItemProvider', (model, position, args) => {
Expand Down Expand Up @@ -269,11 +270,10 @@ registerDefaultLanguageCommand('_executeCompletionItemProvider', (model, positio
});

interface SuggestController extends IEditorContribution {
triggerSuggest(onlyFrom?: modes.CompletionItemProvider[]): void;
triggerSuggest(onlyFrom?: Set<modes.CompletionItemProvider>): void;
}


let _provider = new class implements modes.CompletionItemProvider {
const _provider = new class implements modes.CompletionItemProvider {

onlyOnceSuggestions: modes.CompletionItem[] = [];

Expand All @@ -290,6 +290,6 @@ modes.CompletionProviderRegistry.register('*', _provider);
export function showSimpleSuggestions(editor: ICodeEditor, suggestions: modes.CompletionItem[]) {
setTimeout(() => {
_provider.onlyOnceSuggestions.push(...suggestions);
editor.getContribution<SuggestController>('editor.contrib.suggestController').triggerSuggest([_provider]);
editor.getContribution<SuggestController>('editor.contrib.suggestController').triggerSuggest(new Set<modes.CompletionItemProvider>().add(_provider));
}, 0);
}
2 changes: 1 addition & 1 deletion src/vs/editor/contrib/suggest/suggestController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -298,7 +298,7 @@ export class SuggestController implements IEditorContribution {
}
}

triggerSuggest(onlyFrom?: CompletionItemProvider[]): void {
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);
Expand Down
6 changes: 3 additions & 3 deletions src/vs/editor/contrib/suggest/suggestMemory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { LRUCache, TernarySearchTree } from 'vs/base/common/map';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { ITextModel } from 'vs/editor/common/model';
import { IPosition } from 'vs/editor/common/core/position';
import { CompletionItemKind, completionKindFromLegacyString } from 'vs/editor/common/modes';
import { CompletionItemKind, completionKindFromString } from 'vs/editor/common/modes';
import { Disposable } from 'vs/base/common/lifecycle';
import { RunOnceScheduler } from 'vs/base/common/async';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
Expand Down Expand Up @@ -124,7 +124,7 @@ export class LRUMemory extends Memory {
let seq = 0;
for (const [key, value] of data) {
value.touch = seq;
value.type = typeof value.type === 'number' ? value.type : completionKindFromLegacyString(value.type);
value.type = typeof value.type === 'number' ? value.type : completionKindFromString(value.type);
this._cache.set(key, value);
}
this._seq = this._cache.size;
Expand Down Expand Up @@ -188,7 +188,7 @@ export class PrefixMemory extends Memory {
if (data.length > 0) {
this._seq = data[0][1].touch + 1;
for (const [key, value] of data) {
value.type = typeof value.type === 'number' ? value.type : completionKindFromLegacyString(value.type);
value.type = typeof value.type === 'number' ? value.type : completionKindFromString(value.type);
this._trie.set(key, value);
}
}
Expand Down
37 changes: 27 additions & 10 deletions src/vs/editor/contrib/suggest/suggestModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,14 @@ import { TimeoutTimer } from 'vs/base/common/async';
import { onUnexpectedError } from 'vs/base/common/errors';
import { Emitter, Event } from 'vs/base/common/event';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { values } from 'vs/base/common/map';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { CursorChangeReason, ICursorSelectionChangedEvent } from 'vs/editor/common/controller/cursorEvents';
import { Position } from 'vs/editor/common/core/position';
import { Selection } from 'vs/editor/common/core/selection';
import { ITextModel, IWordAtPosition } from 'vs/editor/common/model';
import { CompletionItemProvider, StandardTokenType, CompletionContext, CompletionProviderRegistry, CompletionTriggerKind } from 'vs/editor/common/modes';
import { CompletionItemProvider, StandardTokenType, CompletionContext, CompletionProviderRegistry, CompletionTriggerKind, CompletionItemKind } from 'vs/editor/common/modes';
import { CompletionModel } from './completionModel';
import { CompletionItem, getSuggestionComparator, provideSuggestionItems, getSnippetSuggestSupport } from './suggest';
import { CompletionItem, getSuggestionComparator, provideSuggestionItems, getSnippetSuggestSupport, SnippetSortOrder, CompletionOptions } from './suggest';
import { SnippetController2 } from 'vs/editor/contrib/snippet/snippetController2';
import { CancellationTokenSource } from 'vs/base/common/cancellation';
import { IEditorWorkerService } from 'vs/editor/common/services/editorWorkerService';
Expand Down Expand Up @@ -209,7 +208,7 @@ export class SuggestModel implements IDisposable {
// keep existing items that where not computed by the
// supports/providers that want to trigger now
const items: CompletionItem[] | undefined = this._completionModel ? this._completionModel.adopt(supports) : undefined;
this.trigger({ auto: true, shy: false, triggerCharacter: lastChar }, Boolean(this._completionModel), values(supports), items);
this.trigger({ auto: true, shy: false, triggerCharacter: lastChar }, Boolean(this._completionModel), supports, items);
}
});
}
Expand Down Expand Up @@ -347,7 +346,7 @@ export class SuggestModel implements IDisposable {
}, 25);
}

trigger(context: SuggestTriggerContext, retrigger: boolean = false, onlyFrom?: CompletionItemProvider[], existingItems?: CompletionItem[]): void {
trigger(context: SuggestTriggerContext, retrigger: boolean = false, onlyFrom?: Set<CompletionItemProvider>, existingItems?: CompletionItem[]): void {
if (!this._editor.hasModel()) {
return;
}
Expand All @@ -371,21 +370,39 @@ export class SuggestModel implements IDisposable {
triggerKind: CompletionTriggerKind.TriggerCharacter,
triggerCharacter: context.triggerCharacter
};
} else if (onlyFrom && onlyFrom.length) {
} else if (onlyFrom && onlyFrom.size > 0) {
suggestCtx = { triggerKind: CompletionTriggerKind.TriggerForIncompleteCompletions };
} else {
suggestCtx = { triggerKind: CompletionTriggerKind.Invoke };
}

this._requestToken = new CancellationTokenSource();

// kind filter and snippet sort rules
let itemKindFilter = new Set<CompletionItemKind>();
let snippetSortOrder = SnippetSortOrder.Inline;
switch (this._editor.getConfiguration().contribInfo.suggest.snippets) {
case 'top':
snippetSortOrder = SnippetSortOrder.Top;
break;
// ↓ that's the default anyways...
// case 'inline':
// snippetSortOrder = SnippetSortOrder.Inline;
// break;
case 'bottom':
snippetSortOrder = SnippetSortOrder.Bottom;
break;
case 'none':
itemKindFilter.add(CompletionItemKind.Snippet);
break;
}

let wordDistance = WordDistance.create(this._editorWorker, this._editor);

let items = provideSuggestionItems(
model,
this._editor.getPosition(),
this._editor.getConfiguration().contribInfo.suggest.snippets,
onlyFrom,
new CompletionOptions(snippetSortOrder, itemKindFilter, onlyFrom),
suggestCtx,
this._requestToken.token
);
Expand All @@ -405,7 +422,7 @@ export class SuggestModel implements IDisposable {
const model = this._editor.getModel();

if (isNonEmptyArray(existingItems)) {
const cmpFn = getSuggestionComparator(this._editor.getConfiguration().contribInfo.suggest.snippets);
const cmpFn = getSuggestionComparator(snippetSortOrder);
items = items.concat(existingItems).sort(cmpFn);
}

Expand Down Expand Up @@ -461,7 +478,7 @@ export class SuggestModel implements IDisposable {
// typed -> moved cursor RIGHT & incomple model & still on a word -> retrigger
const { incomplete } = this._completionModel;
const adopted = this._completionModel.adopt(incomplete);
this.trigger({ auto: this._state === State.Auto, shy: false }, true, values(incomplete), adopted);
this.trigger({ auto: this._state === State.Auto, shy: false }, true, incomplete, adopted);

} else {
// typed -> moved cursor RIGHT -> update UI
Expand Down
4 changes: 2 additions & 2 deletions src/vs/editor/contrib/suggest/test/completionModel.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import * as assert from 'assert';
import { IPosition } from 'vs/editor/common/core/position';
import * as modes from 'vs/editor/common/modes';
import { CompletionModel } from 'vs/editor/contrib/suggest/completionModel';
import { CompletionItem, getSuggestionComparator } from 'vs/editor/contrib/suggest/suggest';
import { CompletionItem, getSuggestionComparator, SnippetSortOrder } from 'vs/editor/contrib/suggest/suggest';
import { WordDistance } from 'vs/editor/contrib/suggest/wordDistance';

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 {
Expand Down Expand Up @@ -250,7 +250,7 @@ suite('CompletionModel', function () {

const item1 = createSuggestItem('<- groups', 2, modes.CompletionItemKind.Property, false, { lineNumber: 1, column: 3 }, '00002', ' groups');
const item2 = createSuggestItem('source', 0, modes.CompletionItemKind.Property, false, { lineNumber: 1, column: 3 }, '00001', 'source');
const items = [item1, item2].sort(getSuggestionComparator('inline'));
const items = [item1, item2].sort(getSuggestionComparator(SnippetSortOrder.Inline));

model = new CompletionModel(items, 3, {
leadingLineContent: ' ',
Expand Down
14 changes: 7 additions & 7 deletions src/vs/editor/contrib/suggest/test/suggest.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
import * as assert from 'assert';
import { URI } from 'vs/base/common/uri';
import { IDisposable } from 'vs/base/common/lifecycle';
import { CompletionProviderRegistry, CompletionItemKind } from 'vs/editor/common/modes';
import { provideSuggestionItems } from 'vs/editor/contrib/suggest/suggest';
import { CompletionProviderRegistry, CompletionItemKind, CompletionItemProvider } from 'vs/editor/common/modes';
import { provideSuggestionItems, SnippetSortOrder, CompletionOptions } from 'vs/editor/contrib/suggest/suggest';
import { Position } from 'vs/editor/common/core/position';
import { TextModel } from 'vs/editor/common/model/textModel';
import { Range } from 'vs/editor/common/core/range';
Expand Down Expand Up @@ -51,31 +51,31 @@ suite('Suggest', function () {
});

test('sort - snippet inline', async function () {
const items = await provideSuggestionItems(model, new Position(1, 1), 'inline');
const items = await provideSuggestionItems(model, new Position(1, 1), new CompletionOptions(SnippetSortOrder.Inline));
assert.equal(items.length, 3);
assert.equal(items[0].completion.label, 'aaa');
assert.equal(items[1].completion.label, 'fff');
assert.equal(items[2].completion.label, 'zzz');
});

test('sort - snippet top', async function () {
const items = await provideSuggestionItems(model, new Position(1, 1), 'top');
const items = await provideSuggestionItems(model, new Position(1, 1), new CompletionOptions(SnippetSortOrder.Top));
assert.equal(items.length, 3);
assert.equal(items[0].completion.label, 'aaa');
assert.equal(items[1].completion.label, 'zzz');
assert.equal(items[2].completion.label, 'fff');
});

test('sort - snippet bottom', async function () {
const items = await provideSuggestionItems(model, new Position(1, 1), 'bottom');
const items = await provideSuggestionItems(model, new Position(1, 1), new CompletionOptions(SnippetSortOrder.Bottom));
assert.equal(items.length, 3);
assert.equal(items[0].completion.label, 'fff');
assert.equal(items[1].completion.label, 'aaa');
assert.equal(items[2].completion.label, 'zzz');
});

test('sort - snippet none', async function () {
const items = await provideSuggestionItems(model, new Position(1, 1), 'none');
const items = await provideSuggestionItems(model, new Position(1, 1), new CompletionOptions(undefined, new Set<CompletionItemKind>().add(CompletionItemKind.Snippet)));
assert.equal(items.length, 1);
assert.equal(items[0].completion.label, 'fff');
});
Expand All @@ -98,7 +98,7 @@ suite('Suggest', function () {
};
const registration = CompletionProviderRegistry.register({ pattern: 'bar/path', scheme: 'foo' }, foo);

provideSuggestionItems(model, new Position(1, 1), undefined, [foo]).then(items => {
provideSuggestionItems(model, new Position(1, 1), new CompletionOptions(undefined, undefined, new Set<CompletionItemProvider>().add(foo))).then(items => {
registration.dispose();

assert.equal(items.length, 1);
Expand Down

0 comments on commit bb750da

Please sign in to comment.