diff --git a/src/vs/editor/common/controller/cursorCommon.ts b/src/vs/editor/common/controller/cursorCommon.ts index e49c7dea933ff15fcf1c73cc2fc015a2aa50e0c1..92d36e239ad869acd7f2bb4b57ef8350c1e50e7d 100644 --- a/src/vs/editor/common/controller/cursorCommon.ts +++ b/src/vs/editor/common/controller/cursorCommon.ts @@ -55,14 +55,6 @@ const autoCloseAlways = () => true; const autoCloseNever = () => false; const autoCloseBeforeWhitespace = (chr: string) => (chr === ' ' || chr === '\t'); -function appendEntry(target: Map, key: K, value: V): void { - if (target.has(key)) { - target.get(key)!.push(value); - } else { - target.set(key, [value]); - } -} - export class CursorConfiguration { _cursorMoveConfigurationBrand: void; @@ -136,8 +128,6 @@ export class CursorConfiguration { this.autoSurround = options.get(EditorOption.autoSurround); this.autoIndent = options.get(EditorOption.autoIndent); - this.autoClosingPairsOpen2 = new Map(); - this.autoClosingPairsClose2 = new Map(); this.surroundingPairs = {}; this._electricChars = null; @@ -146,15 +136,9 @@ export class CursorConfiguration { bracket: CursorConfiguration._getShouldAutoClose(languageIdentifier, this.autoClosingBrackets) }; - let autoClosingPairs = CursorConfiguration._getAutoClosingPairs(languageIdentifier); - if (autoClosingPairs) { - for (const pair of autoClosingPairs) { - appendEntry(this.autoClosingPairsOpen2, pair.open.charAt(pair.open.length - 1), pair); - if (pair.close.length === 1) { - appendEntry(this.autoClosingPairsClose2, pair.close, pair); - } - } - } + const autoClosingPairs = LanguageConfigurationRegistry.getAutoClosingPairs(languageIdentifier.id); + this.autoClosingPairsOpen2 = autoClosingPairs.autoClosingPairsOpen; + this.autoClosingPairsClose2 = autoClosingPairs.autoClosingPairsClose; let surroundingPairs = CursorConfiguration._getSurroundingPairs(languageIdentifier); if (surroundingPairs) { @@ -190,15 +174,6 @@ export class CursorConfiguration { } } - private static _getAutoClosingPairs(languageIdentifier: LanguageIdentifier): StandardAutoClosingPairConditional[] | null { - try { - return LanguageConfigurationRegistry.getAutoClosingPairs(languageIdentifier.id); - } catch (e) { - onUnexpectedError(e); - return null; - } - } - private static _getShouldAutoClose(languageIdentifier: LanguageIdentifier, autoCloseConfig: EditorAutoClosingStrategy): (ch: string) => boolean { switch (autoCloseConfig) { case 'beforeWhitespace': diff --git a/src/vs/editor/common/controller/cursorDeleteOperations.ts b/src/vs/editor/common/controller/cursorDeleteOperations.ts index 3f5e80a3ee96b5d37c2c8743456b854d61dd04af..1147e78ae1c5c8261bcc279e2855e58598e56911 100644 --- a/src/vs/editor/common/controller/cursorDeleteOperations.ts +++ b/src/vs/editor/common/controller/cursorDeleteOperations.ts @@ -5,11 +5,13 @@ import * as strings from 'vs/base/common/strings'; import { ReplaceCommand } from 'vs/editor/common/commands/replaceCommand'; +import { EditorAutoClosingStrategy } from 'vs/editor/common/config/editorOptions'; import { CursorColumns, CursorConfiguration, EditOperationResult, EditOperationType, ICursorSimpleModel, isQuote } from 'vs/editor/common/controller/cursorCommon'; import { MoveOperations } from 'vs/editor/common/controller/cursorMoveOperations'; import { Range } from 'vs/editor/common/core/range'; import { Selection } from 'vs/editor/common/core/selection'; import { ICommand } from 'vs/editor/common/editorCommon'; +import { StandardAutoClosingPairConditional } from 'vs/editor/common/modes/languageConfiguration'; export class DeleteOperations { @@ -47,8 +49,14 @@ export class DeleteOperations { return [shouldPushStackElementBefore, commands]; } - private static _isAutoClosingPairDelete(config: CursorConfiguration, model: ICursorSimpleModel, selections: Selection[]): boolean { - if (config.autoClosingBrackets === 'never' && config.autoClosingQuotes === 'never') { + public static isAutoClosingPairDelete( + autoClosingBrackets: EditorAutoClosingStrategy, + autoClosingQuotes: EditorAutoClosingStrategy, + autoClosingPairsOpen: Map, + model: ICursorSimpleModel, + selections: Selection[] + ): boolean { + if (autoClosingBrackets === 'never' && autoClosingQuotes === 'never') { return false; } @@ -61,24 +69,27 @@ export class DeleteOperations { } const lineText = model.getLineContent(position.lineNumber); - const character = lineText[position.column - 2]; + if (position.column < 2 || position.column >= lineText.length + 1) { + return false; + } + const character = lineText.charAt(position.column - 2); - const autoClosingPairCandidates = config.autoClosingPairsOpen2.get(character); + const autoClosingPairCandidates = autoClosingPairsOpen.get(character); if (!autoClosingPairCandidates) { return false; } if (isQuote(character)) { - if (config.autoClosingQuotes === 'never') { + if (autoClosingQuotes === 'never') { return false; } } else { - if (config.autoClosingBrackets === 'never') { + if (autoClosingBrackets === 'never') { return false; } } - const afterCharacter = lineText[position.column - 1]; + const afterCharacter = lineText.charAt(position.column - 1); let foundAutoClosingPair = false; for (const autoClosingPairCandidate of autoClosingPairCandidates) { @@ -111,7 +122,7 @@ export class DeleteOperations { public static deleteLeft(prevEditOperationType: EditOperationType, config: CursorConfiguration, model: ICursorSimpleModel, selections: Selection[]): [boolean, Array] { - if (this._isAutoClosingPairDelete(config, model, selections)) { + if (this.isAutoClosingPairDelete(config.autoClosingBrackets, config.autoClosingQuotes, config.autoClosingPairsOpen2, model, selections)) { return this._runAutoClosingPairDelete(config, model, selections); } diff --git a/src/vs/editor/common/controller/cursorWordOperations.ts b/src/vs/editor/common/controller/cursorWordOperations.ts index 0ab6c9313269123a3611e96f9f1e2588a5a59c0a..c67d8f968e4f52ef8ff20fd001e4d0272e54b977 100644 --- a/src/vs/editor/common/controller/cursorWordOperations.ts +++ b/src/vs/editor/common/controller/cursorWordOperations.ts @@ -5,12 +5,15 @@ import { CharCode } from 'vs/base/common/charCode'; import * as strings from 'vs/base/common/strings'; +import { EditorAutoClosingStrategy } from 'vs/editor/common/config/editorOptions'; import { CursorConfiguration, ICursorSimpleModel, SingleCursorState } from 'vs/editor/common/controller/cursorCommon'; +import { DeleteOperations } from 'vs/editor/common/controller/cursorDeleteOperations'; import { WordCharacterClass, WordCharacterClassifier, getMapForWordSeparators } from 'vs/editor/common/controller/wordCharacterClassifier'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import { Selection } from 'vs/editor/common/core/selection'; import { ITextModel, IWordAtPosition } from 'vs/editor/common/model'; +import { AutoClosingPairs } from 'vs/editor/common/modes/languageConfiguration'; interface IFindWordResult { /** @@ -44,6 +47,16 @@ export const enum WordNavigationType { WordAccessibility = 3 // Respect chrome defintion of a word } +export interface DeleteWordContext { + wordSeparators: WordCharacterClassifier; + model: ITextModel; + selection: Selection; + whitespaceHeuristics: boolean; + autoClosingBrackets: EditorAutoClosingStrategy; + autoClosingQuotes: EditorAutoClosingStrategy; + autoClosingPairs: AutoClosingPairs; +} + export class WordOperations { private static _createWord(lineContent: string, wordType: WordType, nextCharClass: WordCharacterClass, start: number, end: number): IFindWordResult { @@ -361,11 +374,21 @@ export class WordOperations { return null; } - public static deleteWordLeft(wordSeparators: WordCharacterClassifier, model: ICursorSimpleModel, selection: Selection, whitespaceHeuristics: boolean, wordNavigationType: WordNavigationType): Range | null { + public static deleteWordLeft(ctx: DeleteWordContext, wordNavigationType: WordNavigationType): Range | null { + const wordSeparators = ctx.wordSeparators; + const model = ctx.model; + const selection = ctx.selection; + const whitespaceHeuristics = ctx.whitespaceHeuristics; + if (!selection.isEmpty()) { return selection; } + if (DeleteOperations.isAutoClosingPairDelete(ctx.autoClosingBrackets, ctx.autoClosingQuotes, ctx.autoClosingPairs.autoClosingPairsOpen, ctx.model, [ctx.selection])) { + const position = ctx.selection.getPosition(); + return new Range(position.lineNumber, position.column - 1, position.lineNumber, position.column + 1); + } + const position = new Position(selection.positionLineNumber, selection.positionColumn); let lineNumber = position.lineNumber; @@ -447,7 +470,12 @@ export class WordOperations { return null; } - public static deleteWordRight(wordSeparators: WordCharacterClassifier, model: ICursorSimpleModel, selection: Selection, whitespaceHeuristics: boolean, wordNavigationType: WordNavigationType): Range | null { + public static deleteWordRight(ctx: DeleteWordContext, wordNavigationType: WordNavigationType): Range | null { + const wordSeparators = ctx.wordSeparators; + const model = ctx.model; + const selection = ctx.selection; + const whitespaceHeuristics = ctx.whitespaceHeuristics; + if (!selection.isEmpty()) { return selection; } @@ -621,21 +649,21 @@ export class WordOperations { } export class WordPartOperations extends WordOperations { - public static deleteWordPartLeft(wordSeparators: WordCharacterClassifier, model: ICursorSimpleModel, selection: Selection, whitespaceHeuristics: boolean): Range { + public static deleteWordPartLeft(ctx: DeleteWordContext): Range { const candidates = enforceDefined([ - WordOperations.deleteWordLeft(wordSeparators, model, selection, whitespaceHeuristics, WordNavigationType.WordStart), - WordOperations.deleteWordLeft(wordSeparators, model, selection, whitespaceHeuristics, WordNavigationType.WordEnd), - WordOperations._deleteWordPartLeft(model, selection) + WordOperations.deleteWordLeft(ctx, WordNavigationType.WordStart), + WordOperations.deleteWordLeft(ctx, WordNavigationType.WordEnd), + WordOperations._deleteWordPartLeft(ctx.model, ctx.selection) ]); candidates.sort(Range.compareRangesUsingEnds); return candidates[2]; } - public static deleteWordPartRight(wordSeparators: WordCharacterClassifier, model: ICursorSimpleModel, selection: Selection, whitespaceHeuristics: boolean): Range { + public static deleteWordPartRight(ctx: DeleteWordContext): Range { const candidates = enforceDefined([ - WordOperations.deleteWordRight(wordSeparators, model, selection, whitespaceHeuristics, WordNavigationType.WordStart), - WordOperations.deleteWordRight(wordSeparators, model, selection, whitespaceHeuristics, WordNavigationType.WordEnd), - WordOperations._deleteWordPartRight(model, selection) + WordOperations.deleteWordRight(ctx, WordNavigationType.WordStart), + WordOperations.deleteWordRight(ctx, WordNavigationType.WordEnd), + WordOperations._deleteWordPartRight(ctx.model, ctx.selection) ]); candidates.sort(Range.compareRangesUsingStarts); return candidates[0]; diff --git a/src/vs/editor/common/modes/languageConfiguration.ts b/src/vs/editor/common/modes/languageConfiguration.ts index 98a39849444179e59258805c9a250cbb88c48696..295fbdc01df110cebaabe1df89102a03640b1f1b 100644 --- a/src/vs/editor/common/modes/languageConfiguration.ts +++ b/src/vs/editor/common/modes/languageConfiguration.ts @@ -289,3 +289,31 @@ export class StandardAutoClosingPairConditional { return (this._standardTokenMask & standardToken) === 0; } } + +/** + * @internal + */ +export class AutoClosingPairs { + + public readonly autoClosingPairsOpen: Map; + public readonly autoClosingPairsClose: Map; + + constructor(autoClosingPairs: StandardAutoClosingPairConditional[]) { + this.autoClosingPairsOpen = new Map(); + this.autoClosingPairsClose = new Map(); + for (const pair of autoClosingPairs) { + appendEntry(this.autoClosingPairsOpen, pair.open.charAt(pair.open.length - 1), pair); + if (pair.close.length === 1) { + appendEntry(this.autoClosingPairsClose, pair.close, pair); + } + } + } +} + +function appendEntry(target: Map, key: K, value: V): void { + if (target.has(key)) { + target.get(key)!.push(value); + } else { + target.set(key, [value]); + } +} diff --git a/src/vs/editor/common/modes/languageConfigurationRegistry.ts b/src/vs/editor/common/modes/languageConfigurationRegistry.ts index daadeebb57e1fc9b74cbc1d16c6539a97cdcd865..2d7600d323c3bcfe0cc2c61185d4d5731455c04e 100644 --- a/src/vs/editor/common/modes/languageConfigurationRegistry.ts +++ b/src/vs/editor/common/modes/languageConfigurationRegistry.ts @@ -11,7 +11,7 @@ import { Range } from 'vs/editor/common/core/range'; import { ITextModel } from 'vs/editor/common/model'; import { DEFAULT_WORD_REGEXP, ensureValidWordDefinition } from 'vs/editor/common/model/wordHelper'; import { LanguageId, LanguageIdentifier } from 'vs/editor/common/modes'; -import { EnterAction, FoldingRules, IAutoClosingPair, IndentAction, IndentationRule, LanguageConfiguration, StandardAutoClosingPairConditional, CompleteEnterAction } from 'vs/editor/common/modes/languageConfiguration'; +import { EnterAction, FoldingRules, IAutoClosingPair, IndentAction, IndentationRule, LanguageConfiguration, StandardAutoClosingPairConditional, CompleteEnterAction, AutoClosingPairs } from 'vs/editor/common/modes/languageConfiguration'; import { createScopedLineTokens, ScopedLineTokens } from 'vs/editor/common/modes/supports'; import { CharacterPairSupport } from 'vs/editor/common/modes/supports/characterPair'; import { BracketElectricCharacterSupport, IElectricAction } from 'vs/editor/common/modes/supports/electricCharacter'; @@ -235,12 +235,9 @@ export class LanguageConfigurationRegistryImpl { return value.characterPair || null; } - public getAutoClosingPairs(languageId: LanguageId): StandardAutoClosingPairConditional[] { - let characterPairSupport = this._getCharacterPairSupport(languageId); - if (!characterPairSupport) { - return []; - } - return characterPairSupport.getAutoClosingPairs(); + public getAutoClosingPairs(languageId: LanguageId): AutoClosingPairs { + const characterPairSupport = this._getCharacterPairSupport(languageId); + return new AutoClosingPairs(characterPairSupport ? characterPairSupport.getAutoClosingPairs() : []); } public getAutoCloseBeforeSet(languageId: LanguageId): string { diff --git a/src/vs/editor/contrib/wordOperations/test/wordOperations.test.ts b/src/vs/editor/contrib/wordOperations/test/wordOperations.test.ts index eadfa9ed3d885bc0b498975cbac52ea06a50c28a..ad2afd196a8eb7f0d587a50a2d73d876d93acf3c 100644 --- a/src/vs/editor/contrib/wordOperations/test/wordOperations.test.ts +++ b/src/vs/editor/contrib/wordOperations/test/wordOperations.test.ts @@ -13,6 +13,10 @@ import { CursorWordEndLeft, CursorWordEndLeftSelect, CursorWordEndRight, CursorW import { withTestCodeEditor } from 'vs/editor/test/browser/testCodeEditor'; import { CoreEditingCommands } from 'vs/editor/browser/controller/coreCommands'; import { ViewModel } from 'vs/editor/common/viewModel/viewModelImpl'; +import { LanguageIdentifier } from 'vs/editor/common/modes'; +import { MockMode } from 'vs/editor/test/common/mocks/mockMode'; +import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry'; +import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; suite('WordOperations', () => { @@ -721,4 +725,29 @@ suite('WordOperations', () => { deleteWordLeft(editor); assert.equal(model.getLineContent(1), 'A line with text. And another one', '001'); }); }); + + test('deleteWordLeft - issue #91855: Matching (quote, bracket, paren) doesn\'t get deleted when hitting Ctrl+Backspace', () => { + const languageId = new LanguageIdentifier('myTestMode', 5); + class TestMode extends MockMode { + constructor() { + super(languageId); + this._register(LanguageConfigurationRegistry.register(this.getLanguageIdentifier(), { + autoClosingPairs: [ + { open: '\"', close: '\"' } + ] + })); + } + } + + const mode = new TestMode(); + const model = createTextModel('a ""', undefined, languageId); + + withTestCodeEditor(null, { model }, (editor, _) => { + editor.setPosition(new Position(1, 4)); + deleteWordLeft(editor); assert.equal(model.getLineContent(1), 'a '); + }); + + model.dispose(); + mode.dispose(); + }); }); diff --git a/src/vs/editor/contrib/wordOperations/wordOperations.ts b/src/vs/editor/contrib/wordOperations/wordOperations.ts index 2e2b70132388150c4bc50424e66e1a0ac82c8509..c7db9649e0f6817d2c9faa893804ff969926cdb7 100644 --- a/src/vs/editor/contrib/wordOperations/wordOperations.ts +++ b/src/vs/editor/contrib/wordOperations/wordOperations.ts @@ -9,7 +9,7 @@ import { EditorCommand, ICommandOptions, ServicesAccessor, registerEditorCommand import { ReplaceCommand } from 'vs/editor/common/commands/replaceCommand'; import { CursorState } from 'vs/editor/common/controller/cursorCommon'; import { CursorChangeReason } from 'vs/editor/common/controller/cursorEvents'; -import { WordNavigationType, WordOperations } from 'vs/editor/common/controller/cursorWordOperations'; +import { DeleteWordContext, WordNavigationType, WordOperations } from 'vs/editor/common/controller/cursorWordOperations'; import { WordCharacterClassifier, getMapForWordSeparators } from 'vs/editor/common/controller/wordCharacterClassifier'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; @@ -21,6 +21,7 @@ import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegis import { CONTEXT_ACCESSIBILITY_MODE_ENABLED } from 'vs/platform/accessibility/common/accessibility'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { EditorOption, EditorOptions } from 'vs/editor/common/config/editorOptions'; +import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry'; export interface MoveWordOptions extends ICommandOptions { inSelectionMode: boolean; @@ -354,9 +355,20 @@ export abstract class DeleteWordCommand extends EditorCommand { const wordSeparators = getMapForWordSeparators(editor.getOption(EditorOption.wordSeparators)); const model = editor.getModel(); const selections = editor.getSelections(); + const autoClosingBrackets = editor.getOption(EditorOption.autoClosingBrackets); + const autoClosingQuotes = editor.getOption(EditorOption.autoClosingQuotes); + const autoClosingPairs = LanguageConfigurationRegistry.getAutoClosingPairs(model.getLanguageIdentifier().id); const commands = selections.map((sel) => { - const deleteRange = this._delete(wordSeparators, model, sel, this._whitespaceHeuristics, this._wordNavigationType); + const deleteRange = this._delete({ + wordSeparators, + model, + selection: sel, + whitespaceHeuristics: this._whitespaceHeuristics, + autoClosingBrackets, + autoClosingQuotes, + autoClosingPairs, + }, this._wordNavigationType); return new ReplaceCommand(deleteRange, ''); }); @@ -365,12 +377,12 @@ export abstract class DeleteWordCommand extends EditorCommand { editor.pushUndoStop(); } - protected abstract _delete(wordSeparators: WordCharacterClassifier, model: ITextModel, selection: Selection, whitespaceHeuristics: boolean, wordNavigationType: WordNavigationType): Range; + protected abstract _delete(ctx: DeleteWordContext, wordNavigationType: WordNavigationType): Range; } export class DeleteWordLeftCommand extends DeleteWordCommand { - protected _delete(wordSeparators: WordCharacterClassifier, model: ITextModel, selection: Selection, whitespaceHeuristics: boolean, wordNavigationType: WordNavigationType): Range { - let r = WordOperations.deleteWordLeft(wordSeparators, model, selection, whitespaceHeuristics, wordNavigationType); + protected _delete(ctx: DeleteWordContext, wordNavigationType: WordNavigationType): Range { + let r = WordOperations.deleteWordLeft(ctx, wordNavigationType); if (r) { return r; } @@ -379,13 +391,13 @@ export class DeleteWordLeftCommand extends DeleteWordCommand { } export class DeleteWordRightCommand extends DeleteWordCommand { - protected _delete(wordSeparators: WordCharacterClassifier, model: ITextModel, selection: Selection, whitespaceHeuristics: boolean, wordNavigationType: WordNavigationType): Range { - let r = WordOperations.deleteWordRight(wordSeparators, model, selection, whitespaceHeuristics, wordNavigationType); + protected _delete(ctx: DeleteWordContext, wordNavigationType: WordNavigationType): Range { + let r = WordOperations.deleteWordRight(ctx, wordNavigationType); if (r) { return r; } - const lineCount = model.getLineCount(); - const maxColumn = model.getLineMaxColumn(lineCount); + const lineCount = ctx.model.getLineCount(); + const maxColumn = ctx.model.getLineMaxColumn(lineCount); return new Range(lineCount, maxColumn, lineCount, maxColumn); } } diff --git a/src/vs/editor/contrib/wordPartOperations/wordPartOperations.ts b/src/vs/editor/contrib/wordPartOperations/wordPartOperations.ts index 5887bd84a2136cd8bff22d0524a48ee8146236d7..b968fe1276a3b93c4e29c683a563e1faf2737cf0 100644 --- a/src/vs/editor/contrib/wordPartOperations/wordPartOperations.ts +++ b/src/vs/editor/contrib/wordPartOperations/wordPartOperations.ts @@ -5,11 +5,10 @@ import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { registerEditorCommand } from 'vs/editor/browser/editorExtensions'; -import { WordNavigationType, WordPartOperations } from 'vs/editor/common/controller/cursorWordOperations'; +import { DeleteWordContext, WordNavigationType, WordPartOperations } from 'vs/editor/common/controller/cursorWordOperations'; import { WordCharacterClassifier } from 'vs/editor/common/controller/wordCharacterClassifier'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; -import { Selection } from 'vs/editor/common/core/selection'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { ITextModel } from 'vs/editor/common/model'; import { DeleteWordCommand, MoveWordCommand } from 'vs/editor/contrib/wordOperations/wordOperations'; @@ -32,8 +31,8 @@ export class DeleteWordPartLeft extends DeleteWordCommand { }); } - protected _delete(wordSeparators: WordCharacterClassifier, model: ITextModel, selection: Selection, whitespaceHeuristics: boolean, wordNavigationType: WordNavigationType): Range { - let r = WordPartOperations.deleteWordPartLeft(wordSeparators, model, selection, whitespaceHeuristics); + protected _delete(ctx: DeleteWordContext, wordNavigationType: WordNavigationType): Range { + let r = WordPartOperations.deleteWordPartLeft(ctx); if (r) { return r; } @@ -57,13 +56,13 @@ export class DeleteWordPartRight extends DeleteWordCommand { }); } - protected _delete(wordSeparators: WordCharacterClassifier, model: ITextModel, selection: Selection, whitespaceHeuristics: boolean, wordNavigationType: WordNavigationType): Range { - let r = WordPartOperations.deleteWordPartRight(wordSeparators, model, selection, whitespaceHeuristics); + protected _delete(ctx: DeleteWordContext, wordNavigationType: WordNavigationType): Range { + let r = WordPartOperations.deleteWordPartRight(ctx); if (r) { return r; } - const lineCount = model.getLineCount(); - const maxColumn = model.getLineMaxColumn(lineCount); + const lineCount = ctx.model.getLineCount(); + const maxColumn = ctx.model.getLineMaxColumn(lineCount); return new Range(lineCount, maxColumn, lineCount, maxColumn); } }