Path: blob/main/src/vs/editor/contrib/suggest/test/browser/completionModel.test.ts
4797 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/4import assert from 'assert';5import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js';6import { EditorOptions, InternalSuggestOptions } from '../../../../common/config/editorOptions.js';7import { IPosition } from '../../../../common/core/position.js';8import * as languages from '../../../../common/languages.js';9import { CompletionModel } from '../../browser/completionModel.js';10import { CompletionItem, getSuggestionComparator, SnippetSortOrder } from '../../browser/suggest.js';11import { WordDistance } from '../../browser/wordDistance.js';1213export function createSuggestItem(label: string | languages.CompletionItemLabel, overwriteBefore: number, kind = languages.CompletionItemKind.Property, incomplete: boolean = false, position: IPosition = { lineNumber: 1, column: 1 }, sortText?: string, filterText?: string): CompletionItem {14const suggestion: languages.CompletionItem = {15label,16sortText,17filterText,18range: { startLineNumber: position.lineNumber, startColumn: position.column - overwriteBefore, endLineNumber: position.lineNumber, endColumn: position.column },19insertText: typeof label === 'string' ? label : label.label,20kind21};22const container: languages.CompletionList = {23incomplete,24suggestions: [suggestion]25};26const provider: languages.CompletionItemProvider = {27_debugDisplayName: 'test',28provideCompletionItems(): any {29return;30}31};3233return new CompletionItem(position, suggestion, container, provider);34}35suite('CompletionModel', function () {3637const defaultOptions = <InternalSuggestOptions>{38insertMode: 'insert',39snippetsPreventQuickSuggestions: true,40filterGraceful: true,41localityBonus: false,42shareSuggestSelections: false,43showIcons: true,44showMethods: true,45showFunctions: true,46showConstructors: true,47showDeprecated: true,48showFields: true,49showVariables: true,50showClasses: true,51showStructs: true,52showInterfaces: true,53showModules: true,54showProperties: true,55showEvents: true,56showOperators: true,57showUnits: true,58showValues: true,59showConstants: true,60showEnums: true,61showEnumMembers: true,62showKeywords: true,63showWords: true,64showColors: true,65showFiles: true,66showReferences: true,67showFolders: true,68showTypeParameters: true,69showSnippets: true,70};7172let model: CompletionModel;7374setup(function () {7576model = new CompletionModel([77createSuggestItem('foo', 3),78createSuggestItem('Foo', 3),79createSuggestItem('foo', 2),80], 1, {81leadingLineContent: 'foo',82characterCountDelta: 083}, WordDistance.None, EditorOptions.suggest.defaultValue, EditorOptions.snippetSuggestions.defaultValue, undefined);84});8586ensureNoDisposablesAreLeakedInTestSuite();8788test('filtering - cached', function () {8990const itemsNow = model.items;91let itemsThen = model.items;92assert.ok(itemsNow === itemsThen);9394// still the same context95model.lineContext = { leadingLineContent: 'foo', characterCountDelta: 0 };96itemsThen = model.items;97assert.ok(itemsNow === itemsThen);9899// different context, refilter100model.lineContext = { leadingLineContent: 'foo1', characterCountDelta: 1 };101itemsThen = model.items;102assert.ok(itemsNow !== itemsThen);103});104105106test('complete/incomplete', () => {107108assert.strictEqual(model.getIncompleteProvider().size, 0);109110const incompleteModel = new CompletionModel([111createSuggestItem('foo', 3, undefined, true),112createSuggestItem('foo', 2),113], 1, {114leadingLineContent: 'foo',115characterCountDelta: 0116}, WordDistance.None, EditorOptions.suggest.defaultValue, EditorOptions.snippetSuggestions.defaultValue, undefined);117assert.strictEqual(incompleteModel.getIncompleteProvider().size, 1);118});119120test('Fuzzy matching of snippets stopped working with inline snippet suggestions #49895', function () {121const completeItem1 = createSuggestItem('foobar1', 1, undefined, false, { lineNumber: 1, column: 2 });122const completeItem2 = createSuggestItem('foobar2', 1, undefined, false, { lineNumber: 1, column: 2 });123const completeItem3 = createSuggestItem('foobar3', 1, undefined, false, { lineNumber: 1, column: 2 });124const completeItem4 = createSuggestItem('foobar4', 1, undefined, false, { lineNumber: 1, column: 2 });125const completeItem5 = createSuggestItem('foobar5', 1, undefined, false, { lineNumber: 1, column: 2 });126const incompleteItem1 = createSuggestItem('foofoo1', 1, undefined, true, { lineNumber: 1, column: 2 });127128const model = new CompletionModel(129[130completeItem1,131completeItem2,132completeItem3,133completeItem4,134completeItem5,135incompleteItem1,136], 2, { leadingLineContent: 'f', characterCountDelta: 0 }, WordDistance.None, EditorOptions.suggest.defaultValue, EditorOptions.snippetSuggestions.defaultValue, undefined137);138assert.strictEqual(model.getIncompleteProvider().size, 1);139assert.strictEqual(model.items.length, 6);140});141142test('proper current word when length=0, #16380', function () {143144model = new CompletionModel([145createSuggestItem(' </div', 4),146createSuggestItem('a', 0),147createSuggestItem('p', 0),148createSuggestItem(' </tag', 4),149createSuggestItem(' XYZ', 4),150], 1, {151leadingLineContent: ' <',152characterCountDelta: 0153}, WordDistance.None, EditorOptions.suggest.defaultValue, EditorOptions.snippetSuggestions.defaultValue, undefined);154155assert.strictEqual(model.items.length, 4);156157const [a, b, c, d] = model.items;158assert.strictEqual(a.completion.label, ' </div');159assert.strictEqual(b.completion.label, ' </tag');160assert.strictEqual(c.completion.label, 'a');161assert.strictEqual(d.completion.label, 'p');162});163164test('keep snippet sorting with prefix: top, #25495', function () {165166model = new CompletionModel([167createSuggestItem('Snippet1', 1, languages.CompletionItemKind.Snippet),168createSuggestItem('tnippet2', 1, languages.CompletionItemKind.Snippet),169createSuggestItem('semver', 1, languages.CompletionItemKind.Property),170], 1, {171leadingLineContent: 's',172characterCountDelta: 0173}, WordDistance.None, defaultOptions, 'top', undefined);174175assert.strictEqual(model.items.length, 2);176const [a, b] = model.items;177assert.strictEqual(a.completion.label, 'Snippet1');178assert.strictEqual(b.completion.label, 'semver');179assert.ok(a.score < b.score); // snippet really promoted180181});182183test('keep snippet sorting with prefix: bottom, #25495', function () {184185model = new CompletionModel([186createSuggestItem('snippet1', 1, languages.CompletionItemKind.Snippet),187createSuggestItem('tnippet2', 1, languages.CompletionItemKind.Snippet),188createSuggestItem('Semver', 1, languages.CompletionItemKind.Property),189], 1, {190leadingLineContent: 's',191characterCountDelta: 0192}, WordDistance.None, defaultOptions, 'bottom', undefined);193194assert.strictEqual(model.items.length, 2);195const [a, b] = model.items;196assert.strictEqual(a.completion.label, 'Semver');197assert.strictEqual(b.completion.label, 'snippet1');198assert.ok(a.score < b.score); // snippet really demoted199});200201test('keep snippet sorting with prefix: inline, #25495', function () {202203model = new CompletionModel([204createSuggestItem('snippet1', 1, languages.CompletionItemKind.Snippet),205createSuggestItem('tnippet2', 1, languages.CompletionItemKind.Snippet),206createSuggestItem('Semver', 1),207], 1, {208leadingLineContent: 's',209characterCountDelta: 0210}, WordDistance.None, defaultOptions, 'inline', undefined);211212assert.strictEqual(model.items.length, 2);213const [a, b] = model.items;214assert.strictEqual(a.completion.label, 'snippet1');215assert.strictEqual(b.completion.label, 'Semver');216assert.ok(a.score > b.score); // snippet really demoted217});218219test('filterText seems ignored in autocompletion, #26874', function () {220221const item1 = createSuggestItem('Map - java.util', 1, undefined, undefined, undefined, undefined, 'Map');222const item2 = createSuggestItem('Map - java.util', 1);223224model = new CompletionModel([item1, item2], 1, {225leadingLineContent: 'M',226characterCountDelta: 0227}, WordDistance.None, EditorOptions.suggest.defaultValue, EditorOptions.snippetSuggestions.defaultValue, undefined);228229assert.strictEqual(model.items.length, 2);230231model.lineContext = {232leadingLineContent: 'Map ',233characterCountDelta: 3234};235assert.strictEqual(model.items.length, 1);236});237238test('Vscode 1.12 no longer obeys \'sortText\' in completion items (from language server), #26096', function () {239240const item1 = createSuggestItem('<- groups', 2, languages.CompletionItemKind.Property, false, { lineNumber: 1, column: 3 }, '00002', ' groups');241const item2 = createSuggestItem('source', 0, languages.CompletionItemKind.Property, false, { lineNumber: 1, column: 3 }, '00001', 'source');242const items = [item1, item2].sort(getSuggestionComparator(SnippetSortOrder.Inline));243244model = new CompletionModel(items, 3, {245leadingLineContent: ' ',246characterCountDelta: 0247}, WordDistance.None, EditorOptions.suggest.defaultValue, EditorOptions.snippetSuggestions.defaultValue, undefined);248249assert.strictEqual(model.items.length, 2);250251const [first, second] = model.items;252assert.strictEqual(first.completion.label, 'source');253assert.strictEqual(second.completion.label, '<- groups');254});255256test('Completion item sorting broken when using label details #153026', function () {257const itemZZZ = createSuggestItem({ label: 'ZZZ' }, 0, languages.CompletionItemKind.Operator, false);258const itemAAA = createSuggestItem({ label: 'AAA' }, 0, languages.CompletionItemKind.Operator, false);259const itemIII = createSuggestItem('III', 0, languages.CompletionItemKind.Operator, false);260261const cmp = getSuggestionComparator(SnippetSortOrder.Inline);262const actual = [itemZZZ, itemAAA, itemIII].sort(cmp);263264assert.deepStrictEqual(actual, [itemAAA, itemIII, itemZZZ]);265});266267test('Score only filtered items when typing more, score all when typing less', function () {268model = new CompletionModel([269createSuggestItem('console', 0),270createSuggestItem('co_new', 0),271createSuggestItem('bar', 0),272createSuggestItem('car', 0),273createSuggestItem('foo', 0),274], 1, {275leadingLineContent: '',276characterCountDelta: 0277}, WordDistance.None, EditorOptions.suggest.defaultValue, EditorOptions.snippetSuggestions.defaultValue, undefined);278279assert.strictEqual(model.items.length, 5);280281// narrow down once282model.lineContext = { leadingLineContent: 'c', characterCountDelta: 1 };283assert.strictEqual(model.items.length, 3);284285// query gets longer, narrow down the narrow-down'ed-set from before286model.lineContext = { leadingLineContent: 'cn', characterCountDelta: 2 };287assert.strictEqual(model.items.length, 2);288289// query gets shorter, refilter everything290model.lineContext = { leadingLineContent: '', characterCountDelta: 0 };291assert.strictEqual(model.items.length, 5);292});293294test('Have more relaxed suggest matching algorithm #15419', function () {295model = new CompletionModel([296createSuggestItem('result', 0),297createSuggestItem('replyToUser', 0),298createSuggestItem('randomLolut', 0),299createSuggestItem('car', 0),300createSuggestItem('foo', 0),301], 1, {302leadingLineContent: '',303characterCountDelta: 0304}, WordDistance.None, EditorOptions.suggest.defaultValue, EditorOptions.snippetSuggestions.defaultValue, undefined);305306// query gets longer, narrow down the narrow-down'ed-set from before307model.lineContext = { leadingLineContent: 'rlut', characterCountDelta: 4 };308assert.strictEqual(model.items.length, 3);309310const [first, second, third] = model.items;311assert.strictEqual(first.completion.label, 'result'); // best with `rult`312assert.strictEqual(second.completion.label, 'replyToUser'); // best with `rltu`313assert.strictEqual(third.completion.label, 'randomLolut'); // best with `rlut`314});315316test('Emmet suggestion not appearing at the top of the list in jsx files, #39518', function () {317model = new CompletionModel([318createSuggestItem('from', 0),319createSuggestItem('form', 0),320createSuggestItem('form:get', 0),321createSuggestItem('testForeignMeasure', 0),322createSuggestItem('fooRoom', 0),323], 1, {324leadingLineContent: '',325characterCountDelta: 0326}, WordDistance.None, EditorOptions.suggest.defaultValue, EditorOptions.snippetSuggestions.defaultValue, undefined);327328model.lineContext = { leadingLineContent: 'form', characterCountDelta: 4 };329assert.strictEqual(model.items.length, 5);330const [first, second, third] = model.items;331assert.strictEqual(first.completion.label, 'form'); // best with `form`332assert.strictEqual(second.completion.label, 'form:get'); // best with `form`333assert.strictEqual(third.completion.label, 'from'); // best with `from`334});335});336337338