From fbae8406d07f60b8da6907e3c8c19bbbcd5f723e Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Tue, 18 Jun 2024 16:34:36 +0200 Subject: [PATCH 1/2] adding code --- src/vs/editor/common/cursor/cursor.ts | 4 +- .../common/cursor/cursorTypeEditOperations.ts | 175 ++++++++++++------ src/vs/editor/common/languages/autoIndent.ts | 27 ++- .../test/browser/indentation.test.ts | 35 +++- .../modes/supports/autoClosingPairsRules.ts | 12 +- 5 files changed, 196 insertions(+), 57 deletions(-) diff --git a/src/vs/editor/common/cursor/cursor.ts b/src/vs/editor/common/cursor/cursor.ts index 27491a38a6a36..f84b5135d13d1 100644 --- a/src/vs/editor/common/cursor/cursor.ts +++ b/src/vs/editor/common/cursor/cursor.ts @@ -11,6 +11,7 @@ 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 } from 'vs/editor/common/cursor/cursorTypeOperations'; +import { BaseTypeWithAutoClosingCommand } from 'vs/editor/common/cursor/cursorTypeEditOperations'; 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'; @@ -21,7 +22,6 @@ import { VerticalRevealType, ViewCursorStateChangedEvent, ViewRevealRangeRequest import { dispose, Disposable } from 'vs/base/common/lifecycle'; import { ICoordinatesConverter } from 'vs/editor/common/viewModel'; import { CursorStateChangedEvent, ViewModelEventsCollector } from 'vs/editor/common/viewModelEventDispatcher'; -import { TypeWithAutoClosingCommand } from 'vs/editor/common/cursor/cursorTypeEditOperations'; export class CursorsController extends Disposable { @@ -368,7 +368,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); } diff --git a/src/vs/editor/common/cursor/cursorTypeEditOperations.ts b/src/vs/editor/common/cursor/cursorTypeEditOperations.ts index ec8f484f39590..98353e1e71cca 100644 --- a/src/vs/editor/common/cursor/cursorTypeEditOperations.ts +++ b/src/vs/editor/common/cursor/cursorTypeEditOperations.ts @@ -14,34 +14,23 @@ import { WordCharacterClass, getMapForWordSeparators } from 'vs/editor/common/co 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'; import { IElectricAction } from 'vs/editor/common/languages/supports/electricCharacter'; import { EditorAutoClosingStrategy, EditorAutoIndentStrategy } from 'vs/editor/common/config/editorOptions'; import { createScopedLineTokens } from 'vs/editor/common/languages/supports'; -import { getIndentActionForType, getIndentForEnter, getInheritIndentForLine } from 'vs/editor/common/languages/autoIndent'; +import { getIndentActionForType, getIndentForEnter, getInheritIndentForLine, IIndentConverter } from 'vs/editor/common/languages/autoIndent'; import { getEnterAction } from 'vs/editor/common/languages/enterAction'; export class AutoIndentOperation { public static getEdits(config: CursorConfiguration, model: ITextModel, selections: Selection[], ch: string, isDoingComposition: boolean): EditOperationResult | undefined { if (!isDoingComposition && this._isAutoIndentType(config, model, selections)) { - const commands: Array = []; - 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 autoIndentationEdit = this._runAutoIndentType(config, model, selections, ch); + if (autoIndentationEdit) { + return autoIndentationEdit; } } return; @@ -59,39 +48,78 @@ export class AutoIndentOperation { 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, { + private static _runAutoIndentType(config: CursorConfiguration, model: ITextModel, selections: Selection[], ch: string): EditOperationResult | undefined { + const correctedIndentationsForSelections = this._findCorrectedIndentationForSelections(config, model, selections, ch); + if (correctedIndentationsForSelections === undefined) { + return; + } + const autoClosingPairCounterpart = AutoClosingOpenCharTypeOperation.getAutoClosingPairClose(config, model, selections, ch, false); + if (autoClosingPairCounterpart) { + return this._getIndentationAndAutoClosingEdits(config, model, correctedIndentationsForSelections, ch, autoClosingPairCounterpart); + } + return this._getIndentationEdits(config, model, correctedIndentationsForSelections, ch); + } + + private static _findCorrectedIndentationForSelections(config: CursorConfiguration, model: ITextModel, selections: Selection[], ch: string): { selection: Selection; indentation: string }[] | undefined { + const indentationForSelections: { selection: Selection; indentation: string }[] = []; + for (let i = 0, len = selections.length; i < len; i++) { + const selection = selections[i]; + const indentation = this._findCorrectedIndentationForSelection(config, model, selection, ch); + if (indentation === undefined) { + return; + } + indentationForSelections.push({ selection, indentation }); + } + return indentationForSelections; + } + + private static _findCorrectedIndentationForSelection(config: CursorConfiguration, model: ITextModel, selection: Selection, ch: string): string | undefined { + const indentConverter: IIndentConverter = { shiftIndent: (indentation) => { return shiftIndent(config, indentation); }, unshiftIndent: (indentation) => { return unshiftIndent(config, indentation); }, - }, config.languageConfigurationService); - - if (actualIndentation === null) { - return null; + }; + const correctedIndetation = getIndentActionForType(config, model, selection, ch, indentConverter, config.languageConfigurationService); + if (correctedIndetation === null) { + return; + } + const currentIndentation = getIndentationAtPosition(model, selection.startLineNumber, selection.startColumn); + if (correctedIndetation === config.normalizeIndentation(currentIndentation)) { + return; } + return correctedIndetation; + } - if (actualIndentation !== config.normalizeIndentation(currentIndentation)) { - const firstNonWhitespace = model.getLineFirstNonWhitespaceColumn(range.startLineNumber); - if (firstNonWhitespace === 0) { - return typeCommand( - new Range(range.startLineNumber, 1, range.endLineNumber, range.endColumn), - config.normalizeIndentation(actualIndentation) + ch, - false - ); - } else { - return 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 - ); - } + private static _getIndentationAndAutoClosingEdits(config: CursorConfiguration, model: ITextModel, indentationForSelections: { selection: Selection; indentation: string }[], ch: string, autoClosingPairCounterpart: string): EditOperationResult { + const commands: ICommand[] = indentationForSelections.map(editPerSelection => { + const selection = editPerSelection.selection; + const edit = this._getEditFromIndentation(config, model, editPerSelection.indentation, selection, ch, false); + return new TypeWithIndentationAndAutoClosingCommand(edit, selection, ch, autoClosingPairCounterpart); + }); + const editOptions = { shouldPushStackElementBefore: true, shouldPushStackElementAfter: false }; + return new EditOperationResult(EditOperationType.TypingOther, commands, editOptions); + } + + private static _getIndentationEdits(config: CursorConfiguration, model: ITextModel, indentationForSelections: { selection: Selection; indentation: string }[], ch: string): EditOperationResult { + const edits = indentationForSelections.map(el => this._getEditFromIndentation(config, model, el.indentation, el.selection, ch, true)); + const commands = edits.map((edit) => typeCommand(edit.range, edit.text, false)); + const editOptions = { shouldPushStackElementBefore: true, shouldPushStackElementAfter: false }; + return new EditOperationResult(EditOperationType.TypingOther, commands, editOptions); + } + + private static _getEditFromIndentation(config: CursorConfiguration, model: ITextModel, indentation: string, selection: Selection, ch: string, includeChInEdit: boolean = true): { range: Range; text: string } { + const firstNonWhitespace = model.getLineFirstNonWhitespaceColumn(selection.startLineNumber); + const normalizedIndentation = config.normalizeIndentation(indentation); + const additionalCharacter = (includeChInEdit ? ch : ''); + let text: string = normalizedIndentation + additionalCharacter; + if (firstNonWhitespace !== 0) { + text += model.getLineContent(selection.startLineNumber).substring(firstNonWhitespace - 1, selection.startColumn - 1); } - return null; + const range = new Range(selection.startLineNumber, 1, selection.endLineNumber, selection.endColumn); + return { range, text }; } } @@ -138,7 +166,7 @@ export class AutoClosingOpenCharTypeOperation { public static getEdits(config: CursorConfiguration, model: ITextModel, selections: Selection[], ch: string, chIsAlreadyTyped: boolean, isDoingComposition: boolean): EditOperationResult | undefined { if (!isDoingComposition) { - const autoClosingPairClose = this._getAutoClosingPairClose(config, model, selections, ch, chIsAlreadyTyped); + const autoClosingPairClose = this.getAutoClosingPairClose(config, model, selections, ch, chIsAlreadyTyped); if (autoClosingPairClose !== null) { return this._runAutoClosingOpenCharType(selections, ch, chIsAlreadyTyped, autoClosingPairClose); } @@ -158,7 +186,7 @@ export class AutoClosingOpenCharTypeOperation { }); } - private static _getAutoClosingPairClose(config: CursorConfiguration, model: ITextModel, selections: Selection[], ch: string, chIsAlreadyTyped: boolean): string | null { + public static getAutoClosingPairClose(config: CursorConfiguration, model: ITextModel, selections: Selection[], ch: string, chIsAlreadyTyped: boolean): string | null { for (const selection of selections) { if (!selection.isEmpty()) { return null; @@ -843,30 +871,73 @@ export class TabOperation { } } -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; + public closeCharacterRange: Range | undefined; + public enclosingRange: Range | undefined; - constructor(selection: Selection, openCharacter: string, insertOpenCharacter: boolean, closeCharacter: string) { - super(selection, (insertOpenCharacter ? openCharacter : '') + closeCharacter, 0, -closeCharacter.length); + constructor(range: Range, text: string, lineNumberDeltaOffset: number, columnDeltaOffset: number, openCharacter: string, closeCharacter: string) { + super(range, 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._autoClosingEdit = { range: selection, text }; + this._autoIndentationEdit = autoIndentationEdit; + } + + 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('Not 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); + } +} + function getTypingOperation(typedText: string, previousTypingOperation: EditOperationType): EditOperationType { if (typedText === ' ') { return previousTypingOperation === EditOperationType.TypingFirstSpace diff --git a/src/vs/editor/common/languages/autoIndent.ts b/src/vs/editor/common/languages/autoIndent.ts index 680d3a7d5f9f0..5c643b4fa604f 100644 --- a/src/vs/editor/common/languages/autoIndent.ts +++ b/src/vs/editor/common/languages/autoIndent.ts @@ -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: { @@ -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; } @@ -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; } diff --git a/src/vs/editor/contrib/indentation/test/browser/indentation.test.ts b/src/vs/editor/contrib/indentation/test/browser/indentation.test.ts index f689466ed5114..2beed4f823be3 100644 --- a/src/vs/editor/contrib/indentation/test/browser/indentation.test.ts +++ b/src/vs/editor/contrib/indentation/test/browser/indentation.test.ts @@ -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'; @@ -60,6 +60,7 @@ export function registerLanguageConfiguration(languageConfigurationService: ILan lineComment: '//', blockComment: ['/*', '*/'] }, + autoClosingPairs: javascriptAutoClosingPairsRules, indentationRules: javascriptIndentationRules, onEnterRules: javascriptOnEnterRules }); @@ -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', () => { diff --git a/src/vs/editor/test/common/modes/supports/autoClosingPairsRules.ts b/src/vs/editor/test/common/modes/supports/autoClosingPairsRules.ts index 968bd3508926c..0f5ebc499bd24 100644 --- a/src/vs/editor/test/common/modes/supports/autoClosingPairsRules.ts +++ b/src/vs/editor/test/common/modes/supports/autoClosingPairsRules.ts @@ -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)' }, From 4309e772e5e7d344abc5e13c58c0001be35b496f Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Tue, 18 Jun 2024 16:50:27 +0200 Subject: [PATCH 2/2] polishing the code --- .../common/cursor/cursorTypeEditOperations.ts | 113 ++++++++---------- 1 file changed, 49 insertions(+), 64 deletions(-) diff --git a/src/vs/editor/common/cursor/cursorTypeEditOperations.ts b/src/vs/editor/common/cursor/cursorTypeEditOperations.ts index 98353e1e71cca..df17d2f3918cd 100644 --- a/src/vs/editor/common/cursor/cursorTypeEditOperations.ts +++ b/src/vs/editor/common/cursor/cursorTypeEditOperations.ts @@ -21,17 +21,24 @@ import { getIndentationAtPosition } from 'vs/editor/common/languages/languageCon import { IElectricAction } from 'vs/editor/common/languages/supports/electricCharacter'; import { EditorAutoClosingStrategy, EditorAutoIndentStrategy } from 'vs/editor/common/config/editorOptions'; import { createScopedLineTokens } from 'vs/editor/common/languages/supports'; -import { getIndentActionForType, getIndentForEnter, getInheritIndentForLine, IIndentConverter } from 'vs/editor/common/languages/autoIndent'; +import { getIndentActionForType, getIndentForEnter, getInheritIndentForLine } from 'vs/editor/common/languages/autoIndent'; import { getEnterAction } from 'vs/editor/common/languages/enterAction'; export class AutoIndentOperation { public static getEdits(config: CursorConfiguration, model: ITextModel, selections: Selection[], ch: string, isDoingComposition: boolean): EditOperationResult | undefined { if (!isDoingComposition && this._isAutoIndentType(config, model, selections)) { - const autoIndentationEdit = this._runAutoIndentType(config, model, selections, ch); - if (autoIndentationEdit) { - return autoIndentationEdit; + 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 = AutoClosingOpenCharTypeOperation.getAutoClosingPairClose(config, model, selections, ch, false); + return this._getIndentationAndAutoClosingPairEdits(config, model, indentationForSelections, ch, autoClosingPairClose); } return; } @@ -48,77 +55,53 @@ export class AutoIndentOperation { return true; } - private static _runAutoIndentType(config: CursorConfiguration, model: ITextModel, selections: Selection[], ch: string): EditOperationResult | undefined { - const correctedIndentationsForSelections = this._findCorrectedIndentationForSelections(config, model, selections, ch); - if (correctedIndentationsForSelections === undefined) { - return; - } - const autoClosingPairCounterpart = AutoClosingOpenCharTypeOperation.getAutoClosingPairClose(config, model, selections, ch, false); - if (autoClosingPairCounterpart) { - return this._getIndentationAndAutoClosingEdits(config, model, correctedIndentationsForSelections, ch, autoClosingPairCounterpart); - } - return this._getIndentationEdits(config, model, correctedIndentationsForSelections, ch); - } - - private static _findCorrectedIndentationForSelections(config: CursorConfiguration, model: ITextModel, selections: Selection[], ch: string): { selection: Selection; indentation: string }[] | undefined { - const indentationForSelections: { selection: Selection; indentation: string }[] = []; - for (let i = 0, len = selections.length; i < len; i++) { - const selection = selections[i]; - const indentation = this._findCorrectedIndentationForSelection(config, model, selection, ch); - if (indentation === undefined) { - return; - } - indentationForSelections.push({ selection, indentation }); - } - return indentationForSelections; - } - - private static _findCorrectedIndentationForSelection(config: CursorConfiguration, model: ITextModel, selection: Selection, ch: string): string | undefined { - const indentConverter: IIndentConverter = { + private static _findActualIndentationForSelection(config: CursorConfiguration, model: ITextModel, selection: Selection, ch: string): string | null { + const actualIndentation = getIndentActionForType(config, model, selection, ch, { shiftIndent: (indentation) => { return shiftIndent(config, indentation); }, unshiftIndent: (indentation) => { return unshiftIndent(config, indentation); }, - }; - const correctedIndetation = getIndentActionForType(config, model, selection, ch, indentConverter, config.languageConfigurationService); - if (correctedIndetation === null) { - return; + }, config.languageConfigurationService); + + if (actualIndentation === null) { + return null; } + const currentIndentation = getIndentationAtPosition(model, selection.startLineNumber, selection.startColumn); - if (correctedIndetation === config.normalizeIndentation(currentIndentation)) { - return; + if (actualIndentation === config.normalizeIndentation(currentIndentation)) { + return null; } - return correctedIndetation; + return actualIndentation; } - private static _getIndentationAndAutoClosingEdits(config: CursorConfiguration, model: ITextModel, indentationForSelections: { selection: Selection; indentation: string }[], ch: string, autoClosingPairCounterpart: string): EditOperationResult { - const commands: ICommand[] = indentationForSelections.map(editPerSelection => { - const selection = editPerSelection.selection; - const edit = this._getEditFromIndentation(config, model, editPerSelection.indentation, selection, ch, false); - return new TypeWithIndentationAndAutoClosingCommand(edit, selection, ch, autoClosingPairCounterpart); + 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 typeCommand(indentationEdit.range, indentationEdit.text, false); + } }); const editOptions = { shouldPushStackElementBefore: true, shouldPushStackElementAfter: false }; return new EditOperationResult(EditOperationType.TypingOther, commands, editOptions); } - private static _getIndentationEdits(config: CursorConfiguration, model: ITextModel, indentationForSelections: { selection: Selection; indentation: string }[], ch: string): EditOperationResult { - const edits = indentationForSelections.map(el => this._getEditFromIndentation(config, model, el.indentation, el.selection, ch, true)); - const commands = edits.map((edit) => typeCommand(edit.range, edit.text, false)); - const editOptions = { shouldPushStackElementBefore: true, shouldPushStackElementAfter: false }; - return new EditOperationResult(EditOperationType.TypingOther, commands, editOptions); - } - - private static _getEditFromIndentation(config: CursorConfiguration, model: ITextModel, indentation: string, selection: Selection, ch: string, includeChInEdit: boolean = true): { range: Range; text: string } { - const firstNonWhitespace = model.getLineFirstNonWhitespaceColumn(selection.startLineNumber); - const normalizedIndentation = config.normalizeIndentation(indentation); - const additionalCharacter = (includeChInEdit ? ch : ''); - let text: string = normalizedIndentation + additionalCharacter; - if (firstNonWhitespace !== 0) { - text += model.getLineContent(selection.startLineNumber).substring(firstNonWhitespace - 1, selection.startColumn - 1); + 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); } - const range = new Range(selection.startLineNumber, 1, selection.endLineNumber, selection.endColumn); + text += includeChInEdit ? ch : ''; + const range = new Range(startLineNumber, 1, selection.endLineNumber, selection.endColumn); return { range, text }; } } @@ -875,13 +858,15 @@ export class BaseTypeWithAutoClosingCommand extends ReplaceCommandWithOffsetCurs private readonly _openCharacter: string; private readonly _closeCharacter: string; - public closeCharacterRange: Range | undefined; - public enclosingRange: Range | undefined; + public closeCharacterRange: Range | null; + public enclosingRange: Range | null; - constructor(range: Range, text: string, lineNumberDeltaOffset: number, columnDeltaOffset: number, openCharacter: string, closeCharacter: string) { - super(range, text, lineNumberDeltaOffset, columnDeltaOffset); + 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; } protected _computeCursorStateWithRange(model: ITextModel, range: Range, helper: ICursorStateComputerData): Selection { @@ -917,8 +902,8 @@ class TypeWithIndentationAndAutoClosingCommand extends BaseTypeWithAutoClosingCo const lineNumberDeltaOffset = 0; const columnDeltaOffset = openCharacter.length; super(selection, text, lineNumberDeltaOffset, columnDeltaOffset, openCharacter, closeCharacter); - this._autoClosingEdit = { range: selection, text }; this._autoIndentationEdit = autoIndentationEdit; + this._autoClosingEdit = { range: selection, text }; } public override getEditOperations(model: ITextModel, builder: IEditOperationBuilder): void { @@ -929,7 +914,7 @@ class TypeWithIndentationAndAutoClosingCommand extends BaseTypeWithAutoClosingCo public override computeCursorState(model: ITextModel, helper: ICursorStateComputerData): Selection { const inverseEditOperations = helper.getInverseEditOperations(); if (inverseEditOperations.length !== 2) { - throw new Error('Not two inverse edit operations!'); + throw new Error('There should be two inverse edit operations!'); } const range1 = inverseEditOperations[0].range; const range2 = inverseEditOperations[1].range;