mirror of
https://github.com/Microsoft/vscode
synced 2024-08-27 04:49:35 +00:00
Merge pull request #207587 from microsoft/aiday/autoIndentTestSuite
Auto-indent test suite
This commit is contained in:
commit
b329f9c2a5
|
@ -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';
|
||||
|
|
124
src/vs/editor/contrib/indentation/common/indentation.ts
Normal file
124
src/vs/editor/contrib/indentation/common/indentation.ts
Normal 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;
|
||||
}
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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 }
|
||||
},
|
||||
];
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
312
src/vs/workbench/contrib/codeEditor/test/node/autoindent.test.ts
Normal file
312
src/vs/workbench/contrib/codeEditor/test/node/autoindent.test.ts
Normal 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);
|
||||
});
|
||||
});
|
Loading…
Reference in a new issue