Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/editor/common/cursorCommon.ts
3292 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 { ConfigurationChangedEvent, EditorAutoClosingEditStrategy, EditorAutoClosingStrategy, EditorAutoIndentStrategy, EditorAutoSurroundStrategy, EditorOption } from './config/editorOptions.js';
7
import { LineTokens } from './tokens/lineTokens.js';
8
import { Position } from './core/position.js';
9
import { Range } from './core/range.js';
10
import { ISelection, Selection } from './core/selection.js';
11
import { ICommand } from './editorCommon.js';
12
import { IEditorConfiguration } from './config/editorConfiguration.js';
13
import { PositionAffinity, TextModelResolvedOptions } from './model.js';
14
import { AutoClosingPairs } from './languages/languageConfiguration.js';
15
import { ILanguageConfigurationService } from './languages/languageConfigurationRegistry.js';
16
import { createScopedLineTokens } from './languages/supports.js';
17
import { IElectricAction } from './languages/supports/electricCharacter.js';
18
import { CursorColumns } from './core/cursorColumns.js';
19
import { normalizeIndentation } from './core/misc/indentation.js';
20
import { InputMode } from './inputMode.js';
21
22
export interface IColumnSelectData {
23
isReal: boolean;
24
fromViewLineNumber: number;
25
fromViewVisualColumn: number;
26
toViewLineNumber: number;
27
toViewVisualColumn: number;
28
}
29
30
/**
31
* This is an operation type that will be recorded for undo/redo purposes.
32
* The goal is to introduce an undo stop when the controller switches between different operation types.
33
*/
34
export const enum EditOperationType {
35
Other = 0,
36
DeletingLeft = 2,
37
DeletingRight = 3,
38
TypingOther = 4,
39
TypingFirstSpace = 5,
40
TypingConsecutiveSpace = 6,
41
}
42
43
export interface CharacterMap {
44
[char: string]: string;
45
}
46
47
const autoCloseAlways = () => true;
48
const autoCloseNever = () => false;
49
const autoCloseBeforeWhitespace = (chr: string) => (chr === ' ' || chr === '\t');
50
51
export class CursorConfiguration {
52
_cursorMoveConfigurationBrand: void = undefined;
53
54
public readonly readOnly: boolean;
55
public readonly tabSize: number;
56
public readonly indentSize: number;
57
public readonly insertSpaces: boolean;
58
public readonly stickyTabStops: boolean;
59
public readonly pageSize: number;
60
public readonly lineHeight: number;
61
public readonly typicalHalfwidthCharacterWidth: number;
62
public readonly useTabStops: boolean;
63
public readonly trimWhitespaceOnDelete: boolean;
64
public readonly wordSeparators: string;
65
public readonly emptySelectionClipboard: boolean;
66
public readonly copyWithSyntaxHighlighting: boolean;
67
public readonly multiCursorMergeOverlapping: boolean;
68
public readonly multiCursorPaste: 'spread' | 'full';
69
public readonly multiCursorLimit: number;
70
public readonly autoClosingBrackets: EditorAutoClosingStrategy;
71
public readonly autoClosingComments: EditorAutoClosingStrategy;
72
public readonly autoClosingQuotes: EditorAutoClosingStrategy;
73
public readonly autoClosingDelete: EditorAutoClosingEditStrategy;
74
public readonly autoClosingOvertype: EditorAutoClosingEditStrategy;
75
public readonly autoSurround: EditorAutoSurroundStrategy;
76
public readonly autoIndent: EditorAutoIndentStrategy;
77
public readonly autoClosingPairs: AutoClosingPairs;
78
public readonly surroundingPairs: CharacterMap;
79
public readonly blockCommentStartToken: string | null;
80
public readonly shouldAutoCloseBefore: { quote: (ch: string) => boolean; bracket: (ch: string) => boolean; comment: (ch: string) => boolean };
81
public readonly wordSegmenterLocales: string[];
82
public readonly overtypeOnPaste: boolean;
83
84
private readonly _languageId: string;
85
private _electricChars: { [key: string]: boolean } | null;
86
87
public static shouldRecreate(e: ConfigurationChangedEvent): boolean {
88
return (
89
e.hasChanged(EditorOption.layoutInfo)
90
|| e.hasChanged(EditorOption.wordSeparators)
91
|| e.hasChanged(EditorOption.emptySelectionClipboard)
92
|| e.hasChanged(EditorOption.multiCursorMergeOverlapping)
93
|| e.hasChanged(EditorOption.multiCursorPaste)
94
|| e.hasChanged(EditorOption.multiCursorLimit)
95
|| e.hasChanged(EditorOption.autoClosingBrackets)
96
|| e.hasChanged(EditorOption.autoClosingComments)
97
|| e.hasChanged(EditorOption.autoClosingQuotes)
98
|| e.hasChanged(EditorOption.autoClosingDelete)
99
|| e.hasChanged(EditorOption.autoClosingOvertype)
100
|| e.hasChanged(EditorOption.autoSurround)
101
|| e.hasChanged(EditorOption.useTabStops)
102
|| e.hasChanged(EditorOption.trimWhitespaceOnDelete)
103
|| e.hasChanged(EditorOption.fontInfo)
104
|| e.hasChanged(EditorOption.readOnly)
105
|| e.hasChanged(EditorOption.wordSegmenterLocales)
106
|| e.hasChanged(EditorOption.overtypeOnPaste)
107
);
108
}
109
110
constructor(
111
languageId: string,
112
modelOptions: TextModelResolvedOptions,
113
configuration: IEditorConfiguration,
114
public readonly languageConfigurationService: ILanguageConfigurationService
115
) {
116
this._languageId = languageId;
117
118
const options = configuration.options;
119
const layoutInfo = options.get(EditorOption.layoutInfo);
120
const fontInfo = options.get(EditorOption.fontInfo);
121
122
this.readOnly = options.get(EditorOption.readOnly);
123
this.tabSize = modelOptions.tabSize;
124
this.indentSize = modelOptions.indentSize;
125
this.insertSpaces = modelOptions.insertSpaces;
126
this.stickyTabStops = options.get(EditorOption.stickyTabStops);
127
this.lineHeight = fontInfo.lineHeight;
128
this.typicalHalfwidthCharacterWidth = fontInfo.typicalHalfwidthCharacterWidth;
129
this.pageSize = Math.max(1, Math.floor(layoutInfo.height / this.lineHeight) - 2);
130
this.useTabStops = options.get(EditorOption.useTabStops);
131
this.trimWhitespaceOnDelete = options.get(EditorOption.trimWhitespaceOnDelete);
132
this.wordSeparators = options.get(EditorOption.wordSeparators);
133
this.emptySelectionClipboard = options.get(EditorOption.emptySelectionClipboard);
134
this.copyWithSyntaxHighlighting = options.get(EditorOption.copyWithSyntaxHighlighting);
135
this.multiCursorMergeOverlapping = options.get(EditorOption.multiCursorMergeOverlapping);
136
this.multiCursorPaste = options.get(EditorOption.multiCursorPaste);
137
this.multiCursorLimit = options.get(EditorOption.multiCursorLimit);
138
this.autoClosingBrackets = options.get(EditorOption.autoClosingBrackets);
139
this.autoClosingComments = options.get(EditorOption.autoClosingComments);
140
this.autoClosingQuotes = options.get(EditorOption.autoClosingQuotes);
141
this.autoClosingDelete = options.get(EditorOption.autoClosingDelete);
142
this.autoClosingOvertype = options.get(EditorOption.autoClosingOvertype);
143
this.autoSurround = options.get(EditorOption.autoSurround);
144
this.autoIndent = options.get(EditorOption.autoIndent);
145
this.wordSegmenterLocales = options.get(EditorOption.wordSegmenterLocales);
146
this.overtypeOnPaste = options.get(EditorOption.overtypeOnPaste);
147
148
this.surroundingPairs = {};
149
this._electricChars = null;
150
151
this.shouldAutoCloseBefore = {
152
quote: this._getShouldAutoClose(languageId, this.autoClosingQuotes, true),
153
comment: this._getShouldAutoClose(languageId, this.autoClosingComments, false),
154
bracket: this._getShouldAutoClose(languageId, this.autoClosingBrackets, false),
155
};
156
157
this.autoClosingPairs = this.languageConfigurationService.getLanguageConfiguration(languageId).getAutoClosingPairs();
158
159
const surroundingPairs = this.languageConfigurationService.getLanguageConfiguration(languageId).getSurroundingPairs();
160
if (surroundingPairs) {
161
for (const pair of surroundingPairs) {
162
this.surroundingPairs[pair.open] = pair.close;
163
}
164
}
165
166
const commentsConfiguration = this.languageConfigurationService.getLanguageConfiguration(languageId).comments;
167
this.blockCommentStartToken = commentsConfiguration?.blockCommentStartToken ?? null;
168
}
169
170
public get electricChars() {
171
if (!this._electricChars) {
172
this._electricChars = {};
173
const electricChars = this.languageConfigurationService.getLanguageConfiguration(this._languageId).electricCharacter?.getElectricCharacters();
174
if (electricChars) {
175
for (const char of electricChars) {
176
this._electricChars[char] = true;
177
}
178
}
179
}
180
return this._electricChars;
181
}
182
183
public get inputMode(): 'insert' | 'overtype' {
184
return InputMode.getInputMode();
185
}
186
187
/**
188
* Should return opening bracket type to match indentation with
189
*/
190
public onElectricCharacter(character: string, context: LineTokens, column: number): IElectricAction | null {
191
const scopedLineTokens = createScopedLineTokens(context, column - 1);
192
const electricCharacterSupport = this.languageConfigurationService.getLanguageConfiguration(scopedLineTokens.languageId).electricCharacter;
193
if (!electricCharacterSupport) {
194
return null;
195
}
196
return electricCharacterSupport.onElectricCharacter(character, scopedLineTokens, column - scopedLineTokens.firstCharOffset);
197
}
198
199
public normalizeIndentation(str: string): string {
200
return normalizeIndentation(str, this.indentSize, this.insertSpaces);
201
}
202
203
private _getShouldAutoClose(languageId: string, autoCloseConfig: EditorAutoClosingStrategy, forQuotes: boolean): (ch: string) => boolean {
204
switch (autoCloseConfig) {
205
case 'beforeWhitespace':
206
return autoCloseBeforeWhitespace;
207
case 'languageDefined':
208
return this._getLanguageDefinedShouldAutoClose(languageId, forQuotes);
209
case 'always':
210
return autoCloseAlways;
211
case 'never':
212
return autoCloseNever;
213
}
214
}
215
216
private _getLanguageDefinedShouldAutoClose(languageId: string, forQuotes: boolean): (ch: string) => boolean {
217
const autoCloseBeforeSet = this.languageConfigurationService.getLanguageConfiguration(languageId).getAutoCloseBeforeSet(forQuotes);
218
return c => autoCloseBeforeSet.indexOf(c) !== -1;
219
}
220
221
/**
222
* Returns a visible column from a column.
223
* @see {@link CursorColumns}
224
*/
225
public visibleColumnFromColumn(model: ICursorSimpleModel, position: Position): number {
226
return CursorColumns.visibleColumnFromColumn(model.getLineContent(position.lineNumber), position.column, this.tabSize);
227
}
228
229
/**
230
* Returns a visible column from a column.
231
* @see {@link CursorColumns}
232
*/
233
public columnFromVisibleColumn(model: ICursorSimpleModel, lineNumber: number, visibleColumn: number): number {
234
const result = CursorColumns.columnFromVisibleColumn(model.getLineContent(lineNumber), visibleColumn, this.tabSize);
235
236
const minColumn = model.getLineMinColumn(lineNumber);
237
if (result < minColumn) {
238
return minColumn;
239
}
240
241
const maxColumn = model.getLineMaxColumn(lineNumber);
242
if (result > maxColumn) {
243
return maxColumn;
244
}
245
246
return result;
247
}
248
}
249
250
/**
251
* Represents a simple model (either the model or the view model).
252
*/
253
export interface ICursorSimpleModel {
254
getLineCount(): number;
255
getLineContent(lineNumber: number): string;
256
getLineMinColumn(lineNumber: number): number;
257
getLineMaxColumn(lineNumber: number): number;
258
getLineFirstNonWhitespaceColumn(lineNumber: number): number;
259
getLineLastNonWhitespaceColumn(lineNumber: number): number;
260
normalizePosition(position: Position, affinity: PositionAffinity): Position;
261
262
/**
263
* Gets the column at which indentation stops at a given line.
264
* @internal
265
*/
266
getLineIndentColumn(lineNumber: number): number;
267
}
268
269
export type PartialCursorState = CursorState | PartialModelCursorState | PartialViewCursorState;
270
271
export class CursorState {
272
_cursorStateBrand: void = undefined;
273
274
public static fromModelState(modelState: SingleCursorState): PartialModelCursorState {
275
return new PartialModelCursorState(modelState);
276
}
277
278
public static fromViewState(viewState: SingleCursorState): PartialViewCursorState {
279
return new PartialViewCursorState(viewState);
280
}
281
282
public static fromModelSelection(modelSelection: ISelection): PartialModelCursorState {
283
const selection = Selection.liftSelection(modelSelection);
284
const modelState = new SingleCursorState(
285
Range.fromPositions(selection.getSelectionStart()),
286
SelectionStartKind.Simple, 0,
287
selection.getPosition(), 0
288
);
289
return CursorState.fromModelState(modelState);
290
}
291
292
public static fromModelSelections(modelSelections: readonly ISelection[]): PartialModelCursorState[] {
293
const states: PartialModelCursorState[] = [];
294
for (let i = 0, len = modelSelections.length; i < len; i++) {
295
states[i] = this.fromModelSelection(modelSelections[i]);
296
}
297
return states;
298
}
299
300
readonly modelState: SingleCursorState;
301
readonly viewState: SingleCursorState;
302
303
constructor(modelState: SingleCursorState, viewState: SingleCursorState) {
304
this.modelState = modelState;
305
this.viewState = viewState;
306
}
307
308
public equals(other: CursorState): boolean {
309
return (this.viewState.equals(other.viewState) && this.modelState.equals(other.modelState));
310
}
311
}
312
313
export class PartialModelCursorState {
314
readonly modelState: SingleCursorState;
315
readonly viewState: null;
316
317
constructor(modelState: SingleCursorState) {
318
this.modelState = modelState;
319
this.viewState = null;
320
}
321
}
322
323
export class PartialViewCursorState {
324
readonly modelState: null;
325
readonly viewState: SingleCursorState;
326
327
constructor(viewState: SingleCursorState) {
328
this.modelState = null;
329
this.viewState = viewState;
330
}
331
}
332
333
export const enum SelectionStartKind {
334
Simple,
335
Word,
336
Line
337
}
338
339
/**
340
* Represents the cursor state on either the model or on the view model.
341
*/
342
export class SingleCursorState {
343
_singleCursorStateBrand: void = undefined;
344
345
public readonly selection: Selection;
346
347
constructor(
348
public readonly selectionStart: Range,
349
public readonly selectionStartKind: SelectionStartKind,
350
public readonly selectionStartLeftoverVisibleColumns: number,
351
public readonly position: Position,
352
public readonly leftoverVisibleColumns: number,
353
) {
354
this.selection = SingleCursorState._computeSelection(this.selectionStart, this.position);
355
}
356
357
public equals(other: SingleCursorState) {
358
return (
359
this.selectionStartLeftoverVisibleColumns === other.selectionStartLeftoverVisibleColumns
360
&& this.leftoverVisibleColumns === other.leftoverVisibleColumns
361
&& this.selectionStartKind === other.selectionStartKind
362
&& this.position.equals(other.position)
363
&& this.selectionStart.equalsRange(other.selectionStart)
364
);
365
}
366
367
public hasSelection(): boolean {
368
return (!this.selection.isEmpty() || !this.selectionStart.isEmpty());
369
}
370
371
public move(inSelectionMode: boolean, lineNumber: number, column: number, leftoverVisibleColumns: number): SingleCursorState {
372
if (inSelectionMode) {
373
// move just position
374
return new SingleCursorState(
375
this.selectionStart,
376
this.selectionStartKind,
377
this.selectionStartLeftoverVisibleColumns,
378
new Position(lineNumber, column),
379
leftoverVisibleColumns
380
);
381
} else {
382
// move everything
383
return new SingleCursorState(
384
new Range(lineNumber, column, lineNumber, column),
385
SelectionStartKind.Simple,
386
leftoverVisibleColumns,
387
new Position(lineNumber, column),
388
leftoverVisibleColumns
389
);
390
}
391
}
392
393
private static _computeSelection(selectionStart: Range, position: Position): Selection {
394
if (selectionStart.isEmpty() || !position.isBeforeOrEqual(selectionStart.getStartPosition())) {
395
return Selection.fromPositions(selectionStart.getStartPosition(), position);
396
} else {
397
return Selection.fromPositions(selectionStart.getEndPosition(), position);
398
}
399
}
400
}
401
402
export class EditOperationResult {
403
_editOperationResultBrand: void = undefined;
404
405
readonly type: EditOperationType;
406
readonly commands: Array<ICommand | null>;
407
readonly shouldPushStackElementBefore: boolean;
408
readonly shouldPushStackElementAfter: boolean;
409
410
constructor(
411
type: EditOperationType,
412
commands: Array<ICommand | null>,
413
opts: {
414
shouldPushStackElementBefore: boolean;
415
shouldPushStackElementAfter: boolean;
416
}
417
) {
418
this.type = type;
419
this.commands = commands;
420
this.shouldPushStackElementBefore = opts.shouldPushStackElementBefore;
421
this.shouldPushStackElementAfter = opts.shouldPushStackElementAfter;
422
}
423
}
424
425
export function isQuote(ch: string): boolean {
426
return (ch === '\'' || ch === '"' || ch === '`');
427
}
428
429