Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/editor/contrib/smartSelect/test/browser/smartSelect.test.ts
4780 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
import assert from 'assert';
6
import { CancellationToken } from '../../../../../base/common/cancellation.js';
7
import { Event } from '../../../../../base/common/event.js';
8
import { DisposableStore } from '../../../../../base/common/lifecycle.js';
9
import { URI } from '../../../../../base/common/uri.js';
10
import { Position } from '../../../../common/core/position.js';
11
import { IRange, Range } from '../../../../common/core/range.js';
12
import { SelectionRangeProvider } from '../../../../common/languages.js';
13
import { ILanguageConfigurationService } from '../../../../common/languages/languageConfigurationRegistry.js';
14
import { IModelService } from '../../../../common/services/model.js';
15
import { BracketSelectionRangeProvider } from '../../browser/bracketSelections.js';
16
import { provideSelectionRanges } from '../../browser/smartSelect.js';
17
import { WordSelectionRangeProvider } from '../../browser/wordSelections.js';
18
import { createModelServices } from '../../../../test/common/testTextModel.js';
19
import { javascriptOnEnterRules } from '../../../../test/common/modes/supports/onEnterRules.js';
20
import { LanguageFeatureRegistry } from '../../../../common/languageFeatureRegistry.js';
21
import { ILanguageSelection, ILanguageService } from '../../../../common/languages/language.js';
22
import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js';
23
24
class StaticLanguageSelector implements ILanguageSelection {
25
readonly onDidChange: Event<string> = Event.None;
26
constructor(public readonly languageId: string) { }
27
}
28
29
suite('SmartSelect', () => {
30
31
const OriginalBracketSelectionRangeProviderMaxDuration = BracketSelectionRangeProvider._maxDuration;
32
33
suiteSetup(() => {
34
BracketSelectionRangeProvider._maxDuration = 5000; // 5 seconds
35
});
36
37
suiteTeardown(() => {
38
BracketSelectionRangeProvider._maxDuration = OriginalBracketSelectionRangeProviderMaxDuration;
39
});
40
41
const languageId = 'mockJSMode';
42
let disposables: DisposableStore;
43
let modelService: IModelService;
44
const providers = new LanguageFeatureRegistry<SelectionRangeProvider>();
45
46
setup(() => {
47
disposables = new DisposableStore();
48
const instantiationService = createModelServices(disposables);
49
modelService = instantiationService.get(IModelService);
50
const languagConfigurationService = instantiationService.get(ILanguageConfigurationService);
51
const languageService = instantiationService.get(ILanguageService);
52
disposables.add(languageService.registerLanguage({ id: languageId }));
53
disposables.add(languagConfigurationService.register(languageId, {
54
brackets: [
55
['(', ')'],
56
['{', '}'],
57
['[', ']']
58
],
59
onEnterRules: javascriptOnEnterRules,
60
wordPattern: /(-?\d*\.\d\w*)|([^\`\~\!\@\#\$\%\^\&\*\(\)\=\+\[\{\]\}\\\;\:\'\"\,\.\<\>\/\?\s]+)/g
61
}));
62
});
63
64
teardown(() => {
65
disposables.dispose();
66
});
67
68
ensureNoDisposablesAreLeakedInTestSuite();
69
70
async function assertGetRangesToPosition(text: string[], lineNumber: number, column: number, ranges: Range[], selectLeadingAndTrailingWhitespace = true): Promise<void> {
71
const uri = URI.file('test.js');
72
const model = modelService.createModel(text.join('\n'), new StaticLanguageSelector(languageId), uri);
73
const [actual] = await provideSelectionRanges(providers, model, [new Position(lineNumber, column)], { selectLeadingAndTrailingWhitespace, selectSubwords: true }, CancellationToken.None);
74
const actualStr = actual.map(r => new Range(r.startLineNumber, r.startColumn, r.endLineNumber, r.endColumn).toString());
75
const desiredStr = ranges.reverse().map(r => String(r));
76
77
assert.deepStrictEqual(actualStr, desiredStr, `\nA: ${actualStr} VS \nE: ${desiredStr}`);
78
modelService.destroyModel(uri);
79
}
80
81
test('getRangesToPosition #1', () => {
82
83
return assertGetRangesToPosition([
84
'function a(bar, foo){',
85
'\tif (bar) {',
86
'\t\treturn (bar + (2 * foo))',
87
'\t}',
88
'}'
89
], 3, 20, [
90
new Range(1, 1, 5, 2), // all
91
new Range(1, 21, 5, 2), // {} outside
92
new Range(1, 22, 5, 1), // {} inside
93
new Range(2, 1, 4, 3), // block
94
new Range(2, 1, 4, 3),
95
new Range(2, 2, 4, 3),
96
new Range(2, 11, 4, 3),
97
new Range(2, 12, 4, 2),
98
new Range(3, 1, 3, 27), // line w/ triva
99
new Range(3, 3, 3, 27), // line w/o triva
100
new Range(3, 10, 3, 27), // () outside
101
new Range(3, 11, 3, 26), // () inside
102
new Range(3, 17, 3, 26), // () outside
103
new Range(3, 18, 3, 25), // () inside
104
]);
105
});
106
107
test('config: selectLeadingAndTrailingWhitespace', async () => {
108
109
await assertGetRangesToPosition([
110
'aaa',
111
'\tbbb',
112
''
113
], 2, 3, [
114
new Range(1, 1, 3, 1), // all
115
new Range(2, 1, 2, 5), // line w/ triva
116
new Range(2, 2, 2, 5), // bbb
117
], true);
118
119
await assertGetRangesToPosition([
120
'aaa',
121
'\tbbb',
122
''
123
], 2, 3, [
124
new Range(1, 1, 3, 1), // all
125
new Range(2, 2, 2, 5), // () inside
126
], false);
127
});
128
129
test('getRangesToPosition #56886. Skip empty lines correctly.', () => {
130
131
return assertGetRangesToPosition([
132
'function a(bar, foo){',
133
'\tif (bar) {',
134
'',
135
'\t}',
136
'}'
137
], 3, 1, [
138
new Range(1, 1, 5, 2),
139
new Range(1, 21, 5, 2),
140
new Range(1, 22, 5, 1),
141
new Range(2, 1, 4, 3),
142
new Range(2, 1, 4, 3),
143
new Range(2, 2, 4, 3),
144
new Range(2, 11, 4, 3),
145
new Range(2, 12, 4, 2),
146
]);
147
});
148
149
test('getRangesToPosition #56886. Do not skip lines with only whitespaces.', () => {
150
151
return assertGetRangesToPosition([
152
'function a(bar, foo){',
153
'\tif (bar) {',
154
' ',
155
'\t}',
156
'}'
157
], 3, 1, [
158
new Range(1, 1, 5, 2), // all
159
new Range(1, 21, 5, 2), // {} outside
160
new Range(1, 22, 5, 1), // {} inside
161
new Range(2, 1, 4, 3),
162
new Range(2, 1, 4, 3),
163
new Range(2, 2, 4, 3),
164
new Range(2, 11, 4, 3),
165
new Range(2, 12, 4, 2),
166
new Range(3, 1, 3, 2), // block
167
new Range(3, 1, 3, 2) // empty line
168
]);
169
});
170
171
test('getRangesToPosition #40658. Cursor at first position inside brackets should select line inside.', () => {
172
173
return assertGetRangesToPosition([
174
' [ ]',
175
' { } ',
176
'( ) '
177
], 2, 3, [
178
new Range(1, 1, 3, 5),
179
new Range(2, 1, 2, 6), // line w/ triava
180
new Range(2, 2, 2, 5), // {} inside, line w/o triva
181
new Range(2, 3, 2, 4) // {} inside
182
]);
183
});
184
185
test('getRangesToPosition #40658. Cursor in empty brackets should reveal brackets first.', () => {
186
187
return assertGetRangesToPosition([
188
' [] ',
189
' { } ',
190
' ( ) '
191
], 1, 3, [
192
new Range(1, 1, 3, 7), // all
193
new Range(1, 1, 1, 5), // line w/ trival
194
new Range(1, 2, 1, 4), // [] outside, line w/o trival
195
new Range(1, 3, 1, 3), // [] inside
196
]);
197
});
198
199
test('getRangesToPosition #40658. Tokens before bracket will be revealed first.', () => {
200
201
return assertGetRangesToPosition([
202
' [] ',
203
' { } ',
204
'selectthis( ) '
205
], 3, 11, [
206
new Range(1, 1, 3, 15), // all
207
new Range(3, 1, 3, 15), // line w/ trivia
208
new Range(3, 1, 3, 14), // line w/o trivia
209
new Range(3, 1, 3, 11) // word
210
]);
211
});
212
213
// -- bracket selections
214
215
async function assertRanges(provider: SelectionRangeProvider, value: string, ...expected: IRange[]): Promise<void> {
216
const index = value.indexOf('|');
217
value = value.replace('|', ''); // CodeQL [SM02383] js/incomplete-sanitization this is purpose only the first | character
218
219
const model = modelService.createModel(value, new StaticLanguageSelector(languageId), URI.parse('fake:lang'));
220
const pos = model.getPositionAt(index);
221
const all = await provider.provideSelectionRanges(model, [pos], CancellationToken.None);
222
const ranges = all![0];
223
224
modelService.destroyModel(model.uri);
225
226
assert.strictEqual(expected.length, ranges.length);
227
for (const range of ranges) {
228
const exp = expected.shift() || null;
229
assert.ok(Range.equalsRange(range.range, exp), `A=${range.range} <> E=${exp}`);
230
}
231
}
232
233
test('bracket selection', async () => {
234
await assertRanges(new BracketSelectionRangeProvider(), '(|)',
235
new Range(1, 2, 1, 2), new Range(1, 1, 1, 3)
236
);
237
238
await assertRanges(new BracketSelectionRangeProvider(), '[[[](|)]]',
239
new Range(1, 6, 1, 6), new Range(1, 5, 1, 7), // ()
240
new Range(1, 3, 1, 7), new Range(1, 2, 1, 8), // [[]()]
241
new Range(1, 2, 1, 8), new Range(1, 1, 1, 9), // [[[]()]]
242
);
243
244
await assertRanges(new BracketSelectionRangeProvider(), '[a[](|)a]',
245
new Range(1, 6, 1, 6), new Range(1, 5, 1, 7),
246
new Range(1, 2, 1, 8), new Range(1, 1, 1, 9),
247
);
248
249
// no bracket
250
await assertRanges(new BracketSelectionRangeProvider(), 'fofof|fofo');
251
252
// empty
253
await assertRanges(new BracketSelectionRangeProvider(), '[[[]()]]|');
254
await assertRanges(new BracketSelectionRangeProvider(), '|[[[]()]]');
255
256
// edge
257
await assertRanges(new BracketSelectionRangeProvider(), '[|[[]()]]', new Range(1, 2, 1, 8), new Range(1, 1, 1, 9));
258
await assertRanges(new BracketSelectionRangeProvider(), '[[[]()]|]', new Range(1, 2, 1, 8), new Range(1, 1, 1, 9));
259
260
await assertRanges(new BracketSelectionRangeProvider(), 'aaa(aaa)bbb(b|b)ccc(ccc)', new Range(1, 13, 1, 15), new Range(1, 12, 1, 16));
261
await assertRanges(new BracketSelectionRangeProvider(), '(aaa(aaa)bbb(b|b)ccc(ccc))', new Range(1, 14, 1, 16), new Range(1, 13, 1, 17), new Range(1, 2, 1, 25), new Range(1, 1, 1, 26));
262
});
263
264
test('bracket with leading/trailing', async () => {
265
266
await assertRanges(new BracketSelectionRangeProvider(), 'for(a of b){\n foo(|);\n}',
267
new Range(2, 7, 2, 7), new Range(2, 6, 2, 8),
268
new Range(1, 13, 3, 1), new Range(1, 12, 3, 2),
269
new Range(1, 1, 3, 2), new Range(1, 1, 3, 2),
270
);
271
272
await assertRanges(new BracketSelectionRangeProvider(), 'for(a of b)\n{\n foo(|);\n}',
273
new Range(3, 7, 3, 7), new Range(3, 6, 3, 8),
274
new Range(2, 2, 4, 1), new Range(2, 1, 4, 2),
275
new Range(1, 1, 4, 2), new Range(1, 1, 4, 2),
276
);
277
});
278
279
test('in-word ranges', async () => {
280
281
await assertRanges(new WordSelectionRangeProvider(), 'f|ooBar',
282
new Range(1, 1, 1, 4), // foo
283
new Range(1, 1, 1, 7), // fooBar
284
new Range(1, 1, 1, 7), // doc
285
);
286
287
await assertRanges(new WordSelectionRangeProvider(), 'f|oo_Ba',
288
new Range(1, 1, 1, 4),
289
new Range(1, 1, 1, 7),
290
new Range(1, 1, 1, 7),
291
);
292
293
await assertRanges(new WordSelectionRangeProvider(), 'f|oo-Ba',
294
new Range(1, 1, 1, 4),
295
new Range(1, 1, 1, 7),
296
new Range(1, 1, 1, 7),
297
);
298
});
299
300
test('in-word ranges with selectSubwords=false', async () => {
301
302
await assertRanges(new WordSelectionRangeProvider(false), 'f|ooBar',
303
new Range(1, 1, 1, 7),
304
new Range(1, 1, 1, 7),
305
);
306
307
await assertRanges(new WordSelectionRangeProvider(false), 'f|oo_Ba',
308
new Range(1, 1, 1, 7),
309
new Range(1, 1, 1, 7),
310
);
311
312
await assertRanges(new WordSelectionRangeProvider(false), 'f|oo-Ba',
313
new Range(1, 1, 1, 7),
314
new Range(1, 1, 1, 7),
315
);
316
});
317
318
test('Default selection should select current word/hump first in camelCase #67493', async function () {
319
320
await assertRanges(new WordSelectionRangeProvider(), 'Abs|tractSmartSelect',
321
new Range(1, 1, 1, 9),
322
new Range(1, 1, 1, 20),
323
new Range(1, 1, 1, 20),
324
);
325
326
await assertRanges(new WordSelectionRangeProvider(), 'AbstractSma|rtSelect',
327
new Range(1, 9, 1, 14),
328
new Range(1, 1, 1, 20),
329
new Range(1, 1, 1, 20),
330
);
331
332
await assertRanges(new WordSelectionRangeProvider(), 'Abstrac-Sma|rt-elect',
333
new Range(1, 9, 1, 14),
334
new Range(1, 1, 1, 20),
335
new Range(1, 1, 1, 20),
336
);
337
338
await assertRanges(new WordSelectionRangeProvider(), 'Abstrac_Sma|rt_elect',
339
new Range(1, 9, 1, 14),
340
new Range(1, 1, 1, 20),
341
new Range(1, 1, 1, 20),
342
);
343
344
await assertRanges(new WordSelectionRangeProvider(), 'Abstrac_Sma|rt-elect',
345
new Range(1, 9, 1, 14),
346
new Range(1, 1, 1, 20),
347
new Range(1, 1, 1, 20),
348
);
349
350
await assertRanges(new WordSelectionRangeProvider(), 'Abstrac_Sma|rtSelect',
351
new Range(1, 9, 1, 14),
352
new Range(1, 1, 1, 20),
353
new Range(1, 1, 1, 20),
354
);
355
});
356
357
test('Smart select: only add line ranges if they\'re contained by the next range #73850', async function () {
358
359
const reg = providers.register('*', {
360
provideSelectionRanges() {
361
return [[
362
{ range: { startLineNumber: 1, startColumn: 10, endLineNumber: 1, endColumn: 11 } },
363
{ range: { startLineNumber: 1, startColumn: 10, endLineNumber: 3, endColumn: 2 } },
364
{ range: { startLineNumber: 1, startColumn: 1, endLineNumber: 3, endColumn: 2 } },
365
]];
366
}
367
});
368
369
await assertGetRangesToPosition(['type T = {', '\tx: number', '}'], 1, 10, [
370
new Range(1, 1, 3, 2), // all
371
new Range(1, 10, 3, 2), // { ... }
372
new Range(1, 10, 1, 11), // {
373
]);
374
375
reg.dispose();
376
});
377
378
test('Expand selection in words with underscores is inconsistent #90589', async function () {
379
380
await assertRanges(new WordSelectionRangeProvider(), 'Hel|lo_World',
381
new Range(1, 1, 1, 6),
382
new Range(1, 1, 1, 12),
383
new Range(1, 1, 1, 12),
384
);
385
386
await assertRanges(new WordSelectionRangeProvider(), 'Hello_Wo|rld',
387
new Range(1, 7, 1, 12),
388
new Range(1, 1, 1, 12),
389
new Range(1, 1, 1, 12),
390
);
391
392
await assertRanges(new WordSelectionRangeProvider(), 'Hello|_World',
393
new Range(1, 1, 1, 6),
394
new Range(1, 1, 1, 12),
395
new Range(1, 1, 1, 12),
396
);
397
398
await assertRanges(new WordSelectionRangeProvider(), 'Hello_|World',
399
new Range(1, 7, 1, 12),
400
new Range(1, 1, 1, 12),
401
new Range(1, 1, 1, 12),
402
);
403
404
await assertRanges(new WordSelectionRangeProvider(), 'Hello|-World',
405
new Range(1, 1, 1, 6),
406
new Range(1, 1, 1, 12),
407
new Range(1, 1, 1, 12),
408
);
409
410
await assertRanges(new WordSelectionRangeProvider(), 'Hello-|World',
411
new Range(1, 7, 1, 12),
412
new Range(1, 1, 1, 12),
413
new Range(1, 1, 1, 12),
414
);
415
416
await assertRanges(new WordSelectionRangeProvider(), 'Hello|World',
417
new Range(1, 6, 1, 11),
418
new Range(1, 1, 1, 11),
419
new Range(1, 1, 1, 11),
420
);
421
});
422
});
423
424