mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-03-22 21:00:30 -04:00
* Merge from vscode 81d7885dc2e9dc617e1522697a2966bc4025a45d * Fix vs unit tests and hygiene issue * Fix strict null check issue
345 lines
11 KiB
TypeScript
345 lines
11 KiB
TypeScript
/*---------------------------------------------------------------------------------------------
|
||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||
*--------------------------------------------------------------------------------------------*/
|
||
import * as assert from 'assert';
|
||
import { URI } from 'vs/base/common/uri';
|
||
import { Range, IRange } from 'vs/editor/common/core/range';
|
||
import { Position } from 'vs/editor/common/core/position';
|
||
import { LanguageIdentifier, SelectionRangeProvider, SelectionRangeRegistry } from 'vs/editor/common/modes';
|
||
import { MockMode, StaticLanguageSelector } from 'vs/editor/test/common/mocks/mockMode';
|
||
import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry';
|
||
import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl';
|
||
import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService';
|
||
import { javascriptOnEnterRules } from 'vs/editor/test/common/modes/supports/javascriptOnEnterRules';
|
||
import { ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration';
|
||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||
import { isLinux, isMacintosh } from 'vs/base/common/platform';
|
||
import { BracketSelectionRangeProvider } from 'vs/editor/contrib/smartSelect/bracketSelections';
|
||
import { provideSelectionRanges } from 'vs/editor/contrib/smartSelect/smartSelect';
|
||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||
import { WordSelectionRangeProvider } from 'vs/editor/contrib/smartSelect/wordSelections';
|
||
|
||
class TestTextResourcePropertiesService implements ITextResourcePropertiesService {
|
||
|
||
_serviceBrand: any;
|
||
|
||
constructor(
|
||
@IConfigurationService private readonly configurationService: IConfigurationService,
|
||
) {
|
||
}
|
||
|
||
getEOL(resource: URI | undefined): string {
|
||
const filesConfiguration = this.configurationService.getValue<{ eol: string }>('files');
|
||
if (filesConfiguration && filesConfiguration.eol) {
|
||
if (filesConfiguration.eol !== 'auto') {
|
||
return filesConfiguration.eol;
|
||
}
|
||
}
|
||
return (isLinux || isMacintosh) ? '\n' : '\r\n';
|
||
}
|
||
}
|
||
|
||
class MockJSMode extends MockMode {
|
||
|
||
private static readonly _id = new LanguageIdentifier('mockJSMode', 3);
|
||
|
||
constructor() {
|
||
super(MockJSMode._id);
|
||
|
||
this._register(LanguageConfigurationRegistry.register(this.getLanguageIdentifier(), {
|
||
brackets: [
|
||
['(', ')'],
|
||
['{', '}'],
|
||
['[', ']']
|
||
],
|
||
|
||
onEnterRules: javascriptOnEnterRules,
|
||
wordPattern: /(-?\d*\.\d\w*)|([^\`\~\!\@\#\$\%\^\&\*\(\)\=\+\[\{\]\}\\\;\:\'\"\,\.\<\>\/\?\s]+)/g
|
||
}));
|
||
}
|
||
}
|
||
|
||
suite('SmartSelect', () => {
|
||
|
||
let modelService: ModelServiceImpl;
|
||
let mode: MockJSMode;
|
||
|
||
setup(() => {
|
||
const configurationService = new TestConfigurationService();
|
||
modelService = new ModelServiceImpl(configurationService, new TestTextResourcePropertiesService(configurationService));
|
||
mode = new MockJSMode();
|
||
});
|
||
|
||
teardown(() => {
|
||
modelService.dispose();
|
||
mode.dispose();
|
||
});
|
||
|
||
async function assertGetRangesToPosition(text: string[], lineNumber: number, column: number, ranges: Range[]): Promise<void> {
|
||
let uri = URI.file('test.js');
|
||
let model = modelService.createModel(text.join('\n'), new StaticLanguageSelector(mode.getLanguageIdentifier()), uri);
|
||
let [actual] = await provideSelectionRanges(model, [new Position(lineNumber, column)], CancellationToken.None);
|
||
let actualStr = actual!.map(r => new Range(r.startLineNumber, r.startColumn, r.endLineNumber, r.endColumn).toString());
|
||
let desiredStr = ranges.reverse().map(r => String(r));
|
||
|
||
assert.deepEqual(actualStr, desiredStr, `\nA: ${actualStr} VS \nE: ${desiredStr}`);
|
||
modelService.destroyModel(uri);
|
||
}
|
||
|
||
test('getRangesToPosition #1', () => {
|
||
|
||
return assertGetRangesToPosition([
|
||
'function a(bar, foo){',
|
||
'\tif (bar) {',
|
||
'\t\treturn (bar + (2 * foo))',
|
||
'\t}',
|
||
'}'
|
||
], 3, 20, [
|
||
new Range(1, 1, 5, 2), // all
|
||
new Range(1, 21, 5, 2), // {} outside
|
||
new Range(1, 22, 5, 1), // {} inside
|
||
new Range(2, 1, 4, 3), // block
|
||
new Range(2, 1, 4, 3),
|
||
new Range(2, 2, 4, 3),
|
||
new Range(2, 11, 4, 3),
|
||
new Range(2, 12, 4, 2),
|
||
new Range(3, 1, 3, 27), // line w/ triva
|
||
new Range(3, 3, 3, 27), // line w/o triva
|
||
new Range(3, 10, 3, 27), // () outside
|
||
new Range(3, 11, 3, 26), // () inside
|
||
new Range(3, 17, 3, 26), // () outside
|
||
new Range(3, 18, 3, 25), // () inside
|
||
]);
|
||
});
|
||
|
||
test('getRangesToPosition #56886. Skip empty lines correctly.', () => {
|
||
|
||
return assertGetRangesToPosition([
|
||
'function a(bar, foo){',
|
||
'\tif (bar) {',
|
||
'',
|
||
'\t}',
|
||
'}'
|
||
], 3, 1, [
|
||
new Range(1, 1, 5, 2),
|
||
new Range(1, 21, 5, 2),
|
||
new Range(1, 22, 5, 1),
|
||
new Range(2, 1, 4, 3),
|
||
new Range(2, 1, 4, 3),
|
||
new Range(2, 2, 4, 3),
|
||
new Range(2, 11, 4, 3),
|
||
new Range(2, 12, 4, 2),
|
||
]);
|
||
});
|
||
|
||
test('getRangesToPosition #56886. Do not skip lines with only whitespaces.', () => {
|
||
|
||
return assertGetRangesToPosition([
|
||
'function a(bar, foo){',
|
||
'\tif (bar) {',
|
||
' ',
|
||
'\t}',
|
||
'}'
|
||
], 3, 1, [
|
||
new Range(1, 1, 5, 2), // all
|
||
new Range(1, 21, 5, 2), // {} outside
|
||
new Range(1, 22, 5, 1), // {} inside
|
||
new Range(2, 1, 4, 3),
|
||
new Range(2, 1, 4, 3),
|
||
new Range(2, 2, 4, 3),
|
||
new Range(2, 11, 4, 3),
|
||
new Range(2, 12, 4, 2),
|
||
new Range(3, 1, 3, 2), // block
|
||
new Range(3, 1, 3, 2) // empty line
|
||
]);
|
||
});
|
||
|
||
test('getRangesToPosition #40658. Cursor at first position inside brackets should select line inside.', () => {
|
||
|
||
return assertGetRangesToPosition([
|
||
' [ ]',
|
||
' { } ',
|
||
'( ) '
|
||
], 2, 3, [
|
||
new Range(1, 1, 3, 5),
|
||
new Range(2, 1, 2, 6), // line w/ triava
|
||
new Range(2, 2, 2, 5), // {} inside, line w/o triva
|
||
new Range(2, 3, 2, 4) // {} inside
|
||
]);
|
||
});
|
||
|
||
test('getRangesToPosition #40658. Cursor in empty brackets should reveal brackets first.', () => {
|
||
|
||
return assertGetRangesToPosition([
|
||
' [] ',
|
||
' { } ',
|
||
' ( ) '
|
||
], 1, 3, [
|
||
new Range(1, 1, 3, 7), // all
|
||
new Range(1, 1, 1, 5), // line w/ trival
|
||
new Range(1, 2, 1, 4), // [] outside, line w/o trival
|
||
new Range(1, 3, 1, 3), // [] inside
|
||
]);
|
||
});
|
||
|
||
test('getRangesToPosition #40658. Tokens before bracket will be revealed first.', () => {
|
||
|
||
return assertGetRangesToPosition([
|
||
' [] ',
|
||
' { } ',
|
||
'selectthis( ) '
|
||
], 3, 11, [
|
||
new Range(1, 1, 3, 15), // all
|
||
new Range(3, 1, 3, 15), // line w/ trivia
|
||
new Range(3, 1, 3, 14), // line w/o trivia
|
||
new Range(3, 1, 3, 11) // word
|
||
]);
|
||
});
|
||
|
||
// -- bracket selections
|
||
|
||
async function assertRanges(provider: SelectionRangeProvider, value: string, ...expected: IRange[]): Promise<void> {
|
||
|
||
let model = modelService.createModel(value, new StaticLanguageSelector(mode.getLanguageIdentifier()), URI.parse('fake:lang'));
|
||
let pos = model.getPositionAt(value.indexOf('|'));
|
||
let all = await provider.provideSelectionRanges(model, [pos], CancellationToken.None);
|
||
let ranges = all![0];
|
||
|
||
modelService.destroyModel(model.uri);
|
||
|
||
assert.equal(expected.length, ranges!.length);
|
||
for (const range of ranges!) {
|
||
let exp = expected.shift() || null;
|
||
assert.ok(Range.equalsRange(range.range, exp), `A=${range.range} <> E=${exp}`);
|
||
}
|
||
}
|
||
|
||
test('bracket selection', async () => {
|
||
await assertRanges(new BracketSelectionRangeProvider(), '(|)',
|
||
new Range(1, 2, 1, 3), new Range(1, 1, 1, 4)
|
||
);
|
||
|
||
await assertRanges(new BracketSelectionRangeProvider(), '[[[](|)]]',
|
||
new Range(1, 6, 1, 7), new Range(1, 5, 1, 8), // ()
|
||
new Range(1, 3, 1, 8), new Range(1, 2, 1, 9), // [[]()]
|
||
new Range(1, 2, 1, 9), new Range(1, 1, 1, 10), // [[[]()]]
|
||
);
|
||
|
||
await assertRanges(new BracketSelectionRangeProvider(), '[a[](|)a]',
|
||
new Range(1, 6, 1, 7), new Range(1, 5, 1, 8),
|
||
new Range(1, 2, 1, 9), new Range(1, 1, 1, 10),
|
||
);
|
||
|
||
// no bracket
|
||
await assertRanges(new BracketSelectionRangeProvider(), 'fofof|fofo');
|
||
|
||
// empty
|
||
await assertRanges(new BracketSelectionRangeProvider(), '[[[]()]]|');
|
||
await assertRanges(new BracketSelectionRangeProvider(), '|[[[]()]]');
|
||
|
||
// edge
|
||
await assertRanges(new BracketSelectionRangeProvider(), '[|[[]()]]', new Range(1, 2, 1, 9), new Range(1, 1, 1, 10));
|
||
await assertRanges(new BracketSelectionRangeProvider(), '[[[]()]|]', new Range(1, 2, 1, 9), new Range(1, 1, 1, 10));
|
||
|
||
await assertRanges(new BracketSelectionRangeProvider(), 'aaa(aaa)bbb(b|b)ccc(ccc)', new Range(1, 13, 1, 16), new Range(1, 12, 1, 17));
|
||
await assertRanges(new BracketSelectionRangeProvider(), '(aaa(aaa)bbb(b|b)ccc(ccc))', new Range(1, 14, 1, 17), new Range(1, 13, 1, 18), new Range(1, 2, 1, 26), new Range(1, 1, 1, 27));
|
||
});
|
||
|
||
test('bracket with leading/trailing', async () => {
|
||
|
||
await assertRanges(new BracketSelectionRangeProvider(), 'for(a of b){\n foo(|);\n}',
|
||
new Range(2, 7, 2, 8), new Range(2, 6, 2, 9),
|
||
new Range(1, 13, 3, 1), new Range(1, 12, 3, 2),
|
||
new Range(1, 1, 3, 2), new Range(1, 1, 3, 2),
|
||
);
|
||
|
||
await assertRanges(new BracketSelectionRangeProvider(), 'for(a of b)\n{\n foo(|);\n}',
|
||
new Range(3, 7, 3, 8), new Range(3, 6, 3, 9),
|
||
new Range(2, 2, 4, 1), new Range(2, 1, 4, 2),
|
||
new Range(1, 1, 4, 2), new Range(1, 1, 4, 2),
|
||
);
|
||
});
|
||
|
||
test('in-word ranges', async () => {
|
||
|
||
await assertRanges(new WordSelectionRangeProvider(), 'f|ooBar',
|
||
new Range(1, 1, 1, 5), // foo
|
||
new Range(1, 1, 1, 8), // fooBar
|
||
new Range(1, 1, 1, 8), // doc
|
||
);
|
||
|
||
await assertRanges(new WordSelectionRangeProvider(), 'f|oo_Ba',
|
||
new Range(1, 1, 1, 5),
|
||
new Range(1, 1, 1, 8),
|
||
new Range(1, 1, 1, 8),
|
||
);
|
||
|
||
await assertRanges(new WordSelectionRangeProvider(), 'f|oo-Ba',
|
||
new Range(1, 1, 1, 5),
|
||
new Range(1, 1, 1, 8),
|
||
new Range(1, 1, 1, 8),
|
||
);
|
||
});
|
||
|
||
test('Default selection should select current word/hump first in camelCase #67493', async function () {
|
||
|
||
await assertRanges(new WordSelectionRangeProvider(), 'Abs|tractSmartSelect',
|
||
new Range(1, 1, 1, 10),
|
||
new Range(1, 1, 1, 21),
|
||
new Range(1, 1, 1, 21),
|
||
);
|
||
|
||
await assertRanges(new WordSelectionRangeProvider(), 'AbstractSma|rtSelect',
|
||
new Range(1, 9, 1, 15),
|
||
new Range(1, 1, 1, 21),
|
||
new Range(1, 1, 1, 21),
|
||
);
|
||
|
||
await assertRanges(new WordSelectionRangeProvider(), 'Abstrac-Sma|rt-elect',
|
||
new Range(1, 9, 1, 15),
|
||
new Range(1, 1, 1, 21),
|
||
new Range(1, 1, 1, 21),
|
||
);
|
||
|
||
await assertRanges(new WordSelectionRangeProvider(), 'Abstrac_Sma|rt_elect',
|
||
new Range(1, 9, 1, 15),
|
||
new Range(1, 1, 1, 21),
|
||
new Range(1, 1, 1, 21),
|
||
);
|
||
|
||
await assertRanges(new WordSelectionRangeProvider(), 'Abstrac_Sma|rt-elect',
|
||
new Range(1, 9, 1, 15),
|
||
new Range(1, 1, 1, 21),
|
||
new Range(1, 1, 1, 21),
|
||
);
|
||
|
||
await assertRanges(new WordSelectionRangeProvider(), 'Abstrac_Sma|rtSelect',
|
||
new Range(1, 9, 1, 15),
|
||
new Range(1, 1, 1, 21),
|
||
new Range(1, 1, 1, 21),
|
||
);
|
||
});
|
||
|
||
test('Smart select: only add line ranges if they’re contained by the next range #73850', async function () {
|
||
|
||
const reg = SelectionRangeRegistry.register('*', {
|
||
provideSelectionRanges() {
|
||
return [[
|
||
{ range: { startLineNumber: 1, startColumn: 10, endLineNumber: 1, endColumn: 11 } },
|
||
{ range: { startLineNumber: 1, startColumn: 10, endLineNumber: 3, endColumn: 2 } },
|
||
{ range: { startLineNumber: 1, startColumn: 1, endLineNumber: 3, endColumn: 2 } },
|
||
]];
|
||
}
|
||
});
|
||
|
||
await assertGetRangesToPosition(['type T = {', '\tx: number', '}'], 1, 10, [
|
||
new Range(1, 1, 3, 2), // all
|
||
new Range(1, 10, 3, 2), // { ... }
|
||
new Range(1, 10, 1, 11), // {
|
||
]);
|
||
|
||
reg.dispose();
|
||
});
|
||
});
|