Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/editor/browser/view/viewController.ts
5221 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 { IKeyboardEvent } from '../../../base/browser/keyboardEvent.js';
7
import { CoreNavigationCommands, NavigationCommandRevealType } from '../coreCommands.js';
8
import { IEditorMouseEvent, IPartialEditorMouseEvent } from '../editorBrowser.js';
9
import { ViewUserInputEvents } from './viewUserInputEvents.js';
10
import { Position } from '../../common/core/position.js';
11
import { Selection } from '../../common/core/selection.js';
12
import { IEditorConfiguration } from '../../common/config/editorConfiguration.js';
13
import { IViewModel } from '../../common/viewModel.js';
14
import { IMouseWheelEvent } from '../../../base/browser/mouseEvent.js';
15
import { EditorOption } from '../../common/config/editorOptions.js';
16
import * as platform from '../../../base/common/platform.js';
17
import { StandardTokenType } from '../../common/encodedTokenAttributes.js';
18
import { ITextModel } from '../../common/model.js';
19
20
export interface IMouseDispatchData {
21
position: Position;
22
/**
23
* Desired mouse column (e.g. when position.column gets clamped to text length -- clicking after text on a line).
24
*/
25
mouseColumn: number;
26
revealType: NavigationCommandRevealType;
27
startedOnLineNumbers: boolean;
28
29
inSelectionMode: boolean;
30
mouseDownCount: number;
31
altKey: boolean;
32
ctrlKey: boolean;
33
metaKey: boolean;
34
shiftKey: boolean;
35
36
leftButton: boolean;
37
middleButton: boolean;
38
onInjectedText: boolean;
39
}
40
41
export interface ICommandDelegate {
42
paste(text: string, pasteOnNewLine: boolean, multicursorText: string[] | null, mode: string | null): void;
43
type(text: string): void;
44
compositionType(text: string, replacePrevCharCnt: number, replaceNextCharCnt: number, positionDelta: number): void;
45
startComposition(): void;
46
endComposition(): void;
47
cut(): void;
48
}
49
50
export class ViewController {
51
52
private readonly configuration: IEditorConfiguration;
53
private readonly viewModel: IViewModel;
54
private readonly userInputEvents: ViewUserInputEvents;
55
private readonly commandDelegate: ICommandDelegate;
56
57
constructor(
58
configuration: IEditorConfiguration,
59
viewModel: IViewModel,
60
userInputEvents: ViewUserInputEvents,
61
commandDelegate: ICommandDelegate
62
) {
63
this.configuration = configuration;
64
this.viewModel = viewModel;
65
this.userInputEvents = userInputEvents;
66
this.commandDelegate = commandDelegate;
67
}
68
69
public paste(text: string, pasteOnNewLine: boolean, multicursorText: string[] | null, mode: string | null): void {
70
this.commandDelegate.paste(text, pasteOnNewLine, multicursorText, mode);
71
}
72
73
public type(text: string): void {
74
this.commandDelegate.type(text);
75
}
76
77
public compositionType(text: string, replacePrevCharCnt: number, replaceNextCharCnt: number, positionDelta: number): void {
78
this.commandDelegate.compositionType(text, replacePrevCharCnt, replaceNextCharCnt, positionDelta);
79
}
80
81
public compositionStart(): void {
82
this.commandDelegate.startComposition();
83
}
84
85
public compositionEnd(): void {
86
this.commandDelegate.endComposition();
87
}
88
89
public cut(): void {
90
this.commandDelegate.cut();
91
}
92
93
public setSelection(modelSelection: Selection): void {
94
CoreNavigationCommands.SetSelection.runCoreEditorCommand(this.viewModel, {
95
source: 'keyboard',
96
selection: modelSelection
97
});
98
}
99
100
private _validateViewColumn(viewPosition: Position): Position {
101
const minColumn = this.viewModel.getLineMinColumn(viewPosition.lineNumber);
102
if (viewPosition.column < minColumn) {
103
return new Position(viewPosition.lineNumber, minColumn);
104
}
105
return viewPosition;
106
}
107
108
private _hasMulticursorModifier(data: IMouseDispatchData): boolean {
109
switch (this.configuration.options.get(EditorOption.multiCursorModifier)) {
110
case 'altKey':
111
return data.altKey;
112
case 'ctrlKey':
113
return data.ctrlKey;
114
case 'metaKey':
115
return data.metaKey;
116
default:
117
return false;
118
}
119
}
120
121
private _hasNonMulticursorModifier(data: IMouseDispatchData): boolean {
122
switch (this.configuration.options.get(EditorOption.multiCursorModifier)) {
123
case 'altKey':
124
return data.ctrlKey || data.metaKey;
125
case 'ctrlKey':
126
return data.altKey || data.metaKey;
127
case 'metaKey':
128
return data.ctrlKey || data.altKey;
129
default:
130
return false;
131
}
132
}
133
134
/**
135
* Selects content inside brackets if the position is right after an opening bracket or right before a closing bracket.
136
* @param pos The position in the model.
137
* @param model The text model.
138
*/
139
private static _trySelectBracketContent(model: ITextModel, pos: Position): Selection | undefined {
140
// Try to find bracket match if we're right after an opening bracket.
141
if (pos.column > 1) {
142
const pair = model.bracketPairs.matchBracket(pos.with(undefined, pos.column - 1));
143
if (pair && pair[0].getEndPosition().equals(pos)) {
144
return Selection.fromPositions(pair[0].getEndPosition(), pair[1].getStartPosition());
145
}
146
}
147
148
// Try to find bracket match if we're right before a closing bracket.
149
if (pos.column <= model.getLineMaxColumn(pos.lineNumber)) {
150
const pair = model.bracketPairs.matchBracket(pos);
151
if (pair && pair[1].getStartPosition().equals(pos)) {
152
return Selection.fromPositions(pair[0].getEndPosition(), pair[1].getStartPosition());
153
}
154
}
155
156
return undefined;
157
}
158
159
/**
160
* Selects content inside a string if the position is right after an opening quote or right before a closing quote.
161
* @param pos The position in the model.
162
* @param model The text model.
163
*/
164
private static _trySelectStringContent(model: ITextModel, pos: Position): Selection | undefined {
165
const { lineNumber, column } = pos;
166
const { tokenization: tokens } = model;
167
168
// Ensure we have accurate tokens for the line.
169
if (!tokens.hasAccurateTokensForLine(lineNumber)) {
170
if (tokens.isCheapToTokenize(lineNumber)) {
171
tokens.forceTokenization(lineNumber);
172
} else {
173
return undefined;
174
}
175
}
176
177
// Check if current token is a string.
178
const lineTokens = tokens.getLineTokens(lineNumber);
179
const index = lineTokens.findTokenIndexAtOffset(column - 1);
180
if (lineTokens.getStandardTokenType(index) !== StandardTokenType.String) {
181
return undefined;
182
}
183
184
// Get 1-based boundaries of the string content (excluding quotes).
185
const start = lineTokens.getStartOffset(index) + 2;
186
const end = lineTokens.getEndOffset(index);
187
188
if (column !== start && column !== end) {
189
return undefined;
190
}
191
192
return new Selection(lineNumber, start, lineNumber, end);
193
}
194
195
public dispatchMouse(data: IMouseDispatchData): void {
196
const options = this.configuration.options;
197
const selectionClipboardIsOn = (platform.isLinux && options.get(EditorOption.selectionClipboard));
198
const columnSelection = options.get(EditorOption.columnSelection);
199
const scrollOnMiddleClick = options.get(EditorOption.scrollOnMiddleClick);
200
if (data.middleButton && !selectionClipboardIsOn) {
201
if (scrollOnMiddleClick) {
202
// nothing to do here, handled in the contribution
203
} else {
204
this._columnSelect(data.position, data.mouseColumn, data.inSelectionMode);
205
}
206
} else if (data.startedOnLineNumbers) {
207
// If the dragging started on the gutter, then have operations work on the entire line
208
if (this._hasMulticursorModifier(data)) {
209
if (data.inSelectionMode) {
210
this._lastCursorLineSelect(data.position, data.revealType);
211
} else {
212
this._createCursor(data.position, true);
213
}
214
} else {
215
if (data.inSelectionMode) {
216
this._lineSelectDrag(data.position, data.revealType);
217
} else {
218
this._lineSelect(data.position, data.revealType);
219
}
220
}
221
} else if (data.mouseDownCount >= 4) {
222
this._selectAll();
223
} else if (data.mouseDownCount === 3) {
224
if (this._hasMulticursorModifier(data)) {
225
if (data.inSelectionMode) {
226
this._lastCursorLineSelectDrag(data.position, data.revealType);
227
} else {
228
this._lastCursorLineSelect(data.position, data.revealType);
229
}
230
} else {
231
if (data.inSelectionMode) {
232
this._lineSelectDrag(data.position, data.revealType);
233
} else {
234
this._lineSelect(data.position, data.revealType);
235
}
236
}
237
} else if (data.mouseDownCount === 2) {
238
if (!data.onInjectedText) {
239
if (this._hasMulticursorModifier(data)) {
240
this._lastCursorWordSelect(data.position, data.revealType);
241
} else {
242
if (data.inSelectionMode) {
243
this._wordSelectDrag(data.position, data.revealType);
244
} else {
245
const model = this.viewModel.model;
246
const modelPos = this._convertViewToModelPosition(data.position);
247
const selection = ViewController._trySelectBracketContent(model, modelPos) || ViewController._trySelectStringContent(model, modelPos);
248
if (selection) {
249
this._select(selection);
250
} else {
251
this._wordSelect(data.position, data.revealType);
252
}
253
}
254
}
255
}
256
} else {
257
if (this._hasMulticursorModifier(data)) {
258
if (!this._hasNonMulticursorModifier(data)) {
259
if (data.shiftKey) {
260
this._columnSelect(data.position, data.mouseColumn, true);
261
} else {
262
// Do multi-cursor operations only when purely alt is pressed
263
if (data.inSelectionMode) {
264
this._lastCursorMoveToSelect(data.position, data.revealType);
265
} else {
266
this._createCursor(data.position, false);
267
}
268
}
269
}
270
} else {
271
if (data.inSelectionMode) {
272
if (data.altKey) {
273
this._columnSelect(data.position, data.mouseColumn, true);
274
} else {
275
if (columnSelection) {
276
this._columnSelect(data.position, data.mouseColumn, true);
277
} else {
278
this._moveToSelect(data.position, data.revealType);
279
}
280
}
281
} else {
282
this.moveTo(data.position, data.revealType);
283
}
284
}
285
}
286
}
287
288
private _usualArgs(viewPosition: Position, revealType: NavigationCommandRevealType): CoreNavigationCommands.MoveCommandOptions {
289
viewPosition = this._validateViewColumn(viewPosition);
290
return {
291
source: 'mouse',
292
position: this._convertViewToModelPosition(viewPosition),
293
viewPosition,
294
revealType
295
};
296
}
297
298
public moveTo(viewPosition: Position, revealType: NavigationCommandRevealType): void {
299
CoreNavigationCommands.MoveTo.runCoreEditorCommand(this.viewModel, this._usualArgs(viewPosition, revealType));
300
}
301
302
private _moveToSelect(viewPosition: Position, revealType: NavigationCommandRevealType): void {
303
CoreNavigationCommands.MoveToSelect.runCoreEditorCommand(this.viewModel, this._usualArgs(viewPosition, revealType));
304
}
305
306
private _columnSelect(viewPosition: Position, mouseColumn: number, doColumnSelect: boolean): void {
307
viewPosition = this._validateViewColumn(viewPosition);
308
CoreNavigationCommands.ColumnSelect.runCoreEditorCommand(this.viewModel, {
309
source: 'mouse',
310
position: this._convertViewToModelPosition(viewPosition),
311
viewPosition: viewPosition,
312
mouseColumn: mouseColumn,
313
doColumnSelect: doColumnSelect
314
});
315
}
316
317
private _createCursor(viewPosition: Position, wholeLine: boolean): void {
318
viewPosition = this._validateViewColumn(viewPosition);
319
CoreNavigationCommands.CreateCursor.runCoreEditorCommand(this.viewModel, {
320
source: 'mouse',
321
position: this._convertViewToModelPosition(viewPosition),
322
viewPosition: viewPosition,
323
wholeLine: wholeLine
324
});
325
}
326
327
private _lastCursorMoveToSelect(viewPosition: Position, revealType: NavigationCommandRevealType): void {
328
CoreNavigationCommands.LastCursorMoveToSelect.runCoreEditorCommand(this.viewModel, this._usualArgs(viewPosition, revealType));
329
}
330
331
private _wordSelect(viewPosition: Position, revealType: NavigationCommandRevealType): void {
332
CoreNavigationCommands.WordSelect.runCoreEditorCommand(this.viewModel, this._usualArgs(viewPosition, revealType));
333
}
334
335
private _wordSelectDrag(viewPosition: Position, revealType: NavigationCommandRevealType): void {
336
CoreNavigationCommands.WordSelectDrag.runCoreEditorCommand(this.viewModel, this._usualArgs(viewPosition, revealType));
337
}
338
339
private _lastCursorWordSelect(viewPosition: Position, revealType: NavigationCommandRevealType): void {
340
CoreNavigationCommands.LastCursorWordSelect.runCoreEditorCommand(this.viewModel, this._usualArgs(viewPosition, revealType));
341
}
342
343
private _lineSelect(viewPosition: Position, revealType: NavigationCommandRevealType): void {
344
CoreNavigationCommands.LineSelect.runCoreEditorCommand(this.viewModel, this._usualArgs(viewPosition, revealType));
345
}
346
347
private _lineSelectDrag(viewPosition: Position, revealType: NavigationCommandRevealType): void {
348
CoreNavigationCommands.LineSelectDrag.runCoreEditorCommand(this.viewModel, this._usualArgs(viewPosition, revealType));
349
}
350
351
private _lastCursorLineSelect(viewPosition: Position, revealType: NavigationCommandRevealType): void {
352
CoreNavigationCommands.LastCursorLineSelect.runCoreEditorCommand(this.viewModel, this._usualArgs(viewPosition, revealType));
353
}
354
355
private _lastCursorLineSelectDrag(viewPosition: Position, revealType: NavigationCommandRevealType): void {
356
CoreNavigationCommands.LastCursorLineSelectDrag.runCoreEditorCommand(this.viewModel, this._usualArgs(viewPosition, revealType));
357
}
358
359
private _select(selection: Selection): void {
360
CoreNavigationCommands.SetSelection.runCoreEditorCommand(this.viewModel, { source: 'mouse', selection });
361
}
362
363
private _selectAll(): void {
364
CoreNavigationCommands.SelectAll.runCoreEditorCommand(this.viewModel, { source: 'mouse' });
365
}
366
367
// ----------------------
368
369
private _convertViewToModelPosition(viewPosition: Position): Position {
370
return this.viewModel.coordinatesConverter.convertViewPositionToModelPosition(viewPosition);
371
}
372
373
public emitKeyDown(e: IKeyboardEvent): void {
374
this.userInputEvents.emitKeyDown(e);
375
}
376
377
public emitKeyUp(e: IKeyboardEvent): void {
378
this.userInputEvents.emitKeyUp(e);
379
}
380
381
public emitContextMenu(e: IEditorMouseEvent): void {
382
this.userInputEvents.emitContextMenu(e);
383
}
384
385
public emitMouseMove(e: IEditorMouseEvent): void {
386
this.userInputEvents.emitMouseMove(e);
387
}
388
389
public emitMouseLeave(e: IPartialEditorMouseEvent): void {
390
this.userInputEvents.emitMouseLeave(e);
391
}
392
393
public emitMouseUp(e: IEditorMouseEvent): void {
394
this.userInputEvents.emitMouseUp(e);
395
}
396
397
public emitMouseDown(e: IEditorMouseEvent): void {
398
this.userInputEvents.emitMouseDown(e);
399
}
400
401
public emitMouseDrag(e: IEditorMouseEvent): void {
402
this.userInputEvents.emitMouseDrag(e);
403
}
404
405
public emitMouseDrop(e: IPartialEditorMouseEvent): void {
406
this.userInputEvents.emitMouseDrop(e);
407
}
408
409
public emitMouseDropCanceled(): void {
410
this.userInputEvents.emitMouseDropCanceled();
411
}
412
413
public emitMouseWheel(e: IMouseWheelEvent): void {
414
this.userInputEvents.emitMouseWheel(e);
415
}
416
}
417
418