completionModel.test.ts 11.4 KB
Newer Older
1 2 3 4 5
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
J
Johannes Rieken 已提交
6
import { IPosition } from 'vs/editor/common/core/position';
7
import { CompletionList, CompletionItemProvider, CompletionItem, CompletionItemKind } from 'vs/editor/common/modes';
J
Johannes Rieken 已提交
8
import { CompletionModel } from 'vs/editor/contrib/suggest/completionModel';
J
Johannes Rieken 已提交
9
import { ISuggestionItem, getSuggestionComparator, ensureLowerCaseVariants } from 'vs/editor/contrib/suggest/suggest';
J
Johannes Rieken 已提交
10
import { WordDistance } from 'vs/editor/contrib/suggest/wordDistance';
11

12
export function createSuggestItem(label: string, overwriteBefore: number, kind = CompletionItemKind.Property, incomplete: boolean = false, position: IPosition = { lineNumber: 1, column: 1 }): ISuggestionItem {
13

14
	return new class implements ISuggestionItem {
15

16
		position = position;
17

18
		suggestion: CompletionItem = {
19
			label,
20
			range: { startLineNumber: position.lineNumber, startColumn: position.column - overwriteBefore, endLineNumber: position.lineNumber, endColumn: position.column },
21
			insertText: label,
22
			kind
23
		};
24

25
		container: CompletionList = {
26 27 28
			incomplete,
			suggestions: [this.suggestion]
		};
29

J
Johannes Rieken 已提交
30
		provider: CompletionItemProvider = {
31 32
			provideCompletionItems(): any {
				return;
33 34
			}
		};
35

36
		resolve(): Promise<void> {
37 38 39 40 41 42
			return null;
		}
	};
}
suite('CompletionModel', function () {

43 44 45 46 47 48 49 50 51

	let model: CompletionModel;

	setup(function () {

		model = new CompletionModel([
			createSuggestItem('foo', 3),
			createSuggestItem('Foo', 3),
			createSuggestItem('foo', 2),
52
		], 1, {
J
Johannes Rieken 已提交
53 54
				leadingLineContent: 'foo',
				characterCountDelta: 0
J
Johannes Rieken 已提交
55
			}, WordDistance.None);
56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
	});

	test('filtering - cached', function () {

		const itemsNow = model.items;
		let itemsThen = model.items;
		assert.ok(itemsNow === itemsThen);

		// still the same context
		model.lineContext = { leadingLineContent: 'foo', characterCountDelta: 0 };
		itemsThen = model.items;
		assert.ok(itemsNow === itemsThen);

		// different context, refilter
		model.lineContext = { leadingLineContent: 'foo1', characterCountDelta: 1 };
		itemsThen = model.items;
		assert.ok(itemsNow !== itemsThen);
	});

75

76
	test('complete/incomplete', () => {
J
Johannes Rieken 已提交
77

J
Johannes Rieken 已提交
78
		assert.equal(model.incomplete.size, 0);
J
Johannes Rieken 已提交
79 80

		let incompleteModel = new CompletionModel([
J
Johannes Rieken 已提交
81
			createSuggestItem('foo', 3, undefined, true),
J
Johannes Rieken 已提交
82
			createSuggestItem('foo', 2),
83
		], 1, {
J
Johannes Rieken 已提交
84 85
				leadingLineContent: 'foo',
				characterCountDelta: 0
J
Johannes Rieken 已提交
86
			}, WordDistance.None);
J
Johannes Rieken 已提交
87
		assert.equal(incompleteModel.incomplete.size, 1);
J
Johannes Rieken 已提交
88
	});
89

90
	test('replaceIncomplete', () => {
91

J
Johannes Rieken 已提交
92 93
		const completeItem = createSuggestItem('foobar', 1, undefined, false, { lineNumber: 1, column: 2 });
		const incompleteItem = createSuggestItem('foofoo', 1, undefined, true, { lineNumber: 1, column: 2 });
94

J
Johannes Rieken 已提交
95
		const model = new CompletionModel([completeItem, incompleteItem], 2, { leadingLineContent: 'f', characterCountDelta: 0 }, WordDistance.None);
J
Johannes Rieken 已提交
96
		assert.equal(model.incomplete.size, 1);
97 98
		assert.equal(model.items.length, 2);

J
Johannes Rieken 已提交
99 100
		const { incomplete } = model;
		const complete = model.adopt(incomplete);
J
Johannes Rieken 已提交
101

J
Johannes Rieken 已提交
102
		assert.equal(incomplete.size, 1);
J
Johannes Rieken 已提交
103
		assert.ok(incomplete.has(incompleteItem.provider));
J
Johannes Rieken 已提交
104 105
		assert.equal(complete.length, 1);
		assert.ok(complete[0] === completeItem);
106
	});
107

J
Johannes Rieken 已提交
108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123
	test('Fuzzy matching of snippets stopped working with inline snippet suggestions #49895', function () {
		const completeItem1 = createSuggestItem('foobar1', 1, undefined, false, { lineNumber: 1, column: 2 });
		const completeItem2 = createSuggestItem('foobar2', 1, undefined, false, { lineNumber: 1, column: 2 });
		const completeItem3 = createSuggestItem('foobar3', 1, undefined, false, { lineNumber: 1, column: 2 });
		const completeItem4 = createSuggestItem('foobar4', 1, undefined, false, { lineNumber: 1, column: 2 });
		const completeItem5 = createSuggestItem('foobar5', 1, undefined, false, { lineNumber: 1, column: 2 });
		const incompleteItem1 = createSuggestItem('foofoo1', 1, undefined, true, { lineNumber: 1, column: 2 });

		const model = new CompletionModel(
			[
				completeItem1,
				completeItem2,
				completeItem3,
				completeItem4,
				completeItem5,
				incompleteItem1,
J
Johannes Rieken 已提交
124
			], 2, { leadingLineContent: 'f', characterCountDelta: 0 }, WordDistance.None
J
Johannes Rieken 已提交
125 126 127 128 129 130 131 132
		);
		assert.equal(model.incomplete.size, 1);
		assert.equal(model.items.length, 6);

		const { incomplete } = model;
		const complete = model.adopt(incomplete);

		assert.equal(incomplete.size, 1);
J
Johannes Rieken 已提交
133
		assert.ok(incomplete.has(incompleteItem1.provider));
J
Johannes Rieken 已提交
134 135 136
		assert.equal(complete.length, 5);
	});

J
Johannes Rieken 已提交
137 138 139 140 141 142 143 144 145 146 147
	test('proper current word when length=0, #16380', function () {

		model = new CompletionModel([
			createSuggestItem('    </div', 4),
			createSuggestItem('a', 0),
			createSuggestItem('p', 0),
			createSuggestItem('    </tag', 4),
			createSuggestItem('    XYZ', 4),
		], 1, {
				leadingLineContent: '   <',
				characterCountDelta: 0
J
Johannes Rieken 已提交
148
			}, WordDistance.None);
J
Johannes Rieken 已提交
149 150 151 152 153

		assert.equal(model.items.length, 4);

		const [a, b, c, d] = model.items;
		assert.equal(a.suggestion.label, '    </div');
154 155 156
		assert.equal(b.suggestion.label, '    </tag');
		assert.equal(c.suggestion.label, 'a');
		assert.equal(d.suggestion.label, 'p');
J
Johannes Rieken 已提交
157 158
	});

J
Johannes Rieken 已提交
159 160 161
	test('keep snippet sorting with prefix: top, #25495', function () {

		model = new CompletionModel([
162 163 164
			createSuggestItem('Snippet1', 1, CompletionItemKind.Snippet),
			createSuggestItem('tnippet2', 1, CompletionItemKind.Snippet),
			createSuggestItem('semver', 1, CompletionItemKind.Property),
J
Johannes Rieken 已提交
165 166 167
		], 1, {
				leadingLineContent: 's',
				characterCountDelta: 0
J
Johannes Rieken 已提交
168
			}, WordDistance.None, { snippets: 'top', snippetsPreventQuickSuggestions: true, filterGraceful: true, localityBonus: false, shareSuggestSelections: false });
J
Johannes Rieken 已提交
169 170 171 172 173 174 175 176 177 178 179 180

		assert.equal(model.items.length, 2);
		const [a, b] = model.items;
		assert.equal(a.suggestion.label, 'Snippet1');
		assert.equal(b.suggestion.label, 'semver');
		assert.ok(a.score < b.score); // snippet really promoted

	});

	test('keep snippet sorting with prefix: bottom, #25495', function () {

		model = new CompletionModel([
181 182 183
			createSuggestItem('snippet1', 1, CompletionItemKind.Snippet),
			createSuggestItem('tnippet2', 1, CompletionItemKind.Snippet),
			createSuggestItem('Semver', 1, CompletionItemKind.Property),
J
Johannes Rieken 已提交
184 185 186
		], 1, {
				leadingLineContent: 's',
				characterCountDelta: 0
J
Johannes Rieken 已提交
187
			}, WordDistance.None, { snippets: 'bottom', snippetsPreventQuickSuggestions: true, filterGraceful: true, localityBonus: false, shareSuggestSelections: false });
J
Johannes Rieken 已提交
188 189 190 191 192 193 194 195 196 197 198

		assert.equal(model.items.length, 2);
		const [a, b] = model.items;
		assert.equal(a.suggestion.label, 'Semver');
		assert.equal(b.suggestion.label, 'snippet1');
		assert.ok(a.score < b.score); // snippet really demoted
	});

	test('keep snippet sorting with prefix: inline, #25495', function () {

		model = new CompletionModel([
199 200
			createSuggestItem('snippet1', 1, CompletionItemKind.Snippet),
			createSuggestItem('tnippet2', 1, CompletionItemKind.Snippet),
201
			createSuggestItem('Semver', 1),
J
Johannes Rieken 已提交
202 203 204
		], 1, {
				leadingLineContent: 's',
				characterCountDelta: 0
J
Johannes Rieken 已提交
205
			}, WordDistance.None, { snippets: 'inline', snippetsPreventQuickSuggestions: true, filterGraceful: true, localityBonus: false, shareSuggestSelections: false });
J
Johannes Rieken 已提交
206 207 208 209 210 211 212

		assert.equal(model.items.length, 2);
		const [a, b] = model.items;
		assert.equal(a.suggestion.label, 'snippet1');
		assert.equal(b.suggestion.label, 'Semver');
		assert.ok(a.score > b.score); // snippet really demoted
	});
213 214 215

	test('filterText seems ignored in autocompletion, #26874', function () {

216
		const item1 = createSuggestItem('Map - java.util', 1);
217
		item1.suggestion.filterText = 'Map';
218
		const item2 = createSuggestItem('Map - java.util', 1);
219 220 221 222

		model = new CompletionModel([item1, item2], 1, {
			leadingLineContent: 'M',
			characterCountDelta: 0
J
Johannes Rieken 已提交
223
		}, WordDistance.None);
224 225 226 227 228 229 230 231 232

		assert.equal(model.items.length, 2);

		model.lineContext = {
			leadingLineContent: 'Map ',
			characterCountDelta: 3
		};
		assert.equal(model.items.length, 1);
	});
233

J
Johannes Rieken 已提交
234
	test('Vscode 1.12 no longer obeys \'sortText\' in completion items (from language server), #26096', function () {
235

236
		const item1 = createSuggestItem('<- groups', 2, CompletionItemKind.Property, false, { lineNumber: 1, column: 3 });
237
		item1.suggestion.filterText = '  groups';
J
Johannes Rieken 已提交
238
		item1.suggestion.sortText = '00002';
239

240
		const item2 = createSuggestItem('source', 0, CompletionItemKind.Property, false, { lineNumber: 1, column: 3 });
241
		item2.suggestion.filterText = 'source';
J
Johannes Rieken 已提交
242 243 244 245
		item2.suggestion.sortText = '00001';

		ensureLowerCaseVariants(item1.suggestion);
		ensureLowerCaseVariants(item2.suggestion);
246 247 248 249 250 251

		const items = [item1, item2].sort(getSuggestionComparator('inline'));

		model = new CompletionModel(items, 3, {
			leadingLineContent: '  ',
			characterCountDelta: 0
J
Johannes Rieken 已提交
252
		}, WordDistance.None);
253 254 255 256 257 258 259 260

		assert.equal(model.items.length, 2);

		const [first, second] = model.items;
		assert.equal(first.suggestion.label, 'source');
		assert.equal(second.suggestion.label, '<- groups');
	});

261 262
	test('Score only filtered items when typing more, score all when typing less', function () {
		model = new CompletionModel([
263 264 265 266 267
			createSuggestItem('console', 0),
			createSuggestItem('co_new', 0),
			createSuggestItem('bar', 0),
			createSuggestItem('car', 0),
			createSuggestItem('foo', 0),
268 269 270
		], 1, {
				leadingLineContent: '',
				characterCountDelta: 0
J
Johannes Rieken 已提交
271
			}, WordDistance.None);
272 273 274 275 276 277 278 279 280 281 282 283 284 285 286

		assert.equal(model.items.length, 5);

		// narrow down once
		model.lineContext = { leadingLineContent: 'c', characterCountDelta: 1 };
		assert.equal(model.items.length, 3);

		// query gets longer, narrow down the narrow-down'ed-set from before
		model.lineContext = { leadingLineContent: 'cn', characterCountDelta: 2 };
		assert.equal(model.items.length, 2);

		// query gets shorter, refilter everything
		model.lineContext = { leadingLineContent: '', characterCountDelta: 0 };
		assert.equal(model.items.length, 5);
	});
287 288 289

	test('Have more relaxed suggest matching algorithm #15419', function () {
		model = new CompletionModel([
290 291 292 293 294
			createSuggestItem('result', 0),
			createSuggestItem('replyToUser', 0),
			createSuggestItem('randomLolut', 0),
			createSuggestItem('car', 0),
			createSuggestItem('foo', 0),
295 296 297
		], 1, {
				leadingLineContent: '',
				characterCountDelta: 0
J
Johannes Rieken 已提交
298
			}, WordDistance.None);
299 300 301 302 303 304 305 306 307 308

		// query gets longer, narrow down the narrow-down'ed-set from before
		model.lineContext = { leadingLineContent: 'rlut', characterCountDelta: 4 };
		assert.equal(model.items.length, 3);

		const [first, second, third] = model.items;
		assert.equal(first.suggestion.label, 'result'); // best with `rult`
		assert.equal(second.suggestion.label, 'replyToUser');  // best with `rltu`
		assert.equal(third.suggestion.label, 'randomLolut');  // best with `rlut`
	});
J
Johannes Rieken 已提交
309 310 311

	test('Emmet suggestion not appearing at the top of the list in jsx files, #39518', function () {
		model = new CompletionModel([
312 313 314 315 316
			createSuggestItem('from', 0),
			createSuggestItem('form', 0),
			createSuggestItem('form:get', 0),
			createSuggestItem('testForeignMeasure', 0),
			createSuggestItem('fooRoom', 0),
J
Johannes Rieken 已提交
317 318 319
		], 1, {
				leadingLineContent: '',
				characterCountDelta: 0
J
Johannes Rieken 已提交
320
			}, WordDistance.None);
J
Johannes Rieken 已提交
321 322 323 324 325 326 327 328

		model.lineContext = { leadingLineContent: 'form', characterCountDelta: 4 };
		assert.equal(model.items.length, 5);
		const [first, second, third] = model.items;
		assert.equal(first.suggestion.label, 'form'); // best with `form`
		assert.equal(second.suggestion.label, 'form:get');  // best with `form`
		assert.equal(third.suggestion.label, 'from');  // best with `from`
	});
329
});