| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142 |
- /*---------------------------------------------------------------------------------------------
- * Copyright (c) Microsoft Corporation. All rights reserved.
- * Licensed under the MIT License. See License.txt in the project root for license information.
- *--------------------------------------------------------------------------------------------*/
- import { CursorColumns } from '../core/cursorColumns.js';
- 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.
- */
- static whitespaceVisibleColumn(lineContent, position, tabSize) {
- 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 32 /* CharCode.Space */:
- visibleColumn += 1;
- break;
- case 9 /* 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.
- */
- static atomicPosition(lineContent, position, tabSize, direction) {
- 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;
- switch (direction) {
- case 0 /* Direction.Left */:
- left = true;
- break;
- case 1 /* Direction.Right */:
- left = false;
- break;
- case 2 /* 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 32 /* CharCode.Space */:
- currentVisibleColumn += 1;
- break;
- case 9 /* 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 32 /* CharCode.Space */:
- currentVisibleColumn += 1;
- break;
- case 9 /* 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;
- }
- }
|