@xterm/xterm 5.4.0-beta.2 → 5.4.0-beta.21
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.
- package/css/xterm.css +10 -1
- package/lib/xterm.js +1 -1
- package/lib/xterm.js.map +1 -1
- package/package.json +1 -1
- package/src/browser/AccessibilityManager.ts +132 -3
- package/src/browser/{Linkifier2.ts → Linkifier.ts} +36 -61
- package/src/browser/OscLinkProvider.ts +2 -1
- package/src/browser/RenderDebouncer.ts +6 -5
- package/src/browser/Terminal.ts +36 -28
- package/src/browser/Types.d.ts +2 -9
- package/src/browser/public/Terminal.ts +3 -0
- package/src/browser/renderer/dom/DomRenderer.ts +20 -9
- package/src/browser/renderer/dom/DomRendererRowFactory.ts +2 -2
- package/src/browser/renderer/shared/CellColorResolver.ts +110 -11
- package/src/browser/renderer/shared/RendererUtils.ts +5 -1
- package/src/browser/renderer/shared/SelectionRenderModel.ts +5 -3
- package/src/browser/renderer/shared/TextureAtlas.ts +27 -8
- package/src/browser/renderer/shared/Types.d.ts +2 -2
- package/src/browser/services/LinkProviderService.ts +28 -0
- package/src/browser/services/RenderService.ts +13 -8
- package/src/browser/services/Services.ts +12 -1
- package/src/common/Color.ts +17 -0
- package/src/common/CoreTerminal.ts +1 -0
- package/src/common/InputHandler.ts +15 -19
- package/src/common/Types.d.ts +7 -5
- package/src/common/buffer/AttributeData.ts +15 -0
- package/src/common/buffer/BufferLine.ts +47 -20
- package/src/common/buffer/Constants.ts +10 -2
- package/src/common/input/Keyboard.ts +2 -3
- package/src/common/services/OptionsService.ts +8 -1
- package/typings/xterm.d.ts +22 -0
package/package.json
CHANGED
|
@@ -10,6 +10,7 @@ import { Disposable, toDisposable } from 'common/Lifecycle';
|
|
|
10
10
|
import { ICoreBrowserService, IRenderService } from 'browser/services/Services';
|
|
11
11
|
import { IBuffer } from 'common/buffer/Types';
|
|
12
12
|
import { IInstantiationService } from 'common/services/Services';
|
|
13
|
+
import { addDisposableDomListener } from 'browser/Lifecycle';
|
|
13
14
|
|
|
14
15
|
const MAX_ROWS_TO_READ = 20;
|
|
15
16
|
|
|
@@ -18,11 +19,17 @@ const enum BoundaryPosition {
|
|
|
18
19
|
BOTTOM
|
|
19
20
|
}
|
|
20
21
|
|
|
22
|
+
// Turn this on to unhide the accessibility tree and display it under
|
|
23
|
+
// (instead of overlapping with) the terminal.
|
|
24
|
+
const DEBUG = false;
|
|
25
|
+
|
|
21
26
|
export class AccessibilityManager extends Disposable {
|
|
27
|
+
private _debugRootContainer: HTMLElement | undefined;
|
|
22
28
|
private _accessibilityContainer: HTMLElement;
|
|
23
29
|
|
|
24
30
|
private _rowContainer: HTMLElement;
|
|
25
31
|
private _rowElements: HTMLElement[];
|
|
32
|
+
private _rowColumns: WeakMap<HTMLElement, number[]> = new WeakMap();
|
|
26
33
|
|
|
27
34
|
private _liveRegion: HTMLElement;
|
|
28
35
|
private _liveRegionLineCount: number = 0;
|
|
@@ -80,7 +87,23 @@ export class AccessibilityManager extends Disposable {
|
|
|
80
87
|
if (!this._terminal.element) {
|
|
81
88
|
throw new Error('Cannot enable accessibility before Terminal.open');
|
|
82
89
|
}
|
|
83
|
-
|
|
90
|
+
|
|
91
|
+
if (DEBUG) {
|
|
92
|
+
this._accessibilityContainer.classList.add('debug');
|
|
93
|
+
this._rowContainer.classList.add('debug');
|
|
94
|
+
|
|
95
|
+
// Use a `<div class="xterm">` container so that the css will still apply.
|
|
96
|
+
this._debugRootContainer = document.createElement('div');
|
|
97
|
+
this._debugRootContainer.classList.add('xterm');
|
|
98
|
+
|
|
99
|
+
this._debugRootContainer.appendChild(document.createTextNode('------start a11y------'));
|
|
100
|
+
this._debugRootContainer.appendChild(this._accessibilityContainer);
|
|
101
|
+
this._debugRootContainer.appendChild(document.createTextNode('------end a11y------'));
|
|
102
|
+
|
|
103
|
+
this._terminal.element.insertAdjacentElement('afterend', this._debugRootContainer);
|
|
104
|
+
} else {
|
|
105
|
+
this._terminal.element.insertAdjacentElement('afterbegin', this._accessibilityContainer);
|
|
106
|
+
}
|
|
84
107
|
|
|
85
108
|
this.register(this._terminal.onResize(e => this._handleResize(e.rows)));
|
|
86
109
|
this.register(this._terminal.onRender(e => this._refreshRows(e.start, e.end)));
|
|
@@ -92,11 +115,16 @@ export class AccessibilityManager extends Disposable {
|
|
|
92
115
|
this.register(this._terminal.onKey(e => this._handleKey(e.key)));
|
|
93
116
|
this.register(this._terminal.onBlur(() => this._clearLiveRegion()));
|
|
94
117
|
this.register(this._renderService.onDimensionsChange(() => this._refreshRowsDimensions()));
|
|
118
|
+
this.register(addDisposableDomListener(document, 'selectionchange', () => this._handleSelectionChange()));
|
|
95
119
|
this.register(this._coreBrowserService.onDprChange(() => this._refreshRowsDimensions()));
|
|
96
120
|
|
|
97
121
|
this._refreshRows();
|
|
98
122
|
this.register(toDisposable(() => {
|
|
99
|
-
|
|
123
|
+
if (DEBUG) {
|
|
124
|
+
this._debugRootContainer!.remove();
|
|
125
|
+
} else {
|
|
126
|
+
this._accessibilityContainer.remove();
|
|
127
|
+
}
|
|
100
128
|
this._rowElements.length = 0;
|
|
101
129
|
}));
|
|
102
130
|
}
|
|
@@ -149,14 +177,18 @@ export class AccessibilityManager extends Disposable {
|
|
|
149
177
|
const buffer: IBuffer = this._terminal.buffer;
|
|
150
178
|
const setSize = buffer.lines.length.toString();
|
|
151
179
|
for (let i = start; i <= end; i++) {
|
|
152
|
-
const
|
|
180
|
+
const line = buffer.lines.get(buffer.ydisp + i);
|
|
181
|
+
const columns: number[] = [];
|
|
182
|
+
const lineData = line?.translateToString(true, undefined, undefined, columns) || '';
|
|
153
183
|
const posInSet = (buffer.ydisp + i + 1).toString();
|
|
154
184
|
const element = this._rowElements[i];
|
|
155
185
|
if (element) {
|
|
156
186
|
if (lineData.length === 0) {
|
|
157
187
|
element.innerText = '\u00a0';
|
|
188
|
+
this._rowColumns.set(element, [0, 1]);
|
|
158
189
|
} else {
|
|
159
190
|
element.textContent = lineData;
|
|
191
|
+
this._rowColumns.set(element, columns);
|
|
160
192
|
}
|
|
161
193
|
element.setAttribute('aria-posinset', posInSet);
|
|
162
194
|
element.setAttribute('aria-setsize', setSize);
|
|
@@ -233,6 +265,103 @@ export class AccessibilityManager extends Disposable {
|
|
|
233
265
|
e.stopImmediatePropagation();
|
|
234
266
|
}
|
|
235
267
|
|
|
268
|
+
private _handleSelectionChange(): void {
|
|
269
|
+
if (this._rowElements.length === 0) {
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const selection = document.getSelection();
|
|
274
|
+
if (!selection) {
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
if (selection.isCollapsed) {
|
|
279
|
+
// Only do something when the anchorNode is inside the row container. This
|
|
280
|
+
// behavior mirrors what we do with mouse --- if the mouse clicks
|
|
281
|
+
// somewhere outside of the terminal, we don't clear the selection.
|
|
282
|
+
if (this._rowContainer.contains(selection.anchorNode)) {
|
|
283
|
+
this._terminal.clearSelection();
|
|
284
|
+
}
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
if (!selection.anchorNode || !selection.focusNode) {
|
|
289
|
+
console.error('anchorNode and/or focusNode are null');
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Sort the two selection points in document order.
|
|
294
|
+
let begin = { node: selection.anchorNode, offset: selection.anchorOffset };
|
|
295
|
+
let end = { node: selection.focusNode, offset: selection.focusOffset };
|
|
296
|
+
if ((begin.node.compareDocumentPosition(end.node) & Node.DOCUMENT_POSITION_PRECEDING) || (begin.node === end.node && begin.offset > end.offset) ) {
|
|
297
|
+
[begin, end] = [end, begin];
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// Clamp begin/end to the inside of the row container.
|
|
301
|
+
if (begin.node.compareDocumentPosition(this._rowElements[0]) & (Node.DOCUMENT_POSITION_CONTAINED_BY | Node.DOCUMENT_POSITION_FOLLOWING)) {
|
|
302
|
+
begin = { node: this._rowElements[0].childNodes[0], offset: 0 };
|
|
303
|
+
}
|
|
304
|
+
if (!this._rowContainer.contains(begin.node)) {
|
|
305
|
+
// This happens when `begin` is below the last row.
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
const lastRowElement = this._rowElements.slice(-1)[0];
|
|
309
|
+
if (end.node.compareDocumentPosition(lastRowElement) & (Node.DOCUMENT_POSITION_CONTAINED_BY | Node.DOCUMENT_POSITION_PRECEDING)) {
|
|
310
|
+
end = {
|
|
311
|
+
node: lastRowElement,
|
|
312
|
+
offset: lastRowElement.textContent?.length ?? 0
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
if (!this._rowContainer.contains(end.node)) {
|
|
316
|
+
// This happens when `end` is above the first row.
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
const toRowColumn = ({ node, offset }: typeof begin): {row: number, column: number} | null => {
|
|
321
|
+
// `node` is either the row element or the Text node inside it.
|
|
322
|
+
const rowElement: any = node instanceof Text ? node.parentNode : node;
|
|
323
|
+
let row = parseInt(rowElement?.getAttribute('aria-posinset'), 10) - 1;
|
|
324
|
+
if (isNaN(row)) {
|
|
325
|
+
console.warn('row is invalid. Race condition?');
|
|
326
|
+
return null;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const columns = this._rowColumns.get(rowElement);
|
|
330
|
+
if (!columns) {
|
|
331
|
+
console.warn('columns is null. Race condition?');
|
|
332
|
+
return null;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
let column = offset < columns.length ? columns[offset] : columns.slice(-1)[0] + 1;
|
|
336
|
+
if (column >= this._terminal.cols) {
|
|
337
|
+
++row;
|
|
338
|
+
column = 0;
|
|
339
|
+
}
|
|
340
|
+
return {
|
|
341
|
+
row,
|
|
342
|
+
column
|
|
343
|
+
};
|
|
344
|
+
};
|
|
345
|
+
|
|
346
|
+
const beginRowColumn = toRowColumn(begin);
|
|
347
|
+
const endRowColumn = toRowColumn(end);
|
|
348
|
+
|
|
349
|
+
if (!beginRowColumn || !endRowColumn) {
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
if (beginRowColumn.row > endRowColumn.row || (beginRowColumn.row === endRowColumn.row && beginRowColumn.column >= endRowColumn.column)) {
|
|
354
|
+
// This should not happen unless we have some bugs.
|
|
355
|
+
throw new Error('invalid range');
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
this._terminal.select(
|
|
359
|
+
beginRowColumn.column,
|
|
360
|
+
beginRowColumn.row,
|
|
361
|
+
(endRowColumn.row - beginRowColumn.row) * this._terminal.cols - beginRowColumn.column + endRowColumn.column
|
|
362
|
+
);
|
|
363
|
+
}
|
|
364
|
+
|
|
236
365
|
private _handleResize(rows: number): void {
|
|
237
366
|
// Remove bottom boundary listener
|
|
238
367
|
this._rowElements[this._rowElements.length - 1].removeEventListener('focus', this._bottomBoundaryFocusListener);
|
|
@@ -4,18 +4,14 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { addDisposableDomListener } from 'browser/Lifecycle';
|
|
7
|
-
import { IBufferCellPosition, ILink, ILinkDecorations,
|
|
7
|
+
import { IBufferCellPosition, ILink, ILinkDecorations, ILinkWithState, ILinkifier2, ILinkifierEvent } from 'browser/Types';
|
|
8
8
|
import { EventEmitter } from 'common/EventEmitter';
|
|
9
9
|
import { Disposable, disposeArray, getDisposeArrayDisposable, toDisposable } from 'common/Lifecycle';
|
|
10
10
|
import { IDisposable } from 'common/Types';
|
|
11
11
|
import { IBufferService } from 'common/services/Services';
|
|
12
|
-
import { IMouseService, IRenderService } from './services/Services';
|
|
12
|
+
import { ILinkProviderService, IMouseService, IRenderService } from './services/Services';
|
|
13
13
|
|
|
14
|
-
export class
|
|
15
|
-
private _element: HTMLElement | undefined;
|
|
16
|
-
private _mouseService: IMouseService | undefined;
|
|
17
|
-
private _renderService: IRenderService | undefined;
|
|
18
|
-
private _linkProviders: ILinkProvider[] = [];
|
|
14
|
+
export class Linkifier extends Disposable implements ILinkifier2 {
|
|
19
15
|
public get currentLink(): ILinkWithState | undefined { return this._currentLink; }
|
|
20
16
|
protected _currentLink: ILinkWithState | undefined;
|
|
21
17
|
private _mouseDownLink: ILinkWithState | undefined;
|
|
@@ -33,39 +29,24 @@ export class Linkifier2 extends Disposable implements ILinkifier2 {
|
|
|
33
29
|
public readonly onHideLinkUnderline = this._onHideLinkUnderline.event;
|
|
34
30
|
|
|
35
31
|
constructor(
|
|
36
|
-
|
|
32
|
+
private readonly _element: HTMLElement,
|
|
33
|
+
@IMouseService private readonly _mouseService: IMouseService,
|
|
34
|
+
@IRenderService private readonly _renderService: IRenderService,
|
|
35
|
+
@IBufferService private readonly _bufferService: IBufferService,
|
|
36
|
+
@ILinkProviderService private readonly _linkProviderService: ILinkProviderService
|
|
37
37
|
) {
|
|
38
38
|
super();
|
|
39
39
|
this.register(getDisposeArrayDisposable(this._linkCacheDisposables));
|
|
40
40
|
this.register(toDisposable(() => {
|
|
41
41
|
this._lastMouseEvent = undefined;
|
|
42
|
+
// Clear out link providers as they could easily cause an embedder memory leak
|
|
43
|
+
this._activeProviderReplies?.clear();
|
|
42
44
|
}));
|
|
43
45
|
// Listen to resize to catch the case where it's resized and the cursor is out of the viewport.
|
|
44
46
|
this.register(this._bufferService.onResize(() => {
|
|
45
47
|
this._clearCurrentLink();
|
|
46
48
|
this._wasResized = true;
|
|
47
49
|
}));
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
public registerLinkProvider(linkProvider: ILinkProvider): IDisposable {
|
|
51
|
-
this._linkProviders.push(linkProvider);
|
|
52
|
-
return {
|
|
53
|
-
dispose: () => {
|
|
54
|
-
// Remove the link provider from the list
|
|
55
|
-
const providerIndex = this._linkProviders.indexOf(linkProvider);
|
|
56
|
-
|
|
57
|
-
if (providerIndex !== -1) {
|
|
58
|
-
this._linkProviders.splice(providerIndex, 1);
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
};
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
public attachToDom(element: HTMLElement, mouseService: IMouseService, renderService: IRenderService): void {
|
|
65
|
-
this._element = element;
|
|
66
|
-
this._mouseService = mouseService;
|
|
67
|
-
this._renderService = renderService;
|
|
68
|
-
|
|
69
50
|
this.register(addDisposableDomListener(this._element, 'mouseleave', () => {
|
|
70
51
|
this._isMouseOut = true;
|
|
71
52
|
this._clearCurrentLink();
|
|
@@ -78,10 +59,6 @@ export class Linkifier2 extends Disposable implements ILinkifier2 {
|
|
|
78
59
|
private _handleMouseMove(event: MouseEvent): void {
|
|
79
60
|
this._lastMouseEvent = event;
|
|
80
61
|
|
|
81
|
-
if (!this._element || !this._mouseService) {
|
|
82
|
-
return;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
62
|
const position = this._positionFromMouseEvent(event, this._element, this._mouseService);
|
|
86
63
|
if (!position) {
|
|
87
64
|
return;
|
|
@@ -142,7 +119,7 @@ export class Linkifier2 extends Disposable implements ILinkifier2 {
|
|
|
142
119
|
let linkProvided = false;
|
|
143
120
|
|
|
144
121
|
// There is no link cached, so ask for one
|
|
145
|
-
for (const [i, linkProvider] of this.
|
|
122
|
+
for (const [i, linkProvider] of this._linkProviderService.linkProviders.entries()) {
|
|
146
123
|
if (useLineCache) {
|
|
147
124
|
const existingReply = this._activeProviderReplies?.get(i);
|
|
148
125
|
// If there isn't a reply, the provider hasn't responded yet.
|
|
@@ -164,7 +141,7 @@ export class Linkifier2 extends Disposable implements ILinkifier2 {
|
|
|
164
141
|
|
|
165
142
|
// If all providers have responded, remove lower priority links that intersect ranges of
|
|
166
143
|
// higher priority links
|
|
167
|
-
if (this._activeProviderReplies?.size === this.
|
|
144
|
+
if (this._activeProviderReplies?.size === this._linkProviderService.linkProviders.length) {
|
|
168
145
|
this._removeIntersectingLinks(position.y, this._activeProviderReplies);
|
|
169
146
|
}
|
|
170
147
|
});
|
|
@@ -220,7 +197,7 @@ export class Linkifier2 extends Disposable implements ILinkifier2 {
|
|
|
220
197
|
}
|
|
221
198
|
|
|
222
199
|
// Check if all the providers have responded
|
|
223
|
-
if (this._activeProviderReplies.size === this.
|
|
200
|
+
if (this._activeProviderReplies.size === this._linkProviderService.linkProviders.length && !linkProvided) {
|
|
224
201
|
// Respect the order of the link providers
|
|
225
202
|
for (let j = 0; j < this._activeProviderReplies.size; j++) {
|
|
226
203
|
const currentLink = this._activeProviderReplies.get(j)?.find(link => this._linkAtPosition(link.link, position));
|
|
@@ -240,7 +217,7 @@ export class Linkifier2 extends Disposable implements ILinkifier2 {
|
|
|
240
217
|
}
|
|
241
218
|
|
|
242
219
|
private _handleMouseUp(event: MouseEvent): void {
|
|
243
|
-
if (!this.
|
|
220
|
+
if (!this._currentLink) {
|
|
244
221
|
return;
|
|
245
222
|
}
|
|
246
223
|
|
|
@@ -255,7 +232,7 @@ export class Linkifier2 extends Disposable implements ILinkifier2 {
|
|
|
255
232
|
}
|
|
256
233
|
|
|
257
234
|
private _clearCurrentLink(startRow?: number, endRow?: number): void {
|
|
258
|
-
if (!this.
|
|
235
|
+
if (!this._currentLink || !this._lastMouseEvent) {
|
|
259
236
|
return;
|
|
260
237
|
}
|
|
261
238
|
|
|
@@ -268,7 +245,7 @@ export class Linkifier2 extends Disposable implements ILinkifier2 {
|
|
|
268
245
|
}
|
|
269
246
|
|
|
270
247
|
private _handleNewLink(linkWithState: ILinkWithState): void {
|
|
271
|
-
if (!this.
|
|
248
|
+
if (!this._lastMouseEvent) {
|
|
272
249
|
return;
|
|
273
250
|
}
|
|
274
251
|
|
|
@@ -299,7 +276,7 @@ export class Linkifier2 extends Disposable implements ILinkifier2 {
|
|
|
299
276
|
if (this._currentLink?.state && this._currentLink.state.decorations.pointerCursor !== v) {
|
|
300
277
|
this._currentLink.state.decorations.pointerCursor = v;
|
|
301
278
|
if (this._currentLink.state.isHovered) {
|
|
302
|
-
this._element
|
|
279
|
+
this._element.classList.toggle('xterm-cursor-pointer', v);
|
|
303
280
|
}
|
|
304
281
|
}
|
|
305
282
|
}
|
|
@@ -319,29 +296,27 @@ export class Linkifier2 extends Disposable implements ILinkifier2 {
|
|
|
319
296
|
|
|
320
297
|
// Listen to viewport changes to re-render the link under the cursor (only when the line the
|
|
321
298
|
// link is on changes)
|
|
322
|
-
|
|
323
|
-
this
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
this._askForLink(position, false);
|
|
340
|
-
}
|
|
299
|
+
this._linkCacheDisposables.push(this._renderService.onRenderedViewportChange(e => {
|
|
300
|
+
// Sanity check, this shouldn't happen in practice as this listener would be disposed
|
|
301
|
+
if (!this._currentLink) {
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
// When start is 0 a scroll most likely occurred, make sure links above the fold also get
|
|
305
|
+
// cleared.
|
|
306
|
+
const start = e.start === 0 ? 0 : e.start + 1 + this._bufferService.buffer.ydisp;
|
|
307
|
+
const end = this._bufferService.buffer.ydisp + 1 + e.end;
|
|
308
|
+
// Only clear the link if the viewport change happened on this line
|
|
309
|
+
if (this._currentLink.link.range.start.y >= start && this._currentLink.link.range.end.y <= end) {
|
|
310
|
+
this._clearCurrentLink(start, end);
|
|
311
|
+
if (this._lastMouseEvent) {
|
|
312
|
+
// re-eval previously active link after changes
|
|
313
|
+
const position = this._positionFromMouseEvent(this._lastMouseEvent, this._element, this._mouseService!);
|
|
314
|
+
if (position) {
|
|
315
|
+
this._askForLink(position, false);
|
|
341
316
|
}
|
|
342
317
|
}
|
|
343
|
-
}
|
|
344
|
-
}
|
|
318
|
+
}
|
|
319
|
+
}));
|
|
345
320
|
}
|
|
346
321
|
}
|
|
347
322
|
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
* @license MIT
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { IBufferRange, ILink
|
|
6
|
+
import { IBufferRange, ILink } from 'browser/Types';
|
|
7
|
+
import { ILinkProvider } from 'browser/services/Services';
|
|
7
8
|
import { CellData } from 'common/buffer/CellData';
|
|
8
9
|
import { IBufferService, IOptionsService, IOscLinkService } from 'common/services/Services';
|
|
9
10
|
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { IRenderDebouncerWithCallback } from 'browser/Types';
|
|
7
|
+
import { ICoreBrowserService } from 'browser/services/Services';
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* Debounces calls to render terminal rows using animation frames.
|
|
@@ -16,14 +17,14 @@ export class RenderDebouncer implements IRenderDebouncerWithCallback {
|
|
|
16
17
|
private _refreshCallbacks: FrameRequestCallback[] = [];
|
|
17
18
|
|
|
18
19
|
constructor(
|
|
19
|
-
private
|
|
20
|
-
private
|
|
20
|
+
private _renderCallback: (start: number, end: number) => void,
|
|
21
|
+
private readonly _coreBrowserService: ICoreBrowserService
|
|
21
22
|
) {
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
public dispose(): void {
|
|
25
26
|
if (this._animationFrame) {
|
|
26
|
-
this.
|
|
27
|
+
this._coreBrowserService.window.cancelAnimationFrame(this._animationFrame);
|
|
27
28
|
this._animationFrame = undefined;
|
|
28
29
|
}
|
|
29
30
|
}
|
|
@@ -31,7 +32,7 @@ export class RenderDebouncer implements IRenderDebouncerWithCallback {
|
|
|
31
32
|
public addRefreshCallback(callback: FrameRequestCallback): number {
|
|
32
33
|
this._refreshCallbacks.push(callback);
|
|
33
34
|
if (!this._animationFrame) {
|
|
34
|
-
this._animationFrame = this.
|
|
35
|
+
this._animationFrame = this._coreBrowserService.window.requestAnimationFrame(() => this._innerRefresh());
|
|
35
36
|
}
|
|
36
37
|
return this._animationFrame;
|
|
37
38
|
}
|
|
@@ -49,7 +50,7 @@ export class RenderDebouncer implements IRenderDebouncerWithCallback {
|
|
|
49
50
|
return;
|
|
50
51
|
}
|
|
51
52
|
|
|
52
|
-
this._animationFrame = this.
|
|
53
|
+
this._animationFrame = this._coreBrowserService.window.requestAnimationFrame(() => this._innerRefresh());
|
|
53
54
|
}
|
|
54
55
|
|
|
55
56
|
private _innerRefresh(): void {
|
package/src/browser/Terminal.ts
CHANGED
|
@@ -23,10 +23,10 @@
|
|
|
23
23
|
|
|
24
24
|
import { copyHandler, handlePasteEvent, moveTextAreaUnderMouseCursor, paste, rightClickHandler } from 'browser/Clipboard';
|
|
25
25
|
import { addDisposableDomListener } from 'browser/Lifecycle';
|
|
26
|
-
import {
|
|
26
|
+
import { Linkifier } from './Linkifier';
|
|
27
27
|
import * as Strings from 'browser/LocalizableStrings';
|
|
28
28
|
import { OscLinkProvider } from 'browser/OscLinkProvider';
|
|
29
|
-
import { CharacterJoinerHandler, CustomKeyEventHandler, IBrowser, IBufferRange, ICompositionHelper, ILinkifier2, ITerminal, IViewport } from 'browser/Types';
|
|
29
|
+
import { CharacterJoinerHandler, CustomKeyEventHandler, CustomWheelEventHandler, IBrowser, IBufferRange, ICompositionHelper, ILinkifier2, ITerminal, IViewport } from 'browser/Types';
|
|
30
30
|
import { Viewport } from 'browser/Viewport';
|
|
31
31
|
import { BufferDecorationRenderer } from 'browser/decorations/BufferDecorationRenderer';
|
|
32
32
|
import { OverviewRulerRenderer } from 'browser/decorations/OverviewRulerRenderer';
|
|
@@ -39,7 +39,7 @@ import { CoreBrowserService } from 'browser/services/CoreBrowserService';
|
|
|
39
39
|
import { MouseService } from 'browser/services/MouseService';
|
|
40
40
|
import { RenderService } from 'browser/services/RenderService';
|
|
41
41
|
import { SelectionService } from 'browser/services/SelectionService';
|
|
42
|
-
import { ICharSizeService, ICharacterJoinerService, ICoreBrowserService, IMouseService, IRenderService, ISelectionService, IThemeService } from 'browser/services/Services';
|
|
42
|
+
import { ICharSizeService, ICharacterJoinerService, ICoreBrowserService, ILinkProviderService, IMouseService, IRenderService, ISelectionService, IThemeService } from 'browser/services/Services';
|
|
43
43
|
import { ThemeService } from 'browser/services/ThemeService';
|
|
44
44
|
import { color, rgba } from 'common/Color';
|
|
45
45
|
import { CoreTerminal } from 'common/CoreTerminal';
|
|
@@ -57,6 +57,7 @@ import { IDecorationService } from 'common/services/Services';
|
|
|
57
57
|
import { IDecoration, IDecorationOptions, IDisposable, ILinkProvider, IMarker } from '@xterm/xterm';
|
|
58
58
|
import { WindowsOptionsReportType } from '../common/InputHandler';
|
|
59
59
|
import { AccessibilityManager } from './AccessibilityManager';
|
|
60
|
+
import { LinkProviderService } from 'browser/services/LinkProviderService';
|
|
60
61
|
|
|
61
62
|
export class Terminal extends CoreTerminal implements ITerminal {
|
|
62
63
|
public textarea: HTMLTextAreaElement | undefined;
|
|
@@ -69,14 +70,19 @@ export class Terminal extends CoreTerminal implements ITerminal {
|
|
|
69
70
|
private _helperContainer: HTMLElement | undefined;
|
|
70
71
|
private _compositionView: HTMLElement | undefined;
|
|
71
72
|
|
|
73
|
+
public linkifier: ILinkifier2 | undefined;
|
|
72
74
|
private _overviewRulerRenderer: OverviewRulerRenderer | undefined;
|
|
73
75
|
|
|
74
76
|
public browser: IBrowser = Browser as any;
|
|
75
77
|
|
|
76
78
|
private _customKeyEventHandler: CustomKeyEventHandler | undefined;
|
|
79
|
+
private _customWheelEventHandler: CustomWheelEventHandler | undefined;
|
|
77
80
|
|
|
78
|
-
//
|
|
81
|
+
// Browser services
|
|
79
82
|
private _decorationService: DecorationService;
|
|
83
|
+
private _linkProviderService: ILinkProviderService;
|
|
84
|
+
|
|
85
|
+
// Optional browser services
|
|
80
86
|
private _charSizeService: ICharSizeService | undefined;
|
|
81
87
|
private _coreBrowserService: ICoreBrowserService | undefined;
|
|
82
88
|
private _mouseService: IMouseService | undefined;
|
|
@@ -112,7 +118,6 @@ export class Terminal extends CoreTerminal implements ITerminal {
|
|
|
112
118
|
*/
|
|
113
119
|
private _unprocessedDeadKey: boolean = false;
|
|
114
120
|
|
|
115
|
-
public linkifier2: ILinkifier2;
|
|
116
121
|
public viewport: IViewport | undefined;
|
|
117
122
|
private _compositionHelper: ICompositionHelper | undefined;
|
|
118
123
|
private _accessibilityManager: MutableDisposable<AccessibilityManager> = this.register(new MutableDisposable());
|
|
@@ -148,10 +153,11 @@ export class Terminal extends CoreTerminal implements ITerminal {
|
|
|
148
153
|
|
|
149
154
|
this._setup();
|
|
150
155
|
|
|
151
|
-
this.linkifier2 = this.register(this._instantiationService.createInstance(Linkifier2));
|
|
152
|
-
this.linkifier2.registerLinkProvider(this._instantiationService.createInstance(OscLinkProvider));
|
|
153
156
|
this._decorationService = this._instantiationService.createInstance(DecorationService);
|
|
154
157
|
this._instantiationService.setService(IDecorationService, this._decorationService);
|
|
158
|
+
this._linkProviderService = this._instantiationService.createInstance(LinkProviderService);
|
|
159
|
+
this._instantiationService.setService(ILinkProviderService, this._linkProviderService);
|
|
160
|
+
this._linkProviderService.registerLinkProvider(this._instantiationService.createInstance(OscLinkProvider));
|
|
155
161
|
|
|
156
162
|
// Setup InputHandler listeners
|
|
157
163
|
this.register(this._inputHandler.onRequestBell(() => this._onBell.fire()));
|
|
@@ -260,11 +266,10 @@ export class Terminal extends CoreTerminal implements ITerminal {
|
|
|
260
266
|
/**
|
|
261
267
|
* Binds the desired focus behavior on a given terminal object.
|
|
262
268
|
*/
|
|
263
|
-
private _handleTextAreaFocus(ev:
|
|
269
|
+
private _handleTextAreaFocus(ev: FocusEvent): void {
|
|
264
270
|
if (this.coreService.decPrivateModes.sendFocus) {
|
|
265
271
|
this.coreService.triggerDataEvent(C0.ESC + '[I');
|
|
266
272
|
}
|
|
267
|
-
this.updateCursorStyle(ev);
|
|
268
273
|
this.element!.classList.add('focus');
|
|
269
274
|
this._showCursor();
|
|
270
275
|
this._onFocus.fire();
|
|
@@ -428,6 +433,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
|
|
|
428
433
|
|
|
429
434
|
this.screenElement = this._document.createElement('div');
|
|
430
435
|
this.screenElement.classList.add('xterm-screen');
|
|
436
|
+
this.register(addDisposableDomListener(this.screenElement, 'mousemove', (ev: MouseEvent) => this.updateCursorStyle(ev)));
|
|
431
437
|
// Create the container that will hold helpers like the textarea for
|
|
432
438
|
// capturing DOM Events. Then produce the helpers.
|
|
433
439
|
this._helperContainer = this._document.createElement('div');
|
|
@@ -458,11 +464,10 @@ export class Terminal extends CoreTerminal implements ITerminal {
|
|
|
458
464
|
));
|
|
459
465
|
this._instantiationService.setService(ICoreBrowserService, this._coreBrowserService);
|
|
460
466
|
|
|
461
|
-
this.register(addDisposableDomListener(this.textarea, 'focus', (ev:
|
|
467
|
+
this.register(addDisposableDomListener(this.textarea, 'focus', (ev: FocusEvent) => this._handleTextAreaFocus(ev)));
|
|
462
468
|
this.register(addDisposableDomListener(this.textarea, 'blur', () => this._handleTextAreaBlur()));
|
|
463
469
|
this._helperContainer.appendChild(this.textarea);
|
|
464
470
|
|
|
465
|
-
|
|
466
471
|
this._charSizeService = this._instantiationService.createInstance(CharSizeService, this._document, this._helperContainer);
|
|
467
472
|
this._instantiationService.setService(ICharSizeService, this._charSizeService);
|
|
468
473
|
|
|
@@ -482,6 +487,11 @@ export class Terminal extends CoreTerminal implements ITerminal {
|
|
|
482
487
|
this._compositionHelper = this._instantiationService.createInstance(CompositionHelper, this.textarea, this._compositionView);
|
|
483
488
|
this._helperContainer.appendChild(this._compositionView);
|
|
484
489
|
|
|
490
|
+
this._mouseService = this._instantiationService.createInstance(MouseService);
|
|
491
|
+
this._instantiationService.setService(IMouseService, this._mouseService);
|
|
492
|
+
|
|
493
|
+
this.linkifier = this.register(this._instantiationService.createInstance(Linkifier, this.screenElement));
|
|
494
|
+
|
|
485
495
|
// Performance: Add viewport and helper elements from the fragment
|
|
486
496
|
this.element.appendChild(fragment);
|
|
487
497
|
|
|
@@ -493,9 +503,6 @@ export class Terminal extends CoreTerminal implements ITerminal {
|
|
|
493
503
|
this._renderService.setRenderer(this._createRenderer());
|
|
494
504
|
}
|
|
495
505
|
|
|
496
|
-
this._mouseService = this._instantiationService.createInstance(MouseService);
|
|
497
|
-
this._instantiationService.setService(IMouseService, this._mouseService);
|
|
498
|
-
|
|
499
506
|
this.viewport = this._instantiationService.createInstance(Viewport, this._viewportElement, this._viewportScrollArea);
|
|
500
507
|
this.viewport.onRequestScrollLines(e => this.scrollLines(e.amount, e.suppressScrollEvent, ScrollSource.VIEWPORT)),
|
|
501
508
|
this.register(this._inputHandler.onRequestSyncScrollBar(() => this.viewport!.syncScrollArea()));
|
|
@@ -513,7 +520,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
|
|
|
513
520
|
this._selectionService = this.register(this._instantiationService.createInstance(SelectionService,
|
|
514
521
|
this.element,
|
|
515
522
|
this.screenElement,
|
|
516
|
-
this.
|
|
523
|
+
this.linkifier
|
|
517
524
|
));
|
|
518
525
|
this._instantiationService.setService(ISelectionService, this._selectionService);
|
|
519
526
|
this.register(this._selectionService.onRequestScrollLines(e => this.scrollLines(e.amount, e.suppressScrollEvent)));
|
|
@@ -533,7 +540,6 @@ export class Terminal extends CoreTerminal implements ITerminal {
|
|
|
533
540
|
}));
|
|
534
541
|
this.register(addDisposableDomListener(this._viewportElement, 'scroll', () => this._selectionService!.refresh()));
|
|
535
542
|
|
|
536
|
-
this.linkifier2.attachToDom(this.screenElement, this._mouseService, this._renderService);
|
|
537
543
|
this.register(this._instantiationService.createInstance(BufferDecorationRenderer, this.screenElement));
|
|
538
544
|
this.register(addDisposableDomListener(this.element, 'mousedown', (e: MouseEvent) => this._selectionService!.handleMouseDown(e)));
|
|
539
545
|
|
|
@@ -575,7 +581,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
|
|
|
575
581
|
}
|
|
576
582
|
|
|
577
583
|
private _createRenderer(): IRenderer {
|
|
578
|
-
return this._instantiationService.createInstance(DomRenderer, this._document!, this.element!, this.screenElement!, this._viewportElement!, this._helperContainer!, this.
|
|
584
|
+
return this._instantiationService.createInstance(DomRenderer, this, this._document!, this.element!, this.screenElement!, this._viewportElement!, this._helperContainer!, this.linkifier!);
|
|
579
585
|
}
|
|
580
586
|
|
|
581
587
|
/**
|
|
@@ -633,6 +639,9 @@ export class Terminal extends CoreTerminal implements ITerminal {
|
|
|
633
639
|
but = ev.button < 3 ? ev.button : CoreMouseButton.NONE;
|
|
634
640
|
break;
|
|
635
641
|
case 'wheel':
|
|
642
|
+
if (self._customWheelEventHandler && self._customWheelEventHandler(ev as WheelEvent) === false) {
|
|
643
|
+
return false;
|
|
644
|
+
}
|
|
636
645
|
const amount = self.viewport!.getLinesScrolled(ev as WheelEvent);
|
|
637
646
|
|
|
638
647
|
if (amount === 0) {
|
|
@@ -792,6 +801,10 @@ export class Terminal extends CoreTerminal implements ITerminal {
|
|
|
792
801
|
// do nothing, if app side handles wheel itself
|
|
793
802
|
if (requestedEvents.wheel) return;
|
|
794
803
|
|
|
804
|
+
if (this._customWheelEventHandler && this._customWheelEventHandler(ev) === false) {
|
|
805
|
+
return false;
|
|
806
|
+
}
|
|
807
|
+
|
|
795
808
|
if (!this.buffer.hasScrollback) {
|
|
796
809
|
// Convert wheel events into up/down events when the buffer does not have scrollback, this
|
|
797
810
|
// enables scrolling in apps hosted in the alt buffer such as vim or tmux.
|
|
@@ -847,7 +860,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
|
|
|
847
860
|
/**
|
|
848
861
|
* Change the cursor style for different selection modes
|
|
849
862
|
*/
|
|
850
|
-
public updateCursorStyle(ev: KeyboardEvent): void {
|
|
863
|
+
public updateCursorStyle(ev: KeyboardEvent | MouseEvent): void {
|
|
851
864
|
if (this._selectionService?.shouldColumnSelect(ev)) {
|
|
852
865
|
this.element!.classList.add('column-select');
|
|
853
866
|
} else {
|
|
@@ -878,21 +891,16 @@ export class Terminal extends CoreTerminal implements ITerminal {
|
|
|
878
891
|
paste(data, this.textarea!, this.coreService, this.optionsService);
|
|
879
892
|
}
|
|
880
893
|
|
|
881
|
-
/**
|
|
882
|
-
* Attaches a custom key event handler which is run before keys are processed,
|
|
883
|
-
* giving consumers of xterm.js ultimate control as to what keys should be
|
|
884
|
-
* processed by the terminal and what keys should not.
|
|
885
|
-
* @param customKeyEventHandler The custom KeyboardEvent handler to attach.
|
|
886
|
-
* This is a function that takes a KeyboardEvent, allowing consumers to stop
|
|
887
|
-
* propagation and/or prevent the default action. The function returns whether
|
|
888
|
-
* the event should be processed by xterm.js.
|
|
889
|
-
*/
|
|
890
894
|
public attachCustomKeyEventHandler(customKeyEventHandler: CustomKeyEventHandler): void {
|
|
891
895
|
this._customKeyEventHandler = customKeyEventHandler;
|
|
892
896
|
}
|
|
893
897
|
|
|
898
|
+
public attachCustomWheelEventHandler(customWheelEventHandler: CustomWheelEventHandler): void {
|
|
899
|
+
this._customWheelEventHandler = customWheelEventHandler;
|
|
900
|
+
}
|
|
901
|
+
|
|
894
902
|
public registerLinkProvider(linkProvider: ILinkProvider): IDisposable {
|
|
895
|
-
return this.
|
|
903
|
+
return this._linkProviderService.registerLinkProvider(linkProvider);
|
|
896
904
|
}
|
|
897
905
|
|
|
898
906
|
public registerCharacterJoiner(handler: CharacterJoinerHandler): number {
|