Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/editor/common/cursor/cursorMoveOperations.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 * as strings from '../../../base/common/strings.js';
7
import { Constants } from '../../../base/common/uint.js';
8
import { CursorColumns } from '../core/cursorColumns.js';
9
import { Position } from '../core/position.js';
10
import { Range } from '../core/range.js';
11
import { AtomicTabMoveOperations, Direction } from './cursorAtomicMoveOperations.js';
12
import { CursorConfiguration, ICursorSimpleModel, SelectionStartKind, SingleCursorState } from '../cursorCommon.js';
13
import { PositionAffinity } from '../model.js';
14
15
export class CursorPosition {
16
_cursorPositionBrand: void = undefined;
17
18
public readonly lineNumber: number;
19
public readonly column: number;
20
public readonly leftoverVisibleColumns: number;
21
22
constructor(lineNumber: number, column: number, leftoverVisibleColumns: number) {
23
this.lineNumber = lineNumber;
24
this.column = column;
25
this.leftoverVisibleColumns = leftoverVisibleColumns;
26
}
27
}
28
29
export class MoveOperations {
30
public static leftPosition(model: ICursorSimpleModel, position: Position): Position {
31
if (position.column > model.getLineMinColumn(position.lineNumber)) {
32
return position.delta(undefined, -strings.prevCharLength(model.getLineContent(position.lineNumber), position.column - 1));
33
} else if (position.lineNumber > 1) {
34
const newLineNumber = position.lineNumber - 1;
35
return new Position(newLineNumber, model.getLineMaxColumn(newLineNumber));
36
} else {
37
return position;
38
}
39
}
40
41
private static leftPositionAtomicSoftTabs(model: ICursorSimpleModel, position: Position, tabSize: number): Position {
42
if (position.column <= model.getLineIndentColumn(position.lineNumber)) {
43
const minColumn = model.getLineMinColumn(position.lineNumber);
44
const lineContent = model.getLineContent(position.lineNumber);
45
const newPosition = AtomicTabMoveOperations.atomicPosition(lineContent, position.column - 1, tabSize, Direction.Left);
46
if (newPosition !== -1 && newPosition + 1 >= minColumn) {
47
return new Position(position.lineNumber, newPosition + 1);
48
}
49
}
50
return this.leftPosition(model, position);
51
}
52
53
private static left(config: CursorConfiguration, model: ICursorSimpleModel, position: Position): CursorPosition {
54
const pos = config.stickyTabStops
55
? MoveOperations.leftPositionAtomicSoftTabs(model, position, config.tabSize)
56
: MoveOperations.leftPosition(model, position);
57
return new CursorPosition(pos.lineNumber, pos.column, 0);
58
}
59
60
/**
61
* @param noOfColumns Must be either `1`
62
* or `Math.round(viewModel.getLineContent(viewLineNumber).length / 2)` (for half lines).
63
*/
64
public static moveLeft(config: CursorConfiguration, model: ICursorSimpleModel, cursor: SingleCursorState, inSelectionMode: boolean, noOfColumns: number): SingleCursorState {
65
let lineNumber: number,
66
column: number;
67
68
if (cursor.hasSelection() && !inSelectionMode) {
69
// If the user has a selection and does not want to extend it,
70
// put the cursor at the beginning of the selection.
71
lineNumber = cursor.selection.startLineNumber;
72
column = cursor.selection.startColumn;
73
} else {
74
// This has no effect if noOfColumns === 1.
75
// It is ok to do so in the half-line scenario.
76
const pos = cursor.position.delta(undefined, -(noOfColumns - 1));
77
// We clip the position before normalization, as normalization is not defined
78
// for possibly negative columns.
79
const normalizedPos = model.normalizePosition(MoveOperations.clipPositionColumn(pos, model), PositionAffinity.Left);
80
const p = MoveOperations.left(config, model, normalizedPos);
81
82
lineNumber = p.lineNumber;
83
column = p.column;
84
}
85
86
return cursor.move(inSelectionMode, lineNumber, column, 0);
87
}
88
89
/**
90
* Adjusts the column so that it is within min/max of the line.
91
*/
92
private static clipPositionColumn(position: Position, model: ICursorSimpleModel): Position {
93
return new Position(
94
position.lineNumber,
95
MoveOperations.clipRange(position.column, model.getLineMinColumn(position.lineNumber),
96
model.getLineMaxColumn(position.lineNumber))
97
);
98
}
99
100
private static clipRange(value: number, min: number, max: number): number {
101
if (value < min) {
102
return min;
103
}
104
if (value > max) {
105
return max;
106
}
107
return value;
108
}
109
110
public static rightPosition(model: ICursorSimpleModel, lineNumber: number, column: number): Position {
111
if (column < model.getLineMaxColumn(lineNumber)) {
112
column = column + strings.nextCharLength(model.getLineContent(lineNumber), column - 1);
113
} else if (lineNumber < model.getLineCount()) {
114
lineNumber = lineNumber + 1;
115
column = model.getLineMinColumn(lineNumber);
116
}
117
return new Position(lineNumber, column);
118
}
119
120
public static rightPositionAtomicSoftTabs(model: ICursorSimpleModel, lineNumber: number, column: number, tabSize: number, indentSize: number): Position {
121
if (column < model.getLineIndentColumn(lineNumber)) {
122
const lineContent = model.getLineContent(lineNumber);
123
const newPosition = AtomicTabMoveOperations.atomicPosition(lineContent, column - 1, tabSize, Direction.Right);
124
if (newPosition !== -1) {
125
return new Position(lineNumber, newPosition + 1);
126
}
127
}
128
return this.rightPosition(model, lineNumber, column);
129
}
130
131
public static right(config: CursorConfiguration, model: ICursorSimpleModel, position: Position): CursorPosition {
132
const pos = config.stickyTabStops
133
? MoveOperations.rightPositionAtomicSoftTabs(model, position.lineNumber, position.column, config.tabSize, config.indentSize)
134
: MoveOperations.rightPosition(model, position.lineNumber, position.column);
135
return new CursorPosition(pos.lineNumber, pos.column, 0);
136
}
137
138
public static moveRight(config: CursorConfiguration, model: ICursorSimpleModel, cursor: SingleCursorState, inSelectionMode: boolean, noOfColumns: number): SingleCursorState {
139
let lineNumber: number,
140
column: number;
141
142
if (cursor.hasSelection() && !inSelectionMode) {
143
// If we are in selection mode, move right without selection cancels selection and puts cursor at the end of the selection
144
lineNumber = cursor.selection.endLineNumber;
145
column = cursor.selection.endColumn;
146
} else {
147
const pos = cursor.position.delta(undefined, noOfColumns - 1);
148
const normalizedPos = model.normalizePosition(MoveOperations.clipPositionColumn(pos, model), PositionAffinity.Right);
149
const r = MoveOperations.right(config, model, normalizedPos);
150
lineNumber = r.lineNumber;
151
column = r.column;
152
}
153
154
return cursor.move(inSelectionMode, lineNumber, column, 0);
155
}
156
157
public static vertical(config: CursorConfiguration, model: ICursorSimpleModel, lineNumber: number, column: number, leftoverVisibleColumns: number, newLineNumber: number, allowMoveOnEdgeLine: boolean, normalizationAffinity?: PositionAffinity): CursorPosition {
158
const currentVisibleColumn = CursorColumns.visibleColumnFromColumn(model.getLineContent(lineNumber), column, config.tabSize) + leftoverVisibleColumns;
159
const lineCount = model.getLineCount();
160
const wasOnFirstPosition = (lineNumber === 1 && column === 1);
161
const wasOnLastPosition = (lineNumber === lineCount && column === model.getLineMaxColumn(lineNumber));
162
const wasAtEdgePosition = (newLineNumber < lineNumber ? wasOnFirstPosition : wasOnLastPosition);
163
164
lineNumber = newLineNumber;
165
if (lineNumber < 1) {
166
lineNumber = 1;
167
if (allowMoveOnEdgeLine) {
168
column = model.getLineMinColumn(lineNumber);
169
} else {
170
column = Math.min(model.getLineMaxColumn(lineNumber), column);
171
}
172
} else if (lineNumber > lineCount) {
173
lineNumber = lineCount;
174
if (allowMoveOnEdgeLine) {
175
column = model.getLineMaxColumn(lineNumber);
176
} else {
177
column = Math.min(model.getLineMaxColumn(lineNumber), column);
178
}
179
} else {
180
column = config.columnFromVisibleColumn(model, lineNumber, currentVisibleColumn);
181
}
182
183
if (wasAtEdgePosition) {
184
leftoverVisibleColumns = 0;
185
} else {
186
leftoverVisibleColumns = currentVisibleColumn - CursorColumns.visibleColumnFromColumn(model.getLineContent(lineNumber), column, config.tabSize);
187
}
188
189
if (normalizationAffinity !== undefined) {
190
const position = new Position(lineNumber, column);
191
const newPosition = model.normalizePosition(position, normalizationAffinity);
192
leftoverVisibleColumns = leftoverVisibleColumns + (column - newPosition.column);
193
lineNumber = newPosition.lineNumber;
194
column = newPosition.column;
195
}
196
return new CursorPosition(lineNumber, column, leftoverVisibleColumns);
197
}
198
199
public static down(config: CursorConfiguration, model: ICursorSimpleModel, lineNumber: number, column: number, leftoverVisibleColumns: number, count: number, allowMoveOnLastLine: boolean): CursorPosition {
200
return this.vertical(config, model, lineNumber, column, leftoverVisibleColumns, lineNumber + count, allowMoveOnLastLine, PositionAffinity.RightOfInjectedText);
201
}
202
203
public static moveDown(config: CursorConfiguration, model: ICursorSimpleModel, cursor: SingleCursorState, inSelectionMode: boolean, linesCount: number): SingleCursorState {
204
let lineNumber: number,
205
column: number;
206
207
if (cursor.hasSelection() && !inSelectionMode) {
208
// If we are in selection mode, move down acts relative to the end of selection
209
lineNumber = cursor.selection.endLineNumber;
210
column = cursor.selection.endColumn;
211
} else {
212
lineNumber = cursor.position.lineNumber;
213
column = cursor.position.column;
214
}
215
216
let i = 0;
217
let r: CursorPosition;
218
do {
219
r = MoveOperations.down(config, model, lineNumber + i, column, cursor.leftoverVisibleColumns, linesCount, true);
220
const np = model.normalizePosition(new Position(r.lineNumber, r.column), PositionAffinity.None);
221
if (np.lineNumber > lineNumber) {
222
break;
223
}
224
} while (i++ < 10 && lineNumber + i < model.getLineCount());
225
226
return cursor.move(inSelectionMode, r.lineNumber, r.column, r.leftoverVisibleColumns);
227
}
228
229
public static translateDown(config: CursorConfiguration, model: ICursorSimpleModel, cursor: SingleCursorState): SingleCursorState {
230
const selection = cursor.selection;
231
232
const selectionStart = MoveOperations.down(config, model, selection.selectionStartLineNumber, selection.selectionStartColumn, cursor.selectionStartLeftoverVisibleColumns, 1, false);
233
const position = MoveOperations.down(config, model, selection.positionLineNumber, selection.positionColumn, cursor.leftoverVisibleColumns, 1, false);
234
235
return new SingleCursorState(
236
new Range(selectionStart.lineNumber, selectionStart.column, selectionStart.lineNumber, selectionStart.column),
237
SelectionStartKind.Simple,
238
selectionStart.leftoverVisibleColumns,
239
new Position(position.lineNumber, position.column),
240
position.leftoverVisibleColumns
241
);
242
}
243
244
public static up(config: CursorConfiguration, model: ICursorSimpleModel, lineNumber: number, column: number, leftoverVisibleColumns: number, count: number, allowMoveOnFirstLine: boolean): CursorPosition {
245
return this.vertical(config, model, lineNumber, column, leftoverVisibleColumns, lineNumber - count, allowMoveOnFirstLine, PositionAffinity.LeftOfInjectedText);
246
}
247
248
public static moveUp(config: CursorConfiguration, model: ICursorSimpleModel, cursor: SingleCursorState, inSelectionMode: boolean, linesCount: number): SingleCursorState {
249
let lineNumber: number,
250
column: number;
251
252
if (cursor.hasSelection() && !inSelectionMode) {
253
// If we are in selection mode, move up acts relative to the beginning of selection
254
lineNumber = cursor.selection.startLineNumber;
255
column = cursor.selection.startColumn;
256
} else {
257
lineNumber = cursor.position.lineNumber;
258
column = cursor.position.column;
259
}
260
261
const r = MoveOperations.up(config, model, lineNumber, column, cursor.leftoverVisibleColumns, linesCount, true);
262
263
return cursor.move(inSelectionMode, r.lineNumber, r.column, r.leftoverVisibleColumns);
264
}
265
266
public static translateUp(config: CursorConfiguration, model: ICursorSimpleModel, cursor: SingleCursorState): SingleCursorState {
267
268
const selection = cursor.selection;
269
270
const selectionStart = MoveOperations.up(config, model, selection.selectionStartLineNumber, selection.selectionStartColumn, cursor.selectionStartLeftoverVisibleColumns, 1, false);
271
const position = MoveOperations.up(config, model, selection.positionLineNumber, selection.positionColumn, cursor.leftoverVisibleColumns, 1, false);
272
273
return new SingleCursorState(
274
new Range(selectionStart.lineNumber, selectionStart.column, selectionStart.lineNumber, selectionStart.column),
275
SelectionStartKind.Simple,
276
selectionStart.leftoverVisibleColumns,
277
new Position(position.lineNumber, position.column),
278
position.leftoverVisibleColumns
279
);
280
}
281
282
private static _isBlankLine(model: ICursorSimpleModel, lineNumber: number): boolean {
283
if (model.getLineFirstNonWhitespaceColumn(lineNumber) === 0) {
284
// empty or contains only whitespace
285
return true;
286
}
287
return false;
288
}
289
290
public static moveToPrevBlankLine(config: CursorConfiguration, model: ICursorSimpleModel, cursor: SingleCursorState, inSelectionMode: boolean): SingleCursorState {
291
let lineNumber = cursor.position.lineNumber;
292
293
// If our current line is blank, move to the previous non-blank line
294
while (lineNumber > 1 && this._isBlankLine(model, lineNumber)) {
295
lineNumber--;
296
}
297
298
// Find the previous blank line
299
while (lineNumber > 1 && !this._isBlankLine(model, lineNumber)) {
300
lineNumber--;
301
}
302
303
return cursor.move(inSelectionMode, lineNumber, model.getLineMinColumn(lineNumber), 0);
304
}
305
306
public static moveToNextBlankLine(config: CursorConfiguration, model: ICursorSimpleModel, cursor: SingleCursorState, inSelectionMode: boolean): SingleCursorState {
307
const lineCount = model.getLineCount();
308
let lineNumber = cursor.position.lineNumber;
309
310
// If our current line is blank, move to the next non-blank line
311
while (lineNumber < lineCount && this._isBlankLine(model, lineNumber)) {
312
lineNumber++;
313
}
314
315
// Find the next blank line
316
while (lineNumber < lineCount && !this._isBlankLine(model, lineNumber)) {
317
lineNumber++;
318
}
319
320
return cursor.move(inSelectionMode, lineNumber, model.getLineMinColumn(lineNumber), 0);
321
}
322
323
public static moveToBeginningOfLine(config: CursorConfiguration, model: ICursorSimpleModel, cursor: SingleCursorState, inSelectionMode: boolean): SingleCursorState {
324
const lineNumber = cursor.position.lineNumber;
325
const minColumn = model.getLineMinColumn(lineNumber);
326
const firstNonBlankColumn = model.getLineFirstNonWhitespaceColumn(lineNumber) || minColumn;
327
328
let column: number;
329
330
const relevantColumnNumber = cursor.position.column;
331
if (relevantColumnNumber === firstNonBlankColumn) {
332
column = minColumn;
333
} else {
334
column = firstNonBlankColumn;
335
}
336
337
return cursor.move(inSelectionMode, lineNumber, column, 0);
338
}
339
340
public static moveToEndOfLine(config: CursorConfiguration, model: ICursorSimpleModel, cursor: SingleCursorState, inSelectionMode: boolean, sticky: boolean): SingleCursorState {
341
const lineNumber = cursor.position.lineNumber;
342
const maxColumn = model.getLineMaxColumn(lineNumber);
343
return cursor.move(inSelectionMode, lineNumber, maxColumn, sticky ? Constants.MAX_SAFE_SMALL_INTEGER - maxColumn : 0);
344
}
345
346
public static moveToBeginningOfBuffer(config: CursorConfiguration, model: ICursorSimpleModel, cursor: SingleCursorState, inSelectionMode: boolean): SingleCursorState {
347
return cursor.move(inSelectionMode, 1, 1, 0);
348
}
349
350
public static moveToEndOfBuffer(config: CursorConfiguration, model: ICursorSimpleModel, cursor: SingleCursorState, inSelectionMode: boolean): SingleCursorState {
351
const lastLineNumber = model.getLineCount();
352
const lastColumn = model.getLineMaxColumn(lastLineNumber);
353
354
return cursor.move(inSelectionMode, lastLineNumber, lastColumn, 0);
355
}
356
}
357
358