Path: blob/main/src/vs/editor/contrib/smartSelect/test/browser/smartSelect.test.ts
4780 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 { CancellationToken } from '../../../../../base/common/cancellation.js';6import { Event } from '../../../../../base/common/event.js';7import { DisposableStore } from '../../../../../base/common/lifecycle.js';8import { URI } from '../../../../../base/common/uri.js';9import { Position } from '../../../../common/core/position.js';10import { IRange, Range } from '../../../../common/core/range.js';11import { SelectionRangeProvider } from '../../../../common/languages.js';12import { ILanguageConfigurationService } from '../../../../common/languages/languageConfigurationRegistry.js';13import { IModelService } from '../../../../common/services/model.js';14import { BracketSelectionRangeProvider } from '../../browser/bracketSelections.js';15import { provideSelectionRanges } from '../../browser/smartSelect.js';16import { WordSelectionRangeProvider } from '../../browser/wordSelections.js';17import { createModelServices } from '../../../../test/common/testTextModel.js';18import { javascriptOnEnterRules } from '../../../../test/common/modes/supports/onEnterRules.js';19import { LanguageFeatureRegistry } from '../../../../common/languageFeatureRegistry.js';20import { ILanguageSelection, ILanguageService } from '../../../../common/languages/language.js';21import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js';2223class StaticLanguageSelector implements ILanguageSelection {24readonly onDidChange: Event<string> = Event.None;25constructor(public readonly languageId: string) { }26}2728suite('SmartSelect', () => {2930const OriginalBracketSelectionRangeProviderMaxDuration = BracketSelectionRangeProvider._maxDuration;3132suiteSetup(() => {33BracketSelectionRangeProvider._maxDuration = 5000; // 5 seconds34});3536suiteTeardown(() => {37BracketSelectionRangeProvider._maxDuration = OriginalBracketSelectionRangeProviderMaxDuration;38});3940const languageId = 'mockJSMode';41let disposables: DisposableStore;42let modelService: IModelService;43const providers = new LanguageFeatureRegistry<SelectionRangeProvider>();4445setup(() => {46disposables = new DisposableStore();47const instantiationService = createModelServices(disposables);48modelService = instantiationService.get(IModelService);49const languagConfigurationService = instantiationService.get(ILanguageConfigurationService);50const languageService = instantiationService.get(ILanguageService);51disposables.add(languageService.registerLanguage({ id: languageId }));52disposables.add(languagConfigurationService.register(languageId, {53brackets: [54['(', ')'],55['{', '}'],56['[', ']']57],58onEnterRules: javascriptOnEnterRules,59wordPattern: /(-?\d*\.\d\w*)|([^\`\~\!\@\#\$\%\^\&\*\(\)\=\+\[\{\]\}\\\;\:\'\"\,\.\<\>\/\?\s]+)/g60}));61});6263teardown(() => {64disposables.dispose();65});6667ensureNoDisposablesAreLeakedInTestSuite();6869async function assertGetRangesToPosition(text: string[], lineNumber: number, column: number, ranges: Range[], selectLeadingAndTrailingWhitespace = true): Promise<void> {70const uri = URI.file('test.js');71const model = modelService.createModel(text.join('\n'), new StaticLanguageSelector(languageId), uri);72const [actual] = await provideSelectionRanges(providers, model, [new Position(lineNumber, column)], { selectLeadingAndTrailingWhitespace, selectSubwords: true }, CancellationToken.None);73const actualStr = actual.map(r => new Range(r.startLineNumber, r.startColumn, r.endLineNumber, r.endColumn).toString());74const desiredStr = ranges.reverse().map(r => String(r));7576assert.deepStrictEqual(actualStr, desiredStr, `\nA: ${actualStr} VS \nE: ${desiredStr}`);77modelService.destroyModel(uri);78}7980test('getRangesToPosition #1', () => {8182return assertGetRangesToPosition([83'function a(bar, foo){',84'\tif (bar) {',85'\t\treturn (bar + (2 * foo))',86'\t}',87'}'88], 3, 20, [89new Range(1, 1, 5, 2), // all90new Range(1, 21, 5, 2), // {} outside91new Range(1, 22, 5, 1), // {} inside92new Range(2, 1, 4, 3), // block93new Range(2, 1, 4, 3),94new Range(2, 2, 4, 3),95new Range(2, 11, 4, 3),96new Range(2, 12, 4, 2),97new Range(3, 1, 3, 27), // line w/ triva98new Range(3, 3, 3, 27), // line w/o triva99new Range(3, 10, 3, 27), // () outside100new Range(3, 11, 3, 26), // () inside101new Range(3, 17, 3, 26), // () outside102new Range(3, 18, 3, 25), // () inside103]);104});105106test('config: selectLeadingAndTrailingWhitespace', async () => {107108await assertGetRangesToPosition([109'aaa',110'\tbbb',111''112], 2, 3, [113new Range(1, 1, 3, 1), // all114new Range(2, 1, 2, 5), // line w/ triva115new Range(2, 2, 2, 5), // bbb116], true);117118await assertGetRangesToPosition([119'aaa',120'\tbbb',121''122], 2, 3, [123new Range(1, 1, 3, 1), // all124new Range(2, 2, 2, 5), // () inside125], false);126});127128test('getRangesToPosition #56886. Skip empty lines correctly.', () => {129130return assertGetRangesToPosition([131'function a(bar, foo){',132'\tif (bar) {',133'',134'\t}',135'}'136], 3, 1, [137new Range(1, 1, 5, 2),138new Range(1, 21, 5, 2),139new Range(1, 22, 5, 1),140new Range(2, 1, 4, 3),141new Range(2, 1, 4, 3),142new Range(2, 2, 4, 3),143new Range(2, 11, 4, 3),144new Range(2, 12, 4, 2),145]);146});147148test('getRangesToPosition #56886. Do not skip lines with only whitespaces.', () => {149150return assertGetRangesToPosition([151'function a(bar, foo){',152'\tif (bar) {',153' ',154'\t}',155'}'156], 3, 1, [157new Range(1, 1, 5, 2), // all158new Range(1, 21, 5, 2), // {} outside159new Range(1, 22, 5, 1), // {} inside160new Range(2, 1, 4, 3),161new Range(2, 1, 4, 3),162new Range(2, 2, 4, 3),163new Range(2, 11, 4, 3),164new Range(2, 12, 4, 2),165new Range(3, 1, 3, 2), // block166new Range(3, 1, 3, 2) // empty line167]);168});169170test('getRangesToPosition #40658. Cursor at first position inside brackets should select line inside.', () => {171172return assertGetRangesToPosition([173' [ ]',174' { } ',175'( ) '176], 2, 3, [177new Range(1, 1, 3, 5),178new Range(2, 1, 2, 6), // line w/ triava179new Range(2, 2, 2, 5), // {} inside, line w/o triva180new Range(2, 3, 2, 4) // {} inside181]);182});183184test('getRangesToPosition #40658. Cursor in empty brackets should reveal brackets first.', () => {185186return assertGetRangesToPosition([187' [] ',188' { } ',189' ( ) '190], 1, 3, [191new Range(1, 1, 3, 7), // all192new Range(1, 1, 1, 5), // line w/ trival193new Range(1, 2, 1, 4), // [] outside, line w/o trival194new Range(1, 3, 1, 3), // [] inside195]);196});197198test('getRangesToPosition #40658. Tokens before bracket will be revealed first.', () => {199200return assertGetRangesToPosition([201' [] ',202' { } ',203'selectthis( ) '204], 3, 11, [205new Range(1, 1, 3, 15), // all206new Range(3, 1, 3, 15), // line w/ trivia207new Range(3, 1, 3, 14), // line w/o trivia208new Range(3, 1, 3, 11) // word209]);210});211212// -- bracket selections213214async function assertRanges(provider: SelectionRangeProvider, value: string, ...expected: IRange[]): Promise<void> {215const index = value.indexOf('|');216value = value.replace('|', ''); // CodeQL [SM02383] js/incomplete-sanitization this is purpose only the first | character217218const model = modelService.createModel(value, new StaticLanguageSelector(languageId), URI.parse('fake:lang'));219const pos = model.getPositionAt(index);220const all = await provider.provideSelectionRanges(model, [pos], CancellationToken.None);221const ranges = all![0];222223modelService.destroyModel(model.uri);224225assert.strictEqual(expected.length, ranges.length);226for (const range of ranges) {227const exp = expected.shift() || null;228assert.ok(Range.equalsRange(range.range, exp), `A=${range.range} <> E=${exp}`);229}230}231232test('bracket selection', async () => {233await assertRanges(new BracketSelectionRangeProvider(), '(|)',234new Range(1, 2, 1, 2), new Range(1, 1, 1, 3)235);236237await assertRanges(new BracketSelectionRangeProvider(), '[[[](|)]]',238new Range(1, 6, 1, 6), new Range(1, 5, 1, 7), // ()239new Range(1, 3, 1, 7), new Range(1, 2, 1, 8), // [[]()]240new Range(1, 2, 1, 8), new Range(1, 1, 1, 9), // [[[]()]]241);242243await assertRanges(new BracketSelectionRangeProvider(), '[a[](|)a]',244new Range(1, 6, 1, 6), new Range(1, 5, 1, 7),245new Range(1, 2, 1, 8), new Range(1, 1, 1, 9),246);247248// no bracket249await assertRanges(new BracketSelectionRangeProvider(), 'fofof|fofo');250251// empty252await assertRanges(new BracketSelectionRangeProvider(), '[[[]()]]|');253await assertRanges(new BracketSelectionRangeProvider(), '|[[[]()]]');254255// edge256await assertRanges(new BracketSelectionRangeProvider(), '[|[[]()]]', new Range(1, 2, 1, 8), new Range(1, 1, 1, 9));257await assertRanges(new BracketSelectionRangeProvider(), '[[[]()]|]', new Range(1, 2, 1, 8), new Range(1, 1, 1, 9));258259await assertRanges(new BracketSelectionRangeProvider(), 'aaa(aaa)bbb(b|b)ccc(ccc)', new Range(1, 13, 1, 15), new Range(1, 12, 1, 16));260await 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));261});262263test('bracket with leading/trailing', async () => {264265await assertRanges(new BracketSelectionRangeProvider(), 'for(a of b){\n foo(|);\n}',266new Range(2, 7, 2, 7), new Range(2, 6, 2, 8),267new Range(1, 13, 3, 1), new Range(1, 12, 3, 2),268new Range(1, 1, 3, 2), new Range(1, 1, 3, 2),269);270271await assertRanges(new BracketSelectionRangeProvider(), 'for(a of b)\n{\n foo(|);\n}',272new Range(3, 7, 3, 7), new Range(3, 6, 3, 8),273new Range(2, 2, 4, 1), new Range(2, 1, 4, 2),274new Range(1, 1, 4, 2), new Range(1, 1, 4, 2),275);276});277278test('in-word ranges', async () => {279280await assertRanges(new WordSelectionRangeProvider(), 'f|ooBar',281new Range(1, 1, 1, 4), // foo282new Range(1, 1, 1, 7), // fooBar283new Range(1, 1, 1, 7), // doc284);285286await assertRanges(new WordSelectionRangeProvider(), 'f|oo_Ba',287new Range(1, 1, 1, 4),288new Range(1, 1, 1, 7),289new Range(1, 1, 1, 7),290);291292await assertRanges(new WordSelectionRangeProvider(), 'f|oo-Ba',293new Range(1, 1, 1, 4),294new Range(1, 1, 1, 7),295new Range(1, 1, 1, 7),296);297});298299test('in-word ranges with selectSubwords=false', async () => {300301await assertRanges(new WordSelectionRangeProvider(false), 'f|ooBar',302new Range(1, 1, 1, 7),303new Range(1, 1, 1, 7),304);305306await assertRanges(new WordSelectionRangeProvider(false), 'f|oo_Ba',307new Range(1, 1, 1, 7),308new Range(1, 1, 1, 7),309);310311await assertRanges(new WordSelectionRangeProvider(false), 'f|oo-Ba',312new Range(1, 1, 1, 7),313new Range(1, 1, 1, 7),314);315});316317test('Default selection should select current word/hump first in camelCase #67493', async function () {318319await assertRanges(new WordSelectionRangeProvider(), 'Abs|tractSmartSelect',320new Range(1, 1, 1, 9),321new Range(1, 1, 1, 20),322new Range(1, 1, 1, 20),323);324325await assertRanges(new WordSelectionRangeProvider(), 'AbstractSma|rtSelect',326new Range(1, 9, 1, 14),327new Range(1, 1, 1, 20),328new Range(1, 1, 1, 20),329);330331await assertRanges(new WordSelectionRangeProvider(), 'Abstrac-Sma|rt-elect',332new Range(1, 9, 1, 14),333new Range(1, 1, 1, 20),334new Range(1, 1, 1, 20),335);336337await assertRanges(new WordSelectionRangeProvider(), 'Abstrac_Sma|rt_elect',338new Range(1, 9, 1, 14),339new Range(1, 1, 1, 20),340new Range(1, 1, 1, 20),341);342343await assertRanges(new WordSelectionRangeProvider(), 'Abstrac_Sma|rt-elect',344new Range(1, 9, 1, 14),345new Range(1, 1, 1, 20),346new Range(1, 1, 1, 20),347);348349await assertRanges(new WordSelectionRangeProvider(), 'Abstrac_Sma|rtSelect',350new Range(1, 9, 1, 14),351new Range(1, 1, 1, 20),352new Range(1, 1, 1, 20),353);354});355356test('Smart select: only add line ranges if they\'re contained by the next range #73850', async function () {357358const reg = providers.register('*', {359provideSelectionRanges() {360return [[361{ range: { startLineNumber: 1, startColumn: 10, endLineNumber: 1, endColumn: 11 } },362{ range: { startLineNumber: 1, startColumn: 10, endLineNumber: 3, endColumn: 2 } },363{ range: { startLineNumber: 1, startColumn: 1, endLineNumber: 3, endColumn: 2 } },364]];365}366});367368await assertGetRangesToPosition(['type T = {', '\tx: number', '}'], 1, 10, [369new Range(1, 1, 3, 2), // all370new Range(1, 10, 3, 2), // { ... }371new Range(1, 10, 1, 11), // {372]);373374reg.dispose();375});376377test('Expand selection in words with underscores is inconsistent #90589', async function () {378379await assertRanges(new WordSelectionRangeProvider(), 'Hel|lo_World',380new Range(1, 1, 1, 6),381new Range(1, 1, 1, 12),382new Range(1, 1, 1, 12),383);384385await assertRanges(new WordSelectionRangeProvider(), 'Hello_Wo|rld',386new Range(1, 7, 1, 12),387new Range(1, 1, 1, 12),388new Range(1, 1, 1, 12),389);390391await assertRanges(new WordSelectionRangeProvider(), 'Hello|_World',392new Range(1, 1, 1, 6),393new Range(1, 1, 1, 12),394new Range(1, 1, 1, 12),395);396397await assertRanges(new WordSelectionRangeProvider(), 'Hello_|World',398new Range(1, 7, 1, 12),399new Range(1, 1, 1, 12),400new Range(1, 1, 1, 12),401);402403await assertRanges(new WordSelectionRangeProvider(), 'Hello|-World',404new Range(1, 1, 1, 6),405new Range(1, 1, 1, 12),406new Range(1, 1, 1, 12),407);408409await assertRanges(new WordSelectionRangeProvider(), 'Hello-|World',410new Range(1, 7, 1, 12),411new Range(1, 1, 1, 12),412new Range(1, 1, 1, 12),413);414415await assertRanges(new WordSelectionRangeProvider(), 'Hello|World',416new Range(1, 6, 1, 11),417new Range(1, 1, 1, 11),418new Range(1, 1, 1, 11),419);420});421});422423424