diff --git a/src/vs/editor/common/controller/cursorMoveHelper.ts b/src/vs/editor/common/controller/cursorMoveHelper.ts index 4560f89e94e0b759d246bcc1137baf099e458d1f..e751fcf63340d05da6478290715899ae16cc4d48 100644 --- a/src/vs/editor/common/controller/cursorMoveHelper.ts +++ b/src/vs/editor/common/controller/cursorMoveHelper.ts @@ -6,95 +6,105 @@ import { IPosition } from 'vs/editor/common/editorCommon'; import { Selection } from 'vs/editor/common/core/selection'; +import { CharCode } from 'vs/base/common/charCode'; import * as strings from 'vs/base/common/strings'; -export interface IMoveResult { - lineNumber: number; - column: number; - leftoverVisibleColumns: number; -} +export class CursorMoveConfiguration { + _cursorMoveConfigurationBrand: void; -export interface IViewColumnSelectResult { - viewSelections: Selection[]; - reversed: boolean; -} -export interface IColumnSelectResult extends IViewColumnSelectResult { - selections: Selection[]; - toLineNumber: number; - toVisualColumn: number; + public readonly tabSize: number; + + constructor( + tabSize: number + ) { + this.tabSize = tabSize; + } } export interface ICursorMoveHelperModel { getLineCount(): number; - getLineFirstNonWhitespaceColumn(lineNumber: number): number; + getLineContent(lineNumber: number): string; getLineMinColumn(lineNumber: number): number; getLineMaxColumn(lineNumber: number): number; + getLineFirstNonWhitespaceColumn(lineNumber: number): number; getLineLastNonWhitespaceColumn(lineNumber: number): number; - getLineContent(lineNumber: number): string; } -/** - * Internal indentation options (computed) for the editor. - */ -export interface IInternalIndentationOptions { - /** - * Tab size in spaces. This is used for rendering and for editing. - */ - tabSize: number; -} +export class CursorMoveResult { + _cursorMoveResultBrand: void; -export interface IConfiguration { - getIndentationOptions(): IInternalIndentationOptions; -} + public readonly lineNumber: number; + public readonly column: number; + public readonly leftoverVisibleColumns: number; -function isHighSurrogate(model: ICursorMoveHelperModel, lineNumber: number, column: number) { - return strings.isHighSurrogate(model.getLineContent(lineNumber).charCodeAt(column - 1)); + constructor(lineNumber: number, column: number, leftoverVisibleColumns: number) { + this.lineNumber = lineNumber; + this.column = column; + this.leftoverVisibleColumns = leftoverVisibleColumns; + } } -function isLowSurrogate(model: ICursorMoveHelperModel, lineNumber: number, column: number) { - return strings.isLowSurrogate(model.getLineContent(lineNumber).charCodeAt(column - 1)); -} +/** + * Common operations that work and make sense both on the model and on the view model. + */ +export class CursorMove { -export class CursorMoveHelper { + private static _isLowSurrogate(model: ICursorMoveHelperModel, lineNumber: number, charOffset: number): boolean { + let lineContent = model.getLineContent(lineNumber); + if (charOffset < 0 || charOffset >= lineContent.length) { + return false; + } + return strings.isLowSurrogate(lineContent.charCodeAt(charOffset)); + } - private readonly _tabSize: number; + private static _isHighSurrogate(model: ICursorMoveHelperModel, lineNumber: number, charOffset: number): boolean { + let lineContent = model.getLineContent(lineNumber); + if (charOffset < 0 || charOffset >= lineContent.length) { + return false; + } + return strings.isHighSurrogate(lineContent.charCodeAt(charOffset)); + } - constructor(tabSize: number) { - this._tabSize = tabSize; + private static _isInsideSurrogatePair(model: ICursorMoveHelperModel, lineNumber: number, column: number): boolean { + return this._isHighSurrogate(model, lineNumber, column - 2); } - public getLeftOfPosition(model: ICursorMoveHelperModel, lineNumber: number, column: number): IPosition { + public static left(config: CursorMoveConfiguration, model: ICursorMoveHelperModel, lineNumber: number, column: number): CursorMoveResult { if (column > model.getLineMinColumn(lineNumber)) { - column = column - (isLowSurrogate(model, lineNumber, column - 1) ? 2 : 1); + if (this._isLowSurrogate(model, lineNumber, column - 2)) { + // character before column is a low surrogate + column = column - 2; + } else { + column = column - 1; + } } else if (lineNumber > 1) { lineNumber = lineNumber - 1; column = model.getLineMaxColumn(lineNumber); } - return { - lineNumber: lineNumber, - column: column - }; + return new CursorMoveResult(lineNumber, column, 0); } - public getRightOfPosition(model: ICursorMoveHelperModel, lineNumber: number, column: number): IPosition { + public static right(config: CursorMoveConfiguration, model: ICursorMoveHelperModel, lineNumber: number, column: number): CursorMoveResult { if (column < model.getLineMaxColumn(lineNumber)) { - column = column + (isHighSurrogate(model, lineNumber, column) ? 2 : 1); + if (this._isHighSurrogate(model, lineNumber, column - 1)) { + // character after column is a high surrogate + column = column + 2; + } else { + column = column + 1; + } } else if (lineNumber < model.getLineCount()) { lineNumber = lineNumber + 1; column = model.getLineMinColumn(lineNumber); } - return { - lineNumber: lineNumber, - column: column - }; + return new CursorMoveResult(lineNumber, column, 0); } - public getPositionUp(model: ICursorMoveHelperModel, lineNumber: number, column: number, leftoverVisibleColumns: number, count: number, allowMoveOnFirstLine: boolean): IMoveResult { - var currentVisibleColumn = this.visibleColumnFromColumn(model, lineNumber, column) + leftoverVisibleColumns; + public static up(config: CursorMoveConfiguration, model: ICursorMoveHelperModel, lineNumber: number, column: number, leftoverVisibleColumns: number, count: number, allowMoveOnFirstLine: boolean): CursorMoveResult { + const currentVisibleColumn = this.visibleColumnFromColumn(model.getLineContent(lineNumber), column, config.tabSize) + leftoverVisibleColumns; lineNumber = lineNumber - count; if (lineNumber < 1) { @@ -103,22 +113,24 @@ export class CursorMoveHelper { column = model.getLineMinColumn(lineNumber); } else { column = Math.min(model.getLineMaxColumn(lineNumber), column); + if (this._isInsideSurrogatePair(model, lineNumber, column)) { + column = column - 1; + } } } else { - column = this.columnFromVisibleColumn(model, lineNumber, currentVisibleColumn); + column = this.columnFromVisibleColumn(config, model, lineNumber, currentVisibleColumn); + if (this._isInsideSurrogatePair(model, lineNumber, column)) { + column = column - 1; + } } - leftoverVisibleColumns = currentVisibleColumn - this.visibleColumnFromColumn(model, lineNumber, column); + leftoverVisibleColumns = currentVisibleColumn - this.visibleColumnFromColumn(model.getLineContent(lineNumber), column, config.tabSize); - return { - lineNumber: lineNumber, - column: column, - leftoverVisibleColumns: leftoverVisibleColumns - }; + return new CursorMoveResult(lineNumber, column, leftoverVisibleColumns); } - public getPositionDown(model: ICursorMoveHelperModel, lineNumber: number, column: number, leftoverVisibleColumns: number, count: number, allowMoveOnLastLine: boolean): IMoveResult { - var currentVisibleColumn = this.visibleColumnFromColumn(model, lineNumber, column) + leftoverVisibleColumns; + public static down(config: CursorMoveConfiguration, model: ICursorMoveHelperModel, lineNumber: number, column: number, leftoverVisibleColumns: number, count: number, allowMoveOnLastLine: boolean): CursorMoveResult { + const currentVisibleColumn = this.visibleColumnFromColumn(model.getLineContent(lineNumber), column, config.tabSize) + leftoverVisibleColumns; lineNumber = lineNumber + count; var lineCount = model.getLineCount(); @@ -128,17 +140,135 @@ export class CursorMoveHelper { column = model.getLineMaxColumn(lineNumber); } else { column = Math.min(model.getLineMaxColumn(lineNumber), column); + if (this._isInsideSurrogatePair(model, lineNumber, column)) { + column = column - 1; + } } } else { - column = this.columnFromVisibleColumn(model, lineNumber, currentVisibleColumn); + column = this.columnFromVisibleColumn(config, model, lineNumber, currentVisibleColumn); + if (this._isInsideSurrogatePair(model, lineNumber, column)) { + column = column - 1; + } } - leftoverVisibleColumns = currentVisibleColumn - this.visibleColumnFromColumn(model, lineNumber, column); - return { - lineNumber: lineNumber, - column: column, - leftoverVisibleColumns: leftoverVisibleColumns - }; + leftoverVisibleColumns = currentVisibleColumn - this.visibleColumnFromColumn(model.getLineContent(lineNumber), column, config.tabSize); + + return new CursorMoveResult(lineNumber, column, leftoverVisibleColumns); + } + + public static visibleColumnFromColumn(lineContent: string, column: number, tabSize: number): number { + let endOffset = lineContent.length; + if (endOffset > column - 1) { + endOffset = column - 1; + } + + let result = 0; + for (let i = 0; i < endOffset; i++) { + let charCode = lineContent.charCodeAt(i); + if (charCode === CharCode.Tab) { + result = this.nextTabStop(result, tabSize); + } else { + result = result + 1; + } + } + return result; + } + + private static _columnFromVisibleColumn(lineContent: string, visibleColumn: number, tabSize: number): number { + if (visibleColumn <= 0) { + return 1; + } + + const lineLength = lineContent.length; + + let beforeVisibleColumn = 0; + for (let i = 0; i < lineLength; i++) { + let charCode = lineContent.charCodeAt(i); + + let afterVisibleColumn: number; + if (charCode === CharCode.Tab) { + afterVisibleColumn = this.nextTabStop(beforeVisibleColumn, tabSize); + } else { + afterVisibleColumn = beforeVisibleColumn + 1; + } + + if (afterVisibleColumn >= visibleColumn) { + let prevDelta = visibleColumn - beforeVisibleColumn; + let afterDelta = afterVisibleColumn - visibleColumn; + if (afterDelta < prevDelta) { + return i + 2; + } else { + return i + 1; + } + } + + beforeVisibleColumn = afterVisibleColumn; + } + + // walked the entire string + return lineLength + 1; + } + + public static columnFromVisibleColumn(config: CursorMoveConfiguration, model: ICursorMoveHelperModel, lineNumber: number, visibleColumn: number): number { + let result = this._columnFromVisibleColumn(model.getLineContent(lineNumber), visibleColumn, config.tabSize); + + let minColumn = model.getLineMinColumn(lineNumber); + if (result < minColumn) { + return minColumn; + } + + let maxColumn = model.getLineMaxColumn(lineNumber); + if (result > maxColumn) { + return maxColumn; + } + + return result; + } + + /** + * ATTENTION: This works with 0-based columns (as oposed to the regular 1-based columns) + */ + public static nextTabStop(visibleColumn: number, tabSize: number): number { + return visibleColumn + tabSize - visibleColumn % tabSize; + } +} + + +export interface IViewColumnSelectResult { + viewSelections: Selection[]; + reversed: boolean; +} +export interface IColumnSelectResult extends IViewColumnSelectResult { + selections: Selection[]; + toLineNumber: number; + toVisualColumn: number; +} + + +export class CursorMoveHelper { + + private readonly _tabSize: number; + private readonly _config: CursorMoveConfiguration; + + constructor(tabSize: number) { + this._tabSize = tabSize; + this._config = new CursorMoveConfiguration(this._tabSize); + } + + public getLeftOfPosition(model: ICursorMoveHelperModel, lineNumber: number, column: number): IPosition { + return CursorMove.left(this._config, model, lineNumber, column); + } + + public getRightOfPosition(model: ICursorMoveHelperModel, lineNumber: number, column: number): IPosition { + return CursorMove.right(this._config, model, lineNumber, column); + } + + public getPositionUp(model: ICursorMoveHelperModel, lineNumber: number, column: number, leftoverVisibleColumns: number, count: number, allowMoveOnFirstLine: boolean): CursorMoveResult { + return CursorMove.up(this._config, model, lineNumber, column, leftoverVisibleColumns, count, allowMoveOnFirstLine); + } + + public getPositionDown(model: ICursorMoveHelperModel, lineNumber: number, column: number, leftoverVisibleColumns: number, count: number, allowMoveOnLastLine: boolean): CursorMoveResult { + return CursorMove.down(this._config, model, lineNumber, column, leftoverVisibleColumns, count, allowMoveOnLastLine); } public columnSelect(model: ICursorMoveHelperModel, fromLineNumber: number, fromVisibleColumn: number, toLineNumber: number, toVisibleColumn: number): IViewColumnSelectResult { @@ -223,47 +353,18 @@ export class CursorMoveHelper { } public static visibleColumnFromColumn2(line: string, column: number, tabSize: number): number { - var result = 0; - for (var i = 0; i < column - 1; i++) { - result = (line.charAt(i) === '\t') ? CursorMoveHelper.nextTabColumn(result, tabSize) : result + 1; - } - return result; + return CursorMove.visibleColumnFromColumn(line, column, tabSize); } public columnFromVisibleColumn(model: ICursorMoveHelperModel, lineNumber: number, visibleColumn: number): number { - var line = model.getLineContent(lineNumber); - - var lastVisibleColumn = -1; - var thisVisibleColumn = 0; - - for (var i = 0; i < line.length && thisVisibleColumn <= visibleColumn; i++) { - lastVisibleColumn = thisVisibleColumn; - thisVisibleColumn = (line.charAt(i) === '\t') ? CursorMoveHelper.nextTabColumn(thisVisibleColumn, this._tabSize) : thisVisibleColumn + 1; - } - - // Choose the closest - thisVisibleColumn = Math.abs(visibleColumn - thisVisibleColumn); - lastVisibleColumn = Math.abs(visibleColumn - lastVisibleColumn); - - var result: number; - if (thisVisibleColumn < lastVisibleColumn) { - result = i + 1; - } else { - result = i; - } - - var minColumn = model.getLineMinColumn(lineNumber); - if (result < minColumn) { - result = minColumn; - } - return result; + return CursorMove.columnFromVisibleColumn(this._config, model, lineNumber, visibleColumn); } /** * ATTENTION: This works with 0-based columns (as oposed to the regular 1-based columns) */ public static nextTabColumn(column: number, tabSize: number): number { - return column + tabSize - column % tabSize; + return CursorMove.nextTabStop(column, tabSize); } /** diff --git a/src/vs/editor/test/common/controller/cursorMoveHelper.test.ts b/src/vs/editor/test/common/controller/cursorMoveHelper.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..b5ac693302362721f6dbc6efa3c7a64d880fcfe9 --- /dev/null +++ b/src/vs/editor/test/common/controller/cursorMoveHelper.test.ts @@ -0,0 +1,224 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + +import * as assert from 'assert'; +import * as strings from 'vs/base/common/strings'; +import { CursorMoveHelper, ICursorMoveHelperModel } from 'vs/editor/common/controller/cursorMoveHelper'; + +suite('CursorMove', () => { + + test('nextTabStop', () => { + assert.equal(CursorMoveHelper.nextTabColumn(0, 4), 4); + assert.equal(CursorMoveHelper.nextTabColumn(1, 4), 4); + assert.equal(CursorMoveHelper.nextTabColumn(2, 4), 4); + assert.equal(CursorMoveHelper.nextTabColumn(3, 4), 4); + assert.equal(CursorMoveHelper.nextTabColumn(4, 4), 8); + assert.equal(CursorMoveHelper.nextTabColumn(5, 4), 8); + assert.equal(CursorMoveHelper.nextTabColumn(6, 4), 8); + assert.equal(CursorMoveHelper.nextTabColumn(7, 4), 8); + assert.equal(CursorMoveHelper.nextTabColumn(8, 4), 12); + + assert.equal(CursorMoveHelper.nextTabColumn(0, 2), 2); + assert.equal(CursorMoveHelper.nextTabColumn(1, 2), 2); + assert.equal(CursorMoveHelper.nextTabColumn(2, 2), 4); + assert.equal(CursorMoveHelper.nextTabColumn(3, 2), 4); + assert.equal(CursorMoveHelper.nextTabColumn(4, 2), 6); + assert.equal(CursorMoveHelper.nextTabColumn(5, 2), 6); + assert.equal(CursorMoveHelper.nextTabColumn(6, 2), 8); + assert.equal(CursorMoveHelper.nextTabColumn(7, 2), 8); + assert.equal(CursorMoveHelper.nextTabColumn(8, 2), 10); + + assert.equal(CursorMoveHelper.nextTabColumn(0, 1), 1); + assert.equal(CursorMoveHelper.nextTabColumn(1, 1), 2); + assert.equal(CursorMoveHelper.nextTabColumn(2, 1), 3); + assert.equal(CursorMoveHelper.nextTabColumn(3, 1), 4); + assert.equal(CursorMoveHelper.nextTabColumn(4, 1), 5); + assert.equal(CursorMoveHelper.nextTabColumn(5, 1), 6); + assert.equal(CursorMoveHelper.nextTabColumn(6, 1), 7); + assert.equal(CursorMoveHelper.nextTabColumn(7, 1), 8); + assert.equal(CursorMoveHelper.nextTabColumn(8, 1), 9); + }); + + class OneLineModel implements ICursorMoveHelperModel { + private _line: string; + + constructor(line: string) { + this._line = line; + } + + getLineCount(): number { + return 1; + } + + getLineContent(lineNumber: number): string { + return this._line; + } + + getLineMinColumn(lineNumber: number): number { + return 1; + } + + getLineMaxColumn(lineNumber: number): number { + return this._line.length + 1; + } + + getLineFirstNonWhitespaceColumn(lineNumber: number): number { + let result = strings.firstNonWhitespaceIndex(this._line); + if (result === -1) { + return 0; + } + return result + 1; + } + + getLineLastNonWhitespaceColumn(lineNumber: number): number { + let result = strings.lastNonWhitespaceIndex(this._line); + if (result === -1) { + return 0; + } + return result + 2; + } + + } + + test('visibleColumnFromColumn', () => { + + function testVisibleColumnFromColumn(text:string, tabSize:number, column:number, expected:number): void { + let helper = new CursorMoveHelper(tabSize); + let model = new OneLineModel(text); + assert.equal(helper.visibleColumnFromColumn(model, 1, column), expected); + } + + testVisibleColumnFromColumn('\t\tvar x = 3;', 4, 1, 0); + testVisibleColumnFromColumn('\t\tvar x = 3;', 4, 2, 4); + testVisibleColumnFromColumn('\t\tvar x = 3;', 4, 3, 8); + testVisibleColumnFromColumn('\t\tvar x = 3;', 4, 4, 9); + testVisibleColumnFromColumn('\t\tvar x = 3;', 4, 5, 10); + testVisibleColumnFromColumn('\t\tvar x = 3;', 4, 6, 11); + testVisibleColumnFromColumn('\t\tvar x = 3;', 4, 7, 12); + testVisibleColumnFromColumn('\t\tvar x = 3;', 4, 8, 13); + testVisibleColumnFromColumn('\t\tvar x = 3;', 4, 9, 14); + testVisibleColumnFromColumn('\t\tvar x = 3;', 4, 10, 15); + testVisibleColumnFromColumn('\t\tvar x = 3;', 4, 11, 16); + testVisibleColumnFromColumn('\t\tvar x = 3;', 4, 12, 17); + testVisibleColumnFromColumn('\t\tvar x = 3;', 4, 13, 18); + + testVisibleColumnFromColumn('\t \tvar x = 3;', 4, 1, 0); + testVisibleColumnFromColumn('\t \tvar x = 3;', 4, 2, 4); + testVisibleColumnFromColumn('\t \tvar x = 3;', 4, 3, 5); + testVisibleColumnFromColumn('\t \tvar x = 3;', 4, 4, 8); + testVisibleColumnFromColumn('\t \tvar x = 3;', 4, 5, 9); + testVisibleColumnFromColumn('\t \tvar x = 3;', 4, 6, 10); + testVisibleColumnFromColumn('\t \tvar x = 3;', 4, 7, 11); + testVisibleColumnFromColumn('\t \tvar x = 3;', 4, 8, 12); + testVisibleColumnFromColumn('\t \tvar x = 3;', 4, 9, 13); + testVisibleColumnFromColumn('\t \tvar x = 3;', 4, 10, 14); + testVisibleColumnFromColumn('\t \tvar x = 3;', 4, 11, 15); + testVisibleColumnFromColumn('\t \tvar x = 3;', 4, 12, 16); + testVisibleColumnFromColumn('\t \tvar x = 3;', 4, 13, 17); + testVisibleColumnFromColumn('\t \tvar x = 3;', 4, 14, 18); + + testVisibleColumnFromColumn('\t \tx\t', 4, -1, 0); + testVisibleColumnFromColumn('\t \tx\t', 4, 0, 0); + testVisibleColumnFromColumn('\t \tx\t', 4, 1, 0); + testVisibleColumnFromColumn('\t \tx\t', 4, 2, 4); + testVisibleColumnFromColumn('\t \tx\t', 4, 3, 5); + testVisibleColumnFromColumn('\t \tx\t', 4, 4, 6); + testVisibleColumnFromColumn('\t \tx\t', 4, 5, 8); + testVisibleColumnFromColumn('\t \tx\t', 4, 6, 9); + testVisibleColumnFromColumn('\t \tx\t', 4, 7, 12); + testVisibleColumnFromColumn('\t \tx\t', 4, 8, 12); + testVisibleColumnFromColumn('\t \tx\t', 4, 9, 12); + + testVisibleColumnFromColumn('baz', 4, 1, 0); + testVisibleColumnFromColumn('baz', 4, 2, 1); + testVisibleColumnFromColumn('baz', 4, 3, 2); + testVisibleColumnFromColumn('baz', 4, 4, 3); + + testVisibleColumnFromColumn('📚az', 4, 1, 0); + testVisibleColumnFromColumn('📚az', 4, 2, 1); + testVisibleColumnFromColumn('📚az', 4, 3, 2); + testVisibleColumnFromColumn('📚az', 4, 4, 3); + testVisibleColumnFromColumn('📚az', 4, 5, 4); + }); + + test('columnFromVisibleColumn', () => { + + function testColumnFromVisibleColumn(text:string, tabSize:number, visibleColumn:number, expected:number): void { + let helper = new CursorMoveHelper(tabSize); + let model = new OneLineModel(text); + assert.equal(helper.columnFromVisibleColumn(model, 1, visibleColumn), expected); + } + + // testColumnFromVisibleColumn('\t\tvar x = 3;', 4, 0, 1); + testColumnFromVisibleColumn('\t\tvar x = 3;', 4, 1, 1); + testColumnFromVisibleColumn('\t\tvar x = 3;', 4, 2, 1); + testColumnFromVisibleColumn('\t\tvar x = 3;', 4, 3, 2); + testColumnFromVisibleColumn('\t\tvar x = 3;', 4, 4, 2); + testColumnFromVisibleColumn('\t\tvar x = 3;', 4, 5, 2); + testColumnFromVisibleColumn('\t\tvar x = 3;', 4, 6, 2); + testColumnFromVisibleColumn('\t\tvar x = 3;', 4, 7, 3); + testColumnFromVisibleColumn('\t\tvar x = 3;', 4, 8, 3); + testColumnFromVisibleColumn('\t\tvar x = 3;', 4, 9, 4); + testColumnFromVisibleColumn('\t\tvar x = 3;', 4, 10, 5); + testColumnFromVisibleColumn('\t\tvar x = 3;', 4, 11, 6); + testColumnFromVisibleColumn('\t\tvar x = 3;', 4, 12, 7); + testColumnFromVisibleColumn('\t\tvar x = 3;', 4, 13, 8); + testColumnFromVisibleColumn('\t\tvar x = 3;', 4, 14, 9); + testColumnFromVisibleColumn('\t\tvar x = 3;', 4, 15, 10); + testColumnFromVisibleColumn('\t\tvar x = 3;', 4, 16, 11); + testColumnFromVisibleColumn('\t\tvar x = 3;', 4, 17, 12); + testColumnFromVisibleColumn('\t\tvar x = 3;', 4, 18, 13); + + testColumnFromVisibleColumn('\t \tvar x = 3;', 4, 0, 1); + testColumnFromVisibleColumn('\t \tvar x = 3;', 4, 1, 1); + testColumnFromVisibleColumn('\t \tvar x = 3;', 4, 2, 1); + testColumnFromVisibleColumn('\t \tvar x = 3;', 4, 3, 2); + testColumnFromVisibleColumn('\t \tvar x = 3;', 4, 4, 2); + testColumnFromVisibleColumn('\t \tvar x = 3;', 4, 5, 3); + testColumnFromVisibleColumn('\t \tvar x = 3;', 4, 6, 3); + testColumnFromVisibleColumn('\t \tvar x = 3;', 4, 7, 4); + testColumnFromVisibleColumn('\t \tvar x = 3;', 4, 8, 4); + testColumnFromVisibleColumn('\t \tvar x = 3;', 4, 9, 5); + testColumnFromVisibleColumn('\t \tvar x = 3;', 4, 10, 6); + testColumnFromVisibleColumn('\t \tvar x = 3;', 4, 11, 7); + testColumnFromVisibleColumn('\t \tvar x = 3;', 4, 12, 8); + testColumnFromVisibleColumn('\t \tvar x = 3;', 4, 13, 9); + testColumnFromVisibleColumn('\t \tvar x = 3;', 4, 14, 10); + testColumnFromVisibleColumn('\t \tvar x = 3;', 4, 15, 11); + testColumnFromVisibleColumn('\t \tvar x = 3;', 4, 16, 12); + testColumnFromVisibleColumn('\t \tvar x = 3;', 4, 17, 13); + testColumnFromVisibleColumn('\t \tvar x = 3;', 4, 18, 14); + + testColumnFromVisibleColumn('\t \tx\t', 4, -2, 1); + testColumnFromVisibleColumn('\t \tx\t', 4, -1, 1); + testColumnFromVisibleColumn('\t \tx\t', 4, 0, 1); + testColumnFromVisibleColumn('\t \tx\t', 4, 1, 1); + testColumnFromVisibleColumn('\t \tx\t', 4, 2, 1); + testColumnFromVisibleColumn('\t \tx\t', 4, 3, 2); + testColumnFromVisibleColumn('\t \tx\t', 4, 4, 2); + testColumnFromVisibleColumn('\t \tx\t', 4, 5, 3); + testColumnFromVisibleColumn('\t \tx\t', 4, 6, 4); + testColumnFromVisibleColumn('\t \tx\t', 4, 7, 4); + testColumnFromVisibleColumn('\t \tx\t', 4, 8, 5); + testColumnFromVisibleColumn('\t \tx\t', 4, 9, 6); + testColumnFromVisibleColumn('\t \tx\t', 4, 10, 6); + testColumnFromVisibleColumn('\t \tx\t', 4, 11, 7); + testColumnFromVisibleColumn('\t \tx\t', 4, 12, 7); + testColumnFromVisibleColumn('\t \tx\t', 4, 13, 7); + testColumnFromVisibleColumn('\t \tx\t', 4, 14, 7); + + testColumnFromVisibleColumn('baz', 4, 0, 1); + testColumnFromVisibleColumn('baz', 4, 1, 2); + testColumnFromVisibleColumn('baz', 4, 2, 3); + testColumnFromVisibleColumn('baz', 4, 3, 4); + + testColumnFromVisibleColumn('📚az', 4, 0, 1); + testColumnFromVisibleColumn('📚az', 4, 1, 2); + testColumnFromVisibleColumn('📚az', 4, 2, 3); + testColumnFromVisibleColumn('📚az', 4, 3, 4); + testColumnFromVisibleColumn('📚az', 4, 4, 5); + }); +}); \ No newline at end of file