Merge pull request #207587 from microsoft/aiday/autoIndentTestSuite

Auto-indent test suite
This commit is contained in:
Aiday Marlen Kyzy 2024-03-20 17:00:33 +01:00 committed by GitHub
commit b329f9c2a5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 895 additions and 240 deletions

View file

@ -9,7 +9,7 @@ import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { EditorAction, EditorContributionInstantiation, IActionOptions, registerEditorAction, registerEditorContribution, ServicesAccessor } from 'vs/editor/browser/editorExtensions';
import { ShiftCommand } from 'vs/editor/common/commands/shiftCommand';
import { EditorAutoIndentStrategy, EditorOption } from 'vs/editor/common/config/editorOptions';
import { EditOperation, ISingleEditOperation } from 'vs/editor/common/core/editOperation';
import { ISingleEditOperation } from 'vs/editor/common/core/editOperation';
import { IRange, Range } from 'vs/editor/common/core/range';
import { Selection } from 'vs/editor/common/core/selection';
import { ICommand, ICursorStateComputerData, IEditOperationBuilder, IEditorContribution } from 'vs/editor/common/editorCommon';
@ -20,123 +20,11 @@ import { StandardTokenType } from 'vs/editor/common/encodedTokenAttributes';
import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry';
import { IndentConsts } from 'vs/editor/common/languages/supports/indentRules';
import { IModelService } from 'vs/editor/common/services/model';
import * as indentUtils from 'vs/editor/contrib/indentation/browser/indentUtils';
import * as indentUtils from 'vs/editor/contrib/indentation/common/indentUtils';
import * as nls from 'vs/nls';
import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
import { normalizeIndentation } from 'vs/editor/common/core/indentation';
import { getGoodIndentForLine, getIndentMetadata } from 'vs/editor/common/languages/autoIndent';
export function getReindentEditOperations(model: ITextModel, languageConfigurationService: ILanguageConfigurationService, startLineNumber: number, endLineNumber: number, inheritedIndent?: string): ISingleEditOperation[] {
if (model.getLineCount() === 1 && model.getLineMaxColumn(1) === 1) {
// Model is empty
return [];
}
const indentationRules = languageConfigurationService.getLanguageConfiguration(model.getLanguageId()).indentationRules;
if (!indentationRules) {
return [];
}
endLineNumber = Math.min(endLineNumber, model.getLineCount());
// Skip `unIndentedLinePattern` lines
while (startLineNumber <= endLineNumber) {
if (!indentationRules.unIndentedLinePattern) {
break;
}
const text = model.getLineContent(startLineNumber);
if (!indentationRules.unIndentedLinePattern.test(text)) {
break;
}
startLineNumber++;
}
if (startLineNumber > endLineNumber - 1) {
return [];
}
const { tabSize, indentSize, insertSpaces } = model.getOptions();
const shiftIndent = (indentation: string, count?: number) => {
count = count || 1;
return ShiftCommand.shiftIndent(indentation, indentation.length + count, tabSize, indentSize, insertSpaces);
};
const unshiftIndent = (indentation: string, count?: number) => {
count = count || 1;
return ShiftCommand.unshiftIndent(indentation, indentation.length + count, tabSize, indentSize, insertSpaces);
};
const indentEdits: ISingleEditOperation[] = [];
// indentation being passed to lines below
let globalIndent: string;
// Calculate indentation for the first line
// If there is no passed-in indentation, we use the indentation of the first line as base.
const currentLineText = model.getLineContent(startLineNumber);
let adjustedLineContent = currentLineText;
if (inheritedIndent !== undefined && inheritedIndent !== null) {
globalIndent = inheritedIndent;
const oldIndentation = strings.getLeadingWhitespace(currentLineText);
adjustedLineContent = globalIndent + currentLineText.substring(oldIndentation.length);
if (indentationRules.decreaseIndentPattern && indentationRules.decreaseIndentPattern.test(adjustedLineContent)) {
globalIndent = unshiftIndent(globalIndent);
adjustedLineContent = globalIndent + currentLineText.substring(oldIndentation.length);
}
if (currentLineText !== adjustedLineContent) {
indentEdits.push(EditOperation.replaceMove(new Selection(startLineNumber, 1, startLineNumber, oldIndentation.length + 1), normalizeIndentation(globalIndent, indentSize, insertSpaces)));
}
} else {
globalIndent = strings.getLeadingWhitespace(currentLineText);
}
// idealIndentForNextLine doesn't equal globalIndent when there is a line matching `indentNextLinePattern`.
let idealIndentForNextLine: string = globalIndent;
if (indentationRules.increaseIndentPattern && indentationRules.increaseIndentPattern.test(adjustedLineContent)) {
idealIndentForNextLine = shiftIndent(idealIndentForNextLine);
globalIndent = shiftIndent(globalIndent);
}
else if (indentationRules.indentNextLinePattern && indentationRules.indentNextLinePattern.test(adjustedLineContent)) {
idealIndentForNextLine = shiftIndent(idealIndentForNextLine);
}
startLineNumber++;
// Calculate indentation adjustment for all following lines
for (let lineNumber = startLineNumber; lineNumber <= endLineNumber; lineNumber++) {
const text = model.getLineContent(lineNumber);
const oldIndentation = strings.getLeadingWhitespace(text);
const adjustedLineContent = idealIndentForNextLine + text.substring(oldIndentation.length);
if (indentationRules.decreaseIndentPattern && indentationRules.decreaseIndentPattern.test(adjustedLineContent)) {
idealIndentForNextLine = unshiftIndent(idealIndentForNextLine);
globalIndent = unshiftIndent(globalIndent);
}
if (oldIndentation !== idealIndentForNextLine) {
indentEdits.push(EditOperation.replaceMove(new Selection(lineNumber, 1, lineNumber, oldIndentation.length + 1), normalizeIndentation(idealIndentForNextLine, indentSize, insertSpaces)));
}
// calculate idealIndentForNextLine
if (indentationRules.unIndentedLinePattern && indentationRules.unIndentedLinePattern.test(text)) {
// In reindent phase, if the line matches `unIndentedLinePattern` we inherit indentation from above lines
// but don't change globalIndent and idealIndentForNextLine.
continue;
} else if (indentationRules.increaseIndentPattern && indentationRules.increaseIndentPattern.test(adjustedLineContent)) {
globalIndent = shiftIndent(globalIndent);
idealIndentForNextLine = globalIndent;
} else if (indentationRules.indentNextLinePattern && indentationRules.indentNextLinePattern.test(adjustedLineContent)) {
idealIndentForNextLine = shiftIndent(idealIndentForNextLine);
} else {
idealIndentForNextLine = globalIndent;
}
}
return indentEdits;
}
import { getReindentEditOperations } from '../common/indentation';
export class IndentationToSpacesAction extends EditorAction {
public static readonly ID = 'editor.action.indentationToSpaces';

View file

@ -0,0 +1,124 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as strings from 'vs/base/common/strings';
import { ShiftCommand } from 'vs/editor/common/commands/shiftCommand';
import { EditOperation, ISingleEditOperation } from 'vs/editor/common/core/editOperation';
import { normalizeIndentation } from 'vs/editor/common/core/indentation';
import { Selection } from 'vs/editor/common/core/selection';
import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry';
import { ITextModel } from 'vs/editor/common/model';
export function getReindentEditOperations(model: ITextModel, languageConfigurationService: ILanguageConfigurationService, startLineNumber: number, endLineNumber: number, inheritedIndent?: string): ISingleEditOperation[] {
if (model.getLineCount() === 1 && model.getLineMaxColumn(1) === 1) {
// Model is empty
return [];
}
const indentationRules = languageConfigurationService.getLanguageConfiguration(model.getLanguageId()).indentationRules;
if (!indentationRules) {
return [];
}
endLineNumber = Math.min(endLineNumber, model.getLineCount());
// Skip `unIndentedLinePattern` lines
while (startLineNumber <= endLineNumber) {
if (!indentationRules.unIndentedLinePattern) {
break;
}
const text = model.getLineContent(startLineNumber);
if (!indentationRules.unIndentedLinePattern.test(text)) {
break;
}
startLineNumber++;
}
if (startLineNumber > endLineNumber - 1) {
return [];
}
const { tabSize, indentSize, insertSpaces } = model.getOptions();
const shiftIndent = (indentation: string, count?: number) => {
count = count || 1;
return ShiftCommand.shiftIndent(indentation, indentation.length + count, tabSize, indentSize, insertSpaces);
};
const unshiftIndent = (indentation: string, count?: number) => {
count = count || 1;
return ShiftCommand.unshiftIndent(indentation, indentation.length + count, tabSize, indentSize, insertSpaces);
};
const indentEdits: ISingleEditOperation[] = [];
// indentation being passed to lines below
let globalIndent: string;
// Calculate indentation for the first line
// If there is no passed-in indentation, we use the indentation of the first line as base.
const currentLineText = model.getLineContent(startLineNumber);
let adjustedLineContent = currentLineText;
if (inheritedIndent !== undefined && inheritedIndent !== null) {
globalIndent = inheritedIndent;
const oldIndentation = strings.getLeadingWhitespace(currentLineText);
adjustedLineContent = globalIndent + currentLineText.substring(oldIndentation.length);
if (indentationRules.decreaseIndentPattern && indentationRules.decreaseIndentPattern.test(adjustedLineContent)) {
globalIndent = unshiftIndent(globalIndent);
adjustedLineContent = globalIndent + currentLineText.substring(oldIndentation.length);
}
if (currentLineText !== adjustedLineContent) {
indentEdits.push(EditOperation.replaceMove(new Selection(startLineNumber, 1, startLineNumber, oldIndentation.length + 1), normalizeIndentation(globalIndent, indentSize, insertSpaces)));
}
} else {
globalIndent = strings.getLeadingWhitespace(currentLineText);
}
// idealIndentForNextLine doesn't equal globalIndent when there is a line matching `indentNextLinePattern`.
let idealIndentForNextLine: string = globalIndent;
if (indentationRules.increaseIndentPattern && indentationRules.increaseIndentPattern.test(adjustedLineContent)) {
idealIndentForNextLine = shiftIndent(idealIndentForNextLine);
globalIndent = shiftIndent(globalIndent);
}
else if (indentationRules.indentNextLinePattern && indentationRules.indentNextLinePattern.test(adjustedLineContent)) {
idealIndentForNextLine = shiftIndent(idealIndentForNextLine);
}
startLineNumber++;
// Calculate indentation adjustment for all following lines
for (let lineNumber = startLineNumber; lineNumber <= endLineNumber; lineNumber++) {
const text = model.getLineContent(lineNumber);
const oldIndentation = strings.getLeadingWhitespace(text);
const adjustedLineContent = idealIndentForNextLine + text.substring(oldIndentation.length);
if (indentationRules.decreaseIndentPattern && indentationRules.decreaseIndentPattern.test(adjustedLineContent)) {
idealIndentForNextLine = unshiftIndent(idealIndentForNextLine);
globalIndent = unshiftIndent(globalIndent);
}
if (oldIndentation !== idealIndentForNextLine) {
indentEdits.push(EditOperation.replaceMove(new Selection(lineNumber, 1, lineNumber, oldIndentation.length + 1), normalizeIndentation(idealIndentForNextLine, indentSize, insertSpaces)));
}
// calculate idealIndentForNextLine
if (indentationRules.unIndentedLinePattern && indentationRules.unIndentedLinePattern.test(text)) {
// In reindent phase, if the line matches `unIndentedLinePattern` we inherit indentation from above lines
// but don't change globalIndent and idealIndentForNextLine.
continue;
} else if (indentationRules.increaseIndentPattern && indentationRules.increaseIndentPattern.test(adjustedLineContent)) {
globalIndent = shiftIndent(globalIndent);
idealIndentForNextLine = globalIndent;
} else if (indentationRules.indentNextLinePattern && indentationRules.indentNextLinePattern.test(adjustedLineContent)) {
idealIndentForNextLine = shiftIndent(idealIndentForNextLine);
} else {
idealIndentForNextLine = globalIndent;
}
}
return indentEdits;
}

View file

@ -6,19 +6,25 @@
import * as assert from 'assert';
import { DisposableStore } from 'vs/base/common/lifecycle';
import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils';
import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry';
import { createTextModel } from 'vs/editor/test/common/testTextModel';
import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock';
import { Range } from 'vs/editor/common/core/range';
import { Selection } from 'vs/editor/common/core/selection';
import { MetadataConsts, StandardTokenType } from 'vs/editor/common/encodedTokenAttributes';
import { EncodedTokenizationResult, IState, TokenizationRegistry } from 'vs/editor/common/languages';
import { MetadataConsts } from 'vs/editor/common/encodedTokenAttributes';
import { EncodedTokenizationResult, IState, ITokenizationSupport, TokenizationRegistry } from 'vs/editor/common/languages';
import { ILanguageService } from 'vs/editor/common/languages/language';
import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry';
import { NullState } from 'vs/editor/common/languages/nullTokenize';
import { AutoIndentOnPaste, IndentationToSpacesCommand, IndentationToTabsCommand } from 'vs/editor/contrib/indentation/browser/indentation';
import { withTestCodeEditor } from 'vs/editor/test/browser/testCodeEditor';
import { testCommand } from 'vs/editor/test/browser/testCommand';
import { javascriptIndentationRules } from 'vs/editor/test/common/modes/supports/javascriptIndentationRules';
import { javascriptOnEnterRules } from 'vs/editor/test/common/modes/supports/javascriptOnEnterRules';
import { createTextModel } from 'vs/editor/test/common/testTextModel';
enum Language {
TypeScript,
Ruby
}
function testIndentationToSpacesCommand(lines: string[], selection: Selection, tabSize: number, expectedLines: string[], expectedSelection: Selection): void {
testCommand(lines, null, selection, (accessor, sel) => new IndentationToSpacesCommand(sel, tabSize), expectedLines, expectedSelection);
@ -28,7 +34,72 @@ function testIndentationToTabsCommand(lines: string[], selection: Selection, tab
testCommand(lines, null, selection, (accessor, sel) => new IndentationToTabsCommand(sel, tabSize), expectedLines, expectedSelection);
}
suite('Editor Contrib - Indentation to Spaces', () => {
function registerLanguage(instantiationService: TestInstantiationService, languageId: string, language: Language, disposables: DisposableStore) {
const languageService = instantiationService.get(ILanguageService);
registerLanguageConfiguration(instantiationService, languageId, language, disposables);
disposables.add(languageService.registerLanguage({ id: languageId }));
}
// TODO@aiday-mar read directly the configuration file
function registerLanguageConfiguration(instantiationService: TestInstantiationService, languageId: string, language: Language, disposables: DisposableStore) {
const languageConfigurationService = instantiationService.get(ILanguageConfigurationService);
switch (language) {
case Language.TypeScript:
disposables.add(languageConfigurationService.register(languageId, {
brackets: [
['{', '}'],
['[', ']'],
['(', ')']
],
comments: {
lineComment: '//',
blockComment: ['/*', '*/']
},
indentationRules: javascriptIndentationRules,
onEnterRules: javascriptOnEnterRules
}));
break;
case Language.Ruby:
disposables.add(languageConfigurationService.register(languageId, {
brackets: [
['{', '}'],
['[', ']'],
['(', ')']
],
indentationRules: {
decreaseIndentPattern: /^\s*([}\]]([,)]?\s*(#|$)|\.[a-zA-Z_]\w*\b)|(end|rescue|ensure|else|elsif)\b|(in|when)\s)/,
increaseIndentPattern: /^\s*((begin|class|(private|protected)\s+def|def|else|elsif|ensure|for|if|module|rescue|unless|until|when|in|while|case)|([^#]*\sdo\b)|([^#]*=\s*(case|if|unless)))\b([^#\{;]|(\"|'|\/).*\4)*(#.*)?$/,
},
}));
break;
}
}
function registerTokens(instantiationService: TestInstantiationService, tokens: { startIndex: number; value: number }[][], languageId: string, disposables: DisposableStore) {
let lineIndex = 0;
const languageService = instantiationService.get(ILanguageService);
const tokenizationSupport: ITokenizationSupport = {
getInitialState: () => NullState,
tokenize: undefined!,
tokenizeEncoded: (line: string, hasEOL: boolean, state: IState): EncodedTokenizationResult => {
const tokensOnLine = tokens[lineIndex++];
const encodedLanguageId = languageService.languageIdCodec.encodeLanguageId(languageId);
const result = new Uint32Array(2 * tokensOnLine.length);
for (let i = 0; i < tokensOnLine.length; i++) {
result[2 * i] = tokensOnLine[i].startIndex;
result[2 * i + 1] =
(
(encodedLanguageId << MetadataConsts.LANGUAGEID_OFFSET)
| (tokensOnLine[i].value << MetadataConsts.TOKEN_TYPE_OFFSET)
);
}
return new EncodedTokenizationResult(result, state);
}
};
disposables.add(TokenizationRegistry.register(languageId, tokenizationSupport));
}
suite('Change Indentation to Spaces - TypeScript/Javascript', () => {
ensureNoDisposablesAreLeakedInTestSuite();
@ -117,7 +188,7 @@ suite('Editor Contrib - Indentation to Spaces', () => {
});
});
suite('Editor Contrib - Indentation to Tabs', () => {
suite('Change Indentation to Tabs - TypeScript/Javascript', () => {
ensureNoDisposablesAreLeakedInTestSuite();
@ -202,7 +273,9 @@ suite('Editor Contrib - Indentation to Tabs', () => {
});
});
suite('Editor Contrib - Auto Indent On Paste', () => {
suite('`Full` Auto Indent On Paste - TypeScript/JavaScript', () => {
const languageId = 'ts-test';
let disposables: DisposableStore;
setup(() => {
@ -216,95 +289,61 @@ suite('Editor Contrib - Auto Indent On Paste', () => {
ensureNoDisposablesAreLeakedInTestSuite();
test('issue #119225: Do not add extra leading space when pasting JSDoc', () => {
const languageId = 'leadingSpacePaste';
const model = createTextModel("", languageId, {});
disposables.add(model);
withTestCodeEditor(model, { autoIndent: 'full' }, (editor, viewModel, instantiationService) => {
const languageService = instantiationService.get(ILanguageService);
const languageConfigurationService = instantiationService.get(ILanguageConfigurationService);
disposables.add(languageService.registerLanguage({ id: languageId }));
disposables.add(TokenizationRegistry.register(languageId, {
getInitialState: (): IState => NullState,
tokenize: () => {
throw new Error('not implemented');
},
tokenizeEncoded: (line: string, hasEOL: boolean, state: IState): EncodedTokenizationResult => {
const tokensArr: number[] = [];
if (line.indexOf('*') !== -1) {
tokensArr.push(0);
tokensArr.push(StandardTokenType.Comment << MetadataConsts.TOKEN_TYPE_OFFSET);
} else {
tokensArr.push(0);
tokensArr.push(StandardTokenType.Other << MetadataConsts.TOKEN_TYPE_OFFSET);
}
const tokens = new Uint32Array(tokensArr.length);
for (let i = 0; i < tokens.length; i++) {
tokens[i] = tokensArr[i];
}
return new EncodedTokenizationResult(tokens, state);
}
}));
disposables.add(languageConfigurationService.register(languageId, {
brackets: [
['{', '}'],
['[', ']'],
['(', ')']
],
comments: {
lineComment: '//',
blockComment: ['/*', '*/']
},
indentationRules: javascriptIndentationRules,
onEnterRules: javascriptOnEnterRules
}));
const autoIndentOnPasteController = editor.registerAndInstantiateContribution(AutoIndentOnPaste.ID, AutoIndentOnPaste);
withTestCodeEditor(model, { autoIndent: 'full' }, (editor, viewModel, instantiationService) => {
const pasteText = [
'/**',
' * JSDoc',
' */',
'function a() {}'
].join('\n');
const tokens = [
[
{ startIndex: 0, value: 1 },
{ startIndex: 3, value: 1 },
],
[
{ startIndex: 0, value: 1 },
{ startIndex: 2, value: 1 },
{ startIndex: 8, value: 1 },
],
[
{ startIndex: 0, value: 1 },
{ startIndex: 1, value: 1 },
{ startIndex: 3, value: 0 },
],
[
{ startIndex: 0, value: 0 },
{ startIndex: 8, value: 0 },
{ startIndex: 9, value: 0 },
{ startIndex: 10, value: 0 },
{ startIndex: 11, value: 0 },
{ startIndex: 12, value: 0 },
{ startIndex: 13, value: 0 },
{ startIndex: 14, value: 0 },
{ startIndex: 15, value: 0 },
]
];
registerLanguage(instantiationService, languageId, Language.TypeScript, disposables);
registerTokens(instantiationService, tokens, languageId, disposables);
const autoIndentOnPasteController = editor.registerAndInstantiateContribution(AutoIndentOnPaste.ID, AutoIndentOnPaste);
viewModel.paste(pasteText, true, undefined, 'keyboard');
autoIndentOnPasteController.trigger(new Range(1, 1, 4, 16));
assert.strictEqual(model.getValue(), pasteText);
});
});
});
suite('Editor Contrib - Keep Indent On Paste', () => {
let disposables: DisposableStore;
setup(() => {
disposables = new DisposableStore();
});
teardown(() => {
disposables.dispose();
});
ensureNoDisposablesAreLeakedInTestSuite();
test('issue #167299: Blank line removes indent', () => {
const languageId = 'blankLineRemovesIndent';
const model = createTextModel("", languageId, {});
disposables.add(model);
withTestCodeEditor(model, { autoIndent: 'full' }, (editor, viewModel, instantiationService) => {
const languageService = instantiationService.get(ILanguageService);
const languageConfigurationService = instantiationService.get(ILanguageConfigurationService);
disposables.add(languageService.registerLanguage({ id: languageId }));
disposables.add(languageConfigurationService.register(languageId, {
brackets: [
['{', '}'],
['[', ']'],
['(', ')']
],
indentationRules: javascriptIndentationRules,
onEnterRules: javascriptOnEnterRules
}));
const autoIndentOnPasteController = editor.registerAndInstantiateContribution(AutoIndentOnPaste.ID, AutoIndentOnPaste);
withTestCodeEditor(model, { autoIndent: 'full' }, (editor, viewModel, instantiationService) => {
// no need for tokenization because there are no comments
const pasteText = [
'',
'export type IncludeReference =',
@ -319,14 +358,324 @@ suite('Editor Contrib - Keep Indent On Paste', () => {
'}'
].join('\n');
registerLanguage(instantiationService, languageId, Language.TypeScript, disposables);
const autoIndentOnPasteController = editor.registerAndInstantiateContribution(AutoIndentOnPaste.ID, AutoIndentOnPaste);
viewModel.paste(pasteText, true, undefined, 'keyboard');
autoIndentOnPasteController.trigger(new Range(1, 1, 11, 2));
assert.strictEqual(model.getValue(), pasteText);
});
});
// Failing tests found in issues...
test.skip('issue #181065: Incorrect paste of object within comment', () => {
// https://github.com/microsoft/vscode/issues/181065
const model = createTextModel("", languageId, {});
disposables.add(model);
withTestCodeEditor(model, { autoIndent: 'full' }, (editor, viewModel, instantiationService) => {
const text = [
'/**',
' * @typedef {',
' * }',
' */'
].join('\n');
const tokens = [
[
{ startIndex: 0, value: 1 },
{ startIndex: 3, value: 1 },
],
[
{ startIndex: 0, value: 1 },
{ startIndex: 2, value: 1 },
{ startIndex: 3, value: 1 },
{ startIndex: 11, value: 1 },
{ startIndex: 12, value: 0 },
{ startIndex: 13, value: 0 },
],
[
{ startIndex: 0, value: 1 },
{ startIndex: 2, value: 0 },
{ startIndex: 3, value: 0 },
{ startIndex: 4, value: 0 },
],
[
{ startIndex: 0, value: 1 },
{ startIndex: 1, value: 1 },
{ startIndex: 3, value: 0 },
]
];
registerLanguage(instantiationService, languageId, Language.TypeScript, disposables);
registerTokens(instantiationService, tokens, languageId, disposables);
const autoIndentOnPasteController = editor.registerAndInstantiateContribution(AutoIndentOnPaste.ID, AutoIndentOnPaste);
viewModel.paste(text, true, undefined, 'keyboard');
autoIndentOnPasteController.trigger(new Range(1, 1, 4, 4));
assert.strictEqual(model.getValue(), text);
});
});
test.skip('issue #86301: preserve cursor at inserted indentation level', () => {
// https://github.com/microsoft/vscode/issues/86301
const model = createTextModel([
'() => {',
'',
'}',
].join('\n'), languageId, {});
disposables.add(model);
withTestCodeEditor(model, { autoIndent: 'full' }, (editor, viewModel, instantiationService) => {
editor.setSelection(new Selection(2, 1, 2, 1));
const text = [
'() => {',
'',
'}',
''
].join('\n');
registerLanguage(instantiationService, languageId, Language.TypeScript, disposables);
const autoIndentOnPasteController = editor.registerAndInstantiateContribution(AutoIndentOnPaste.ID, AutoIndentOnPaste);
viewModel.paste(text, true, undefined, 'keyboard');
autoIndentOnPasteController.trigger(new Range(2, 1, 5, 1));
// notes:
// why is line 3 not indented to the same level as line 2?
// looks like the indentation is inserted correctly at line 5, but the cursor does not appear at the maximum indentation level?
assert.strictEqual(model.getValue(), [
'() => {',
' () => {',
' ', // <- should also be indented
' }',
' ', // <- cursor should be at the end of the indentation
'}',
].join('\n'));
const selection = viewModel.getSelection();
assert.deepStrictEqual(selection, new Selection(5, 5, 5, 5));
});
});
test.skip('issue #85781: indent line with extra white space', () => {
// https://github.com/microsoft/vscode/issues/85781
// note: still to determine whether this is a bug or not
const model = createTextModel([
'() => {',
' console.log("a");',
'}',
].join('\n'), languageId, {});
disposables.add(model);
withTestCodeEditor(model, { autoIndent: 'full' }, (editor, viewModel, instantiationService) => {
editor.setSelection(new Selection(2, 5, 2, 5));
const text = [
'() => {',
' console.log("b")',
'}',
' '
].join('\n');
registerLanguage(instantiationService, languageId, Language.TypeScript, disposables);
const autoIndentOnPasteController = editor.registerAndInstantiateContribution(AutoIndentOnPaste.ID, AutoIndentOnPaste);
viewModel.paste(text, true, undefined, 'keyboard');
// todo@aiday-mar, make sure range is correct, and make test work as in real life
autoIndentOnPasteController.trigger(new Range(2, 5, 5, 6));
assert.strictEqual(model.getValue(), [
'() => {',
' () => {',
' console.log("b")',
' }',
' console.log("a");',
'}',
].join('\n'));
});
});
test.skip('issue #29589: incorrect indentation of closing brace on paste', () => {
// https://github.com/microsoft/vscode/issues/29589
const model = createTextModel('', languageId, {});
disposables.add(model);
withTestCodeEditor(model, { autoIndent: 'full' }, (editor, viewModel, instantiationService) => {
editor.setSelection(new Selection(2, 5, 2, 5));
const text = [
'function makeSub(a,b) {',
'subsent = sent.substring(a,b);',
'return subsent;',
'}',
].join('\n');
registerLanguage(instantiationService, languageId, Language.TypeScript, disposables);
const autoIndentOnPasteController = editor.registerAndInstantiateContribution(AutoIndentOnPaste.ID, AutoIndentOnPaste);
viewModel.paste(text, true, undefined, 'keyboard');
// todo@aiday-mar, make sure range is correct, and make test work as in real life
autoIndentOnPasteController.trigger(new Range(1, 1, 4, 2));
assert.strictEqual(model.getValue(), [
'function makeSub(a,b) {',
' subsent = sent.substring(a,b);',
' return subsent;',
'}',
].join('\n'));
});
});
});
suite('Editor Contrib - Auto Dedent On Type', () => {
suite('`Full` Auto Indent On Type - TypeScript/JavaScript', () => {
const languageId = "ts-test";
let disposables: DisposableStore;
setup(() => {
disposables = new DisposableStore();
});
teardown(() => {
disposables.dispose();
});
ensureNoDisposablesAreLeakedInTestSuite();
// Test added so there is at least one non-ignored test in this suite
test('temporary test', () => {
assert.ok(true);
});
// Failing tests from issues...
test.skip('issue #116843: indent after arrow function', () => {
// https://github.com/microsoft/vscode/issues/116843
const model = createTextModel("", languageId, {});
disposables.add(model);
withTestCodeEditor(model, { autoIndent: "full" }, (editor, viewModel, instantiationService) => {
registerLanguage(instantiationService, languageId, Language.TypeScript, disposables);
viewModel.type('const add1 = (n) =>');
viewModel.type("\n", 'keyboard');
assert.strictEqual(model.getValue(), [
'const add1 = (n) =>',
' ',
].join('\n'));
viewModel.type('n + 1;');
viewModel.type("\n", 'keyboard');
assert.strictEqual(model.getValue(), [
'const add1 = (n) =>',
' n + 1;',
'',
].join('\n'));
});
});
test.skip('issue #40115: keep indentation when added', () => {
// https://github.com/microsoft/vscode/issues/40115
const model = createTextModel('function foo() {}', languageId, {});
disposables.add(model);
withTestCodeEditor(model, { autoIndent: "full" }, (editor, viewModel, instantiationService) => {
registerLanguage(instantiationService, languageId, Language.TypeScript, disposables);
editor.setSelection(new Selection(1, 17, 1, 17));
viewModel.type("\n", 'keyboard');
assert.strictEqual(model.getValue(), [
'function foo() {',
' ',
'}',
].join('\n'));
editor.setSelection(new Selection(2, 5, 2, 5));
viewModel.type("\n", 'keyboard');
assert.strictEqual(model.getValue(), [
'function foo() {',
' ',
' ',
'}',
].join('\n'));
});
});
test.skip('issue #193875: incorrect indentation on enter', () => {
// https://github.com/microsoft/vscode/issues/193875
const model = createTextModel([
'{',
' for(;;)',
' for(;;) {}',
'}',
].join('\n'), languageId, {});
disposables.add(model);
withTestCodeEditor(model, { autoIndent: "full" }, (editor, viewModel, instantiationService) => {
registerLanguage(instantiationService, languageId, Language.TypeScript, disposables);
editor.setSelection(new Selection(3, 14, 3, 14));
viewModel.type("\n", 'keyboard');
assert.strictEqual(model.getValue(), [
'{',
' for(;;)',
' for(;;) {',
' ',
' }',
'}',
].join('\n'));
});
});
test.skip('issue #43244: incorrect indentation', () => {
// https://github.com/microsoft/vscode/issues/43244
const model = createTextModel([
'function f() {',
' if (condition)',
' return;',
'}'
].join('\n'), languageId, {});
disposables.add(model);
withTestCodeEditor(model, { autoIndent: "full" }, (editor, viewModel, instantiationService) => {
registerLanguage(instantiationService, languageId, Language.TypeScript, disposables);
editor.setSelection(new Selection(3, 16, 3, 16));
viewModel.type("\n", 'keyboard');
assert.strictEqual(model.getValue(), [
'function f() {',
' if (condition)',
' return;',
'',
'}',
].join('\n'));
viewModel.type("\n", 'keyboard');
assert.strictEqual(model.getValue(), [
'function f() {',
' if (condition)',
' return;',
'',
'',
'}',
].join('\n'));
});
});
// Add tests for:
// https://github.com/microsoft/vscode/issues/88638
// https://github.com/microsoft/vscode/issues/63388
// https://github.com/microsoft/vscode/issues/46401
// https://github.com/microsoft/vscode/issues/174044
});
suite('Auto Indent On Type - Ruby', () => {
const languageId = "ruby-test";
let disposables: DisposableStore;
setup(() => {
@ -340,45 +689,14 @@ suite('Editor Contrib - Auto Dedent On Type', () => {
ensureNoDisposablesAreLeakedInTestSuite();
test('issue #198350: in or when incorrectly match non keywords for Ruby', () => {
const languageId = "ruby";
const model = createTextModel("", languageId, {});
disposables.add(model);
withTestCodeEditor(model, { autoIndent: "full" }, (editor, viewModel, instantiationService) => {
const languageService = instantiationService.get(ILanguageService);
const languageConfigurationService = instantiationService.get(ILanguageConfigurationService);
disposables.add(languageService.registerLanguage({ id: languageId }));
const languageModel = languageConfigurationService.register(languageId, {
brackets: [
['{', '}'],
['[', ']'],
['(', ')']
],
indentationRules: {
decreaseIndentPattern: /\s*([}\]]([,)]?\s*(#|$)|\.[a-zA-Z_]\w*\b)|(end|rescue|ensure|else|elsif|when|in)\b)/,
increaseIndentPattern: /^\s*((begin|class|(private|protected)\s+def|def|else|elsif|ensure|for|if|module|rescue|unless|until|when|in|while|case)|([^#]*\sdo\b)|([^#]*=\s*(case|if|unless)))\b([^#\{;]|(\"|'|\/).*\4)*(#.*)?$/,
},
});
viewModel.type("def foo\n i", 'keyboard');
viewModel.type("n", 'keyboard');
// The 'in' triggers decreaseIndentPattern immediately, which is incorrect
assert.strictEqual(model.getValue(), "def foo\nin");
languageModel.dispose();
registerLanguage(instantiationService, languageId, Language.Ruby, disposables);
const improvedLanguageModel = languageConfigurationService.register(languageId, {
brackets: [
['{', '}'],
['[', ']'],
['(', ')']
],
indentationRules: {
decreaseIndentPattern: /^\s*([}\]]([,)]?\s*(#|$)|\.[a-zA-Z_]\w*\b)|(end|rescue|ensure|else|elsif)\b|(in|when)\s)/,
increaseIndentPattern: /^\s*((begin|class|(private|protected)\s+def|def|else|elsif|ensure|for|if|module|rescue|unless|until|when|in|while|case)|([^#]*\sdo\b)|([^#]*=\s*(case|if|unless)))\b([^#\{;]|(\"|'|\/).*\4)*(#.*)?$/,
},
});
viewModel.model.setValue("");
viewModel.type("def foo\n i");
viewModel.type("n", 'keyboard');
assert.strictEqual(model.getValue(), "def foo\n in");
@ -390,7 +708,6 @@ suite('Editor Contrib - Auto Dedent On Type', () => {
assert.strictEqual(model.getValue(), " # in");
viewModel.type(" ", 'keyboard');
assert.strictEqual(model.getValue(), " # in ");
improvedLanguageModel.dispose();
});
});
});

View file

@ -13,7 +13,7 @@ import { ITextModel } from 'vs/editor/common/model';
import { CompleteEnterAction, IndentAction } from 'vs/editor/common/languages/languageConfiguration';
import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry';
import { IndentConsts } from 'vs/editor/common/languages/supports/indentRules';
import * as indentUtils from 'vs/editor/contrib/indentation/browser/indentUtils';
import * as indentUtils from 'vs/editor/contrib/indentation/common/indentUtils';
import { getGoodIndentForLine, getIndentMetadata, IIndentConverter, IVirtualModel } from 'vs/editor/common/languages/autoIndent';
import { getEnterAction } from 'vs/editor/common/languages/enterAction';

View file

@ -29,5 +29,15 @@ export const javascriptOnEnterRules = [
// e.g. *-----*/|
beforeText: /^(\t|[ ])*[ ]\*[^/]*\*\/\s*$/,
action: { indentAction: IndentAction.None, removeText: 1 }
}
},
{
beforeText: /^\s*(\bcase\s.+:|\bdefault:)$/,
afterText: /^(?!\s*(\bcase\b|\bdefault\b))/,
action: { indentAction: IndentAction.Indent }
},
{
previousLineText: /^\s*(((else ?)?if|for|while)\s*\(.*\)\s*|else\s*)$/,
beforeText: /^\s+([^{i\s]|i(?!f\b))/,
action: { indentAction: IndentAction.Outdent }
},
];

View file

@ -11,7 +11,7 @@ import { JSONValidationExtensionPoint } from 'vs/workbench/api/common/jsonValida
import { ColorExtensionPoint } from 'vs/workbench/services/themes/common/colorExtensionPoint';
import { IconExtensionPoint } from 'vs/workbench/services/themes/common/iconExtensionPoint';
import { TokenClassificationExtensionPoints } from 'vs/workbench/services/themes/common/tokenClassificationExtensionPoint';
import { LanguageConfigurationFileHandler } from 'vs/workbench/contrib/codeEditor/browser/languageConfigurationExtensionPoint';
import { LanguageConfigurationFileHandler } from 'vs/workbench/contrib/codeEditor/common/languageConfigurationExtensionPoint';
import { StatusBarItemsExtensionPoint } from 'vs/workbench/api/browser/statusBarExtensionPoint';
// --- mainThread participants

View file

@ -47,7 +47,7 @@ interface IOnEnterRule {
/**
* Serialized form of a language configuration
*/
interface ILanguageConfiguration {
export interface ILanguageConfiguration {
comments?: CommentRule;
brackets?: CharacterPair[];
autoClosingPairs?: Array<CharacterPair | IAutoClosingPairConditional>;
@ -149,7 +149,7 @@ export class LanguageConfigurationFileHandler extends Disposable {
}
}
private _extractValidCommentRule(languageId: string, configuration: ILanguageConfiguration): CommentRule | undefined {
private static _extractValidCommentRule(languageId: string, configuration: ILanguageConfiguration): CommentRule | undefined {
const source = configuration.comments;
if (typeof source === 'undefined') {
return undefined;
@ -179,7 +179,7 @@ export class LanguageConfigurationFileHandler extends Disposable {
return result;
}
private _extractValidBrackets(languageId: string, configuration: ILanguageConfiguration): CharacterPair[] | undefined {
private static _extractValidBrackets(languageId: string, configuration: ILanguageConfiguration): CharacterPair[] | undefined {
const source = configuration.brackets;
if (typeof source === 'undefined') {
return undefined;
@ -203,7 +203,7 @@ export class LanguageConfigurationFileHandler extends Disposable {
return result;
}
private _extractValidAutoClosingPairs(languageId: string, configuration: ILanguageConfiguration): IAutoClosingPairConditional[] | undefined {
private static _extractValidAutoClosingPairs(languageId: string, configuration: ILanguageConfiguration): IAutoClosingPairConditional[] | undefined {
const source = configuration.autoClosingPairs;
if (typeof source === 'undefined') {
return undefined;
@ -249,7 +249,7 @@ export class LanguageConfigurationFileHandler extends Disposable {
return result;
}
private _extractValidSurroundingPairs(languageId: string, configuration: ILanguageConfiguration): IAutoClosingPair[] | undefined {
private static _extractValidSurroundingPairs(languageId: string, configuration: ILanguageConfiguration): IAutoClosingPair[] | undefined {
const source = configuration.surroundingPairs;
if (typeof source === 'undefined') {
return undefined;
@ -289,7 +289,7 @@ export class LanguageConfigurationFileHandler extends Disposable {
return result;
}
private _extractValidColorizedBracketPairs(languageId: string, configuration: ILanguageConfiguration): CharacterPair[] | undefined {
private static _extractValidColorizedBracketPairs(languageId: string, configuration: ILanguageConfiguration): CharacterPair[] | undefined {
const source = configuration.colorizedBracketPairs;
if (typeof source === 'undefined') {
return undefined;
@ -312,7 +312,7 @@ export class LanguageConfigurationFileHandler extends Disposable {
return result;
}
private _extractValidOnEnterRules(languageId: string, configuration: ILanguageConfiguration): OnEnterRule[] | undefined {
private static _extractValidOnEnterRules(languageId: string, configuration: ILanguageConfiguration): OnEnterRule[] | undefined {
const source = configuration.onEnterRules;
if (typeof source === 'undefined') {
return undefined;
@ -385,7 +385,7 @@ export class LanguageConfigurationFileHandler extends Disposable {
return result;
}
private _handleConfig(languageId: string, configuration: ILanguageConfiguration): void {
public static extractValidConfig(languageId: string, configuration: ILanguageConfiguration): ExplicitLanguageConfiguration {
const comments = this._extractValidCommentRule(languageId, configuration);
const brackets = this._extractValidBrackets(languageId, configuration);
@ -421,11 +421,15 @@ export class LanguageConfigurationFileHandler extends Disposable {
folding,
__electricCharacterSupport: undefined,
};
return richEditConfig;
}
private _handleConfig(languageId: string, configuration: ILanguageConfiguration): void {
const richEditConfig = LanguageConfigurationFileHandler.extractValidConfig(languageId, configuration);
this._languageConfigurationService.register(languageId, richEditConfig, 50);
}
private _parseRegex(languageId: string, confPath: string, value: string | IRegExp): RegExp | undefined {
private static _parseRegex(languageId: string, confPath: string, value: string | IRegExp): RegExp | undefined {
if (typeof value === 'string') {
try {
return new RegExp(value, '');
@ -454,7 +458,7 @@ export class LanguageConfigurationFileHandler extends Disposable {
return undefined;
}
private _mapIndentationRules(languageId: string, indentationRules: IIndentationRules): IndentationRule | undefined {
private static _mapIndentationRules(languageId: string, indentationRules: IIndentationRules): IndentationRule | undefined {
const increaseIndentPattern = this._parseRegex(languageId, `indentationRules.increaseIndentPattern`, indentationRules.increaseIndentPattern);
if (!increaseIndentPattern) {
return undefined;

View file

@ -0,0 +1,312 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as fs from 'fs';
import * as path from 'path';
import * as assert from 'assert';
import { DisposableStore } from 'vs/base/common/lifecycle';
import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils';
import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry';
import { getReindentEditOperations } from 'vs/editor/contrib/indentation/common/indentation';
import { IRelaxedTextModelCreationOptions, createModelServices, instantiateTextModel } from 'vs/editor/test/common/testTextModel';
import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock';
import { ILanguageConfiguration, LanguageConfigurationFileHandler } from 'vs/workbench/contrib/codeEditor/common/languageConfigurationExtensionPoint';
import { parse } from 'vs/base/common/json';
import { IRange } from 'vs/editor/common/core/range';
function getIRange(range: IRange): IRange {
return {
startLineNumber: range.startLineNumber,
startColumn: range.startColumn,
endLineNumber: range.endLineNumber,
endColumn: range.endColumn
};
}
suite('Auto-Reindentation - TypeScript/JavaScript', () => {
const languageId = 'ts-test';
const options: IRelaxedTextModelCreationOptions = {};
let disposables: DisposableStore;
let instantiationService: TestInstantiationService;
let languageConfigurationService: ILanguageConfigurationService;
setup(() => {
disposables = new DisposableStore();
instantiationService = createModelServices(disposables);
languageConfigurationService = instantiationService.get(ILanguageConfigurationService);
const configPath = path.join('extensions', 'typescript-basics', 'language-configuration.json');
const configString = fs.readFileSync(configPath).toString();
const config = <ILanguageConfiguration>parse(configString, []);
const configParsed = LanguageConfigurationFileHandler.extractValidConfig(languageId, config);
disposables.add(languageConfigurationService.register(languageId, configParsed));
});
teardown(() => {
disposables.dispose();
});
ensureNoDisposablesAreLeakedInTestSuite();
// Test which can be ran to find cases of incorrect indentation...
test.skip('Find Cases of Incorrect Indentation', () => {
const filePath = path.join('..', 'TypeScript', 'src', 'server', 'utilities.ts');
const fileContents = fs.readFileSync(filePath).toString();
const model = disposables.add(instantiateTextModel(instantiationService, fileContents, languageId, options));
const editOperations = getReindentEditOperations(model, languageConfigurationService, 1, model.getLineCount());
model.applyEdits(editOperations);
// save the files to disk
const initialFile = path.join('..', 'autoindent', 'initial.ts');
const finalFile = path.join('..', 'autoindent', 'final.ts');
fs.writeFileSync(initialFile, fileContents);
fs.writeFileSync(finalFile, model.getValue());
});
// Unit tests for increase and decrease indent patterns...
/**
* First increase indent and decrease indent patterns:
*
* - decreaseIndentPattern: /^(.*\*\/)?\s*\}.*$/
* - In (https://macromates.com/manual/en/appendix)
* Either we have white space before the closing bracket, or we have a multi line comment ending on that line followed by whitespaces
* This is followed by any character.
* Textmate decrease indent pattern is as follows: /^(.*\*\/)?\s*\}[;\s]*$/
* Presumably allowing multi line comments ending on that line implies that } is itself not part of a multi line comment
*
* - increaseIndentPattern: /^.*\{[^}"']*$/
* - In (https://macromates.com/manual/en/appendix)
* This regex means that we increase the indent when we have any characters followed by the opening brace, followed by characters
* except for closing brace }, double quotes " or single quote '.
* The } is checked in order to avoid the indentation in the following case `int arr[] = { 1, 2, 3 };`
* The double quote and single quote are checked in order to avoid the indentation in the following case: str = "foo {";
*/
test('Issue #25437', () => {
// issue: https://github.com/microsoft/vscode/issues/25437
// fix: https://github.com/microsoft/vscode/commit/8c82a6c6158574e098561c28d470711f1b484fc8
// explanation: var foo = `{`; should not increase indentation
// increaseIndentPattern: /^.*\{[^}"']*$/ -> /^.*\{[^}"'`]*$/
const fileContents = [
'const foo = `{`;',
' ',
].join('\n');
const model = disposables.add(instantiateTextModel(instantiationService, fileContents, languageId, options));
const editOperations = getReindentEditOperations(model, languageConfigurationService, 1, model.getLineCount());
assert.deepStrictEqual(editOperations.length, 1);
const operation = editOperations[0];
assert.deepStrictEqual(getIRange(operation.range), {
"startLineNumber": 2,
"startColumn": 1,
"endLineNumber": 2,
"endColumn": 5,
});
assert.deepStrictEqual(operation.text, '');
});
test('Enriching the hover', () => {
// issue: -
// fix: https://github.com/microsoft/vscode/commit/19ae0932c45b1096443a8c1335cf1e02eb99e16d
// explanation:
// - decrease indent on ) and ] also
// - increase indent on ( and [ also
// decreaseIndentPattern: /^(.*\*\/)?\s*\}.*$/ -> /^(.*\*\/)?\s*[\}\]\)].*$/
// increaseIndentPattern: /^.*\{[^}"'`]*$/ -> /^.*(\{[^}"'`]*|\([^)"'`]*|\[[^\]"'`]*)$/
let fileContents = [
'function foo(',
' bar: string',
' ){}',
].join('\n');
let model = disposables.add(instantiateTextModel(instantiationService, fileContents, languageId, options));
let editOperations = getReindentEditOperations(model, languageConfigurationService, 1, model.getLineCount());
assert.deepStrictEqual(editOperations.length, 1);
let operation = editOperations[0];
assert.deepStrictEqual(getIRange(operation.range), {
"startLineNumber": 3,
"startColumn": 1,
"endLineNumber": 3,
"endColumn": 5,
});
assert.deepStrictEqual(operation.text, '');
fileContents = [
'function foo(',
'bar: string',
'){}',
].join('\n');
model = disposables.add(instantiateTextModel(instantiationService, fileContents, languageId, options));
editOperations = getReindentEditOperations(model, languageConfigurationService, 1, model.getLineCount());
assert.deepStrictEqual(editOperations.length, 1);
operation = editOperations[0];
assert.deepStrictEqual(getIRange(operation.range), {
"startLineNumber": 2,
"startColumn": 1,
"endLineNumber": 2,
"endColumn": 1,
});
assert.deepStrictEqual(operation.text, ' ');
});
test('Issue #86176', () => {
// issue: https://github.com/microsoft/vscode/issues/86176
// fix: https://github.com/microsoft/vscode/commit/d89e2e17a5d1ba37c99b1d3929eb6180a5bfc7a8
// explanation: When quotation marks are present on the first line of an if statement or for loop, following line should not be indented
// increaseIndentPattern: /^((?!\/\/).)*(\{[^}"'`]*|\([^)"'`]*|\[[^\]"'`]*)$/ -> /^((?!\/\/).)*(\{([^}"'`]*|(\t|[ ])*\/\/.*)|\([^)"'`]*|\[[^\]"'`]*)$/
// explanation: after open brace, do not decrease indent if it is followed on the same line by "<whitespace characters> // <any characters>"
// todo@aiday-mar: should also apply for when it follows ( and [
const fileContents = [
`if () { // '`,
`x = 4`,
`}`
].join('\n');
const model = disposables.add(instantiateTextModel(instantiationService, fileContents, languageId, options));
const editOperations = getReindentEditOperations(model, languageConfigurationService, 1, model.getLineCount());
assert.deepStrictEqual(editOperations.length, 1);
const operation = editOperations[0];
assert.deepStrictEqual(getIRange(operation.range), {
"startLineNumber": 2,
"startColumn": 1,
"endLineNumber": 2,
"endColumn": 1,
});
assert.deepStrictEqual(operation.text, ' ');
});
test('Issue #141816', () => {
// issue: https://github.com/microsoft/vscode/issues/141816
// fix: https://github.com/microsoft/vscode/pull/141997/files
// explanation: if (, [, {, is followed by a forward slash then assume we are in a regex pattern, and do not indent
// increaseIndentPattern: /^((?!\/\/).)*(\{([^}"'`]*|(\t|[ ])*\/\/.*)|\([^)"'`]*|\[[^\]"'`]*)$/ -> /^((?!\/\/).)*(\{([^}"'`/]*|(\t|[ ])*\/\/.*)|\([^)"'`/]*|\[[^\]"'`/]*)$/
// -> Final current increase indent pattern
const fileContents = [
'const r = /{/;',
' ',
].join('\n');
const model = disposables.add(instantiateTextModel(instantiationService, fileContents, languageId, options));
const editOperations = getReindentEditOperations(model, languageConfigurationService, 1, model.getLineCount());
assert.deepStrictEqual(editOperations.length, 1);
const operation = editOperations[0];
assert.deepStrictEqual(getIRange(operation.range), {
"startLineNumber": 2,
"startColumn": 1,
"endLineNumber": 2,
"endColumn": 4,
});
assert.deepStrictEqual(operation.text, '');
});
test('Issue #29886', () => {
// issue: https://github.com/microsoft/vscode/issues/29886
// fix: https://github.com/microsoft/vscode/commit/7910b3d7bab8a721aae98dc05af0b5e1ea9d9782
// decreaseIndentPattern: /^(.*\*\/)?\s*[\}\]\)].*$/ -> /^((?!.*?\/\*).*\*\/)?\s*[\}\]\)].*$/
// -> Final current decrease indent pattern
// explanation: Positive lookahead: (?= «pattern») matches if pattern matches what comes after the current location in the input string.
// Negative lookahead: (?! «pattern») matches if pattern does not match what comes after the current location in the input string
// The change proposed is to not decrease the indent if there is a multi-line comment ending on the same line before the closing parentheses
const fileContents = [
'function foo() {',
' bar(/* */)',
'};',
].join('\n');
const model = disposables.add(instantiateTextModel(instantiationService, fileContents, languageId, options));
const editOperations = getReindentEditOperations(model, languageConfigurationService, 1, model.getLineCount());
assert.deepStrictEqual(editOperations.length, 0);
});
// Failing tests inferred from the current regexes...
test.skip('Incorrect deindentation after `*/}` string', () => {
// explanation: If */ was not before the }, the regex does not allow characters before the }, so there would not be an indent
// Here since there is */ before the }, the regex allows all the characters before, hence there is a deindent
const fileContents = [
`const obj = {`,
` obj1: {`,
` brace : '*/}'`,
` }`,
`}`,
].join('\n');
const model = disposables.add(instantiateTextModel(instantiationService, fileContents, languageId, options));
const editOperations = getReindentEditOperations(model, languageConfigurationService, 1, model.getLineCount());
assert.deepStrictEqual(editOperations.length, 0);
});
// Failing tests from issues...
test.skip('Issue #56275', () => {
// issue: https://github.com/microsoft/vscode/issues/56275
// explanation: If */ was not before the }, the regex does not allow characters before the }, so there would not be an indent
// Here since there is */ before the }, the regex allows all the characters before, hence there is a deindent
let fileContents = [
'function foo() {',
' var bar = (/b*/);',
'}',
].join('\n');
let model = disposables.add(instantiateTextModel(instantiationService, fileContents, languageId, options));
let editOperations = getReindentEditOperations(model, languageConfigurationService, 1, model.getLineCount());
assert.deepStrictEqual(editOperations.length, 0);
fileContents = [
'function foo() {',
' var bar = "/b*/)";',
'}',
].join('\n');
model = disposables.add(instantiateTextModel(instantiationService, fileContents, languageId, options));
editOperations = getReindentEditOperations(model, languageConfigurationService, 1, model.getLineCount());
assert.deepStrictEqual(editOperations.length, 0);
});
test.skip('Issue #116843', () => {
// issue: https://github.com/microsoft/vscode/issues/116843
// related: https://github.com/microsoft/vscode/issues/43244
// explanation: When you have an arrow function, you don't have { or }, but you would expect indentation to still be done in that way
/*
Notes: Currently the reindent edit operations does not call the onEnter rules
The reindent should also call the onEnter rules to get the correct indentation
*/
const fileContents = [
'const add1 = (n) =>',
' n + 1;',
].join('\n');
const model = disposables.add(instantiateTextModel(instantiationService, fileContents, languageId, options));
const editOperations = getReindentEditOperations(model, languageConfigurationService, 1, model.getLineCount());
assert.deepStrictEqual(editOperations.length, 0);
});
test.skip('Issue #185252', () => {
// issue: https://github.com/microsoft/vscode/issues/185252
// explanation: Reindenting the comment correctly
const fileContents = [
'/*',
' * This is a comment.',
' */',
].join('\n');
const model = disposables.add(instantiateTextModel(instantiationService, fileContents, languageId, options));
const editOperations = getReindentEditOperations(model, languageConfigurationService, 1, model.getLineCount());
assert.deepStrictEqual(editOperations.length, 0);
});
});