@xterm/xterm 5.4.0-beta.2 → 5.4.0-beta.20
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 +3 -0
- package/src/browser/RenderDebouncer.ts +6 -5
- package/src/browser/Terminal.ts +18 -16
- package/src/browser/Types.d.ts +1 -0
- 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/RenderService.ts +13 -8
- package/src/common/Color.ts +17 -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);
|
|
@@ -39,6 +39,9 @@ export class Linkifier2 extends Disposable implements ILinkifier2 {
|
|
|
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._linkProviders.length = 0;
|
|
44
|
+
this._activeProviderReplies?.clear();
|
|
42
45
|
}));
|
|
43
46
|
// Listen to resize to catch the case where it's resized and the cursor is out of the viewport.
|
|
44
47
|
this.register(this._bufferService.onResize(() => {
|
|
@@ -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
|
@@ -26,7 +26,7 @@ import { addDisposableDomListener } from 'browser/Lifecycle';
|
|
|
26
26
|
import { Linkifier2 } from 'browser/Linkifier2';
|
|
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';
|
|
@@ -74,6 +74,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
|
|
|
74
74
|
public browser: IBrowser = Browser as any;
|
|
75
75
|
|
|
76
76
|
private _customKeyEventHandler: CustomKeyEventHandler | undefined;
|
|
77
|
+
private _customWheelEventHandler: CustomWheelEventHandler | undefined;
|
|
77
78
|
|
|
78
79
|
// browser services
|
|
79
80
|
private _decorationService: DecorationService;
|
|
@@ -260,11 +261,10 @@ export class Terminal extends CoreTerminal implements ITerminal {
|
|
|
260
261
|
/**
|
|
261
262
|
* Binds the desired focus behavior on a given terminal object.
|
|
262
263
|
*/
|
|
263
|
-
private _handleTextAreaFocus(ev:
|
|
264
|
+
private _handleTextAreaFocus(ev: FocusEvent): void {
|
|
264
265
|
if (this.coreService.decPrivateModes.sendFocus) {
|
|
265
266
|
this.coreService.triggerDataEvent(C0.ESC + '[I');
|
|
266
267
|
}
|
|
267
|
-
this.updateCursorStyle(ev);
|
|
268
268
|
this.element!.classList.add('focus');
|
|
269
269
|
this._showCursor();
|
|
270
270
|
this._onFocus.fire();
|
|
@@ -428,6 +428,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
|
|
|
428
428
|
|
|
429
429
|
this.screenElement = this._document.createElement('div');
|
|
430
430
|
this.screenElement.classList.add('xterm-screen');
|
|
431
|
+
this.register(addDisposableDomListener(this.screenElement, 'mousemove', (ev: MouseEvent) => this.updateCursorStyle(ev)));
|
|
431
432
|
// Create the container that will hold helpers like the textarea for
|
|
432
433
|
// capturing DOM Events. Then produce the helpers.
|
|
433
434
|
this._helperContainer = this._document.createElement('div');
|
|
@@ -458,11 +459,10 @@ export class Terminal extends CoreTerminal implements ITerminal {
|
|
|
458
459
|
));
|
|
459
460
|
this._instantiationService.setService(ICoreBrowserService, this._coreBrowserService);
|
|
460
461
|
|
|
461
|
-
this.register(addDisposableDomListener(this.textarea, 'focus', (ev:
|
|
462
|
+
this.register(addDisposableDomListener(this.textarea, 'focus', (ev: FocusEvent) => this._handleTextAreaFocus(ev)));
|
|
462
463
|
this.register(addDisposableDomListener(this.textarea, 'blur', () => this._handleTextAreaBlur()));
|
|
463
464
|
this._helperContainer.appendChild(this.textarea);
|
|
464
465
|
|
|
465
|
-
|
|
466
466
|
this._charSizeService = this._instantiationService.createInstance(CharSizeService, this._document, this._helperContainer);
|
|
467
467
|
this._instantiationService.setService(ICharSizeService, this._charSizeService);
|
|
468
468
|
|
|
@@ -575,7 +575,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
|
|
|
575
575
|
}
|
|
576
576
|
|
|
577
577
|
private _createRenderer(): IRenderer {
|
|
578
|
-
return this._instantiationService.createInstance(DomRenderer, this._document!, this.element!, this.screenElement!, this._viewportElement!, this._helperContainer!, this.linkifier2);
|
|
578
|
+
return this._instantiationService.createInstance(DomRenderer, this, this._document!, this.element!, this.screenElement!, this._viewportElement!, this._helperContainer!, this.linkifier2);
|
|
579
579
|
}
|
|
580
580
|
|
|
581
581
|
/**
|
|
@@ -633,6 +633,9 @@ export class Terminal extends CoreTerminal implements ITerminal {
|
|
|
633
633
|
but = ev.button < 3 ? ev.button : CoreMouseButton.NONE;
|
|
634
634
|
break;
|
|
635
635
|
case 'wheel':
|
|
636
|
+
if (self._customWheelEventHandler && self._customWheelEventHandler(ev as WheelEvent) === false) {
|
|
637
|
+
return false;
|
|
638
|
+
}
|
|
636
639
|
const amount = self.viewport!.getLinesScrolled(ev as WheelEvent);
|
|
637
640
|
|
|
638
641
|
if (amount === 0) {
|
|
@@ -792,6 +795,10 @@ export class Terminal extends CoreTerminal implements ITerminal {
|
|
|
792
795
|
// do nothing, if app side handles wheel itself
|
|
793
796
|
if (requestedEvents.wheel) return;
|
|
794
797
|
|
|
798
|
+
if (this._customWheelEventHandler && this._customWheelEventHandler(ev) === false) {
|
|
799
|
+
return false;
|
|
800
|
+
}
|
|
801
|
+
|
|
795
802
|
if (!this.buffer.hasScrollback) {
|
|
796
803
|
// Convert wheel events into up/down events when the buffer does not have scrollback, this
|
|
797
804
|
// enables scrolling in apps hosted in the alt buffer such as vim or tmux.
|
|
@@ -847,7 +854,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
|
|
|
847
854
|
/**
|
|
848
855
|
* Change the cursor style for different selection modes
|
|
849
856
|
*/
|
|
850
|
-
public updateCursorStyle(ev: KeyboardEvent): void {
|
|
857
|
+
public updateCursorStyle(ev: KeyboardEvent | MouseEvent): void {
|
|
851
858
|
if (this._selectionService?.shouldColumnSelect(ev)) {
|
|
852
859
|
this.element!.classList.add('column-select');
|
|
853
860
|
} else {
|
|
@@ -878,19 +885,14 @@ export class Terminal extends CoreTerminal implements ITerminal {
|
|
|
878
885
|
paste(data, this.textarea!, this.coreService, this.optionsService);
|
|
879
886
|
}
|
|
880
887
|
|
|
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
888
|
public attachCustomKeyEventHandler(customKeyEventHandler: CustomKeyEventHandler): void {
|
|
891
889
|
this._customKeyEventHandler = customKeyEventHandler;
|
|
892
890
|
}
|
|
893
891
|
|
|
892
|
+
public attachCustomWheelEventHandler(customWheelEventHandler: CustomWheelEventHandler): void {
|
|
893
|
+
this._customWheelEventHandler = customWheelEventHandler;
|
|
894
|
+
}
|
|
895
|
+
|
|
894
896
|
public registerLinkProvider(linkProvider: ILinkProvider): IDisposable {
|
|
895
897
|
return this.linkifier2.registerLinkProvider(linkProvider);
|
|
896
898
|
}
|
package/src/browser/Types.d.ts
CHANGED
|
@@ -32,6 +32,7 @@ export interface ITerminal extends InternalPassthroughApis, ICoreTerminal {
|
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
export type CustomKeyEventHandler = (event: KeyboardEvent) => boolean;
|
|
35
|
+
export type CustomWheelEventHandler = (event: WheelEvent) => boolean;
|
|
35
36
|
|
|
36
37
|
export type LineData = CharData[];
|
|
37
38
|
|
|
@@ -148,6 +148,9 @@ export class Terminal extends Disposable implements ITerminalApi {
|
|
|
148
148
|
public attachCustomKeyEventHandler(customKeyEventHandler: (event: KeyboardEvent) => boolean): void {
|
|
149
149
|
this._core.attachCustomKeyEventHandler(customKeyEventHandler);
|
|
150
150
|
}
|
|
151
|
+
public attachCustomWheelEventHandler(customWheelEventHandler: (event: WheelEvent) => boolean): void {
|
|
152
|
+
this._core.attachCustomWheelEventHandler(customWheelEventHandler);
|
|
153
|
+
}
|
|
151
154
|
public registerLinkProvider(linkProvider: ILinkProvider): IDisposable {
|
|
152
155
|
return this._core.registerLinkProvider(linkProvider);
|
|
153
156
|
}
|
|
@@ -7,9 +7,10 @@ import { DomRendererRowFactory, RowCss } from 'browser/renderer/dom/DomRendererR
|
|
|
7
7
|
import { WidthCache } from 'browser/renderer/dom/WidthCache';
|
|
8
8
|
import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/shared/Constants';
|
|
9
9
|
import { createRenderDimensions } from 'browser/renderer/shared/RendererUtils';
|
|
10
|
-
import {
|
|
10
|
+
import { createSelectionRenderModel } from 'browser/renderer/shared/SelectionRenderModel';
|
|
11
|
+
import { IRenderDimensions, IRenderer, IRequestRedrawEvent, ISelectionRenderModel } from 'browser/renderer/shared/Types';
|
|
11
12
|
import { ICharSizeService, ICoreBrowserService, IThemeService } from 'browser/services/Services';
|
|
12
|
-
import { ILinkifier2, ILinkifierEvent, ReadonlyColorSet } from 'browser/Types';
|
|
13
|
+
import { ILinkifier2, ILinkifierEvent, ITerminal, ReadonlyColorSet } from 'browser/Types';
|
|
13
14
|
import { color } from 'common/Color';
|
|
14
15
|
import { EventEmitter } from 'common/EventEmitter';
|
|
15
16
|
import { Disposable, toDisposable } from 'common/Lifecycle';
|
|
@@ -25,7 +26,6 @@ const SELECTION_CLASS = 'xterm-selection';
|
|
|
25
26
|
|
|
26
27
|
let nextTerminalId = 1;
|
|
27
28
|
|
|
28
|
-
|
|
29
29
|
/**
|
|
30
30
|
* A fallback renderer for when canvas is slow. This is not meant to be
|
|
31
31
|
* particularly fast or feature complete, more just stable and usable for when
|
|
@@ -41,12 +41,14 @@ export class DomRenderer extends Disposable implements IRenderer {
|
|
|
41
41
|
private _rowElements: HTMLElement[] = [];
|
|
42
42
|
private _selectionContainer: HTMLElement;
|
|
43
43
|
private _widthCache: WidthCache;
|
|
44
|
+
private _selectionRenderModel: ISelectionRenderModel = createSelectionRenderModel();
|
|
44
45
|
|
|
45
46
|
public dimensions: IRenderDimensions;
|
|
46
47
|
|
|
47
48
|
public readonly onRequestRedraw = this.register(new EventEmitter<IRequestRedrawEvent>()).event;
|
|
48
49
|
|
|
49
50
|
constructor(
|
|
51
|
+
private readonly _terminal: ITerminal,
|
|
50
52
|
private readonly _document: Document,
|
|
51
53
|
private readonly _element: HTMLElement,
|
|
52
54
|
private readonly _screenElement: HTMLElement,
|
|
@@ -291,6 +293,7 @@ export class DomRenderer extends Disposable implements IRenderer {
|
|
|
291
293
|
public handleResize(cols: number, rows: number): void {
|
|
292
294
|
this._refreshRowElements(cols, rows);
|
|
293
295
|
this._updateDimensions();
|
|
296
|
+
this.handleSelectionChanged(this._selectionRenderModel.selectionStart, this._selectionRenderModel.selectionEnd, this._selectionRenderModel.columnSelectMode);
|
|
294
297
|
}
|
|
295
298
|
|
|
296
299
|
public handleCharSizeChanged(): void {
|
|
@@ -320,11 +323,13 @@ export class DomRenderer extends Disposable implements IRenderer {
|
|
|
320
323
|
return;
|
|
321
324
|
}
|
|
322
325
|
|
|
326
|
+
this._selectionRenderModel.update(this._terminal, start, end, columnSelectMode);
|
|
327
|
+
|
|
323
328
|
// Translate from buffer position to viewport position
|
|
324
|
-
const viewportStartRow =
|
|
325
|
-
const viewportEndRow =
|
|
326
|
-
const viewportCappedStartRow =
|
|
327
|
-
const viewportCappedEndRow =
|
|
329
|
+
const viewportStartRow = this._selectionRenderModel.viewportStartRow;
|
|
330
|
+
const viewportEndRow = this._selectionRenderModel.viewportEndRow;
|
|
331
|
+
const viewportCappedStartRow = this._selectionRenderModel.viewportCappedStartRow;
|
|
332
|
+
const viewportCappedEndRow = this._selectionRenderModel.viewportCappedEndRow;
|
|
328
333
|
|
|
329
334
|
// No need to draw the selection
|
|
330
335
|
if (viewportCappedStartRow >= this._bufferService.rows || viewportCappedEndRow < 0) {
|
|
@@ -365,10 +370,16 @@ export class DomRenderer extends Disposable implements IRenderer {
|
|
|
365
370
|
*/
|
|
366
371
|
private _createSelectionElement(row: number, colStart: number, colEnd: number, rowCount: number = 1): HTMLElement {
|
|
367
372
|
const element = this._document.createElement('div');
|
|
373
|
+
const left = colStart * this.dimensions.css.cell.width;
|
|
374
|
+
let width = this.dimensions.css.cell.width * (colEnd - colStart);
|
|
375
|
+
if (left + width > this.dimensions.css.canvas.width) {
|
|
376
|
+
width = this.dimensions.css.canvas.width - left;
|
|
377
|
+
}
|
|
378
|
+
|
|
368
379
|
element.style.height = `${rowCount * this.dimensions.css.cell.height}px`;
|
|
369
380
|
element.style.top = `${row * this.dimensions.css.cell.height}px`;
|
|
370
|
-
element.style.left = `${
|
|
371
|
-
element.style.width = `${
|
|
381
|
+
element.style.left = `${left}px`;
|
|
382
|
+
element.style.width = `${width}px`;
|
|
372
383
|
return element;
|
|
373
384
|
}
|
|
374
385
|
|
|
@@ -11,7 +11,7 @@ import { ICoreService, IDecorationService, IOptionsService } from 'common/servic
|
|
|
11
11
|
import { color, rgba } from 'common/Color';
|
|
12
12
|
import { ICharacterJoinerService, ICoreBrowserService, IThemeService } from 'browser/services/Services';
|
|
13
13
|
import { JoinedCellData } from 'browser/services/CharacterJoinerService';
|
|
14
|
-
import {
|
|
14
|
+
import { treatGlyphAsBackgroundColor } from 'browser/renderer/shared/RendererUtils';
|
|
15
15
|
import { AttributeData } from 'common/buffer/AttributeData';
|
|
16
16
|
import { WidthCache } from 'browser/renderer/dom/WidthCache';
|
|
17
17
|
import { IColorContrastCache } from 'browser/Types';
|
|
@@ -458,7 +458,7 @@ export class DomRendererRowFactory {
|
|
|
458
458
|
}
|
|
459
459
|
|
|
460
460
|
private _applyMinimumContrast(element: HTMLElement, bg: IColor, fg: IColor, cell: ICellData, bgOverride: IColor | undefined, fgOverride: IColor | undefined): boolean {
|
|
461
|
-
if (this._optionsService.rawOptions.minimumContrastRatio === 1 ||
|
|
461
|
+
if (this._optionsService.rawOptions.minimumContrastRatio === 1 || treatGlyphAsBackgroundColor(cell.getCode())) {
|
|
462
462
|
return false;
|
|
463
463
|
}
|
|
464
464
|
|