Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/editor/common/cursor/cursorAtomicMoveOperations.ts
3294 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { CharCode } from '../../../base/common/charCode.js';
7
import { CursorColumns } from '../core/cursorColumns.js';
8
9
export const enum Direction {
10
Left,
11
Right,
12
Nearest,
13
}
14
15
export class AtomicTabMoveOperations {
16
/**
17
* Get the visible column at the position. If we get to a non-whitespace character first
18
* or past the end of string then return -1.
19
*
20
* **Note** `position` and the return value are 0-based.
21
*/
22
public static whitespaceVisibleColumn(lineContent: string, position: number, tabSize: number): [number, number, number] {
23
const lineLength = lineContent.length;
24
let visibleColumn = 0;
25
let prevTabStopPosition = -1;
26
let prevTabStopVisibleColumn = -1;
27
for (let i = 0; i < lineLength; i++) {
28
if (i === position) {
29
return [prevTabStopPosition, prevTabStopVisibleColumn, visibleColumn];
30
}
31
if (visibleColumn % tabSize === 0) {
32
prevTabStopPosition = i;
33
prevTabStopVisibleColumn = visibleColumn;
34
}
35
const chCode = lineContent.charCodeAt(i);
36
switch (chCode) {
37
case CharCode.Space:
38
visibleColumn += 1;
39
break;
40
case CharCode.Tab:
41
// Skip to the next multiple of tabSize.
42
visibleColumn = CursorColumns.nextRenderTabStop(visibleColumn, tabSize);
43
break;
44
default:
45
return [-1, -1, -1];
46
}
47
}
48
if (position === lineLength) {
49
return [prevTabStopPosition, prevTabStopVisibleColumn, visibleColumn];
50
}
51
return [-1, -1, -1];
52
}
53
54
/**
55
* Return the position that should result from a move left, right or to the
56
* nearest tab, if atomic tabs are enabled. Left and right are used for the
57
* arrow key movements, nearest is used for mouse selection. It returns
58
* -1 if atomic tabs are not relevant and you should fall back to normal
59
* behaviour.
60
*
61
* **Note**: `position` and the return value are 0-based.
62
*/
63
public static atomicPosition(lineContent: string, position: number, tabSize: number, direction: Direction): number {
64
const lineLength = lineContent.length;
65
66
// Get the 0-based visible column corresponding to the position, or return
67
// -1 if it is not in the initial whitespace.
68
const [prevTabStopPosition, prevTabStopVisibleColumn, visibleColumn] = AtomicTabMoveOperations.whitespaceVisibleColumn(lineContent, position, tabSize);
69
70
if (visibleColumn === -1) {
71
return -1;
72
}
73
74
// Is the output left or right of the current position. The case for nearest
75
// where it is the same as the current position is handled in the switch.
76
let left: boolean;
77
switch (direction) {
78
case Direction.Left:
79
left = true;
80
break;
81
case Direction.Right:
82
left = false;
83
break;
84
case Direction.Nearest:
85
// The code below assumes the output position is either left or right
86
// of the input position. If it is the same, return immediately.
87
if (visibleColumn % tabSize === 0) {
88
return position;
89
}
90
// Go to the nearest indentation.
91
left = visibleColumn % tabSize <= (tabSize / 2);
92
break;
93
}
94
95
// If going left, we can just use the info about the last tab stop position and
96
// last tab stop visible column that we computed in the first walk over the whitespace.
97
if (left) {
98
if (prevTabStopPosition === -1) {
99
return -1;
100
}
101
// If the direction is left, we need to keep scanning right to ensure
102
// that targetVisibleColumn + tabSize is before non-whitespace.
103
// This is so that when we press left at the end of a partial
104
// indentation it only goes one character. For example ' foo' with
105
// tabSize 4, should jump from position 6 to position 5, not 4.
106
let currentVisibleColumn = prevTabStopVisibleColumn;
107
for (let i = prevTabStopPosition; i < lineLength; ++i) {
108
if (currentVisibleColumn === prevTabStopVisibleColumn + tabSize) {
109
// It is a full indentation.
110
return prevTabStopPosition;
111
}
112
113
const chCode = lineContent.charCodeAt(i);
114
switch (chCode) {
115
case CharCode.Space:
116
currentVisibleColumn += 1;
117
break;
118
case CharCode.Tab:
119
currentVisibleColumn = CursorColumns.nextRenderTabStop(currentVisibleColumn, tabSize);
120
break;
121
default:
122
return -1;
123
}
124
}
125
if (currentVisibleColumn === prevTabStopVisibleColumn + tabSize) {
126
return prevTabStopPosition;
127
}
128
// It must have been a partial indentation.
129
return -1;
130
}
131
132
// We are going right.
133
const targetVisibleColumn = CursorColumns.nextRenderTabStop(visibleColumn, tabSize);
134
135
// We can just continue from where whitespaceVisibleColumn got to.
136
let currentVisibleColumn = visibleColumn;
137
for (let i = position; i < lineLength; i++) {
138
if (currentVisibleColumn === targetVisibleColumn) {
139
return i;
140
}
141
142
const chCode = lineContent.charCodeAt(i);
143
switch (chCode) {
144
case CharCode.Space:
145
currentVisibleColumn += 1;
146
break;
147
case CharCode.Tab:
148
currentVisibleColumn = CursorColumns.nextRenderTabStop(currentVisibleColumn, tabSize);
149
break;
150
default:
151
return -1;
152
}
153
}
154
// This condition handles when the target column is at the end of the line.
155
if (currentVisibleColumn === targetVisibleColumn) {
156
return lineLength;
157
}
158
return -1;
159
}
160
}
161
162