Merge from vscode e3c4990c67c40213af168300d1cfeb71d680f877 (#16569)

This commit is contained in:
Cory Rivera
2021-08-25 16:28:29 -07:00
committed by GitHub
parent ab1112bfb3
commit cb7b7da0a4
1752 changed files with 59525 additions and 33878 deletions

View File

@@ -2608,6 +2608,186 @@ suite('Editor Controller - Regression tests', () => {
model.dispose();
});
test('issue #122914: Left delete behavior in some languages is changed (useTabStops: false)', () => {
let model = createTextModel(
[
'สวัสดี'
].join('\n')
);
withTestCodeEditor(null, { model: model, useTabStops: false }, (editor, viewModel) => {
editor.setSelections([
new Selection(1, 7, 1, 7)
]);
CoreEditingCommands.DeleteLeft.runEditorCommand(null, editor, null);
assert.strictEqual(model.getValue(EndOfLinePreference.LF), 'สวัสด');
CoreEditingCommands.DeleteLeft.runEditorCommand(null, editor, null);
assert.strictEqual(model.getValue(EndOfLinePreference.LF), 'สวัส');
CoreEditingCommands.DeleteLeft.runEditorCommand(null, editor, null);
assert.strictEqual(model.getValue(EndOfLinePreference.LF), 'สวั');
CoreEditingCommands.DeleteLeft.runEditorCommand(null, editor, null);
assert.strictEqual(model.getValue(EndOfLinePreference.LF), 'สว');
CoreEditingCommands.DeleteLeft.runEditorCommand(null, editor, null);
assert.strictEqual(model.getValue(EndOfLinePreference.LF), 'ส');
CoreEditingCommands.DeleteLeft.runEditorCommand(null, editor, null);
assert.strictEqual(model.getValue(EndOfLinePreference.LF), '');
});
model.dispose();
});
test('issue #99629: Emoji modifiers in text treated separately when using backspace', () => {
const model = createTextModel(
[
'👶🏾'
].join('\n')
);
withTestCodeEditor(null, { model: model, useTabStops: false }, (editor, viewModel) => {
const len = model.getValueLength();
editor.setSelections([
new Selection(1, 1 + len, 1, 1 + len)
]);
CoreEditingCommands.DeleteLeft.runEditorCommand(null, editor, null);
assert.strictEqual(model.getValue(EndOfLinePreference.LF), '');
});
model.dispose();
});
test('issue #99629: Emoji modifiers in text treated separately when using backspace (ZWJ sequence)', () => {
let model = createTextModel(
[
'👨‍👩🏽‍👧‍👦'
].join('\n')
);
withTestCodeEditor(null, { model: model, useTabStops: false }, (editor, viewModel) => {
const len = model.getValueLength();
editor.setSelections([
new Selection(1, 1 + len, 1, 1 + len)
]);
CoreEditingCommands.DeleteLeft.runEditorCommand(null, editor, null);
assert.strictEqual(model.getValue(EndOfLinePreference.LF), '👨‍👩🏽‍👧');
CoreEditingCommands.DeleteLeft.runEditorCommand(null, editor, null);
assert.strictEqual(model.getValue(EndOfLinePreference.LF), '👨‍👩🏽');
CoreEditingCommands.DeleteLeft.runEditorCommand(null, editor, null);
assert.strictEqual(model.getValue(EndOfLinePreference.LF), '👨');
CoreEditingCommands.DeleteLeft.runEditorCommand(null, editor, null);
assert.strictEqual(model.getValue(EndOfLinePreference.LF), '');
});
model.dispose();
});
test('issue #105730: move left behaves differently for multiple cursors', () => {
const model = createTextModel('asdfghjkl, asdfghjkl, asdfghjkl, ');
withTestCodeEditor(
null,
{
model: model,
wordWrap: 'wordWrapColumn',
wordWrapColumn: 24
},
(editor, viewModel) => {
viewModel.setSelections('test', [
new Selection(1, 10, 1, 12),
new Selection(1, 21, 1, 23),
new Selection(1, 32, 1, 34)
]);
moveLeft(editor, viewModel, false);
assertCursor(viewModel, [
new Selection(1, 10, 1, 10),
new Selection(1, 21, 1, 21),
new Selection(1, 32, 1, 32)
]);
viewModel.setSelections('test', [
new Selection(1, 10, 1, 12),
new Selection(1, 21, 1, 23),
new Selection(1, 32, 1, 34)
]);
moveLeft(editor, viewModel, true);
assertCursor(viewModel, [
new Selection(1, 10, 1, 11),
new Selection(1, 21, 1, 22),
new Selection(1, 32, 1, 33)
]);
});
});
test('issue #105730: move right should always skip wrap point', () => {
const model = createTextModel('asdfghjkl, asdfghjkl, asdfghjkl, \nasdfghjkl,');
withTestCodeEditor(
null,
{
model: model,
wordWrap: 'wordWrapColumn',
wordWrapColumn: 24
},
(editor, viewModel) => {
viewModel.setSelections('test', [
new Selection(1, 22, 1, 22)
]);
moveRight(editor, viewModel, false);
moveRight(editor, viewModel, false);
assertCursor(viewModel, [
new Selection(1, 24, 1, 24),
]);
viewModel.setSelections('test', [
new Selection(1, 22, 1, 22)
]);
moveRight(editor, viewModel, true);
moveRight(editor, viewModel, true);
assertCursor(viewModel, [
new Selection(1, 22, 1, 24),
]);
}
);
});
test('issue #123178: sticky tab in consecutive wrapped lines', () => {
const model = createTextModel(' aaaa aaaa', { tabSize: 4 });
withTestCodeEditor(
null,
{
model: model,
wordWrap: 'wordWrapColumn',
wordWrapColumn: 8,
stickyTabStops: true,
},
(editor, viewModel) => {
viewModel.setSelections('test', [
new Selection(1, 9, 1, 9)
]);
moveRight(editor, viewModel, false);
assertCursor(viewModel, [
new Selection(1, 10, 1, 10),
]);
moveLeft(editor, viewModel, false);
assertCursor(viewModel, [
new Selection(1, 9, 1, 9),
]);
}
);
});
});
suite('Editor Controller - Cursor Configuration', () => {
@@ -4299,6 +4479,29 @@ suite('Editor Controller - Indentation Rules', () => {
assert.strictEqual(model.getValue(), ' let a,\n\t b,\n\t c;');
});
});
test('issue #122714: tabSize=1 prevent typing a string matching decreaseIndentPattern in an empty file', () => {
let latexMode = new IndentRulesMode({
increaseIndentPattern: new RegExp('\\\\begin{(?!document)([^}]*)}(?!.*\\\\end{\\1})'),
decreaseIndentPattern: new RegExp('^\\s*\\\\end{(?!document)')
});
let model = createTextModel(
'\\end',
{ tabSize: 1 },
latexMode.getLanguageIdentifier()
);
withTestCodeEditor(null, { model: model, autoIndent: 'full' }, (editor, viewModel) => {
moveTo(editor, viewModel, 1, 5, false);
assertCursor(viewModel, new Selection(1, 5, 1, 5));
viewModel.type('{', 'keyboard');
assert.strictEqual(model.getLineContent(1), '\\end{}');
});
latexMode.dispose();
model.dispose();
});
});
interface ICursorOpts {
@@ -6080,4 +6283,62 @@ suite('Undo stops', () => {
assert.strictEqual(model.getValue(), 'hello world\nhello world');
});
});
test('there is a single undo stop for consecutive whitespaces', () => {
let model = createTextModel(
[
''
].join('\n'),
{
insertSpaces: false,
}
);
withTestCodeEditor(null, { model: model }, (editor, viewModel) => {
viewModel.type('a', 'keyboard');
viewModel.type('b', 'keyboard');
viewModel.type(' ', 'keyboard');
viewModel.type(' ', 'keyboard');
viewModel.type('c', 'keyboard');
viewModel.type('d', 'keyboard');
assert.strictEqual(model.getValue(EndOfLinePreference.LF), 'ab cd', 'assert1');
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
assert.strictEqual(model.getValue(EndOfLinePreference.LF), 'ab ', 'assert2');
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
assert.strictEqual(model.getValue(EndOfLinePreference.LF), 'ab', 'assert3');
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
assert.strictEqual(model.getValue(EndOfLinePreference.LF), '', 'assert4');
});
});
test('there is no undo stop after a single whitespace', () => {
let model = createTextModel(
[
''
].join('\n'),
{
insertSpaces: false,
}
);
withTestCodeEditor(null, { model: model }, (editor, viewModel) => {
viewModel.type('a', 'keyboard');
viewModel.type('b', 'keyboard');
viewModel.type(' ', 'keyboard');
viewModel.type('c', 'keyboard');
viewModel.type('d', 'keyboard');
assert.strictEqual(model.getValue(EndOfLinePreference.LF), 'ab cd', 'assert1');
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
assert.strictEqual(model.getValue(EndOfLinePreference.LF), 'ab', 'assert3');
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
assert.strictEqual(model.getValue(EndOfLinePreference.LF), '', 'assert4');
});
});
});

View File

@@ -19,9 +19,9 @@ export class TestCodeEditorService extends AbstractCodeEditorService {
this.lastInput = input;
return Promise.resolve(null);
}
public registerDecorationType(key: string, options: IDecorationRenderOptions, parentTypeKey?: string): void { }
public registerDecorationType(description: string, key: string, options: IDecorationRenderOptions, parentTypeKey?: string): void { }
public removeDecorationType(key: string): void { }
public resolveDecorationOptions(decorationTypeKey: string, writable: boolean): IModelDecorationOptions { return {}; }
public resolveDecorationOptions(decorationTypeKey: string, writable: boolean): IModelDecorationOptions { return { description: 'test' }; }
public resolveDecorationCSSRules(decorationTypeKey: string): CSSRuleList | null { return null; }
}

View File

@@ -59,12 +59,12 @@ suite.skip('Decoration Render Options', () => { // {{SQL CARBON EDIT}} skip suit
};
test('register and resolve decoration type', () => {
let s = new TestCodeEditorServiceImpl(null, themeServiceMock);
s.registerDecorationType('example', options);
s.registerDecorationType('test', 'example', options);
assert.notStrictEqual(s.resolveDecorationOptions('example', false), undefined);
});
test('remove decoration type', () => {
let s = new TestCodeEditorServiceImpl(null, themeServiceMock);
s.registerDecorationType('example', options);
s.registerDecorationType('test', 'example', options);
assert.notStrictEqual(s.resolveDecorationOptions('example', false), undefined);
s.removeDecorationType('example');
assert.throws(() => s.resolveDecorationOptions('example', false));
@@ -77,7 +77,7 @@ suite.skip('Decoration Render Options', () => { // {{SQL CARBON EDIT}} skip suit
test('css properties', () => {
const styleSheet = new TestGlobalStyleSheet();
const s = new TestCodeEditorServiceImpl(styleSheet, themeServiceMock);
s.registerDecorationType('example', options);
s.registerDecorationType('test', 'example', options);
const sheet = readStyleSheet(styleSheet);
assert(sheet.indexOf(`{background:url('https://github.com/microsoft/vscode/blob/main/resources/linux/code.png') center center no-repeat;background-size:contain;}`) >= 0);
assert(sheet.indexOf(`{background-color:red;border-color:yellow;box-sizing: border-box;}`) >= 0);
@@ -94,7 +94,7 @@ suite.skip('Decoration Render Options', () => { // {{SQL CARBON EDIT}} skip suit
editorBackground: '#FF0000'
}));
const s = new TestCodeEditorServiceImpl(styleSheet, themeService);
s.registerDecorationType('example', options);
s.registerDecorationType('test', 'example', options);
assert.strictEqual(readStyleSheet(styleSheet), '.monaco-editor .ced-example-0 {background-color:#ff0000;border-color:transparent;box-sizing: border-box;}');
themeService.setTheme(new TestColorTheme({
@@ -127,7 +127,7 @@ suite.skip('Decoration Render Options', () => { // {{SQL CARBON EDIT}} skip suit
infoForeground: '#444444'
}));
const s = new TestCodeEditorServiceImpl(styleSheet, themeService);
s.registerDecorationType('example', options);
s.registerDecorationType('test', 'example', options);
const expected = [
'.vs-dark.monaco-editor .ced-example-4::after, .hc-black.monaco-editor .ced-example-4::after {color:#444444 !important;}',
'.vs-dark.monaco-editor .ced-example-1, .hc-black.monaco-editor .ced-example-1 {color:#000000 !important;}',
@@ -145,7 +145,7 @@ suite.skip('Decoration Render Options', () => { // {{SQL CARBON EDIT}} skip suit
const s = new TestCodeEditorServiceImpl(styleSheet, themeServiceMock);
// URI, only minimal encoding
s.registerDecorationType('example', { gutterIconPath: URI.parse('data:image/svg+xml;base64,PHN2ZyB4b+') });
s.registerDecorationType('test', 'example', { gutterIconPath: URI.parse('data:image/svg+xml;base64,PHN2ZyB4b+') });
assert(readStyleSheet(styleSheet).indexOf(`{background:url('data:image/svg+xml;base64,PHN2ZyB4b+') center center no-repeat;}`) > 0);
s.removeDecorationType('example');
@@ -159,27 +159,27 @@ suite.skip('Decoration Render Options', () => { // {{SQL CARBON EDIT}} skip suit
if (platform.isWindows) {
// windows file path (used as string)
s.registerDecorationType('example', { gutterIconPath: URI.file('c:\\files\\miles\\more.png') });
s.registerDecorationType('test', 'example', { gutterIconPath: URI.file('c:\\files\\miles\\more.png') });
assertBackground('file:///c:/files/miles/more.png', 'vscode-file://vscode-app/c:/files/miles/more.png');
s.removeDecorationType('example');
// single quote must always be escaped/encoded
s.registerDecorationType('example', { gutterIconPath: URI.file('c:\\files\\foo\\b\'ar.png') });
s.registerDecorationType('test', 'example', { gutterIconPath: URI.file('c:\\files\\foo\\b\'ar.png') });
assertBackground('file:///c:/files/foo/b%27ar.png', 'vscode-file://vscode-app/c:/files/foo/b%27ar.png');
s.removeDecorationType('example');
} else {
// unix file path (used as string)
s.registerDecorationType('example', { gutterIconPath: URI.file('/Users/foo/bar.png') });
s.registerDecorationType('test', 'example', { gutterIconPath: URI.file('/Users/foo/bar.png') });
assertBackground('file:///Users/foo/bar.png', 'vscode-file://vscode-app/Users/foo/bar.png');
s.removeDecorationType('example');
// single quote must always be escaped/encoded
s.registerDecorationType('example', { gutterIconPath: URI.file('/Users/foo/b\'ar.png') });
s.registerDecorationType('test', 'example', { gutterIconPath: URI.file('/Users/foo/b\'ar.png') });
assertBackground('file:///Users/foo/b%27ar.png', 'vscode-file://vscode-app/Users/foo/b%27ar.png');
s.removeDecorationType('example');
}
s.registerDecorationType('example', { gutterIconPath: URI.parse('http://test/pa\'th') });
s.registerDecorationType('test', 'example', { gutterIconPath: URI.parse('http://test/pa\'th') });
assert(readStyleSheet(styleSheet).indexOf(`{background:url('http://test/pa%27th') center center no-repeat;}`) > 0);
s.removeDecorationType('example');
});

View File

@@ -8,6 +8,7 @@ import { URI } from 'vs/base/common/uri';
import { OpenerService } from 'vs/editor/browser/services/openerService';
import { TestCodeEditorService } from 'vs/editor/test/browser/editorTestServices';
import { CommandsRegistry, ICommandService, NullCommandService } from 'vs/platform/commands/common/commands';
import { ITextEditorOptions } from 'vs/platform/editor/common/editor';
import { matchesScheme } from 'vs/platform/opener/common/opener';
suite('OpenerService', function () {
@@ -32,28 +33,28 @@ suite('OpenerService', function () {
test('delegate to editorService, scheme:///fff', async function () {
const openerService = new OpenerService(editorService, NullCommandService);
await openerService.open(URI.parse('another:///somepath'));
assert.strictEqual(editorService.lastInput!.options!.selection, undefined);
assert.strictEqual((editorService.lastInput!.options as ITextEditorOptions)!.selection, undefined);
});
test('delegate to editorService, scheme:///fff#L123', async function () {
const openerService = new OpenerService(editorService, NullCommandService);
await openerService.open(URI.parse('file:///somepath#L23'));
assert.strictEqual(editorService.lastInput!.options!.selection!.startLineNumber, 23);
assert.strictEqual(editorService.lastInput!.options!.selection!.startColumn, 1);
assert.strictEqual(editorService.lastInput!.options!.selection!.endLineNumber, undefined);
assert.strictEqual(editorService.lastInput!.options!.selection!.endColumn, undefined);
assert.strictEqual((editorService.lastInput!.options as ITextEditorOptions)!.selection!.startLineNumber, 23);
assert.strictEqual((editorService.lastInput!.options as ITextEditorOptions)!.selection!.startColumn, 1);
assert.strictEqual((editorService.lastInput!.options as ITextEditorOptions)!.selection!.endLineNumber, undefined);
assert.strictEqual((editorService.lastInput!.options as ITextEditorOptions)!.selection!.endColumn, undefined);
assert.strictEqual(editorService.lastInput!.resource.fragment, '');
await openerService.open(URI.parse('another:///somepath#L23'));
assert.strictEqual(editorService.lastInput!.options!.selection!.startLineNumber, 23);
assert.strictEqual(editorService.lastInput!.options!.selection!.startColumn, 1);
assert.strictEqual((editorService.lastInput!.options as ITextEditorOptions)!.selection!.startLineNumber, 23);
assert.strictEqual((editorService.lastInput!.options as ITextEditorOptions)!.selection!.startColumn, 1);
await openerService.open(URI.parse('another:///somepath#L23,45'));
assert.strictEqual(editorService.lastInput!.options!.selection!.startLineNumber, 23);
assert.strictEqual(editorService.lastInput!.options!.selection!.startColumn, 45);
assert.strictEqual(editorService.lastInput!.options!.selection!.endLineNumber, undefined);
assert.strictEqual(editorService.lastInput!.options!.selection!.endColumn, undefined);
assert.strictEqual((editorService.lastInput!.options as ITextEditorOptions)!.selection!.startLineNumber, 23);
assert.strictEqual((editorService.lastInput!.options as ITextEditorOptions)!.selection!.startColumn, 45);
assert.strictEqual((editorService.lastInput!.options as ITextEditorOptions)!.selection!.endLineNumber, undefined);
assert.strictEqual((editorService.lastInput!.options as ITextEditorOptions)!.selection!.endColumn, undefined);
assert.strictEqual(editorService.lastInput!.resource.fragment, '');
});
@@ -61,17 +62,17 @@ suite('OpenerService', function () {
const openerService = new OpenerService(editorService, NullCommandService);
await openerService.open(URI.parse('file:///somepath#23'));
assert.strictEqual(editorService.lastInput!.options!.selection!.startLineNumber, 23);
assert.strictEqual(editorService.lastInput!.options!.selection!.startColumn, 1);
assert.strictEqual(editorService.lastInput!.options!.selection!.endLineNumber, undefined);
assert.strictEqual(editorService.lastInput!.options!.selection!.endColumn, undefined);
assert.strictEqual((editorService.lastInput!.options as ITextEditorOptions)!.selection!.startLineNumber, 23);
assert.strictEqual((editorService.lastInput!.options as ITextEditorOptions)!.selection!.startColumn, 1);
assert.strictEqual((editorService.lastInput!.options as ITextEditorOptions)!.selection!.endLineNumber, undefined);
assert.strictEqual((editorService.lastInput!.options as ITextEditorOptions)!.selection!.endColumn, undefined);
assert.strictEqual(editorService.lastInput!.resource.fragment, '');
await openerService.open(URI.parse('file:///somepath#23,45'));
assert.strictEqual(editorService.lastInput!.options!.selection!.startLineNumber, 23);
assert.strictEqual(editorService.lastInput!.options!.selection!.startColumn, 45);
assert.strictEqual(editorService.lastInput!.options!.selection!.endLineNumber, undefined);
assert.strictEqual(editorService.lastInput!.options!.selection!.endColumn, undefined);
assert.strictEqual((editorService.lastInput!.options as ITextEditorOptions)!.selection!.startLineNumber, 23);
assert.strictEqual((editorService.lastInput!.options as ITextEditorOptions)!.selection!.startColumn, 45);
assert.strictEqual((editorService.lastInput!.options as ITextEditorOptions)!.selection!.endLineNumber, undefined);
assert.strictEqual((editorService.lastInput!.options as ITextEditorOptions)!.selection!.endColumn, undefined);
assert.strictEqual(editorService.lastInput!.resource.fragment, '');
});
@@ -125,33 +126,6 @@ suite('OpenerService', function () {
assert.strictEqual(httpsResult, false);
});
test('delegate to commandsService, command:someid', async function () {
const openerService = new OpenerService(editorService, commandService);
const id = `aCommand${Math.random()}`;
CommandsRegistry.registerCommand(id, function () { });
await openerService.open(URI.parse('command:' + id).with({ query: '\"123\"' }), { allowCommands: true });
assert.strictEqual(lastCommand!.id, id);
assert.strictEqual(lastCommand!.args.length, 1);
assert.strictEqual(lastCommand!.args[0], '123');
await openerService.open(URI.parse('command:' + id), { allowCommands: true });
assert.strictEqual(lastCommand!.id, id);
assert.strictEqual(lastCommand!.args.length, 0);
await openerService.open(URI.parse('command:' + id).with({ query: '123' }), { allowCommands: true });
assert.strictEqual(lastCommand!.id, id);
assert.strictEqual(lastCommand!.args.length, 1);
assert.strictEqual(lastCommand!.args[0], 123);
await openerService.open(URI.parse('command:' + id).with({ query: JSON.stringify([12, true]) }), { allowCommands: true });
assert.strictEqual(lastCommand!.id, id);
assert.strictEqual(lastCommand!.args.length, 2);
assert.strictEqual(lastCommand!.args[0], 12);
assert.strictEqual(lastCommand!.args[1], true);
});
test('links validated by validators go to openers', async function () {
const openerService = new OpenerService(editorService, commandService);
@@ -272,4 +246,25 @@ suite('OpenerService', function () {
assert.ok(!matchesScheme(URI.parse('htt://microsoft.com'), 'http'));
assert.ok(!matchesScheme(URI.parse('z://microsoft.com'), 'http'));
});
test('resolveExternalUri', async function () {
const openerService = new OpenerService(editorService, NullCommandService);
try {
await openerService.resolveExternalUri(URI.parse('file:///Users/user/folder'));
assert.fail('Should not reach here');
} catch {
// OK
}
const disposable = openerService.registerExternalUriResolver({
async resolveExternalUri(uri) {
return { resolved: uri, dispose() { } };
}
});
const result = await openerService.resolveExternalUri(URI.parse('file:///Users/user/folder'));
assert.deepStrictEqual(result.resolved.toString(), 'file:///Users/user/folder');
disposable.dispose();
});
});

View File

@@ -1,6 +1,6 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';

View File

@@ -916,4 +916,55 @@ suite('Editor Diff - DiffComputer', () => {
];
assertDiff(original, modified, expected, false, false, false);
});
test('issue #121436: Diff chunk contains an unchanged line part 1', () => {
const original = [
'if (cond) {',
' cmd',
'}',
];
const modified = [
'if (cond) {',
' if (other_cond) {',
' cmd',
' }',
'}',
];
const expected = [
createLineChange(
1, 0, 2, 2
),
createLineChange(
2, 0, 4, 4
)
];
assertDiff(original, modified, expected, false, false, true);
});
test('issue #121436: Diff chunk contains an unchanged line part 2', () => {
const original = [
'if (cond) {',
' cmd',
'}',
];
const modified = [
'if (cond) {',
' if (other_cond) {',
' cmd',
' }',
'}',
];
const expected = [
createLineChange(
1, 0, 2, 2
),
createLineChange(
2, 2, 3, 3
),
createLineChange(
2, 0, 4, 4
)
];
assertDiff(original, modified, expected, false, false, false);
});
});

View File

@@ -3,4 +3,4 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
require('../../../../../../bootstrap-amd').load('vs/editor/test/common/model/benchmark/entry');
require('../../../../../../bootstrap-amd').load('vs/editor/test/common/model/benchmark/entry');

View File

@@ -5,4 +5,4 @@
import 'vs/editor/test/common/model/benchmark/modelbuilder.benchmark';
import 'vs/editor/test/common/model/benchmark/operations.benchmark';
import 'vs/editor/test/common/model/benchmark/searchNReplace.benchmark';
import 'vs/editor/test/common/model/benchmark/searchNReplace.benchmark';

View File

@@ -1,6 +1,6 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';

View File

@@ -45,6 +45,7 @@ function modelHasNoDecorations(model: TextModel) {
function addDecoration(model: TextModel, startLineNumber: number, startColumn: number, endLineNumber: number, endColumn: number, className: string): string {
return model.changeDecorations((changeAccessor) => {
return changeAccessor.addDecoration(new Range(startLineNumber, startColumn, endLineNumber, endColumn), {
description: 'test',
className: className
});
})!;
@@ -407,7 +408,7 @@ suite('Editor Model - Model Decorations', () => {
});
test('removeAllDecorationsWithOwnerId works', () => {
thisModel.deltaDecorations([], [{ range: new Range(1, 2, 4, 1), options: { className: 'myType1' } }], 1);
thisModel.deltaDecorations([], [{ range: new Range(1, 2, 4, 1), options: { description: 'test', className: 'myType1' } }], 1);
thisModel.removeAllDecorationsWithOwnerId(1);
modelHasNoDecorations(thisModel);
});
@@ -422,7 +423,7 @@ suite('Decorations and editing', () => {
'Third Line'
].join('\n'));
const id = model.deltaDecorations([], [{ range: decRange, options: { stickiness: stickiness } }])[0];
const id = model.deltaDecorations([], [{ range: decRange, options: { description: 'test', stickiness: stickiness } }])[0];
model.applyEdits([{
range: editRange,
text: editText,
@@ -1123,6 +1124,7 @@ suite('deltaDecorations', () => {
return {
range: dec.range,
options: {
description: 'test',
className: dec.id
}
};
@@ -1276,6 +1278,7 @@ suite('deltaDecorations', () => {
endColumn: 1
},
options: {
description: 'test',
hoverMessage: { value: 'hello1' }
}
}]);
@@ -1288,6 +1291,7 @@ suite('deltaDecorations', () => {
endColumn: 1
},
options: {
description: 'test',
hoverMessage: { value: 'hello2' }
}
}]);
@@ -1312,9 +1316,11 @@ suite('deltaDecorations', () => {
startColumn: 1,
endLineNumber: 1,
endColumn: 1
}, {
stickiness: TrackedRangeStickiness.AlwaysGrowsWhenTypingAtEdges
}
},
{
description: 'test',
stickiness: TrackedRangeStickiness.AlwaysGrowsWhenTypingAtEdges
}
);
});
model.changeDecorations((changeAccessor) => {
@@ -1349,16 +1355,16 @@ suite('deltaDecorations', () => {
].join('\n'));
model.deltaDecorations([], [
{ range: new Range(1, 1, 1, 1), options: { className: '1' } },
{ range: new Range(1, 13, 1, 13), options: { className: '2' } },
{ range: new Range(2, 1, 2, 1), options: { className: '3' } },
{ range: new Range(2, 1, 2, 4), options: { className: '4' } },
{ range: new Range(2, 8, 2, 13), options: { className: '5' } },
{ range: new Range(3, 1, 4, 6), options: { className: '6' } },
{ range: new Range(1, 1, 3, 6), options: { className: 'x1' } },
{ range: new Range(2, 5, 2, 8), options: { className: 'x2' } },
{ range: new Range(1, 1, 2, 8), options: { className: 'x3' } },
{ range: new Range(2, 5, 3, 1), options: { className: 'x4' } },
{ range: new Range(1, 1, 1, 1), options: { description: 'test', className: '1' } },
{ range: new Range(1, 13, 1, 13), options: { description: 'test', className: '2' } },
{ range: new Range(2, 1, 2, 1), options: { description: 'test', className: '3' } },
{ range: new Range(2, 1, 2, 4), options: { description: 'test', className: '4' } },
{ range: new Range(2, 8, 2, 13), options: { description: 'test', className: '5' } },
{ range: new Range(3, 1, 4, 6), options: { description: 'test', className: '6' } },
{ range: new Range(1, 1, 3, 6), options: { description: 'test', className: 'x1' } },
{ range: new Range(2, 5, 2, 8), options: { description: 'test', className: 'x2' } },
{ range: new Range(1, 1, 2, 8), options: { description: 'test', className: 'x3' } },
{ range: new Range(2, 5, 3, 1), options: { description: 'test', className: 'x4' } },
]);
let inRange = model.getDecorationsInRange(new Range(2, 6, 2, 6));
@@ -1376,7 +1382,7 @@ suite('deltaDecorations', () => {
'My First Line'
].join('\n'));
const id = model.deltaDecorations([], [{ range: new Range(1, 2, 1, 14), options: { stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, collapseOnReplaceEdit: true } }])[0];
const id = model.deltaDecorations([], [{ range: new Range(1, 2, 1, 14), options: { description: 'test', stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, collapseOnReplaceEdit: true } }])[0];
model.applyEdits([{
range: new Range(1, 1, 1, 14),
text: 'Some new text that is longer than the previous one',

View File

@@ -230,4 +230,25 @@ suite('Editor Modes - Link Computer', () => {
' http://tree-mark.chips.jp/レーズン&ベリーミックス '
);
});
test('issue #121438: Link detection stops at【...】', () => {
assertLink(
'aa https://zh.wikipedia.org/wiki/【我推的孩子】 aa',
' https://zh.wikipedia.org/wiki/【我推的孩子】 '
);
});
test('issue #121438: Link detection stops at《...》', () => {
assertLink(
'aa https://zh.wikipedia.org/wiki/《新青年》编辑部旧址 aa',
' https://zh.wikipedia.org/wiki/《新青年》编辑部旧址 '
);
});
test('issue #121438: Link detection stops at “...”', () => {
assertLink(
'aa https://zh.wikipedia.org/wiki/“常凯申”误译事件 aa',
' https://zh.wikipedia.org/wiki/“常凯申”误译事件 '
);
});
});

View File

@@ -47,6 +47,40 @@ suite('OnEnter', () => {
testIndentAction('begin', '', IndentAction.Indent);
});
test('Issue #121125: onEnterRules with global modifier', () => {
const support = new OnEnterSupport({
onEnterRules: [
{
action: {
appendText: '/// ',
indentAction: IndentAction.Outdent
},
beforeText: /^\s*\/{3}.*$/gm
}
]
});
let testIndentAction = (previousLineText: string, beforeText: string, afterText: string, expectedIndentAction: IndentAction | null, expectedAppendText: string | null, removeText: number = 0) => {
let actual = support.onEnter(EditorAutoIndentStrategy.Advanced, previousLineText, beforeText, afterText);
if (expectedIndentAction === null) {
assert.strictEqual(actual, null, 'isNull:' + beforeText);
} else {
assert.strictEqual(actual !== null, true, 'isNotNull:' + beforeText);
assert.strictEqual(actual!.indentAction, expectedIndentAction, 'indentAction:' + beforeText);
if (expectedAppendText !== null) {
assert.strictEqual(actual!.appendText, expectedAppendText, 'appendText:' + beforeText);
}
if (removeText !== 0) {
assert.strictEqual(actual!.removeText, removeText, 'removeText:' + beforeText);
}
}
};
testIndentAction('/// line', '/// line', '', IndentAction.Outdent, '/// ');
testIndentAction('/// line', '/// line', '', IndentAction.Outdent, '/// ');
});
test('uses regExpRules', () => {
let support = new OnEnterSupport({
onEnterRules: javascriptOnEnterRules

View File

@@ -9,7 +9,7 @@ import * as strings from 'vs/base/common/strings';
import { IViewLineTokens } from 'vs/editor/common/core/lineTokens';
import { MetadataConsts } from 'vs/editor/common/modes';
import { LineDecoration } from 'vs/editor/common/viewLayout/lineDecorations';
import { CharacterMapping, RenderLineInput, renderViewLine2 as renderViewLine, LineRange } from 'vs/editor/common/viewLayout/viewLineRenderer';
import { CharacterMapping, RenderLineInput, renderViewLine2 as renderViewLine, LineRange, DomPosition } from 'vs/editor/common/viewLayout/viewLineRenderer';
import { InlineDecorationType } from 'vs/editor/common/viewModel/viewModel';
import { ViewLineToken, ViewLineTokens } from 'vs/editor/test/common/core/viewLineToken';
@@ -25,8 +25,8 @@ function createPart(endIndex: number, foreground: number): ViewLineToken {
suite('viewLineRenderer.renderLine', () => {
function assertCharacterReplacement(lineContent: string, tabSize: number, expected: string, expectedCharOffsetInPart: number[][], expectedPartLengts: number[]): void {
let _actual = renderViewLine(new RenderLineInput(
function assertCharacterReplacement(lineContent: string, tabSize: number, expected: string, expectedCharOffsetInPart: number[]): void {
const _actual = renderViewLine(new RenderLineInput(
false,
true,
lineContent,
@@ -49,36 +49,37 @@ suite('viewLineRenderer.renderLine', () => {
));
assert.strictEqual(_actual.html, '<span><span class="mtk0">' + expected + '</span></span>');
assertCharacterMapping(_actual.characterMapping, expectedCharOffsetInPart, expectedPartLengts);
const info = expectedCharOffsetInPart.map<CharacterMappingInfo>((absoluteOffset) => [absoluteOffset, [0, absoluteOffset]]);
assertCharacterMapping3(_actual.characterMapping, info);
}
test('replaces spaces', () => {
assertCharacterReplacement(' ', 4, '\u00a0', [[0, 1]], [1]);
assertCharacterReplacement(' ', 4, '\u00a0\u00a0', [[0, 1, 2]], [2]);
assertCharacterReplacement('a b', 4, 'a\u00a0\u00a0b', [[0, 1, 2, 3, 4]], [4]);
assertCharacterReplacement(' ', 4, '\u00a0', [0, 1]);
assertCharacterReplacement(' ', 4, '\u00a0\u00a0', [0, 1, 2]);
assertCharacterReplacement('a b', 4, 'a\u00a0\u00a0b', [0, 1, 2, 3, 4]);
});
test('escapes HTML markup', () => {
assertCharacterReplacement('a<b', 4, 'a&lt;b', [[0, 1, 2, 3]], [3]);
assertCharacterReplacement('a>b', 4, 'a&gt;b', [[0, 1, 2, 3]], [3]);
assertCharacterReplacement('a&b', 4, 'a&amp;b', [[0, 1, 2, 3]], [3]);
assertCharacterReplacement('a<b', 4, 'a&lt;b', [0, 1, 2, 3]);
assertCharacterReplacement('a>b', 4, 'a&gt;b', [0, 1, 2, 3]);
assertCharacterReplacement('a&b', 4, 'a&amp;b', [0, 1, 2, 3]);
});
test('replaces some bad characters', () => {
assertCharacterReplacement('a\0b', 4, 'a&#00;b', [[0, 1, 2, 3]], [3]);
assertCharacterReplacement('a' + String.fromCharCode(CharCode.UTF8_BOM) + 'b', 4, 'a\ufffdb', [[0, 1, 2, 3]], [3]);
assertCharacterReplacement('a\u2028b', 4, 'a\ufffdb', [[0, 1, 2, 3]], [3]);
assertCharacterReplacement('a\0b', 4, 'a&#00;b', [0, 1, 2, 3]);
assertCharacterReplacement('a' + String.fromCharCode(CharCode.UTF8_BOM) + 'b', 4, 'a\ufffdb', [0, 1, 2, 3]);
assertCharacterReplacement('a\u2028b', 4, 'a\ufffdb', [0, 1, 2, 3]);
});
test('handles tabs', () => {
assertCharacterReplacement('\t', 4, '\u00a0\u00a0\u00a0\u00a0', [[0, 4]], [4]);
assertCharacterReplacement('x\t', 4, 'x\u00a0\u00a0\u00a0', [[0, 1, 4]], [4]);
assertCharacterReplacement('xx\t', 4, 'xx\u00a0\u00a0', [[0, 1, 2, 4]], [4]);
assertCharacterReplacement('xxx\t', 4, 'xxx\u00a0', [[0, 1, 2, 3, 4]], [4]);
assertCharacterReplacement('xxxx\t', 4, 'xxxx\u00a0\u00a0\u00a0\u00a0', [[0, 1, 2, 3, 4, 8]], [8]);
assertCharacterReplacement('\t', 4, '\u00a0\u00a0\u00a0\u00a0', [0, 4]);
assertCharacterReplacement('x\t', 4, 'x\u00a0\u00a0\u00a0', [0, 1, 4]);
assertCharacterReplacement('xx\t', 4, 'xx\u00a0\u00a0', [0, 1, 2, 4]);
assertCharacterReplacement('xxx\t', 4, 'xxx\u00a0', [0, 1, 2, 3, 4]);
assertCharacterReplacement('xxxx\t', 4, 'xxxx\u00a0\u00a0\u00a0\u00a0', [0, 1, 2, 3, 4, 8]);
});
function assertParts(lineContent: string, tabSize: number, parts: ViewLineToken[], expected: string, expectedCharOffsetInPart: number[][], expectedPartLengts: number[]): void {
function assertParts(lineContent: string, tabSize: number, parts: ViewLineToken[], expected: string, info: CharacterMappingInfo[]): void {
let _actual = renderViewLine(new RenderLineInput(
false,
true,
@@ -102,23 +103,23 @@ suite('viewLineRenderer.renderLine', () => {
));
assert.strictEqual(_actual.html, '<span>' + expected + '</span>');
assertCharacterMapping(_actual.characterMapping, expectedCharOffsetInPart, expectedPartLengts);
assertCharacterMapping3(_actual.characterMapping, info);
}
test('empty line', () => {
assertParts('', 4, [], '<span></span>', [], []);
assertParts('', 4, [], '<span></span>', []);
});
test('uses part type', () => {
assertParts('x', 4, [createPart(1, 10)], '<span class="mtk10">x</span>', [[0, 1]], [1]);
assertParts('x', 4, [createPart(1, 20)], '<span class="mtk20">x</span>', [[0, 1]], [1]);
assertParts('x', 4, [createPart(1, 30)], '<span class="mtk30">x</span>', [[0, 1]], [1]);
assertParts('x', 4, [createPart(1, 10)], '<span class="mtk10">x</span>', [[0, [0, 0]], [1, [0, 1]]]);
assertParts('x', 4, [createPart(1, 20)], '<span class="mtk20">x</span>', [[0, [0, 0]], [1, [0, 1]]]);
assertParts('x', 4, [createPart(1, 30)], '<span class="mtk30">x</span>', [[0, [0, 0]], [1, [0, 1]]]);
});
test('two parts', () => {
assertParts('xy', 4, [createPart(1, 1), createPart(2, 2)], '<span class="mtk1">x</span><span class="mtk2">y</span>', [[0], [0, 1]], [1, 1]);
assertParts('xyz', 4, [createPart(1, 1), createPart(3, 2)], '<span class="mtk1">x</span><span class="mtk2">yz</span>', [[0], [0, 1, 2]], [1, 2]);
assertParts('xyz', 4, [createPart(2, 1), createPart(3, 2)], '<span class="mtk1">xy</span><span class="mtk2">z</span>', [[0, 1], [0, 1]], [2, 1]);
assertParts('xy', 4, [createPart(1, 1), createPart(2, 2)], '<span class="mtk1">x</span><span class="mtk2">y</span>', [[0, [0, 0]], [1, [1, 0]], [2, [1, 1]]]);
assertParts('xyz', 4, [createPart(1, 1), createPart(3, 2)], '<span class="mtk1">x</span><span class="mtk2">yz</span>', [[0, [0, 0]], [1, [1, 0]], [2, [1, 1]], [3, [1, 2]]]);
assertParts('xyz', 4, [createPart(2, 1), createPart(3, 2)], '<span class="mtk1">xy</span><span class="mtk2">z</span>', [[0, [0, 0]], [1, [0, 1]], [2, [1, 0]], [3, [1, 1]]]);
});
test('overflow', () => {
@@ -168,16 +169,17 @@ suite('viewLineRenderer.renderLine', () => {
].join('');
assert.strictEqual(_actual.html, '<span>' + expectedOutput + '</span>');
assertCharacterMapping(_actual.characterMapping,
assertCharacterMapping3(
_actual.characterMapping,
[
[0],
[0],
[0],
[0],
[0],
[0, 1],
],
[1, 1, 1, 1, 1, 1]
[0, [0, 0]],
[1, [1, 0]],
[2, [2, 0]],
[3, [3, 0]],
[4, [4, 0]],
[5, [5, 0]],
[6, [5, 1]],
]
);
});
@@ -213,24 +215,25 @@ suite('viewLineRenderer.renderLine', () => {
'<span class="mtkz" style="width:20px">\u00b7\u00b7</span>',
'<span class="mtkz" style="width:30px">\u00b7\u00b7\u00b7</span>'
].join('');
let expectedOffsetsArr = [
[0],
[0, 1, 2, 3],
[0, 1, 2, 3, 4, 5],
[0],
[0, 1, 2, 3, 4],
[0],
[0, 1, 2, 3],
[0],
[0],
[0],
[0, 1, 2],
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14],
[0, 1],
[0, 1, 2, 3],
const info: CharacterMappingInfo[] = [
[0, [0, 0]],
[4, [1, 0]], [5, [1, 1]], [6, [1, 2]], [7, [1, 3]],
[8, [2, 0]], [9, [2, 1]], [10, [2, 2]], [11, [2, 3]], [12, [2, 4]], [13, [2, 5]],
[14, [3, 0]],
[15, [4, 0]], [16, [4, 1]], [17, [4, 2]], [18, [4, 3]], [19, [4, 4]],
[20, [5, 0]],
[21, [6, 0]], [22, [6, 1]], [23, [6, 2]], [24, [6, 3]],
[25, [7, 0]],
[26, [8, 0]],
[27, [9, 0]],
[28, [10, 0]], [29, [10, 1]], [30, [10, 2]],
[31, [11, 0]], [32, [11, 1]], [33, [11, 2]], [34, [11, 3]], [35, [11, 4]], [36, [11, 5]], [37, [11, 6]], [38, [11, 7]], [39, [11, 8]], [40, [11, 9]], [41, [11, 10]], [42, [11, 11]], [43, [11, 12]], [44, [11, 13]], [45, [11, 14]],
[46, [12, 0]], [47, [12, 1]],
[48, [13, 0]], [49, [13, 1]], [50, [13, 2]], [51, [13, 3]],
];
let _actual = renderViewLine(new RenderLineInput(
const _actual = renderViewLine(new RenderLineInput(
false,
true,
lineText,
@@ -253,7 +256,7 @@ suite('viewLineRenderer.renderLine', () => {
));
assert.strictEqual(_actual.html, '<span>' + expectedOutput + '</span>');
assertCharacterMapping(_actual.characterMapping, expectedOffsetsArr, [4, 4, 6, 1, 5, 1, 4, 1, 1, 1, 3, 15, 2, 3]);
assertCharacterMapping3(_actual.characterMapping, info);
});
test('issue #2255: Weird line rendering part 1', () => {
@@ -283,20 +286,21 @@ suite('viewLineRenderer.renderLine', () => {
'<span class="mtk9">)</span>',
'<span class="mtk10">,</span>',
].join('');
let expectedOffsetsArr = [
[0, 4, 8], // 3 chars
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], // 12 chars
[0, 4, 8, 12, 16, 20], // 6 chars
[0], // 1 char
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20], // 21 chars
[0, 1], // 2 chars
[0], // 1 char
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19], // 20 chars
[0], // 1 char
[0, 1] // 2 chars
const info: CharacterMappingInfo[] = [
[0, [0, 0]], [4, [0, 4]], [8, [0, 8]],
[12, [1, 0]], [13, [1, 1]], [14, [1, 2]], [15, [1, 3]], [16, [1, 4]], [17, [1, 5]], [18, [1, 6]], [19, [1, 7]], [20, [1, 8]], [21, [1, 9]], [22, [1, 10]], [23, [1, 11]],
[24, [2, 0]], [28, [2, 4]], [32, [2, 8]], [36, [2, 12]], [40, [2, 16]], [44, [2, 20]],
[48, [3, 0]],
[49, [4, 0]], [50, [4, 1]], [51, [4, 2]], [52, [4, 3]], [53, [4, 4]], [54, [4, 5]], [55, [4, 6]], [56, [4, 7]], [57, [4, 8]], [58, [4, 9]], [59, [4, 10]], [60, [4, 11]], [61, [4, 12]], [62, [4, 13]], [63, [4, 14]], [64, [4, 15]], [65, [4, 16]], [66, [4, 17]], [67, [4, 18]], [68, [4, 19]], [69, [4, 20]],
[70, [5, 0]], [71, [5, 1]],
[72, [6, 0]],
[73, [7, 0]], [74, [7, 1]], [75, [7, 2]], [76, [7, 3]], [77, [7, 4]], [78, [7, 5]], [79, [7, 6]], [80, [7, 7]], [81, [7, 8]], [82, [7, 9]], [83, [7, 10]], [84, [7, 11]], [85, [7, 12]], [86, [7, 13]], [87, [7, 14]], [88, [7, 15]], [89, [7, 16]], [90, [7, 17]], [91, [7, 18]], [92, [7, 19]],
[93, [8, 0]],
[94, [9, 0]], [95, [9, 1]],
];
let _actual = renderViewLine(new RenderLineInput(
const _actual = renderViewLine(new RenderLineInput(
false,
true,
lineText,
@@ -319,7 +323,7 @@ suite('viewLineRenderer.renderLine', () => {
));
assert.strictEqual(_actual.html, '<span>' + expectedOutput + '</span>');
assertCharacterMapping(_actual.characterMapping, expectedOffsetsArr, [12, 12, 24, 1, 21, 2, 1, 20, 1, 1]);
assertCharacterMapping3(_actual.characterMapping, info);
});
test('issue #2255: Weird line rendering part 2', () => {
@@ -349,20 +353,21 @@ suite('viewLineRenderer.renderLine', () => {
'<span class="mtk9">)</span>',
'<span class="mtk10">,</span>',
].join('');
let expectedOffsetsArr = [
[0, 1, 4, 8], // 4 chars
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], // 12 chars
[0, 4, 8, 12, 16, 20], // 6 chars
[0], // 1 char
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20], // 21 chars
[0, 1], // 2 chars
[0], // 1 char
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19], // 20 chars
[0], // 1 char
[0, 1] // 2 chars
const info: CharacterMappingInfo[] = [
[0, [0, 0]], [1, [0, 1]], [4, [0, 4]], [8, [0, 8]],
[12, [1, 0]], [13, [1, 1]], [14, [1, 2]], [15, [1, 3]], [16, [1, 4]], [17, [1, 5]], [18, [1, 6]], [19, [1, 7]], [20, [1, 8]], [21, [1, 9]], [22, [1, 10]], [23, [1, 11]],
[24, [2, 0]], [28, [2, 4]], [32, [2, 8]], [36, [2, 12]], [40, [2, 16]], [44, [2, 20]],
[48, [3, 0]],
[49, [4, 0]], [50, [4, 1]], [51, [4, 2]], [52, [4, 3]], [53, [4, 4]], [54, [4, 5]], [55, [4, 6]], [56, [4, 7]], [57, [4, 8]], [58, [4, 9]], [59, [4, 10]], [60, [4, 11]], [61, [4, 12]], [62, [4, 13]], [63, [4, 14]], [64, [4, 15]], [65, [4, 16]], [66, [4, 17]], [67, [4, 18]], [68, [4, 19]], [69, [4, 20]],
[70, [5, 0]], [71, [5, 1]],
[72, [6, 0]],
[73, [7, 0]], [74, [7, 1]], [75, [7, 2]], [76, [7, 3]], [77, [7, 4]], [78, [7, 5]], [79, [7, 6]], [80, [7, 7]], [81, [7, 8]], [82, [7, 9]], [83, [7, 10]], [84, [7, 11]], [85, [7, 12]], [86, [7, 13]], [87, [7, 14]], [88, [7, 15]], [89, [7, 16]], [90, [7, 17]], [91, [7, 18]], [92, [7, 19]],
[93, [8, 0]],
[94, [9, 0]], [95, [9, 1]],
];
let _actual = renderViewLine(new RenderLineInput(
const _actual = renderViewLine(new RenderLineInput(
false,
true,
lineText,
@@ -385,7 +390,7 @@ suite('viewLineRenderer.renderLine', () => {
));
assert.strictEqual(_actual.html, '<span>' + expectedOutput + '</span>');
assertCharacterMapping(_actual.characterMapping, expectedOffsetsArr, [12, 12, 24, 1, 21, 2, 1, 20, 1, 1]);
assertCharacterMapping3(_actual.characterMapping, info);
});
test('issue #91178: after decoration type shown before cursor', () => {
@@ -401,23 +406,23 @@ suite('viewLineRenderer.renderLine', () => {
].join('');
const expectedCharacterMapping = new CharacterMapping(17, 4);
expectedCharacterMapping.setPartData(0, 0, 0, 0);
expectedCharacterMapping.setPartData(1, 0, 1, 0);
expectedCharacterMapping.setPartData(2, 0, 2, 0);
expectedCharacterMapping.setPartData(3, 0, 3, 0);
expectedCharacterMapping.setPartData(4, 0, 4, 0);
expectedCharacterMapping.setPartData(5, 0, 5, 0);
expectedCharacterMapping.setPartData(6, 0, 6, 0);
expectedCharacterMapping.setPartData(7, 0, 7, 0);
expectedCharacterMapping.setPartData(8, 0, 8, 0);
expectedCharacterMapping.setPartData(9, 0, 9, 0);
expectedCharacterMapping.setPartData(10, 0, 10, 0);
expectedCharacterMapping.setPartData(11, 0, 11, 0);
expectedCharacterMapping.setPartData(12, 2, 0, 12);
expectedCharacterMapping.setPartData(13, 3, 1, 12);
expectedCharacterMapping.setPartData(14, 3, 2, 12);
expectedCharacterMapping.setPartData(15, 3, 3, 12);
expectedCharacterMapping.setPartData(16, 3, 4, 12);
expectedCharacterMapping.setColumnInfo(1, 0, 0, 0);
expectedCharacterMapping.setColumnInfo(2, 0, 1, 0);
expectedCharacterMapping.setColumnInfo(3, 0, 2, 0);
expectedCharacterMapping.setColumnInfo(4, 0, 3, 0);
expectedCharacterMapping.setColumnInfo(5, 0, 4, 0);
expectedCharacterMapping.setColumnInfo(6, 0, 5, 0);
expectedCharacterMapping.setColumnInfo(7, 0, 6, 0);
expectedCharacterMapping.setColumnInfo(8, 0, 7, 0);
expectedCharacterMapping.setColumnInfo(9, 0, 8, 0);
expectedCharacterMapping.setColumnInfo(10, 0, 9, 0);
expectedCharacterMapping.setColumnInfo(11, 0, 10, 0);
expectedCharacterMapping.setColumnInfo(12, 0, 11, 0);
expectedCharacterMapping.setColumnInfo(13, 2, 0, 12);
expectedCharacterMapping.setColumnInfo(14, 3, 1, 12);
expectedCharacterMapping.setColumnInfo(15, 3, 2, 12);
expectedCharacterMapping.setColumnInfo(16, 3, 3, 12);
expectedCharacterMapping.setColumnInfo(17, 3, 4, 12);
const actual = renderViewLine(new RenderLineInput(
true,
@@ -792,14 +797,12 @@ suite('viewLineRenderer.renderLine', () => {
function decodeCharacterMapping(source: CharacterMapping) {
const mapping: ICharMappingData[] = [];
for (let charOffset = 0; charOffset < source.length; charOffset++) {
const partData = source.charOffsetToPartData(charOffset);
const partIndex = CharacterMapping.getPartIndex(partData);
const charIndex = CharacterMapping.getCharIndex(partData);
mapping.push({ charOffset, partIndex, charIndex });
const domPosition = source.getDomPosition(charOffset + 1);
mapping.push({ charOffset, partIndex: domPosition.partIndex, charIndex: domPosition.charIndex });
}
const absoluteOffsets: number[] = [];
for (const absoluteOffset of source.getAbsoluteOffsets()) {
absoluteOffsets.push(absoluteOffset);
for (let i = 0; i < source.length; i++) {
absoluteOffsets[i] = source.getAbsoluteOffset(i + 1);
}
return { mapping, absoluteOffsets };
}
@@ -809,64 +812,37 @@ suite('viewLineRenderer.renderLine', () => {
const _expected = decodeCharacterMapping(expected);
assert.deepStrictEqual(_actual, _expected);
}
function assertCharacterMapping(actual: CharacterMapping, expectedCharPartOffsets: number[][], expectedPartLengths: number[]): void {
assertCharPartOffsets(actual, expectedCharPartOffsets);
let expectedCharAbsoluteOffset: number[] = [], currentPartAbsoluteOffset = 0;
for (let partIndex = 0; partIndex < expectedCharPartOffsets.length; partIndex++) {
const part = expectedCharPartOffsets[partIndex];
for (const charIndex of part) {
expectedCharAbsoluteOffset.push(currentPartAbsoluteOffset + charIndex);
}
currentPartAbsoluteOffset += expectedPartLengths[partIndex];
}
let actualCharOffset: number[] = [];
let tmp = actual.getAbsoluteOffsets();
for (let i = 0; i < tmp.length; i++) {
actualCharOffset[i] = tmp[i];
}
assert.deepStrictEqual(actualCharOffset, expectedCharAbsoluteOffset);
}
function assertCharPartOffsets(actual: CharacterMapping, expected: number[][]): void {
let charOffset = 0;
for (let partIndex = 0; partIndex < expected.length; partIndex++) {
let part = expected[partIndex];
for (const charIndex of part) {
// here
let _actualPartData = actual.charOffsetToPartData(charOffset);
let actualPartIndex = CharacterMapping.getPartIndex(_actualPartData);
let actualCharIndex = CharacterMapping.getCharIndex(_actualPartData);
assert.deepStrictEqual(
{ partIndex: actualPartIndex, charIndex: actualCharIndex },
{ partIndex: partIndex, charIndex: charIndex },
`character mapping for offset ${charOffset}`
);
// here
let actualOffset = actual.partDataToCharOffset(partIndex, part[part.length - 1] + 1, charIndex);
assert.strictEqual(
actualOffset,
charOffset,
`character mapping for part ${partIndex}, ${charIndex}`
);
charOffset++;
}
}
assert.strictEqual(actual.length, charOffset);
}
});
type CharacterMappingInfo = [number, [number, number]];
function assertCharacterMapping3(actual: CharacterMapping, expectedInfo: CharacterMappingInfo[]): void {
for (let i = 0; i < expectedInfo.length; i++) {
const [absoluteOffset, [partIndex, charIndex]] = expectedInfo[i];
const actualDomPosition = actual.getDomPosition(i + 1);
assert.deepStrictEqual(actualDomPosition, new DomPosition(partIndex, charIndex), `getDomPosition(${i + 1})`);
let partLength = charIndex + 1;
for (let j = i + 1; j < expectedInfo.length; j++) {
const [, [nextPartIndex, nextCharIndex]] = expectedInfo[j];
if (nextPartIndex === partIndex) {
partLength = nextCharIndex + 1;
} else {
break;
}
}
const actualColumn = actual.getColumn(new DomPosition(partIndex, charIndex), partLength);
assert.strictEqual(actualColumn, i + 1, `actual.getColumn(${partIndex}, ${charIndex})`);
const actualAbsoluteOffset = actual.getAbsoluteOffset(i + 1);
assert.strictEqual(actualAbsoluteOffset, absoluteOffset, `actual.getAbsoluteOffset(${i + 1})`);
}
assert.strictEqual(actual.length, expectedInfo.length, `length mismatch`);
}
suite('viewLineRenderer.renderLine 2', () => {
function testCreateLineParts(fontIsMonospace: boolean, lineContent: string, tokens: ViewLineToken[], fauxIndentLength: number, renderWhitespace: 'none' | 'boundary' | 'selection' | 'trailing' | 'all', selections: LineRange[] | null, expected: string): void {
@@ -1739,7 +1715,7 @@ suite('viewLineRenderer.renderLine 2', () => {
let expected = [
'<span>',
'<span class="mtk3">\u00a0\u00a0\u00a0\u00a0}</span>',
'<span class="ced-TextEditorDecorationType2-5e9b9b3f-3 ced-TextEditorDecorationType2-3 ced-TextEditorDecorationType2-5e9b9b3f-4 ced-TextEditorDecorationType2-4"></span>',
'<span class="ced-TextEditorDecorationType2-5e9b9b3f-3 ced-TextEditorDecorationType2-3"></span><span class="ced-TextEditorDecorationType2-5e9b9b3f-4 ced-TextEditorDecorationType2-4"></span>',
'</span>'
].join('');
@@ -2138,6 +2114,53 @@ suite('viewLineRenderer.renderLine 2', () => {
assert.deepStrictEqual(actual.html, expected);
});
test('issue #124038: Multiple end-of-line text decorations get merged', () => {
const actual = renderViewLine(new RenderLineInput(
true,
false,
' if',
false,
true,
false,
0,
createViewLineTokens([createPart(4, 1), createPart(6, 2)]),
[
new LineDecoration(7, 7, 'ced-1-TextEditorDecorationType2-17c14d98-3 ced-1-TextEditorDecorationType2-3', InlineDecorationType.Before),
new LineDecoration(7, 7, 'ced-1-TextEditorDecorationType2-17c14d98-4 ced-1-TextEditorDecorationType2-4', InlineDecorationType.After),
new LineDecoration(7, 7, 'ced-ghost-text-1-4', InlineDecorationType.After),
],
4,
0,
10,
10,
10,
10000,
'all',
false,
false,
null
));
const expected = [
'<span>',
'<span class="mtkw">····</span><span class="mtk2">if</span><span class="ced-1-TextEditorDecorationType2-17c14d98-3 ced-1-TextEditorDecorationType2-3"></span><span class="ced-1-TextEditorDecorationType2-17c14d98-4 ced-1-TextEditorDecorationType2-4"></span><span class="ced-ghost-text-1-4"></span>',
'</span>'
].join('');
assert.deepStrictEqual(actual.html, expected);
assertCharacterMapping3(actual.characterMapping,
[
[0, [0, 0]],
[1, [0, 1]],
[2, [0, 2]],
[3, [0, 3]],
[4, [1, 0]],
[5, [1, 1]],
[6, [3, 0]],
]
);
});
function createTestGetColumnOfLinePartOffset(lineContent: string, tabSize: number, parts: ViewLineToken[], expectedPartLengths: number[]): (partIndex: number, partLength: number, offset: number, expected: number) => void {
let renderLineOutput = renderViewLine(new RenderLineInput(
@@ -2163,9 +2186,8 @@ suite('viewLineRenderer.renderLine 2', () => {
));
return (partIndex: number, partLength: number, offset: number, expected: number) => {
let charOffset = renderLineOutput.characterMapping.partDataToCharOffset(partIndex, partLength, offset);
let actual = charOffset + 1;
assert.strictEqual(actual, expected, 'getColumnOfLinePartOffset for ' + partIndex + ' @ ' + offset);
const actualColumn = renderLineOutput.characterMapping.getColumn(new DomPosition(partIndex, offset), partLength);
assert.strictEqual(actualColumn, expected, 'getColumn for ' + partIndex + ', ' + offset);
};
}

View File

@@ -28,6 +28,7 @@ suite('ViewModelDecorations', () => {
model.changeDecorations((accessor) => {
let createOpts = (id: string) => {
return {
description: 'test',
className: id,
inlineClassName: 'i-' + id,
beforeContentClassName: 'b-' + id,
@@ -165,6 +166,7 @@ suite('ViewModelDecorations', () => {
accessor.addDecoration(
new Range(1, 50, 1, 51),
{
description: 'test',
beforeContentClassName: 'dec1'
}
);
@@ -199,6 +201,7 @@ suite('ViewModelDecorations', () => {
accessor.addDecoration(
new Range(1, 1, 1, 1),
{
description: 'test',
beforeContentClassName: 'before1',
afterContentClassName: 'after1'
}