Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Keepins same indentation if increaseIndentPattern is satisifed on line n and increaseNextLinePattern is satisfied on line n-1 #213321

Closed
wants to merge 22 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/vs/editor/common/cursor/cursor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { CursorConfiguration, CursorState, EditOperationResult, EditOperationTyp
import { CursorContext } from 'vs/editor/common/cursor/cursorContext';
import { DeleteOperations } from 'vs/editor/common/cursor/cursorDeleteOperations';
import { CursorChangeReason } from 'vs/editor/common/cursorEvents';
import { CompositionOutcome, TypeOperations, TypeWithAutoClosingCommand } from 'vs/editor/common/cursor/cursorTypeOperations';
import { BaseTypeWithAutoClosingCommand, CompositionOutcome, TypeOperations } from 'vs/editor/common/cursor/cursorTypeOperations';
import { Position } from 'vs/editor/common/core/position';
import { Range, IRange } from 'vs/editor/common/core/range';
import { ISelection, Selection, SelectionDirection } from 'vs/editor/common/core/selection';
Expand Down Expand Up @@ -367,7 +367,7 @@ export class CursorsController extends Disposable {

for (let i = 0; i < opResult.commands.length; i++) {
const command = opResult.commands[i];
if (command instanceof TypeWithAutoClosingCommand && command.enclosingRange && command.closeCharacterRange) {
if (command instanceof BaseTypeWithAutoClosingCommand && command.enclosingRange && command.closeCharacterRange) {
autoClosedCharactersRanges.push(command.closeCharacterRange);
autoClosedEnclosingRanges.push(command.enclosingRange);
}
Expand Down
179 changes: 122 additions & 57 deletions src/vs/editor/common/cursor/cursorTypeOperations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
import { Range } from 'vs/editor/common/core/range';
import { Selection } from 'vs/editor/common/core/selection';
import { Position } from 'vs/editor/common/core/position';
import { ICommand, ICursorStateComputerData } from 'vs/editor/common/editorCommon';
import { ICommand, ICursorStateComputerData, IEditOperationBuilder } from 'vs/editor/common/editorCommon';
import { ITextModel } from 'vs/editor/common/model';
import { EnterAction, IndentAction, StandardAutoClosingPairConditional } from 'vs/editor/common/languages/languageConfiguration';
import { getIndentationAtPosition } from 'vs/editor/common/languages/languageConfigurationRegistry';
Expand Down Expand Up @@ -393,42 +393,6 @@
return true;
}

private static _runAutoIndentType(config: CursorConfiguration, model: ITextModel, range: Range, ch: string): ICommand | null {
const currentIndentation = getIndentationAtPosition(model, range.startLineNumber, range.startColumn);
const actualIndentation = getIndentActionForType(config.autoIndent, model, range, ch, {
shiftIndent: (indentation) => {
return TypeOperations.shiftIndent(config, indentation);
},
unshiftIndent: (indentation) => {
return TypeOperations.unshiftIndent(config, indentation);
},
}, config.languageConfigurationService);

if (actualIndentation === null) {
return null;
}

if (actualIndentation !== config.normalizeIndentation(currentIndentation)) {
const firstNonWhitespace = model.getLineFirstNonWhitespaceColumn(range.startLineNumber);
if (firstNonWhitespace === 0) {
return TypeOperations._typeCommand(
new Range(range.startLineNumber, 1, range.endLineNumber, range.endColumn),
config.normalizeIndentation(actualIndentation) + ch,
false
);
} else {
return TypeOperations._typeCommand(
new Range(range.startLineNumber, 1, range.endLineNumber, range.endColumn),
config.normalizeIndentation(actualIndentation) +
model.getLineContent(range.startLineNumber).substring(firstNonWhitespace - 1, range.startColumn - 1) + ch,
false
);
}
}

return null;
}

private static _isAutoClosingOvertype(config: CursorConfiguration, model: ITextModel, selections: Selection[], autoClosedCharacters: Range[], ch: string): boolean {
if (config.autoClosingOvertype === 'never') {
return false;
Expand Down Expand Up @@ -945,20 +909,9 @@
}

if (!isDoingComposition && this._isAutoIndentType(config, model, selections)) {
const commands: Array<ICommand | null> = [];
let autoIndentFails = false;
for (let i = 0, len = selections.length; i < len; i++) {
commands[i] = this._runAutoIndentType(config, model, selections[i], ch);
if (!commands[i]) {
autoIndentFails = true;
break;
}
}
if (!autoIndentFails) {
return new EditOperationResult(EditOperationType.TypingOther, commands, {
shouldPushStackElementBefore: true,
shouldPushStackElementAfter: false,
});
const autoIndentEdits = AutoIndentOperation.getEdits(config, model, selections, ch);
if (autoIndentEdits !== undefined) {
return autoIndentEdits;
}
}

Expand Down Expand Up @@ -1055,30 +1008,142 @@
}
}

export class TypeWithAutoClosingCommand extends ReplaceCommandWithOffsetCursorState {
export class BaseTypeWithAutoClosingCommand extends ReplaceCommandWithOffsetCursorState {

private readonly _openCharacter: string;
private readonly _closeCharacter: string;
public closeCharacterRange: Range | null;
public enclosingRange: Range | null;

constructor(selection: Selection, openCharacter: string, insertOpenCharacter: boolean, closeCharacter: string) {
super(selection, (insertOpenCharacter ? openCharacter : '') + closeCharacter, 0, -closeCharacter.length);
constructor(selection: Selection, text: string, lineNumberDeltaOffset: number, columnDeltaOffset: number, openCharacter: string, closeCharacter: string) {
super(selection, text, lineNumberDeltaOffset, columnDeltaOffset);
this._openCharacter = openCharacter;
this._closeCharacter = closeCharacter;
this.closeCharacterRange = null;
this.enclosingRange = null;
}

public override computeCursorState(model: ITextModel, helper: ICursorStateComputerData): Selection {
const inverseEditOperations = helper.getInverseEditOperations();
const range = inverseEditOperations[0].range;
protected _computeCursorStateWithRange(model: ITextModel, range: Range, helper: ICursorStateComputerData): Selection {
this.closeCharacterRange = new Range(range.startLineNumber, range.endColumn - this._closeCharacter.length, range.endLineNumber, range.endColumn);
this.enclosingRange = new Range(range.startLineNumber, range.endColumn - this._openCharacter.length - this._closeCharacter.length, range.endLineNumber, range.endColumn);
return super.computeCursorState(model, helper);
}
}

class TypeWithAutoClosingCommand extends BaseTypeWithAutoClosingCommand {

constructor(selection: Selection, openCharacter: string, insertOpenCharacter: boolean, closeCharacter: string) {
const text = (insertOpenCharacter ? openCharacter : '') + closeCharacter;
const lineNumberDeltaOffset = 0;
const columnDeltaOffset = -closeCharacter.length;
super(selection, text, lineNumberDeltaOffset, columnDeltaOffset, openCharacter, closeCharacter);
}

public override computeCursorState(model: ITextModel, helper: ICursorStateComputerData): Selection {
const inverseEditOperations = helper.getInverseEditOperations();
const range = inverseEditOperations[0].range;
return this._computeCursorStateWithRange(model, range, helper);
}
}

class TypeWithIndentationAndAutoClosingCommand extends BaseTypeWithAutoClosingCommand {

private readonly _autoIndentationEdit: { range: Range; text: string };
private readonly _autoClosingEdit: { range: Range; text: string };

constructor(autoIndentationEdit: { range: Range; text: string }, selection: Selection, openCharacter: string, closeCharacter: string) {
const text = openCharacter + closeCharacter;
const lineNumberDeltaOffset = 0;
const columnDeltaOffset = openCharacter.length;
super(selection, text, lineNumberDeltaOffset, columnDeltaOffset, openCharacter, closeCharacter);
this._autoIndentationEdit = autoIndentationEdit;
this._autoClosingEdit = { range: selection, text };
}

public override getEditOperations(model: ITextModel, builder: IEditOperationBuilder): void {
builder.addTrackedEditOperation(this._autoIndentationEdit.range, this._autoIndentationEdit.text);
builder.addTrackedEditOperation(this._autoClosingEdit.range, this._autoClosingEdit.text);
}

public override computeCursorState(model: ITextModel, helper: ICursorStateComputerData): Selection {
const inverseEditOperations = helper.getInverseEditOperations();
if (inverseEditOperations.length !== 2) {
throw new Error('There should be two inverse edit operations!');
}
const range1 = inverseEditOperations[0].range;
const range2 = inverseEditOperations[1].range;
const range = range1.plusRange(range2);
return this._computeCursorStateWithRange(model, range, helper);
}
}

class AutoIndentOperation {

public static getEdits(config: CursorConfiguration, model: ITextModel, selections: Selection[], ch: string): EditOperationResult | undefined {
const indentationForSelections: { selection: Selection; indentation: string }[] = [];
for (const selection of selections) {
const indentation = this._findActualIndentationForSelection(config, model, selection, ch);
if (indentation === null) {
// Auto indentation failed
return;
}
indentationForSelections.push({ selection, indentation });
}
const autoClosingPairClose = this._getAutoClosingPairClose(config, model, selections, ch, false);

Check failure on line 1092 in src/vs/editor/common/cursor/cursorTypeOperations.ts

View workflow job for this annotation

GitHub Actions / Monaco Editor checks

Property '_getAutoClosingPairClose' does not exist on type 'typeof AutoIndentOperation'.
return this._getIndentationAndAutoClosingPairEdits(config, model, indentationForSelections, ch, autoClosingPairClose);
}

private static _findActualIndentationForSelection(config: CursorConfiguration, model: ITextModel, selection: Selection, ch: string): string | null {
const actualIndentation = getIndentActionForType(config, model, selection, ch, {
shiftIndent: (indentation) => {
return TypeOperations.shiftIndent(config, indentation);
},
unshiftIndent: (indentation) => {
return TypeOperations.unshiftIndent(config, indentation);
},
}, config.languageConfigurationService);

if (actualIndentation === null) {
return null;
}

const currentIndentation = getIndentationAtPosition(model, selection.startLineNumber, selection.startColumn);
if (actualIndentation === config.normalizeIndentation(currentIndentation)) {
return null;
}
return actualIndentation;
}

private static _getIndentationAndAutoClosingPairEdits(config: CursorConfiguration, model: ITextModel, indentationForSelections: { selection: Selection; indentation: string }[], ch: string, autoClosingPairClose: string | null): EditOperationResult {
const commands: ICommand[] = indentationForSelections.map(({ selection, indentation }) => {
if (autoClosingPairClose !== null) {
// Apply both auto closing pair edits and auto indentation edits
const indentationEdit = this._getEditFromIndentationAndSelection(config, model, indentation, selection, ch, false);
return new TypeWithIndentationAndAutoClosingCommand(indentationEdit, selection, ch, autoClosingPairClose);
} else {
// Apply only auto indentation edits
const indentationEdit = this._getEditFromIndentationAndSelection(config, model, indentation, selection, ch, true);
return TypeOperations._typeCommand(indentationEdit.range, indentationEdit.text, false);

Check failure on line 1126 in src/vs/editor/common/cursor/cursorTypeOperations.ts

View workflow job for this annotation

GitHub Actions / Monaco Editor checks

Property '_typeCommand' is private and only accessible within class 'TypeOperations'.
}
});
const editOptions = { shouldPushStackElementBefore: true, shouldPushStackElementAfter: false };
return new EditOperationResult(EditOperationType.TypingOther, commands, editOptions);
}

private static _getEditFromIndentationAndSelection(config: CursorConfiguration, model: ITextModel, indentation: string, selection: Selection, ch: string, includeChInEdit: boolean = true): { range: Range; text: string } {
const startLineNumber = selection.startLineNumber;
const firstNonWhitespaceColumn = model.getLineFirstNonWhitespaceColumn(startLineNumber);
let text: string = config.normalizeIndentation(indentation);
if (firstNonWhitespaceColumn !== 0) {
const startLine = model.getLineContent(startLineNumber);
text += startLine.substring(firstNonWhitespaceColumn - 1, selection.startColumn - 1);
}
text += includeChInEdit ? ch : '';
const range = new Range(startLineNumber, 1, selection.endLineNumber, selection.endColumn);
return { range, text };
}
}

export class CompositionOutcome {
constructor(
public readonly deletedText: string,
Expand Down
27 changes: 26 additions & 1 deletion src/vs/editor/common/languages/autoIndent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { EditorAutoIndentStrategy } from 'vs/editor/common/config/editorOptions'
import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry';
import { IViewLineTokens } from 'vs/editor/common/tokens/lineTokens';
import { IndentationContextProcessor, isLanguageDifferentFromLineStart, ProcessedIndentRulesSupport } from 'vs/editor/common/languages/supports/indentationLineProcessor';
import { CursorConfiguration } from 'vs/editor/common/cursorCommon';

export interface IVirtualModel {
tokenization: {
Expand Down Expand Up @@ -357,13 +358,14 @@ export function getIndentForEnter(
* this line doesn't match decreaseIndentPattern, we should not adjust the indentation.
*/
export function getIndentActionForType(
autoIndent: EditorAutoIndentStrategy,
cursorConfig: CursorConfiguration,
model: ITextModel,
range: Range,
ch: string,
indentConverter: IIndentConverter,
languageConfigurationService: ILanguageConfigurationService
): string | null {
const autoIndent = cursorConfig.autoIndent;
if (autoIndent < EditorAutoIndentStrategy.Full) {
return null;
}
Expand Down Expand Up @@ -404,6 +406,29 @@ export function getIndentActionForType(
return indentation;
}

const previousLineNumber = range.startLineNumber - 1;
if (previousLineNumber > 0) {
const previousLine = model.getLineContent(previousLineNumber);
if (indentRulesSupport.shouldIndentNextLine(previousLine) && indentRulesSupport.shouldIncrease(textAroundRangeWithCharacter)) {
const inheritedIndentationData = getInheritIndentForLine(autoIndent, model, range.startLineNumber, false, languageConfigurationService);
const inheritedIndentation = inheritedIndentationData?.indentation;
if (inheritedIndentation !== undefined) {
const currentLine = model.getLineContent(range.startLineNumber);
const actualCurrentIndentation = strings.getLeadingWhitespace(currentLine);
const inferredCurrentIndentation = indentConverter.shiftIndent(inheritedIndentation);
// If the inferred current indentation is not equal to the actual current indentation, then the indentation has been intentionally changed, in that case keep it
const inferredIndentationEqualsActual = inferredCurrentIndentation === actualCurrentIndentation;
const textAroundRangeContainsOnlyWhitespace = /^\s*$/.test(textAroundRange);
const autoClosingPairs = cursorConfig.autoClosingPairs.autoClosingPairsOpenByEnd.get(ch);
const autoClosingPairExists = autoClosingPairs && autoClosingPairs.length > 0;
const isChFirstNonWhitespaceCharacterAndInAutoClosingPair = autoClosingPairExists && textAroundRangeContainsOnlyWhitespace;
if (inferredIndentationEqualsActual && isChFirstNonWhitespaceCharacterAndInAutoClosingPair) {
return inheritedIndentation;
}
}
}
}

return null;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import { goIndentationRules, htmlIndentationRules, javascriptIndentationRules, l
import { cppOnEnterRules, htmlOnEnterRules, javascriptOnEnterRules, phpOnEnterRules } from 'vs/editor/test/common/modes/supports/onEnterRules';
import { TypeOperations } from 'vs/editor/common/cursor/cursorTypeOperations';
import { cppBracketRules, goBracketRules, htmlBracketRules, latexBracketRules, luaBracketRules, phpBracketRules, rubyBracketRules, typescriptBracketRules, vbBracketRules } from 'vs/editor/test/common/modes/supports/bracketRules';
import { latexAutoClosingPairsRules } from 'vs/editor/test/common/modes/supports/autoClosingPairsRules';
import { javascriptAutoClosingPairsRules, latexAutoClosingPairsRules } from 'vs/editor/test/common/modes/supports/autoClosingPairsRules';
import { LanguageService } from 'vs/editor/common/services/languageService';
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
import { TestLanguageConfigurationService } from 'vs/editor/test/common/modes/testLanguageConfigurationService';
Expand Down Expand Up @@ -61,6 +61,7 @@ export function registerLanguageConfiguration(languageConfigurationService: ILan
blockComment: ['/*', '*/']
},
indentationRules: javascriptIndentationRules,
autoClosingPairs: javascriptAutoClosingPairsRules,
onEnterRules: javascriptOnEnterRules
});
case Language.Ruby:
Expand Down Expand Up @@ -1076,6 +1077,38 @@ suite('Auto Indent On Type - TypeScript/JavaScript', () => {
});
});

test('issue #209802: allman style braces in JavaScript', () => {

// https://github.com/microsoft/vscode/issues/209802

const model = createTextModel([
'if (/*condition*/)',
].join('\n'), languageId, {});
disposables.add(model);

withTestCodeEditor(model, { autoIndent: "full", serviceCollection }, (editor, viewModel) => {
editor.setSelection(new Selection(1, 19, 1, 19));
viewModel.type("\n", 'keyboard');
assert.strictEqual(model.getValue(), [
'if (/*condition*/)',
' '
].join('\n'));
viewModel.type("{", 'keyboard');
assert.strictEqual(model.getValue(), [
'if (/*condition*/)',
'{}'
].join('\n'));
editor.setSelection(new Selection(2, 2, 2, 2));
viewModel.type("\n", 'keyboard');
assert.strictEqual(model.getValue(), [
'if (/*condition*/)',
'{',
' ',
'}'
].join('\n'));
});
});

// Failing tests...

test.skip('issue #43244: indent after equal sign is detected', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,17 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { IAutoClosingPair } from 'vs/editor/common/languages/languageConfiguration';
import { IAutoClosingPair, IAutoClosingPairConditional } from 'vs/editor/common/languages/languageConfiguration';

export const javascriptAutoClosingPairsRules: IAutoClosingPairConditional[] = [
{ open: '{', close: '}' },
{ open: '[', close: ']' },
{ open: '(', close: ')' },
{ open: '\'', close: '\'', notIn: ['string', 'comment'] },
{ open: '"', close: '"', notIn: ['string'] },
{ open: '`', close: '`', notIn: ['string', 'comment'] },
{ open: '/**', close: ' */', notIn: ['string'] }
];

export const latexAutoClosingPairsRules: IAutoClosingPair[] = [
{ open: '\\left(', close: '\\right)' },
Expand Down
Loading