completionModel.test.ts 11.0 KB
Newer Older
1 2 3 4 5 6 7
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/
'use strict';

import * as assert from 'assert';
J
Johannes Rieken 已提交
8
import { IPosition } from 'vs/editor/common/core/position';
9
import { CompletionList, CompletionItemProvider, CompletionItem, CompletionKind } from 'vs/editor/common/modes';
J
Johannes Rieken 已提交
10 11
import { CompletionModel } from 'vs/editor/contrib/suggest/completionModel';
import { ISuggestionItem, getSuggestionComparator } from 'vs/editor/contrib/suggest/suggest';
J
Johannes Rieken 已提交
12
import { WordDistance } from 'vs/editor/contrib/suggest/wordDistance';
13

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

16
	return new class implements ISuggestionItem {
17

18
		position = position;
19

20
		suggestion: CompletionItem = {
21 22 23
			label,
			overwriteBefore,
			insertText: label,
24
			kind
25
		};
26

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

32
		support: CompletionItemProvider = {
33 34
			provideCompletionItems(): any {
				return;
35 36
			}
		};
37

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

45 46 47 48 49 50 51 52 53

	let model: CompletionModel;

	setup(function () {

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

	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);
	});

77

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

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

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

92
	test('replaceIncomplete', () => {
93

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

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

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

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

J
Johannes Rieken 已提交
110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125
	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 已提交
126
			], 2, { leadingLineContent: 'f', characterCountDelta: 0 }, WordDistance.None
J
Johannes Rieken 已提交
127 128 129 130 131 132 133 134 135 136 137 138
		);
		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);
		assert.ok(incomplete.has(incompleteItem1.support));
		assert.equal(complete.length, 5);
	});

J
Johannes Rieken 已提交
139 140 141 142 143 144 145 146 147 148 149
	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 已提交
150
			}, WordDistance.None);
J
Johannes Rieken 已提交
151 152 153 154 155

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

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

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

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

		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([
183 184 185
			createSuggestItem('snippet1', 1, CompletionKind.Snippet),
			createSuggestItem('tnippet2', 1, CompletionKind.Snippet),
			createSuggestItem('Semver', 1, CompletionKind.Property),
J
Johannes Rieken 已提交
186 187 188
		], 1, {
				leadingLineContent: 's',
				characterCountDelta: 0
J
Johannes Rieken 已提交
189
			}, WordDistance.None, { snippets: 'bottom', snippetsPreventQuickSuggestions: true, filterGraceful: true, localityBonus: false });
J
Johannes Rieken 已提交
190 191 192 193 194 195 196 197 198 199 200

		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([
201 202
			createSuggestItem('snippet1', 1, CompletionKind.Snippet),
			createSuggestItem('tnippet2', 1, CompletionKind.Snippet),
203
			createSuggestItem('Semver', 1),
J
Johannes Rieken 已提交
204 205 206
		], 1, {
				leadingLineContent: 's',
				characterCountDelta: 0
J
Johannes Rieken 已提交
207
			}, WordDistance.None, { snippets: 'inline', snippetsPreventQuickSuggestions: true, filterGraceful: true, localityBonus: false });
J
Johannes Rieken 已提交
208 209 210 211 212 213 214

		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
	});
215 216 217

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

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

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

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

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

	test('Vscode 1.12 no longer obeys \'sortText\' in completion items (from language server), #26096', function () {

238
		const item1 = createSuggestItem('<- groups', 2, CompletionKind.Property, false, { lineNumber: 1, column: 3 });
239 240 241
		item1.suggestion.filterText = '  groups';
		item1.suggestion.sortText = '00002';

242
		const item2 = createSuggestItem('source', 0, CompletionKind.Property, false, { lineNumber: 1, column: 3 });
243 244 245 246 247 248 249 250
		item2.suggestion.filterText = 'source';
		item2.suggestion.sortText = '00001';

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

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

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

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

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

		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);
	});
286 287 288

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

		// 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 已提交
308 309 310

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

		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`
	});
328
});