@xterm/xterm 5.4.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (108) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +235 -0
  3. package/css/xterm.css +209 -0
  4. package/lib/xterm.js +2 -0
  5. package/lib/xterm.js.map +1 -0
  6. package/package.json +101 -0
  7. package/src/browser/AccessibilityManager.ts +278 -0
  8. package/src/browser/Clipboard.ts +93 -0
  9. package/src/browser/ColorContrastCache.ts +34 -0
  10. package/src/browser/Lifecycle.ts +33 -0
  11. package/src/browser/Linkifier2.ts +416 -0
  12. package/src/browser/LocalizableStrings.ts +12 -0
  13. package/src/browser/OscLinkProvider.ts +128 -0
  14. package/src/browser/RenderDebouncer.ts +83 -0
  15. package/src/browser/Terminal.ts +1317 -0
  16. package/src/browser/TimeBasedDebouncer.ts +86 -0
  17. package/src/browser/Types.d.ts +181 -0
  18. package/src/browser/Viewport.ts +401 -0
  19. package/src/browser/decorations/BufferDecorationRenderer.ts +134 -0
  20. package/src/browser/decorations/ColorZoneStore.ts +117 -0
  21. package/src/browser/decorations/OverviewRulerRenderer.ts +218 -0
  22. package/src/browser/input/CompositionHelper.ts +246 -0
  23. package/src/browser/input/Mouse.ts +54 -0
  24. package/src/browser/input/MoveToCell.ts +249 -0
  25. package/src/browser/public/Terminal.ts +260 -0
  26. package/src/browser/renderer/dom/DomRenderer.ts +509 -0
  27. package/src/browser/renderer/dom/DomRendererRowFactory.ts +526 -0
  28. package/src/browser/renderer/dom/WidthCache.ts +160 -0
  29. package/src/browser/renderer/shared/CellColorResolver.ts +137 -0
  30. package/src/browser/renderer/shared/CharAtlasCache.ts +96 -0
  31. package/src/browser/renderer/shared/CharAtlasUtils.ts +75 -0
  32. package/src/browser/renderer/shared/Constants.ts +14 -0
  33. package/src/browser/renderer/shared/CursorBlinkStateManager.ts +146 -0
  34. package/src/browser/renderer/shared/CustomGlyphs.ts +687 -0
  35. package/src/browser/renderer/shared/DevicePixelObserver.ts +41 -0
  36. package/src/browser/renderer/shared/README.md +1 -0
  37. package/src/browser/renderer/shared/RendererUtils.ts +58 -0
  38. package/src/browser/renderer/shared/SelectionRenderModel.ts +91 -0
  39. package/src/browser/renderer/shared/TextureAtlas.ts +1082 -0
  40. package/src/browser/renderer/shared/Types.d.ts +173 -0
  41. package/src/browser/selection/SelectionModel.ts +144 -0
  42. package/src/browser/selection/Types.d.ts +15 -0
  43. package/src/browser/services/CharSizeService.ts +102 -0
  44. package/src/browser/services/CharacterJoinerService.ts +339 -0
  45. package/src/browser/services/CoreBrowserService.ts +137 -0
  46. package/src/browser/services/MouseService.ts +46 -0
  47. package/src/browser/services/RenderService.ts +279 -0
  48. package/src/browser/services/SelectionService.ts +1031 -0
  49. package/src/browser/services/Services.ts +147 -0
  50. package/src/browser/services/ThemeService.ts +237 -0
  51. package/src/common/CircularList.ts +241 -0
  52. package/src/common/Clone.ts +23 -0
  53. package/src/common/Color.ts +357 -0
  54. package/src/common/CoreTerminal.ts +284 -0
  55. package/src/common/EventEmitter.ts +78 -0
  56. package/src/common/InputHandler.ts +3461 -0
  57. package/src/common/Lifecycle.ts +108 -0
  58. package/src/common/MultiKeyMap.ts +42 -0
  59. package/src/common/Platform.ts +44 -0
  60. package/src/common/SortedList.ts +118 -0
  61. package/src/common/TaskQueue.ts +166 -0
  62. package/src/common/TypedArrayUtils.ts +17 -0
  63. package/src/common/Types.d.ts +553 -0
  64. package/src/common/WindowsMode.ts +27 -0
  65. package/src/common/buffer/AttributeData.ts +196 -0
  66. package/src/common/buffer/Buffer.ts +654 -0
  67. package/src/common/buffer/BufferLine.ts +524 -0
  68. package/src/common/buffer/BufferRange.ts +13 -0
  69. package/src/common/buffer/BufferReflow.ts +223 -0
  70. package/src/common/buffer/BufferSet.ts +134 -0
  71. package/src/common/buffer/CellData.ts +94 -0
  72. package/src/common/buffer/Constants.ts +149 -0
  73. package/src/common/buffer/Marker.ts +43 -0
  74. package/src/common/buffer/Types.d.ts +52 -0
  75. package/src/common/data/Charsets.ts +256 -0
  76. package/src/common/data/EscapeSequences.ts +153 -0
  77. package/src/common/input/Keyboard.ts +398 -0
  78. package/src/common/input/TextDecoder.ts +346 -0
  79. package/src/common/input/UnicodeV6.ts +145 -0
  80. package/src/common/input/WriteBuffer.ts +246 -0
  81. package/src/common/input/XParseColor.ts +80 -0
  82. package/src/common/parser/Constants.ts +58 -0
  83. package/src/common/parser/DcsParser.ts +192 -0
  84. package/src/common/parser/EscapeSequenceParser.ts +792 -0
  85. package/src/common/parser/OscParser.ts +238 -0
  86. package/src/common/parser/Params.ts +229 -0
  87. package/src/common/parser/Types.d.ts +275 -0
  88. package/src/common/public/AddonManager.ts +53 -0
  89. package/src/common/public/BufferApiView.ts +35 -0
  90. package/src/common/public/BufferLineApiView.ts +29 -0
  91. package/src/common/public/BufferNamespaceApi.ts +36 -0
  92. package/src/common/public/ParserApi.ts +37 -0
  93. package/src/common/public/UnicodeApi.ts +27 -0
  94. package/src/common/services/BufferService.ts +151 -0
  95. package/src/common/services/CharsetService.ts +34 -0
  96. package/src/common/services/CoreMouseService.ts +318 -0
  97. package/src/common/services/CoreService.ts +87 -0
  98. package/src/common/services/DecorationService.ts +140 -0
  99. package/src/common/services/InstantiationService.ts +85 -0
  100. package/src/common/services/LogService.ts +124 -0
  101. package/src/common/services/OptionsService.ts +202 -0
  102. package/src/common/services/OscLinkService.ts +115 -0
  103. package/src/common/services/ServiceRegistry.ts +49 -0
  104. package/src/common/services/Services.ts +373 -0
  105. package/src/common/services/UnicodeService.ts +111 -0
  106. package/src/headless/Terminal.ts +136 -0
  107. package/src/headless/public/Terminal.ts +195 -0
  108. package/typings/xterm.d.ts +1857 -0
@@ -0,0 +1,1031 @@
1
+ /**
2
+ * Copyright (c) 2017 The xterm.js authors. All rights reserved.
3
+ * @license MIT
4
+ */
5
+
6
+ import { IBufferRange, ILinkifier2 } from 'browser/Types';
7
+ import { getCoordsRelativeToElement } from 'browser/input/Mouse';
8
+ import { moveToCellSequence } from 'browser/input/MoveToCell';
9
+ import { SelectionModel } from 'browser/selection/SelectionModel';
10
+ import { ISelectionRedrawRequestEvent, ISelectionRequestScrollLinesEvent } from 'browser/selection/Types';
11
+ import { ICoreBrowserService, IMouseService, IRenderService, ISelectionService } from 'browser/services/Services';
12
+ import { EventEmitter } from 'common/EventEmitter';
13
+ import { Disposable, toDisposable } from 'common/Lifecycle';
14
+ import * as Browser from 'common/Platform';
15
+ import { IBufferLine, IDisposable } from 'common/Types';
16
+ import { getRangeLength } from 'common/buffer/BufferRange';
17
+ import { CellData } from 'common/buffer/CellData';
18
+ import { IBuffer } from 'common/buffer/Types';
19
+ import { IBufferService, ICoreService, IOptionsService } from 'common/services/Services';
20
+
21
+ /**
22
+ * The number of pixels the mouse needs to be above or below the viewport in
23
+ * order to scroll at the maximum speed.
24
+ */
25
+ const DRAG_SCROLL_MAX_THRESHOLD = 50;
26
+
27
+ /**
28
+ * The maximum scrolling speed
29
+ */
30
+ const DRAG_SCROLL_MAX_SPEED = 15;
31
+
32
+ /**
33
+ * The number of milliseconds between drag scroll updates.
34
+ */
35
+ const DRAG_SCROLL_INTERVAL = 50;
36
+
37
+ /**
38
+ * The maximum amount of time that can have elapsed for an alt click to move the
39
+ * cursor.
40
+ */
41
+ const ALT_CLICK_MOVE_CURSOR_TIME = 500;
42
+
43
+ const NON_BREAKING_SPACE_CHAR = String.fromCharCode(160);
44
+ const ALL_NON_BREAKING_SPACE_REGEX = new RegExp(NON_BREAKING_SPACE_CHAR, 'g');
45
+
46
+ /**
47
+ * Represents a position of a word on a line.
48
+ */
49
+ interface IWordPosition {
50
+ start: number;
51
+ length: number;
52
+ }
53
+
54
+ /**
55
+ * A selection mode, this drives how the selection behaves on mouse move.
56
+ */
57
+ export const enum SelectionMode {
58
+ NORMAL,
59
+ WORD,
60
+ LINE,
61
+ COLUMN
62
+ }
63
+
64
+ /**
65
+ * A class that manages the selection of the terminal. With help from
66
+ * SelectionModel, SelectionService handles with all logic associated with
67
+ * dealing with the selection, including handling mouse interaction, wide
68
+ * characters and fetching the actual text within the selection. Rendering is
69
+ * not handled by the SelectionService but the onRedrawRequest event is fired
70
+ * when the selection is ready to be redrawn (on an animation frame).
71
+ */
72
+ export class SelectionService extends Disposable implements ISelectionService {
73
+ public serviceBrand: undefined;
74
+
75
+ protected _model: SelectionModel;
76
+
77
+ /**
78
+ * The amount to scroll every drag scroll update (depends on how far the mouse
79
+ * drag is above or below the terminal).
80
+ */
81
+ private _dragScrollAmount: number = 0;
82
+
83
+ /**
84
+ * The current selection mode.
85
+ */
86
+ protected _activeSelectionMode: SelectionMode;
87
+
88
+ /**
89
+ * A setInterval timer that is active while the mouse is down whose callback
90
+ * scrolls the viewport when necessary.
91
+ */
92
+ private _dragScrollIntervalTimer: number | undefined;
93
+
94
+ /**
95
+ * The animation frame ID used for refreshing the selection.
96
+ */
97
+ private _refreshAnimationFrame: number | undefined;
98
+
99
+ /**
100
+ * Whether selection is enabled.
101
+ */
102
+ private _enabled = true;
103
+
104
+ private _mouseMoveListener: EventListener;
105
+ private _mouseUpListener: EventListener;
106
+ private _trimListener: IDisposable;
107
+ private _workCell: CellData = new CellData();
108
+
109
+ private _mouseDownTimeStamp: number = 0;
110
+ private _oldHasSelection: boolean = false;
111
+ private _oldSelectionStart: [number, number] | undefined = undefined;
112
+ private _oldSelectionEnd: [number, number] | undefined = undefined;
113
+
114
+ private readonly _onLinuxMouseSelection = this.register(new EventEmitter<string>());
115
+ public readonly onLinuxMouseSelection = this._onLinuxMouseSelection.event;
116
+ private readonly _onRedrawRequest = this.register(new EventEmitter<ISelectionRedrawRequestEvent>());
117
+ public readonly onRequestRedraw = this._onRedrawRequest.event;
118
+ private readonly _onSelectionChange = this.register(new EventEmitter<void>());
119
+ public readonly onSelectionChange = this._onSelectionChange.event;
120
+ private readonly _onRequestScrollLines = this.register(new EventEmitter<ISelectionRequestScrollLinesEvent>());
121
+ public readonly onRequestScrollLines = this._onRequestScrollLines.event;
122
+
123
+ constructor(
124
+ private readonly _element: HTMLElement,
125
+ private readonly _screenElement: HTMLElement,
126
+ private readonly _linkifier: ILinkifier2,
127
+ @IBufferService private readonly _bufferService: IBufferService,
128
+ @ICoreService private readonly _coreService: ICoreService,
129
+ @IMouseService private readonly _mouseService: IMouseService,
130
+ @IOptionsService private readonly _optionsService: IOptionsService,
131
+ @IRenderService private readonly _renderService: IRenderService,
132
+ @ICoreBrowserService private readonly _coreBrowserService: ICoreBrowserService
133
+ ) {
134
+ super();
135
+
136
+ // Init listeners
137
+ this._mouseMoveListener = event => this._handleMouseMove(event as MouseEvent);
138
+ this._mouseUpListener = event => this._handleMouseUp(event as MouseEvent);
139
+ this._coreService.onUserInput(() => {
140
+ if (this.hasSelection) {
141
+ this.clearSelection();
142
+ }
143
+ });
144
+ this._trimListener = this._bufferService.buffer.lines.onTrim(amount => this._handleTrim(amount));
145
+ this.register(this._bufferService.buffers.onBufferActivate(e => this._handleBufferActivate(e)));
146
+
147
+ this.enable();
148
+
149
+ this._model = new SelectionModel(this._bufferService);
150
+ this._activeSelectionMode = SelectionMode.NORMAL;
151
+
152
+ this.register(toDisposable(() => {
153
+ this._removeMouseDownListeners();
154
+ }));
155
+ }
156
+
157
+ public reset(): void {
158
+ this.clearSelection();
159
+ }
160
+
161
+ /**
162
+ * Disables the selection manager. This is useful for when terminal mouse
163
+ * are enabled.
164
+ */
165
+ public disable(): void {
166
+ this.clearSelection();
167
+ this._enabled = false;
168
+ }
169
+
170
+ /**
171
+ * Enable the selection manager.
172
+ */
173
+ public enable(): void {
174
+ this._enabled = true;
175
+ }
176
+
177
+ public get selectionStart(): [number, number] | undefined { return this._model.finalSelectionStart; }
178
+ public get selectionEnd(): [number, number] | undefined { return this._model.finalSelectionEnd; }
179
+
180
+ /**
181
+ * Gets whether there is an active text selection.
182
+ */
183
+ public get hasSelection(): boolean {
184
+ const start = this._model.finalSelectionStart;
185
+ const end = this._model.finalSelectionEnd;
186
+ if (!start || !end) {
187
+ return false;
188
+ }
189
+ return start[0] !== end[0] || start[1] !== end[1];
190
+ }
191
+
192
+ /**
193
+ * Gets the text currently selected.
194
+ */
195
+ public get selectionText(): string {
196
+ const start = this._model.finalSelectionStart;
197
+ const end = this._model.finalSelectionEnd;
198
+ if (!start || !end) {
199
+ return '';
200
+ }
201
+
202
+ const buffer = this._bufferService.buffer;
203
+ const result: string[] = [];
204
+
205
+ if (this._activeSelectionMode === SelectionMode.COLUMN) {
206
+ // Ignore zero width selections
207
+ if (start[0] === end[0]) {
208
+ return '';
209
+ }
210
+
211
+ // For column selection it's not enough to rely on final selection's swapping of reversed
212
+ // values, it also needs the x coordinates to swap independently of the y coordinate is needed
213
+ const startCol = start[0] < end[0] ? start[0] : end[0];
214
+ const endCol = start[0] < end[0] ? end[0] : start[0];
215
+ for (let i = start[1]; i <= end[1]; i++) {
216
+ const lineText = buffer.translateBufferLineToString(i, true, startCol, endCol);
217
+ result.push(lineText);
218
+ }
219
+ } else {
220
+ // Get first row
221
+ const startRowEndCol = start[1] === end[1] ? end[0] : undefined;
222
+ result.push(buffer.translateBufferLineToString(start[1], true, start[0], startRowEndCol));
223
+
224
+ // Get middle rows
225
+ for (let i = start[1] + 1; i <= end[1] - 1; i++) {
226
+ const bufferLine = buffer.lines.get(i);
227
+ const lineText = buffer.translateBufferLineToString(i, true);
228
+ if (bufferLine?.isWrapped) {
229
+ result[result.length - 1] += lineText;
230
+ } else {
231
+ result.push(lineText);
232
+ }
233
+ }
234
+
235
+ // Get final row
236
+ if (start[1] !== end[1]) {
237
+ const bufferLine = buffer.lines.get(end[1]);
238
+ const lineText = buffer.translateBufferLineToString(end[1], true, 0, end[0]);
239
+ if (bufferLine && bufferLine!.isWrapped) {
240
+ result[result.length - 1] += lineText;
241
+ } else {
242
+ result.push(lineText);
243
+ }
244
+ }
245
+ }
246
+
247
+ // Format string by replacing non-breaking space chars with regular spaces
248
+ // and joining the array into a multi-line string.
249
+ const formattedResult = result.map(line => {
250
+ return line.replace(ALL_NON_BREAKING_SPACE_REGEX, ' ');
251
+ }).join(Browser.isWindows ? '\r\n' : '\n');
252
+
253
+ return formattedResult;
254
+ }
255
+
256
+ /**
257
+ * Clears the current terminal selection.
258
+ */
259
+ public clearSelection(): void {
260
+ this._model.clearSelection();
261
+ this._removeMouseDownListeners();
262
+ this.refresh();
263
+ this._onSelectionChange.fire();
264
+ }
265
+
266
+ /**
267
+ * Queues a refresh, redrawing the selection on the next opportunity.
268
+ * @param isLinuxMouseSelection Whether the selection should be registered as a new
269
+ * selection on Linux.
270
+ */
271
+ public refresh(isLinuxMouseSelection?: boolean): void {
272
+ // Queue the refresh for the renderer
273
+ if (!this._refreshAnimationFrame) {
274
+ this._refreshAnimationFrame = this._coreBrowserService.window.requestAnimationFrame(() => this._refresh());
275
+ }
276
+
277
+ // If the platform is Linux and the refresh call comes from a mouse event,
278
+ // we need to update the selection for middle click to paste selection.
279
+ if (Browser.isLinux && isLinuxMouseSelection) {
280
+ const selectionText = this.selectionText;
281
+ if (selectionText.length) {
282
+ this._onLinuxMouseSelection.fire(this.selectionText);
283
+ }
284
+ }
285
+ }
286
+
287
+ /**
288
+ * Fires the refresh event, causing consumers to pick it up and redraw the
289
+ * selection state.
290
+ */
291
+ private _refresh(): void {
292
+ this._refreshAnimationFrame = undefined;
293
+ this._onRedrawRequest.fire({
294
+ start: this._model.finalSelectionStart,
295
+ end: this._model.finalSelectionEnd,
296
+ columnSelectMode: this._activeSelectionMode === SelectionMode.COLUMN
297
+ });
298
+ }
299
+
300
+ /**
301
+ * Checks if the current click was inside the current selection
302
+ * @param event The mouse event
303
+ */
304
+ private _isClickInSelection(event: MouseEvent): boolean {
305
+ const coords = this._getMouseBufferCoords(event);
306
+ const start = this._model.finalSelectionStart;
307
+ const end = this._model.finalSelectionEnd;
308
+
309
+ if (!start || !end || !coords) {
310
+ return false;
311
+ }
312
+
313
+ return this._areCoordsInSelection(coords, start, end);
314
+ }
315
+
316
+ public isCellInSelection(x: number, y: number): boolean {
317
+ const start = this._model.finalSelectionStart;
318
+ const end = this._model.finalSelectionEnd;
319
+ if (!start || !end) {
320
+ return false;
321
+ }
322
+ return this._areCoordsInSelection([x, y], start, end);
323
+ }
324
+
325
+ protected _areCoordsInSelection(coords: [number, number], start: [number, number], end: [number, number]): boolean {
326
+ return (coords[1] > start[1] && coords[1] < end[1]) ||
327
+ (start[1] === end[1] && coords[1] === start[1] && coords[0] >= start[0] && coords[0] < end[0]) ||
328
+ (start[1] < end[1] && coords[1] === end[1] && coords[0] < end[0]) ||
329
+ (start[1] < end[1] && coords[1] === start[1] && coords[0] >= start[0]);
330
+ }
331
+
332
+ /**
333
+ * Selects word at the current mouse event coordinates.
334
+ * @param event The mouse event.
335
+ */
336
+ private _selectWordAtCursor(event: MouseEvent, allowWhitespaceOnlySelection: boolean): boolean {
337
+ // Check if there is a link under the cursor first and select that if so
338
+ const range = this._linkifier.currentLink?.link?.range;
339
+ if (range) {
340
+ this._model.selectionStart = [range.start.x - 1, range.start.y - 1];
341
+ this._model.selectionStartLength = getRangeLength(range, this._bufferService.cols);
342
+ this._model.selectionEnd = undefined;
343
+ return true;
344
+ }
345
+
346
+ const coords = this._getMouseBufferCoords(event);
347
+ if (coords) {
348
+ this._selectWordAt(coords, allowWhitespaceOnlySelection);
349
+ this._model.selectionEnd = undefined;
350
+ return true;
351
+ }
352
+ return false;
353
+ }
354
+
355
+ /**
356
+ * Selects all text within the terminal.
357
+ */
358
+ public selectAll(): void {
359
+ this._model.isSelectAllActive = true;
360
+ this.refresh();
361
+ this._onSelectionChange.fire();
362
+ }
363
+
364
+ public selectLines(start: number, end: number): void {
365
+ this._model.clearSelection();
366
+ start = Math.max(start, 0);
367
+ end = Math.min(end, this._bufferService.buffer.lines.length - 1);
368
+ this._model.selectionStart = [0, start];
369
+ this._model.selectionEnd = [this._bufferService.cols, end];
370
+ this.refresh();
371
+ this._onSelectionChange.fire();
372
+ }
373
+
374
+ /**
375
+ * Handle the buffer being trimmed, adjust the selection position.
376
+ * @param amount The amount the buffer is being trimmed.
377
+ */
378
+ private _handleTrim(amount: number): void {
379
+ const needsRefresh = this._model.handleTrim(amount);
380
+ if (needsRefresh) {
381
+ this.refresh();
382
+ }
383
+ }
384
+
385
+ /**
386
+ * Gets the 0-based [x, y] buffer coordinates of the current mouse event.
387
+ * @param event The mouse event.
388
+ */
389
+ private _getMouseBufferCoords(event: MouseEvent): [number, number] | undefined {
390
+ const coords = this._mouseService.getCoords(event, this._screenElement, this._bufferService.cols, this._bufferService.rows, true);
391
+ if (!coords) {
392
+ return undefined;
393
+ }
394
+
395
+ // Convert to 0-based
396
+ coords[0]--;
397
+ coords[1]--;
398
+
399
+ // Convert viewport coords to buffer coords
400
+ coords[1] += this._bufferService.buffer.ydisp;
401
+ return coords;
402
+ }
403
+
404
+ /**
405
+ * Gets the amount the viewport should be scrolled based on how far out of the
406
+ * terminal the mouse is.
407
+ * @param event The mouse event.
408
+ */
409
+ private _getMouseEventScrollAmount(event: MouseEvent): number {
410
+ let offset = getCoordsRelativeToElement(this._coreBrowserService.window, event, this._screenElement)[1];
411
+ const terminalHeight = this._renderService.dimensions.css.canvas.height;
412
+ if (offset >= 0 && offset <= terminalHeight) {
413
+ return 0;
414
+ }
415
+ if (offset > terminalHeight) {
416
+ offset -= terminalHeight;
417
+ }
418
+
419
+ offset = Math.min(Math.max(offset, -DRAG_SCROLL_MAX_THRESHOLD), DRAG_SCROLL_MAX_THRESHOLD);
420
+ offset /= DRAG_SCROLL_MAX_THRESHOLD;
421
+ return (offset / Math.abs(offset)) + Math.round(offset * (DRAG_SCROLL_MAX_SPEED - 1));
422
+ }
423
+
424
+ /**
425
+ * Returns whether the selection manager should force selection, regardless of
426
+ * whether the terminal is in mouse events mode.
427
+ * @param event The mouse event.
428
+ */
429
+ public shouldForceSelection(event: MouseEvent): boolean {
430
+ if (Browser.isMac) {
431
+ return event.altKey && this._optionsService.rawOptions.macOptionClickForcesSelection;
432
+ }
433
+
434
+ return event.shiftKey;
435
+ }
436
+
437
+ /**
438
+ * Handles te mousedown event, setting up for a new selection.
439
+ * @param event The mousedown event.
440
+ */
441
+ public handleMouseDown(event: MouseEvent): void {
442
+ this._mouseDownTimeStamp = event.timeStamp;
443
+ // If we have selection, we want the context menu on right click even if the
444
+ // terminal is in mouse mode.
445
+ if (event.button === 2 && this.hasSelection) {
446
+ return;
447
+ }
448
+
449
+ // Only action the primary button
450
+ if (event.button !== 0) {
451
+ return;
452
+ }
453
+
454
+ // Allow selection when using a specific modifier key, even when disabled
455
+ if (!this._enabled) {
456
+ if (!this.shouldForceSelection(event)) {
457
+ return;
458
+ }
459
+
460
+ // Don't send the mouse down event to the current process, we want to select
461
+ event.stopPropagation();
462
+ }
463
+
464
+ // Tell the browser not to start a regular selection
465
+ event.preventDefault();
466
+
467
+ // Reset drag scroll state
468
+ this._dragScrollAmount = 0;
469
+
470
+ if (this._enabled && event.shiftKey) {
471
+ this._handleIncrementalClick(event);
472
+ } else {
473
+ if (event.detail === 1) {
474
+ this._handleSingleClick(event);
475
+ } else if (event.detail === 2) {
476
+ this._handleDoubleClick(event);
477
+ } else if (event.detail === 3) {
478
+ this._handleTripleClick(event);
479
+ }
480
+ }
481
+
482
+ this._addMouseDownListeners();
483
+ this.refresh(true);
484
+ }
485
+
486
+ /**
487
+ * Adds listeners when mousedown is triggered.
488
+ */
489
+ private _addMouseDownListeners(): void {
490
+ // Listen on the document so that dragging outside of viewport works
491
+ if (this._screenElement.ownerDocument) {
492
+ this._screenElement.ownerDocument.addEventListener('mousemove', this._mouseMoveListener);
493
+ this._screenElement.ownerDocument.addEventListener('mouseup', this._mouseUpListener);
494
+ }
495
+ this._dragScrollIntervalTimer = this._coreBrowserService.window.setInterval(() => this._dragScroll(), DRAG_SCROLL_INTERVAL);
496
+ }
497
+
498
+ /**
499
+ * Removes the listeners that are registered when mousedown is triggered.
500
+ */
501
+ private _removeMouseDownListeners(): void {
502
+ if (this._screenElement.ownerDocument) {
503
+ this._screenElement.ownerDocument.removeEventListener('mousemove', this._mouseMoveListener);
504
+ this._screenElement.ownerDocument.removeEventListener('mouseup', this._mouseUpListener);
505
+ }
506
+ this._coreBrowserService.window.clearInterval(this._dragScrollIntervalTimer);
507
+ this._dragScrollIntervalTimer = undefined;
508
+ }
509
+
510
+ /**
511
+ * Performs an incremental click, setting the selection end position to the mouse
512
+ * position.
513
+ * @param event The mouse event.
514
+ */
515
+ private _handleIncrementalClick(event: MouseEvent): void {
516
+ if (this._model.selectionStart) {
517
+ this._model.selectionEnd = this._getMouseBufferCoords(event);
518
+ }
519
+ }
520
+
521
+ /**
522
+ * Performs a single click, resetting relevant state and setting the selection
523
+ * start position.
524
+ * @param event The mouse event.
525
+ */
526
+ private _handleSingleClick(event: MouseEvent): void {
527
+ this._model.selectionStartLength = 0;
528
+ this._model.isSelectAllActive = false;
529
+ this._activeSelectionMode = this.shouldColumnSelect(event) ? SelectionMode.COLUMN : SelectionMode.NORMAL;
530
+
531
+ // Initialize the new selection
532
+ this._model.selectionStart = this._getMouseBufferCoords(event);
533
+ if (!this._model.selectionStart) {
534
+ return;
535
+ }
536
+ this._model.selectionEnd = undefined;
537
+
538
+ // Ensure the line exists
539
+ const line = this._bufferService.buffer.lines.get(this._model.selectionStart[1]);
540
+ if (!line) {
541
+ return;
542
+ }
543
+
544
+ // Return early if the click event is not in the buffer (eg. in scroll bar)
545
+ if (line.length === this._model.selectionStart[0]) {
546
+ return;
547
+ }
548
+
549
+ // If the mouse is over the second half of a wide character, adjust the
550
+ // selection to cover the whole character
551
+ if (line.hasWidth(this._model.selectionStart[0]) === 0) {
552
+ this._model.selectionStart[0]++;
553
+ }
554
+ }
555
+
556
+ /**
557
+ * Performs a double click, selecting the current word.
558
+ * @param event The mouse event.
559
+ */
560
+ private _handleDoubleClick(event: MouseEvent): void {
561
+ if (this._selectWordAtCursor(event, true)) {
562
+ this._activeSelectionMode = SelectionMode.WORD;
563
+ }
564
+ }
565
+
566
+ /**
567
+ * Performs a triple click, selecting the current line and activating line
568
+ * select mode.
569
+ * @param event The mouse event.
570
+ */
571
+ private _handleTripleClick(event: MouseEvent): void {
572
+ const coords = this._getMouseBufferCoords(event);
573
+ if (coords) {
574
+ this._activeSelectionMode = SelectionMode.LINE;
575
+ this._selectLineAt(coords[1]);
576
+ }
577
+ }
578
+
579
+ /**
580
+ * Returns whether the selection manager should operate in column select mode
581
+ * @param event the mouse or keyboard event
582
+ */
583
+ public shouldColumnSelect(event: KeyboardEvent | MouseEvent): boolean {
584
+ return event.altKey && !(Browser.isMac && this._optionsService.rawOptions.macOptionClickForcesSelection);
585
+ }
586
+
587
+ /**
588
+ * Handles the mousemove event when the mouse button is down, recording the
589
+ * end of the selection and refreshing the selection.
590
+ * @param event The mousemove event.
591
+ */
592
+ private _handleMouseMove(event: MouseEvent): void {
593
+ // If the mousemove listener is active it means that a selection is
594
+ // currently being made, we should stop propagation to prevent mouse events
595
+ // to be sent to the pty.
596
+ event.stopImmediatePropagation();
597
+
598
+ // Do nothing if there is no selection start, this can happen if the first
599
+ // click in the terminal is an incremental click
600
+ if (!this._model.selectionStart) {
601
+ return;
602
+ }
603
+
604
+ // Record the previous position so we know whether to redraw the selection
605
+ // at the end.
606
+ const previousSelectionEnd = this._model.selectionEnd ? [this._model.selectionEnd[0], this._model.selectionEnd[1]] : null;
607
+
608
+ // Set the initial selection end based on the mouse coordinates
609
+ this._model.selectionEnd = this._getMouseBufferCoords(event);
610
+ if (!this._model.selectionEnd) {
611
+ this.refresh(true);
612
+ return;
613
+ }
614
+
615
+ // Select the entire line if line select mode is active.
616
+ if (this._activeSelectionMode === SelectionMode.LINE) {
617
+ if (this._model.selectionEnd[1] < this._model.selectionStart[1]) {
618
+ this._model.selectionEnd[0] = 0;
619
+ } else {
620
+ this._model.selectionEnd[0] = this._bufferService.cols;
621
+ }
622
+ } else if (this._activeSelectionMode === SelectionMode.WORD) {
623
+ this._selectToWordAt(this._model.selectionEnd);
624
+ }
625
+
626
+ // Determine the amount of scrolling that will happen.
627
+ this._dragScrollAmount = this._getMouseEventScrollAmount(event);
628
+
629
+ // If the cursor was above or below the viewport, make sure it's at the
630
+ // start or end of the viewport respectively. This should only happen when
631
+ // NOT in column select mode.
632
+ if (this._activeSelectionMode !== SelectionMode.COLUMN) {
633
+ if (this._dragScrollAmount > 0) {
634
+ this._model.selectionEnd[0] = this._bufferService.cols;
635
+ } else if (this._dragScrollAmount < 0) {
636
+ this._model.selectionEnd[0] = 0;
637
+ }
638
+ }
639
+
640
+ // If the character is a wide character include the cell to the right in the
641
+ // selection. Note that selections at the very end of the line will never
642
+ // have a character.
643
+ const buffer = this._bufferService.buffer;
644
+ if (this._model.selectionEnd[1] < buffer.lines.length) {
645
+ const line = buffer.lines.get(this._model.selectionEnd[1]);
646
+ if (line && line.hasWidth(this._model.selectionEnd[0]) === 0) {
647
+ if (this._model.selectionEnd[0] < this._bufferService.cols) {
648
+ this._model.selectionEnd[0]++;
649
+ }
650
+ }
651
+ }
652
+
653
+ // Only draw here if the selection changes.
654
+ if (!previousSelectionEnd ||
655
+ previousSelectionEnd[0] !== this._model.selectionEnd[0] ||
656
+ previousSelectionEnd[1] !== this._model.selectionEnd[1]) {
657
+ this.refresh(true);
658
+ }
659
+ }
660
+
661
+ /**
662
+ * The callback that occurs every DRAG_SCROLL_INTERVAL ms that does the
663
+ * scrolling of the viewport.
664
+ */
665
+ private _dragScroll(): void {
666
+ if (!this._model.selectionEnd || !this._model.selectionStart) {
667
+ return;
668
+ }
669
+ if (this._dragScrollAmount) {
670
+ this._onRequestScrollLines.fire({ amount: this._dragScrollAmount, suppressScrollEvent: false });
671
+ // Re-evaluate selection
672
+ // If the cursor was above or below the viewport, make sure it's at the
673
+ // start or end of the viewport respectively. This should only happen when
674
+ // NOT in column select mode.
675
+ const buffer = this._bufferService.buffer;
676
+ if (this._dragScrollAmount > 0) {
677
+ if (this._activeSelectionMode !== SelectionMode.COLUMN) {
678
+ this._model.selectionEnd[0] = this._bufferService.cols;
679
+ }
680
+ this._model.selectionEnd[1] = Math.min(buffer.ydisp + this._bufferService.rows, buffer.lines.length - 1);
681
+ } else {
682
+ if (this._activeSelectionMode !== SelectionMode.COLUMN) {
683
+ this._model.selectionEnd[0] = 0;
684
+ }
685
+ this._model.selectionEnd[1] = buffer.ydisp;
686
+ }
687
+ this.refresh();
688
+ }
689
+ }
690
+
691
+ /**
692
+ * Handles the mouseup event, removing the mousedown listeners.
693
+ * @param event The mouseup event.
694
+ */
695
+ private _handleMouseUp(event: MouseEvent): void {
696
+ const timeElapsed = event.timeStamp - this._mouseDownTimeStamp;
697
+
698
+ this._removeMouseDownListeners();
699
+
700
+ if (this.selectionText.length <= 1 && timeElapsed < ALT_CLICK_MOVE_CURSOR_TIME && event.altKey && this._optionsService.rawOptions.altClickMovesCursor) {
701
+ if (this._bufferService.buffer.ybase === this._bufferService.buffer.ydisp) {
702
+ const coordinates = this._mouseService.getCoords(
703
+ event,
704
+ this._element,
705
+ this._bufferService.cols,
706
+ this._bufferService.rows,
707
+ false
708
+ );
709
+ if (coordinates && coordinates[0] !== undefined && coordinates[1] !== undefined) {
710
+ const sequence = moveToCellSequence(coordinates[0] - 1, coordinates[1] - 1, this._bufferService, this._coreService.decPrivateModes.applicationCursorKeys);
711
+ this._coreService.triggerDataEvent(sequence, true);
712
+ }
713
+ }
714
+ } else {
715
+ this._fireEventIfSelectionChanged();
716
+ }
717
+ }
718
+
719
+ private _fireEventIfSelectionChanged(): void {
720
+ const start = this._model.finalSelectionStart;
721
+ const end = this._model.finalSelectionEnd;
722
+ const hasSelection = !!start && !!end && (start[0] !== end[0] || start[1] !== end[1]);
723
+
724
+ if (!hasSelection) {
725
+ if (this._oldHasSelection) {
726
+ this._fireOnSelectionChange(start, end, hasSelection);
727
+ }
728
+ return;
729
+ }
730
+
731
+ // Sanity check, these should not be undefined as there is a selection
732
+ if (!start || !end) {
733
+ return;
734
+ }
735
+
736
+ if (!this._oldSelectionStart || !this._oldSelectionEnd || (
737
+ start[0] !== this._oldSelectionStart[0] || start[1] !== this._oldSelectionStart[1] ||
738
+ end[0] !== this._oldSelectionEnd[0] || end[1] !== this._oldSelectionEnd[1])) {
739
+
740
+ this._fireOnSelectionChange(start, end, hasSelection);
741
+ }
742
+ }
743
+
744
+ private _fireOnSelectionChange(start: [number, number] | undefined, end: [number, number] | undefined, hasSelection: boolean): void {
745
+ this._oldSelectionStart = start;
746
+ this._oldSelectionEnd = end;
747
+ this._oldHasSelection = hasSelection;
748
+ this._onSelectionChange.fire();
749
+ }
750
+
751
+ private _handleBufferActivate(e: {activeBuffer: IBuffer, inactiveBuffer: IBuffer}): void {
752
+ this.clearSelection();
753
+ // Only adjust the selection on trim, shiftElements is rarely used (only in
754
+ // reverseIndex) and delete in a splice is only ever used when the same
755
+ // number of elements was just added. Given this is could actually be
756
+ // beneficial to leave the selection as is for these cases.
757
+ this._trimListener.dispose();
758
+ this._trimListener = e.activeBuffer.lines.onTrim(amount => this._handleTrim(amount));
759
+ }
760
+
761
+ /**
762
+ * Converts a viewport column (0 to cols - 1) to the character index on the
763
+ * buffer line, the latter takes into account wide and null characters.
764
+ * @param bufferLine The buffer line to use.
765
+ * @param x The x index in the buffer line to convert.
766
+ */
767
+ private _convertViewportColToCharacterIndex(bufferLine: IBufferLine, x: number): number {
768
+ let charIndex = x;
769
+ for (let i = 0; x >= i; i++) {
770
+ const length = bufferLine.loadCell(i, this._workCell).getChars().length;
771
+ if (this._workCell.getWidth() === 0) {
772
+ // Wide characters aren't included in the line string so decrement the
773
+ // index so the index is back on the wide character.
774
+ charIndex--;
775
+ } else if (length > 1 && x !== i) {
776
+ // Emojis take up multiple characters, so adjust accordingly. For these
777
+ // we don't want ot include the character at the column as we're
778
+ // returning the start index in the string, not the end index.
779
+ charIndex += length - 1;
780
+ }
781
+ }
782
+ return charIndex;
783
+ }
784
+
785
+ public setSelection(col: number, row: number, length: number): void {
786
+ this._model.clearSelection();
787
+ this._removeMouseDownListeners();
788
+ this._model.selectionStart = [col, row];
789
+ this._model.selectionStartLength = length;
790
+ this.refresh();
791
+ this._fireEventIfSelectionChanged();
792
+ }
793
+
794
+ public rightClickSelect(ev: MouseEvent): void {
795
+ if (!this._isClickInSelection(ev)) {
796
+ if (this._selectWordAtCursor(ev, false)) {
797
+ this.refresh(true);
798
+ }
799
+ this._fireEventIfSelectionChanged();
800
+ }
801
+ }
802
+
803
+ /**
804
+ * Gets positional information for the word at the coordinated specified.
805
+ * @param coords The coordinates to get the word at.
806
+ */
807
+ private _getWordAt(coords: [number, number], allowWhitespaceOnlySelection: boolean, followWrappedLinesAbove: boolean = true, followWrappedLinesBelow: boolean = true): IWordPosition | undefined {
808
+ // Ensure coords are within viewport (eg. not within scroll bar)
809
+ if (coords[0] >= this._bufferService.cols) {
810
+ return undefined;
811
+ }
812
+
813
+ const buffer = this._bufferService.buffer;
814
+ const bufferLine = buffer.lines.get(coords[1]);
815
+ if (!bufferLine) {
816
+ return undefined;
817
+ }
818
+
819
+ const line = buffer.translateBufferLineToString(coords[1], false);
820
+
821
+ // Get actual index, taking into consideration wide characters
822
+ let startIndex = this._convertViewportColToCharacterIndex(bufferLine, coords[0]);
823
+ let endIndex = startIndex;
824
+
825
+ // Record offset to be used later
826
+ const charOffset = coords[0] - startIndex;
827
+ let leftWideCharCount = 0;
828
+ let rightWideCharCount = 0;
829
+ let leftLongCharOffset = 0;
830
+ let rightLongCharOffset = 0;
831
+
832
+ if (line.charAt(startIndex) === ' ') {
833
+ // Expand until non-whitespace is hit
834
+ while (startIndex > 0 && line.charAt(startIndex - 1) === ' ') {
835
+ startIndex--;
836
+ }
837
+ while (endIndex < line.length && line.charAt(endIndex + 1) === ' ') {
838
+ endIndex++;
839
+ }
840
+ } else {
841
+ // Expand until whitespace is hit. This algorithm works by scanning left
842
+ // and right from the starting position, keeping both the index format
843
+ // (line) and the column format (bufferLine) in sync. When a wide
844
+ // character is hit, it is recorded and the column index is adjusted.
845
+ let startCol = coords[0];
846
+ let endCol = coords[0];
847
+
848
+ // Consider the initial position, skip it and increment the wide char
849
+ // variable
850
+ if (bufferLine.getWidth(startCol) === 0) {
851
+ leftWideCharCount++;
852
+ startCol--;
853
+ }
854
+ if (bufferLine.getWidth(endCol) === 2) {
855
+ rightWideCharCount++;
856
+ endCol++;
857
+ }
858
+
859
+ // Adjust the end index for characters whose length are > 1 (emojis)
860
+ const length = bufferLine.getString(endCol).length;
861
+ if (length > 1) {
862
+ rightLongCharOffset += length - 1;
863
+ endIndex += length - 1;
864
+ }
865
+
866
+ // Expand the string in both directions until a space is hit
867
+ while (startCol > 0 && startIndex > 0 && !this._isCharWordSeparator(bufferLine.loadCell(startCol - 1, this._workCell))) {
868
+ bufferLine.loadCell(startCol - 1, this._workCell);
869
+ const length = this._workCell.getChars().length;
870
+ if (this._workCell.getWidth() === 0) {
871
+ // If the next character is a wide char, record it and skip the column
872
+ leftWideCharCount++;
873
+ startCol--;
874
+ } else if (length > 1) {
875
+ // If the next character's string is longer than 1 char (eg. emoji),
876
+ // adjust the index
877
+ leftLongCharOffset += length - 1;
878
+ startIndex -= length - 1;
879
+ }
880
+ startIndex--;
881
+ startCol--;
882
+ }
883
+ while (endCol < bufferLine.length && endIndex + 1 < line.length && !this._isCharWordSeparator(bufferLine.loadCell(endCol + 1, this._workCell))) {
884
+ bufferLine.loadCell(endCol + 1, this._workCell);
885
+ const length = this._workCell.getChars().length;
886
+ if (this._workCell.getWidth() === 2) {
887
+ // If the next character is a wide char, record it and skip the column
888
+ rightWideCharCount++;
889
+ endCol++;
890
+ } else if (length > 1) {
891
+ // If the next character's string is longer than 1 char (eg. emoji),
892
+ // adjust the index
893
+ rightLongCharOffset += length - 1;
894
+ endIndex += length - 1;
895
+ }
896
+ endIndex++;
897
+ endCol++;
898
+ }
899
+ }
900
+
901
+ // Incremenet the end index so it is at the start of the next character
902
+ endIndex++;
903
+
904
+ // Calculate the start _column_, converting the the string indexes back to
905
+ // column coordinates.
906
+ let start =
907
+ startIndex // The index of the selection's start char in the line string
908
+ + charOffset // The difference between the initial char's column and index
909
+ - leftWideCharCount // The number of wide chars left of the initial char
910
+ + leftLongCharOffset; // The number of additional chars left of the initial char added by columns with strings longer than 1 (emojis)
911
+
912
+ // Calculate the length in _columns_, converting the the string indexes back
913
+ // to column coordinates.
914
+ let length = Math.min(this._bufferService.cols, // Disallow lengths larger than the terminal cols
915
+ endIndex // The index of the selection's end char in the line string
916
+ - startIndex // The index of the selection's start char in the line string
917
+ + leftWideCharCount // The number of wide chars left of the initial char
918
+ + rightWideCharCount // The number of wide chars right of the initial char (inclusive)
919
+ - leftLongCharOffset // The number of additional chars left of the initial char added by columns with strings longer than 1 (emojis)
920
+ - rightLongCharOffset); // The number of additional chars right of the initial char (inclusive) added by columns with strings longer than 1 (emojis)
921
+
922
+ if (!allowWhitespaceOnlySelection && line.slice(startIndex, endIndex).trim() === '') {
923
+ return undefined;
924
+ }
925
+
926
+ // Recurse upwards if the line is wrapped and the word wraps to the above line
927
+ if (followWrappedLinesAbove) {
928
+ if (start === 0 && bufferLine.getCodePoint(0) !== 32 /* ' ' */) {
929
+ const previousBufferLine = buffer.lines.get(coords[1] - 1);
930
+ if (previousBufferLine && bufferLine.isWrapped && previousBufferLine.getCodePoint(this._bufferService.cols - 1) !== 32 /* ' ' */) {
931
+ const previousLineWordPosition = this._getWordAt([this._bufferService.cols - 1, coords[1] - 1], false, true, false);
932
+ if (previousLineWordPosition) {
933
+ const offset = this._bufferService.cols - previousLineWordPosition.start;
934
+ start -= offset;
935
+ length += offset;
936
+ }
937
+ }
938
+ }
939
+ }
940
+
941
+ // Recurse downwards if the line is wrapped and the word wraps to the next line
942
+ if (followWrappedLinesBelow) {
943
+ if (start + length === this._bufferService.cols && bufferLine.getCodePoint(this._bufferService.cols - 1) !== 32 /* ' ' */) {
944
+ const nextBufferLine = buffer.lines.get(coords[1] + 1);
945
+ if (nextBufferLine?.isWrapped && nextBufferLine.getCodePoint(0) !== 32 /* ' ' */) {
946
+ const nextLineWordPosition = this._getWordAt([0, coords[1] + 1], false, false, true);
947
+ if (nextLineWordPosition) {
948
+ length += nextLineWordPosition.length;
949
+ }
950
+ }
951
+ }
952
+ }
953
+
954
+ return { start, length };
955
+ }
956
+
957
+ /**
958
+ * Selects the word at the coordinates specified.
959
+ * @param coords The coordinates to get the word at.
960
+ * @param allowWhitespaceOnlySelection If whitespace should be selected
961
+ */
962
+ protected _selectWordAt(coords: [number, number], allowWhitespaceOnlySelection: boolean): void {
963
+ const wordPosition = this._getWordAt(coords, allowWhitespaceOnlySelection);
964
+ if (wordPosition) {
965
+ // Adjust negative start value
966
+ while (wordPosition.start < 0) {
967
+ wordPosition.start += this._bufferService.cols;
968
+ coords[1]--;
969
+ }
970
+ this._model.selectionStart = [wordPosition.start, coords[1]];
971
+ this._model.selectionStartLength = wordPosition.length;
972
+ }
973
+ }
974
+
975
+ /**
976
+ * Sets the selection end to the word at the coordinated specified.
977
+ * @param coords The coordinates to get the word at.
978
+ */
979
+ private _selectToWordAt(coords: [number, number]): void {
980
+ const wordPosition = this._getWordAt(coords, true);
981
+ if (wordPosition) {
982
+ let endRow = coords[1];
983
+
984
+ // Adjust negative start value
985
+ while (wordPosition.start < 0) {
986
+ wordPosition.start += this._bufferService.cols;
987
+ endRow--;
988
+ }
989
+
990
+ // Adjust wrapped length value, this only needs to happen when values are reversed as in that
991
+ // case we're interested in the start of the word, not the end
992
+ if (!this._model.areSelectionValuesReversed()) {
993
+ while (wordPosition.start + wordPosition.length > this._bufferService.cols) {
994
+ wordPosition.length -= this._bufferService.cols;
995
+ endRow++;
996
+ }
997
+ }
998
+
999
+ this._model.selectionEnd = [this._model.areSelectionValuesReversed() ? wordPosition.start : wordPosition.start + wordPosition.length, endRow];
1000
+ }
1001
+ }
1002
+
1003
+ /**
1004
+ * Gets whether the character is considered a word separator by the select
1005
+ * word logic.
1006
+ * @param cell The cell to check.
1007
+ */
1008
+ private _isCharWordSeparator(cell: CellData): boolean {
1009
+ // Zero width characters are never separators as they are always to the
1010
+ // right of wide characters
1011
+ if (cell.getWidth() === 0) {
1012
+ return false;
1013
+ }
1014
+ return this._optionsService.rawOptions.wordSeparator.indexOf(cell.getChars()) >= 0;
1015
+ }
1016
+
1017
+ /**
1018
+ * Selects the line specified.
1019
+ * @param line The line index.
1020
+ */
1021
+ protected _selectLineAt(line: number): void {
1022
+ const wrappedRange = this._bufferService.buffer.getWrappedRangeForLine(line);
1023
+ const range: IBufferRange = {
1024
+ start: { x: 0, y: wrappedRange.first },
1025
+ end: { x: this._bufferService.cols - 1, y: wrappedRange.last }
1026
+ };
1027
+ this._model.selectionStart = [0, wrappedRange.first];
1028
+ this._model.selectionEnd = undefined;
1029
+ this._model.selectionStartLength = getRangeLength(range, this._bufferService.cols);
1030
+ }
1031
+ }