Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/editor/common/cursor/oneCursor.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 { CursorState, ICursorSimpleModel, SelectionStartKind, SingleCursorState } from '../cursorCommon.js';
7
import { CursorContext } from './cursorContext.js';
8
import { Position } from '../core/position.js';
9
import { Range } from '../core/range.js';
10
import { Selection } from '../core/selection.js';
11
import { PositionAffinity, TrackedRangeStickiness } from '../model.js';
12
13
/**
14
* Represents a single cursor.
15
*/
16
export class Cursor {
17
18
public modelState!: SingleCursorState;
19
public viewState!: SingleCursorState;
20
21
private _selTrackedRange: string | null;
22
private _trackSelection: boolean;
23
24
constructor(context: CursorContext) {
25
this._selTrackedRange = null;
26
this._trackSelection = true;
27
28
this._setState(
29
context,
30
new SingleCursorState(new Range(1, 1, 1, 1), SelectionStartKind.Simple, 0, new Position(1, 1), 0),
31
new SingleCursorState(new Range(1, 1, 1, 1), SelectionStartKind.Simple, 0, new Position(1, 1), 0)
32
);
33
}
34
35
public dispose(context: CursorContext): void {
36
this._removeTrackedRange(context);
37
}
38
39
public startTrackingSelection(context: CursorContext): void {
40
this._trackSelection = true;
41
this._updateTrackedRange(context);
42
}
43
44
public stopTrackingSelection(context: CursorContext): void {
45
this._trackSelection = false;
46
this._removeTrackedRange(context);
47
}
48
49
private _updateTrackedRange(context: CursorContext): void {
50
if (!this._trackSelection) {
51
// don't track the selection
52
return;
53
}
54
this._selTrackedRange = context.model._setTrackedRange(this._selTrackedRange, this.modelState.selection, TrackedRangeStickiness.AlwaysGrowsWhenTypingAtEdges);
55
}
56
57
private _removeTrackedRange(context: CursorContext): void {
58
this._selTrackedRange = context.model._setTrackedRange(this._selTrackedRange, null, TrackedRangeStickiness.AlwaysGrowsWhenTypingAtEdges);
59
}
60
61
public asCursorState(): CursorState {
62
return new CursorState(this.modelState, this.viewState);
63
}
64
65
public readSelectionFromMarkers(context: CursorContext): Selection {
66
const range = context.model._getTrackedRange(this._selTrackedRange!)!;
67
68
if (this.modelState.selection.isEmpty() && !range.isEmpty()) {
69
// Avoid selecting text when recovering from markers
70
return Selection.fromRange(range.collapseToEnd(), this.modelState.selection.getDirection());
71
}
72
73
return Selection.fromRange(range, this.modelState.selection.getDirection());
74
}
75
76
public ensureValidState(context: CursorContext): void {
77
this._setState(context, this.modelState, this.viewState);
78
}
79
80
public setState(context: CursorContext, modelState: SingleCursorState | null, viewState: SingleCursorState | null): void {
81
this._setState(context, modelState, viewState);
82
}
83
84
private static _validatePositionWithCache(viewModel: ICursorSimpleModel, position: Position, cacheInput: Position, cacheOutput: Position): Position {
85
if (position.equals(cacheInput)) {
86
return cacheOutput;
87
}
88
return viewModel.normalizePosition(position, PositionAffinity.None);
89
}
90
91
private static _validateViewState(viewModel: ICursorSimpleModel, viewState: SingleCursorState): SingleCursorState {
92
const position = viewState.position;
93
const sStartPosition = viewState.selectionStart.getStartPosition();
94
const sEndPosition = viewState.selectionStart.getEndPosition();
95
96
const validPosition = viewModel.normalizePosition(position, PositionAffinity.None);
97
const validSStartPosition = this._validatePositionWithCache(viewModel, sStartPosition, position, validPosition);
98
const validSEndPosition = this._validatePositionWithCache(viewModel, sEndPosition, sStartPosition, validSStartPosition);
99
100
if (position.equals(validPosition) && sStartPosition.equals(validSStartPosition) && sEndPosition.equals(validSEndPosition)) {
101
// fast path: the state is valid
102
return viewState;
103
}
104
105
return new SingleCursorState(
106
Range.fromPositions(validSStartPosition, validSEndPosition),
107
viewState.selectionStartKind,
108
viewState.selectionStartLeftoverVisibleColumns + sStartPosition.column - validSStartPosition.column,
109
validPosition,
110
viewState.leftoverVisibleColumns + position.column - validPosition.column,
111
);
112
}
113
114
private _setState(context: CursorContext, modelState: SingleCursorState | null, viewState: SingleCursorState | null): void {
115
if (viewState) {
116
viewState = Cursor._validateViewState(context.viewModel, viewState);
117
}
118
119
if (!modelState) {
120
if (!viewState) {
121
return;
122
}
123
// We only have the view state => compute the model state
124
const selectionStart = context.model.validateRange(
125
context.coordinatesConverter.convertViewRangeToModelRange(viewState.selectionStart)
126
);
127
128
const position = context.model.validatePosition(
129
context.coordinatesConverter.convertViewPositionToModelPosition(viewState.position)
130
);
131
132
modelState = new SingleCursorState(selectionStart, viewState.selectionStartKind, viewState.selectionStartLeftoverVisibleColumns, position, viewState.leftoverVisibleColumns);
133
} else {
134
// Validate new model state
135
const selectionStart = context.model.validateRange(modelState.selectionStart);
136
const selectionStartLeftoverVisibleColumns = modelState.selectionStart.equalsRange(selectionStart) ? modelState.selectionStartLeftoverVisibleColumns : 0;
137
138
const position = context.model.validatePosition(
139
modelState.position
140
);
141
const leftoverVisibleColumns = modelState.position.equals(position) ? modelState.leftoverVisibleColumns : 0;
142
143
modelState = new SingleCursorState(selectionStart, modelState.selectionStartKind, selectionStartLeftoverVisibleColumns, position, leftoverVisibleColumns);
144
}
145
146
if (!viewState) {
147
// We only have the model state => compute the view state
148
const viewSelectionStart1 = context.coordinatesConverter.convertModelPositionToViewPosition(new Position(modelState.selectionStart.startLineNumber, modelState.selectionStart.startColumn));
149
const viewSelectionStart2 = context.coordinatesConverter.convertModelPositionToViewPosition(new Position(modelState.selectionStart.endLineNumber, modelState.selectionStart.endColumn));
150
const viewSelectionStart = new Range(viewSelectionStart1.lineNumber, viewSelectionStart1.column, viewSelectionStart2.lineNumber, viewSelectionStart2.column);
151
const viewPosition = context.coordinatesConverter.convertModelPositionToViewPosition(modelState.position);
152
viewState = new SingleCursorState(viewSelectionStart, modelState.selectionStartKind, modelState.selectionStartLeftoverVisibleColumns, viewPosition, modelState.leftoverVisibleColumns);
153
} else {
154
// Validate new view state
155
const viewSelectionStart = context.coordinatesConverter.validateViewRange(viewState.selectionStart, modelState.selectionStart);
156
const viewPosition = context.coordinatesConverter.validateViewPosition(viewState.position, modelState.position);
157
viewState = new SingleCursorState(viewSelectionStart, modelState.selectionStartKind, modelState.selectionStartLeftoverVisibleColumns, viewPosition, modelState.leftoverVisibleColumns);
158
}
159
160
this.modelState = modelState;
161
this.viewState = viewState;
162
163
this._updateTrackedRange(context);
164
}
165
}
166
167