mirror of
https://github.com/microsoft/vscode.git
synced 2026-09-14 07:12:00 +01:00
161 lines
5.3 KiB
TypeScript
161 lines
5.3 KiB
TypeScript
/*---------------------------------------------------------------------------------------------
|
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
|
*--------------------------------------------------------------------------------------------*/
|
|
|
|
import { CharCode } from 'vs/base/common/charCode';
|
|
import { CursorColumns } from 'vs/editor/common/controller/cursorCommon';
|
|
|
|
export const enum Direction {
|
|
Left,
|
|
Right,
|
|
Nearest,
|
|
}
|
|
|
|
export class AtomicTabMoveOperations {
|
|
/**
|
|
* Get the visible column at the position. If we get to a non-whitespace character first
|
|
* or past the end of string then return -1.
|
|
*
|
|
* **Note** `position` and the return value are 0-based.
|
|
*/
|
|
public static whitespaceVisibleColumn(lineContent: string, position: number, tabSize: number): [number, number, number] {
|
|
const lineLength = lineContent.length;
|
|
let visibleColumn = 0;
|
|
let prevTabStopPosition = -1;
|
|
let prevTabStopVisibleColumn = -1;
|
|
for (let i = 0; i < lineLength; i++) {
|
|
if (i === position) {
|
|
return [prevTabStopPosition, prevTabStopVisibleColumn, visibleColumn];
|
|
}
|
|
if (visibleColumn % tabSize === 0) {
|
|
prevTabStopPosition = i;
|
|
prevTabStopVisibleColumn = visibleColumn;
|
|
}
|
|
const chCode = lineContent.charCodeAt(i);
|
|
switch (chCode) {
|
|
case CharCode.Space:
|
|
visibleColumn += 1;
|
|
break;
|
|
case CharCode.Tab:
|
|
// Skip to the next multiple of tabSize.
|
|
visibleColumn = CursorColumns.nextRenderTabStop(visibleColumn, tabSize);
|
|
break;
|
|
default:
|
|
return [-1, -1, -1];
|
|
}
|
|
}
|
|
if (position === lineLength) {
|
|
return [prevTabStopPosition, prevTabStopVisibleColumn, visibleColumn];
|
|
}
|
|
return [-1, -1, -1];
|
|
}
|
|
|
|
/**
|
|
* Return the position that should result from a move left, right or to the
|
|
* nearest tab, if atomic tabs are enabled. Left and right are used for the
|
|
* arrow key movements, nearest is used for mouse selection. It returns
|
|
* -1 if atomic tabs are not relevant and you should fall back to normal
|
|
* behaviour.
|
|
*
|
|
* **Note**: `position` and the return value are 0-based.
|
|
*/
|
|
public static atomicPosition(lineContent: string, position: number, tabSize: number, direction: Direction): number {
|
|
const lineLength = lineContent.length;
|
|
|
|
// Get the 0-based visible column corresponding to the position, or return
|
|
// -1 if it is not in the initial whitespace.
|
|
const [prevTabStopPosition, prevTabStopVisibleColumn, visibleColumn] = AtomicTabMoveOperations.whitespaceVisibleColumn(lineContent, position, tabSize);
|
|
|
|
if (visibleColumn === -1) {
|
|
return -1;
|
|
}
|
|
|
|
// Is the output left or right of the current position. The case for nearest
|
|
// where it is the same as the current position is handled in the switch.
|
|
let left: boolean;
|
|
switch (direction) {
|
|
case Direction.Left:
|
|
left = true;
|
|
break;
|
|
case Direction.Right:
|
|
left = false;
|
|
break;
|
|
case Direction.Nearest:
|
|
// The code below assumes the output position is either left or right
|
|
// of the input position. If it is the same, return immediately.
|
|
if (visibleColumn % tabSize === 0) {
|
|
return position;
|
|
}
|
|
// Go to the nearest indentation.
|
|
left = visibleColumn % tabSize <= (tabSize / 2);
|
|
break;
|
|
}
|
|
|
|
// If going left, we can just use the info about the last tab stop position and
|
|
// last tab stop visible column that we computed in the first walk over the whitespace.
|
|
if (left) {
|
|
if (prevTabStopPosition === -1) {
|
|
return -1;
|
|
}
|
|
// If the direction is left, we need to keep scanning right to ensure
|
|
// that targetVisibleColumn + tabSize is before non-whitespace.
|
|
// This is so that when we press left at the end of a partial
|
|
// indentation it only goes one character. For example ' foo' with
|
|
// tabSize 4, should jump from position 6 to position 5, not 4.
|
|
let currentVisibleColumn = prevTabStopVisibleColumn;
|
|
for (let i = prevTabStopPosition; i < lineLength; ++i) {
|
|
if (currentVisibleColumn === prevTabStopVisibleColumn + tabSize) {
|
|
// It is a full indentation.
|
|
return prevTabStopPosition;
|
|
}
|
|
|
|
const chCode = lineContent.charCodeAt(i);
|
|
switch (chCode) {
|
|
case CharCode.Space:
|
|
currentVisibleColumn += 1;
|
|
break;
|
|
case CharCode.Tab:
|
|
currentVisibleColumn = CursorColumns.nextRenderTabStop(currentVisibleColumn, tabSize);
|
|
break;
|
|
default:
|
|
return -1;
|
|
}
|
|
}
|
|
if (currentVisibleColumn === prevTabStopVisibleColumn + tabSize) {
|
|
return prevTabStopPosition;
|
|
}
|
|
// It must have been a partial indentation.
|
|
return -1;
|
|
}
|
|
|
|
// We are going right.
|
|
const targetVisibleColumn = CursorColumns.nextRenderTabStop(visibleColumn, tabSize);
|
|
|
|
// We can just continue from where whitespaceVisibleColumn got to.
|
|
let currentVisibleColumn = visibleColumn;
|
|
for (let i = position; i < lineLength; i++) {
|
|
if (currentVisibleColumn === targetVisibleColumn) {
|
|
return i;
|
|
}
|
|
|
|
const chCode = lineContent.charCodeAt(i);
|
|
switch (chCode) {
|
|
case CharCode.Space:
|
|
currentVisibleColumn += 1;
|
|
break;
|
|
case CharCode.Tab:
|
|
currentVisibleColumn = CursorColumns.nextRenderTabStop(currentVisibleColumn, tabSize);
|
|
break;
|
|
default:
|
|
return -1;
|
|
}
|
|
}
|
|
// This condition handles when the target column is at the end of the line.
|
|
if (currentVisibleColumn === targetVisibleColumn) {
|
|
return lineLength;
|
|
}
|
|
return -1;
|
|
}
|
|
}
|