From 8adf9473b76a146112c67b4e06ef746935a826db Mon Sep 17 00:00:00 2001 From: Simon Lamon Date: Sat, 12 Mar 2022 09:36:06 +0000 Subject: [PATCH 01/37] check scrollback in fit addon --- addons/xterm-addon-fit/src/FitAddon.ts | 5 ++++- src/browser/Viewport.ts | 18 ------------------ 2 files changed, 4 insertions(+), 19 deletions(-) diff --git a/addons/xterm-addon-fit/src/FitAddon.ts b/addons/xterm-addon-fit/src/FitAddon.ts index 360397ec..7b9c228f 100644 --- a/addons/xterm-addon-fit/src/FitAddon.ts +++ b/addons/xterm-addon-fit/src/FitAddon.ts @@ -63,6 +63,9 @@ export class FitAddon implements ITerminalAddon { return undefined; } + const scrollbarWidth = this._terminal.options.scrollback === 0 ? + 0 : core.viewport.scrollBarWidth; + const parentElementStyle = window.getComputedStyle(this._terminal.element.parentElement); const parentElementHeight = parseInt(parentElementStyle.getPropertyValue('height')); const parentElementWidth = Math.max(0, parseInt(parentElementStyle.getPropertyValue('width'))); @@ -76,7 +79,7 @@ export class FitAddon implements ITerminalAddon { const elementPaddingVer = elementPadding.top + elementPadding.bottom; const elementPaddingHor = elementPadding.right + elementPadding.left; const availableHeight = parentElementHeight - elementPaddingVer; - const availableWidth = parentElementWidth - elementPaddingHor - core.viewport.scrollBarWidth; + const availableWidth = parentElementWidth - elementPaddingHor - scrollbarWidth; const geometry = { cols: Math.max(MINIMUM_COLS, Math.floor(availableWidth / core._renderService.dimensions.actualCellWidth)), rows: Math.max(MINIMUM_ROWS, Math.floor(availableHeight / core._renderService.dimensions.actualCellHeight)) diff --git a/src/browser/Viewport.ts b/src/browser/Viewport.ts index 14fab897..1eb9dc4e 100644 --- a/src/browser/Viewport.ts +++ b/src/browser/Viewport.ts @@ -26,7 +26,6 @@ export class Viewport extends Disposable implements IViewport { private _lastRecordedBufferHeight: number = 0; private _lastTouchY: number = 0; private _lastScrollTop: number = 0; - private _lastHadScrollBar: boolean = false; private _activeBuffer: IBuffer; private _renderDimensions: IRenderDimensions; @@ -54,7 +53,6 @@ export class Viewport extends Disposable implements IViewport { // Unfortunately the overlay scrollbar would be hidden underneath the screen element in that case, // therefore we account for a standard amount to make it visible this.scrollBarWidth = (this._viewportElement.offsetWidth - this._scrollArea.offsetWidth) || FALLBACK_SCROLL_BAR_WIDTH; - this._lastHadScrollBar = true; this.register(addDisposableDomListener(this._viewportElement, 'scroll', this._onScroll.bind(this))); // Track properties used in performance critical code manually to avoid using slow getters @@ -109,17 +107,6 @@ export class Viewport extends Disposable implements IViewport { this._viewportElement.scrollTop = scrollTop; } - // Update scroll bar width - if (this._optionsService.rawOptions.scrollback === 0) { - this.scrollBarWidth = 0; - } else { - this.scrollBarWidth = (this._viewportElement.offsetWidth - this._scrollArea.offsetWidth) || FALLBACK_SCROLL_BAR_WIDTH; - } - this._lastHadScrollBar = this.scrollBarWidth > 0; - - const elementStyle = window.getComputedStyle(this._element); - const elementPadding = parseInt(elementStyle.paddingLeft) + parseInt(elementStyle.paddingRight); - this._viewportElement.style.width = (this._renderService.dimensions.actualCellWidth * (this._bufferService.cols) + this.scrollBarWidth + (this._lastHadScrollBar ? elementPadding : 0)).toString() + 'px'; this._refreshAnimationFrame = null; } @@ -151,11 +138,6 @@ export class Viewport extends Disposable implements IViewport { this._refresh(immediate); return; } - - // If the scroll bar visibility changed - if (this._lastHadScrollBar !== (this._optionsService.rawOptions.scrollback > 0)) { - this._refresh(immediate); - } } /** From 3d41f028e4966eda695a069b2ff1ddeab450a6b6 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 11 May 2022 10:12:48 -0700 Subject: [PATCH 02/37] Layout dimension left/right after renderer dims change Fixes #3773 --- .../Decorations/BufferDecorationRenderer.ts | 23 ++++++++++++++----- src/browser/TestUtils.test.ts | 2 +- src/browser/services/RenderService.ts | 4 ++-- src/browser/services/Services.ts | 1 - 4 files changed, 20 insertions(+), 10 deletions(-) diff --git a/src/browser/Decorations/BufferDecorationRenderer.ts b/src/browser/Decorations/BufferDecorationRenderer.ts index 22dc73e9..5bd0f940 100644 --- a/src/browser/Decorations/BufferDecorationRenderer.ts +++ b/src/browser/Decorations/BufferDecorationRenderer.ts @@ -14,6 +14,7 @@ export class BufferDecorationRenderer extends Disposable { private _animationFrame: number | undefined; private _altBufferIsActive: boolean = false; + private _dimensionsChanged: boolean = false; constructor( private readonly _screenElement: HTMLElement, @@ -28,7 +29,10 @@ export class BufferDecorationRenderer extends Disposable { this._screenElement.appendChild(this._container); this.register(this._renderService.onRenderedBufferChange(() => this._queueRefresh())); - this.register(this._renderService.onDimensionsChange(() => this._queueRefresh())); + this.register(this._renderService.onDimensionsChange(() => { + this._dimensionsChanged = true; + this._queueRefresh(); + })); this.register(addDisposableDomListener(window, 'resize', () => this._queueRefresh())); this.register(this._bufferService.buffers.onBufferActivate(() => { this._altBufferIsActive = this._bufferService.buffer === this._bufferService.buffers.alt; @@ -70,6 +74,9 @@ export class BufferDecorationRenderer extends Disposable { this._container.appendChild(element); } this._refreshStyle(decoration, element); + if (this._dimensionsChanged) { + this._refreshXPosition(decoration, element); + } decoration.onRenderEmitter.fire(element); } @@ -86,11 +93,7 @@ export class BufferDecorationRenderer extends Disposable { // exceeded the container width, so hide element.style.display = 'none'; } - if ((decoration.options.anchor || 'left') === 'right') { - element.style.right = x ? `${x * this._renderService.dimensions.actualCellWidth}px` : ''; - } else { - element.style.left = x ? `${x * this._renderService.dimensions.actualCellWidth}px` : ''; - } + this._refreshXPosition(decoration, element, x); return element; } @@ -106,6 +109,14 @@ export class BufferDecorationRenderer extends Disposable { } } + private _refreshXPosition(decoration: IInternalDecoration, element: HTMLElement, x: number = decoration.options.x ?? 0): void { + if ((decoration.options.anchor || 'left') === 'right') { + element.style.right = x ? `${x * this._renderService.dimensions.actualCellWidth}px` : ''; + } else { + element.style.left = x ? `${x * this._renderService.dimensions.actualCellWidth}px` : ''; + } + } + private _removeDecoration(decoration: IInternalDecoration): void { this._decorationElements.get(decoration)?.remove(); this._decorationElements.delete(decoration); diff --git a/src/browser/TestUtils.test.ts b/src/browser/TestUtils.test.ts index 85e2bb55..cc45f5e4 100644 --- a/src/browser/TestUtils.test.ts +++ b/src/browser/TestUtils.test.ts @@ -396,7 +396,7 @@ export class MockRenderService implements IRenderService { public resize(cols: number, rows: number): void { throw new Error('Method not implemented.'); } - public changeOptions(): void { + public _handleOptionsChanged(): void { throw new Error('Method not implemented.'); } public setRenderer(renderer: IRenderer): void { diff --git a/src/browser/services/RenderService.ts b/src/browser/services/RenderService.ts index 91b510a3..b575858a 100644 --- a/src/browser/services/RenderService.ts +++ b/src/browser/services/RenderService.ts @@ -69,7 +69,7 @@ export class RenderService extends Disposable implements IRenderService { this.register(bufferService.onResize(() => this._fullRefresh())); this.register(bufferService.buffers.onBufferActivate(() => this._renderer?.clear())); - this.register(optionsService.onOptionChange(() => this._renderer.onOptionsChanged())); + this.register(optionsService.onOptionChange(() => this._handleOptionsChanged())); this.register(this._charSizeService.onCharSizeChange(() => this.onCharSizeChanged())); // No need to register this as renderer is explicitly disposed in RenderService.dispose @@ -135,7 +135,7 @@ export class RenderService extends Disposable implements IRenderService { this._fireOnCanvasResize(); } - public changeOptions(): void { + private _handleOptionsChanged(): void { this._renderer.onOptionsChanged(); this.refreshRows(0, this._rowCount - 1); this._fireOnCanvasResize(); diff --git a/src/browser/services/Services.ts b/src/browser/services/Services.ts index 7191d0ed..a9f76a90 100644 --- a/src/browser/services/Services.ts +++ b/src/browser/services/Services.ts @@ -61,7 +61,6 @@ export interface IRenderService extends IDisposable { refreshRows(start: number, end: number): void; clearTextureAtlas(): void; resize(cols: number, rows: number): void; - changeOptions(): void; setRenderer(renderer: IRenderer): void; setColors(colors: IColorSet): void; onDevicePixelRatioChange(): void; From aeb4fa0fe9da0d43fc10674aebd36ba4ada9433e Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 11 May 2022 10:15:18 -0700 Subject: [PATCH 03/37] Fix compile after bad merge --- .../Decorations/BufferDecorationRenderer.ts | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/browser/Decorations/BufferDecorationRenderer.ts b/src/browser/Decorations/BufferDecorationRenderer.ts index f548750a..e695c824 100644 --- a/src/browser/Decorations/BufferDecorationRenderer.ts +++ b/src/browser/Decorations/BufferDecorationRenderer.ts @@ -66,7 +66,7 @@ export class BufferDecorationRenderer extends Disposable { private _renderDecoration(decoration: IInternalDecoration): void { this._refreshStyle(decoration); if (this._dimensionsChanged) { - this._refreshXPosition(decoration, element); + this._refreshXPosition(decoration); } } @@ -83,7 +83,7 @@ export class BufferDecorationRenderer extends Disposable { // exceeded the container width, so hide element.style.display = 'none'; } - this._refreshXPosition(decoration, element, x); + this._refreshXPosition(decoration); return element; } @@ -111,11 +111,15 @@ export class BufferDecorationRenderer extends Disposable { } } - private _refreshXPosition(decoration: IInternalDecoration, element: HTMLElement, x: number = decoration.options.x ?? 0): void { + private _refreshXPosition(decoration: IInternalDecoration): void { + if (!decoration.element) { + return; + } + const x = decoration.options.x ?? 0; if ((decoration.options.anchor || 'left') === 'right') { - element.style.right = x ? `${x * this._renderService.dimensions.actualCellWidth}px` : ''; + decoration.element.style.right = x ? `${x * this._renderService.dimensions.actualCellWidth}px` : ''; } else { - element.style.left = x ? `${x * this._renderService.dimensions.actualCellWidth}px` : ''; + decoration.element.style.left = x ? `${x * this._renderService.dimensions.actualCellWidth}px` : ''; } } From 0976b15709f7dd7649c4471a6e59349d4ace8787 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 11 May 2022 11:57:13 -0700 Subject: [PATCH 04/37] Fix lint --- src/browser/TestUtils.test.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/browser/TestUtils.test.ts b/src/browser/TestUtils.test.ts index cc45f5e4..cb487dbc 100644 --- a/src/browser/TestUtils.test.ts +++ b/src/browser/TestUtils.test.ts @@ -396,9 +396,6 @@ export class MockRenderService implements IRenderService { public resize(cols: number, rows: number): void { throw new Error('Method not implemented.'); } - public _handleOptionsChanged(): void { - throw new Error('Method not implemented.'); - } public setRenderer(renderer: IRenderer): void { throw new Error('Method not implemented.'); } From f18528d2bb82dc45bd51c1a2c835290ef7f699dc Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 12 May 2022 10:58:50 -0700 Subject: [PATCH 05/37] Clear _dimensionsChanged variable --- src/browser/Decorations/BufferDecorationRenderer.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/browser/Decorations/BufferDecorationRenderer.ts b/src/browser/Decorations/BufferDecorationRenderer.ts index e695c824..a063f9bd 100644 --- a/src/browser/Decorations/BufferDecorationRenderer.ts +++ b/src/browser/Decorations/BufferDecorationRenderer.ts @@ -61,6 +61,7 @@ export class BufferDecorationRenderer extends Disposable { for (const decoration of this._decorationService.decorations) { this._renderDecoration(decoration); } + this._dimensionsChanged = false; } private _renderDecoration(decoration: IInternalDecoration): void { From 1a50cfabe08593e020003e60a0c590e3d2797ec9 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 13 May 2022 07:56:45 -0700 Subject: [PATCH 06/37] Dom renderer: top decorations above selection Fixes #3799 --- css/xterm.css | 5 +++++ src/browser/renderer/dom/DomRendererRowFactory.ts | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/css/xterm.css b/css/xterm.css index 2f84c859..95fc61ed 100644 --- a/css/xterm.css +++ b/css/xterm.css @@ -183,3 +183,8 @@ right: 0; pointer-events: none; } + +.xterm-decoration-top { + z-index: 2; + position: relative; +} diff --git a/src/browser/renderer/dom/DomRendererRowFactory.ts b/src/browser/renderer/dom/DomRendererRowFactory.ts index bf3939e8..3491b76f 100644 --- a/src/browser/renderer/dom/DomRendererRowFactory.ts +++ b/src/browser/renderer/dom/DomRendererRowFactory.ts @@ -195,6 +195,11 @@ export class DomRendererRowFactory { isTop = d.options.layer === 'top'; } + // If it's a top decoration, render on above the selection + if (isTop) { + charElement.classList.add(`xterm-decoration-top`); + } + // Foreground switch (fgColorMode) { case Attributes.CM_P16: From 77d5d52e712d7a3ed007e5c77a55f9c5dcaa9bfc Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 13 May 2022 08:10:35 -0700 Subject: [PATCH 07/37] Allow styling active result decoration via .xterm-find-active-result-decoration Fixes #3801 --- addons/xterm-addon-search/src/SearchAddon.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/addons/xterm-addon-search/src/SearchAddon.ts b/addons/xterm-addon-search/src/SearchAddon.ts index e7ece483..cf751622 100644 --- a/addons/xterm-addon-search/src/SearchAddon.ts +++ b/addons/xterm-addon-search/src/SearchAddon.ts @@ -679,7 +679,7 @@ export class SearchAddon implements ITerminalAddon { color: options.activeMatchColorOverviewRuler } }); - this._selectedDecoration?.onRender((e) => this._applyStyles(e, options.activeMatchBorder)); + this._selectedDecoration?.onRender((e) => this._applyStyles(e, options.activeMatchBorder, true)); this._selectedDecoration?.onDispose(() => marker.dispose()); } } @@ -702,7 +702,7 @@ export class SearchAddon implements ITerminalAddon { * @param borderColor the border color to apply * @returns */ - private _applyStyles(element: HTMLElement, borderColor: string | undefined): void { + private _applyStyles(element: HTMLElement, borderColor: string | undefined, isActiveResult: boolean): void { if (element.clientWidth <= 0) { return; } @@ -712,6 +712,9 @@ export class SearchAddon implements ITerminalAddon { element.style.outline = `1px solid ${borderColor}`; } } + if (isActiveResult) { + element.classList.add('xterm-find-active-result-decoration'); + } } /** @@ -736,7 +739,7 @@ export class SearchAddon implements ITerminalAddon { position: 'center' } }); - findResultDecoration?.onRender((e) => this._applyStyles(e, options.matchBorder)); + findResultDecoration?.onRender((e) => this._applyStyles(e, options.matchBorder, false)); findResultDecoration?.onDispose(() => marker.dispose()); return findResultDecoration; } From a458bae36d0986628ddfbbbf62ee2543d55c3f8f Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 13 May 2022 08:46:40 -0700 Subject: [PATCH 08/37] Update src/browser/renderer/dom/DomRendererRowFactory.ts Co-authored-by: Megan Rogge --- src/browser/renderer/dom/DomRendererRowFactory.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/browser/renderer/dom/DomRendererRowFactory.ts b/src/browser/renderer/dom/DomRendererRowFactory.ts index 3491b76f..4bed48e5 100644 --- a/src/browser/renderer/dom/DomRendererRowFactory.ts +++ b/src/browser/renderer/dom/DomRendererRowFactory.ts @@ -195,7 +195,7 @@ export class DomRendererRowFactory { isTop = d.options.layer === 'top'; } - // If it's a top decoration, render on above the selection + // If it's a top decoration, render above the selection if (isTop) { charElement.classList.add(`xterm-decoration-top`); } From 82a6144fd175a57917f2e797d8003aad768e9445 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Fri, 13 May 2022 08:47:56 -0700 Subject: [PATCH 09/37] reset search result index when buffer changes/ terminal is resized (#3793) --- addons/xterm-addon-search/src/SearchAddon.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/addons/xterm-addon-search/src/SearchAddon.ts b/addons/xterm-addon-search/src/SearchAddon.ts index cf751622..1eb126e5 100644 --- a/addons/xterm-addon-search/src/SearchAddon.ts +++ b/addons/xterm-addon-search/src/SearchAddon.ts @@ -88,6 +88,7 @@ export class SearchAddon implements ITerminalAddon { if (this._cachedSearchTerm && this._lastSearchOptions?.decorations) { this._highlightTimeout = setTimeout(() => { this.findPrevious(this._cachedSearchTerm!, { ...this._lastSearchOptions, incremental: true, noScroll: true }); + this._resultIndex = this._searchResults ? this._searchResults.size -1 : -1; this._onDidChangeResults.fire({ resultIndex: this._searchResults ? this._searchResults.size - 1 : -1, resultCount: this._searchResults ? this._searchResults.size : -1 }); }, 200); } From 5d98304249d959c0e724b06fe6ad1349b4c1b52f Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 13 May 2022 09:42:39 -0700 Subject: [PATCH 10/37] Fire onSelectionChange when Terminal.select is called Fixes #3804 --- src/browser/services/SelectionService.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/browser/services/SelectionService.ts b/src/browser/services/SelectionService.ts index 53020b53..4b882e6d 100644 --- a/src/browser/services/SelectionService.ts +++ b/src/browser/services/SelectionService.ts @@ -771,6 +771,7 @@ export class SelectionService extends Disposable implements ISelectionService { this._model.selectionStart = [col, row]; this._model.selectionStartLength = length; this.refresh(); + this._fireEventIfSelectionChanged(); } public rightClickSelect(ev: MouseEvent): void { From de03391c2d6bc1eb3b66c56e838ec8968b85cad8 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 13 May 2022 11:19:38 -0700 Subject: [PATCH 11/37] Allow ensureContrastRatio to change luminance the other way The higher ratio of the two results will be picked. Fixes #3720 --- src/common/Color.ts | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/src/common/Color.ts b/src/common/Color.ts index b197cd66..a2a3cbad 100644 --- a/src/common/Color.ts +++ b/src/common/Color.ts @@ -150,15 +150,42 @@ export namespace rgb { * Helper functions where the source type is "rgba" (number: 0xrrggbbaa). */ export namespace rgba { + /** + * Given a foreground color and a background color, either increase or reduce the luminance of the + * foreground color until the specified contrast ratio is met. If pure white or black is hit + * without the contrast ratio being met, go the other direction using the background color as the + * foreground color and take either the first or second result depending on which has the higher + * contrast ratio. + * + * `undefined` will be returned if the contrast ratio is already met. + * + * @param bgRgba The background color in rgba format. + * @param fgRgba The foreground color in rgba format. + * @param ratio The contrast ratio to achieve. + */ export function ensureContrastRatio(bgRgba: number, fgRgba: number, ratio: number): number | undefined { const bgL = rgb.relativeLuminance(bgRgba >> 8); const fgL = rgb.relativeLuminance(fgRgba >> 8); const cr = contrastRatio(bgL, fgL); if (cr < ratio) { if (fgL < bgL) { - return reduceLuminance(bgRgba, fgRgba, ratio); + const resultA = reduceLuminance(bgRgba, fgRgba, ratio); + const resultARatio = contrastRatio(bgL, rgb.relativeLuminance(resultA >> 8)); + if (resultARatio < ratio) { + const resultB = increaseLuminance(bgRgba, bgRgba, ratio); + const resultBRatio = contrastRatio(bgL, rgb.relativeLuminance(resultB >> 8)); + return resultARatio > resultBRatio ? resultA : resultB; + } + return resultA; } - return increaseLuminance(bgRgba, fgRgba, ratio); + const resultA = increaseLuminance(bgRgba, fgRgba, ratio); + const resultARatio = contrastRatio(bgL, rgb.relativeLuminance(resultA >> 8)); + if (resultARatio < ratio) { + const resultB = reduceLuminance(bgRgba, bgRgba, ratio); + const resultBRatio = contrastRatio(bgL, rgb.relativeLuminance(resultB >> 8)); + return resultARatio > resultBRatio ? resultA : resultB; + } + return resultA; } return undefined; } From d7fb0141d246399f3f5ea375543f5a5733439fbb Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 13 May 2022 12:32:17 -0700 Subject: [PATCH 12/37] Whitespace change to trigger build The fix for #3720 didn't touch any file in the webgl addon so it didn't end up releasing. --- addons/xterm-addon-webgl/src/WebglRenderer.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index d060c4d1..4739d498 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -167,7 +167,6 @@ export class WebglRenderer extends Disposable implements IRenderer { this._core.screenElement!.style.height = `${this.dimensions.canvasHeight}px`; this._rectangleRenderer.onResize(); - this._glyphRenderer.setDimensions(this.dimensions); this._glyphRenderer.onResize(); From c5022723731c503d7862758a8c846e397babaeb2 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 16 May 2022 09:40:07 -0700 Subject: [PATCH 13/37] Support selection foreground in webgl Part of #3810 --- addons/xterm-addon-webgl/src/WebglRenderer.ts | 3 +++ addons/xterm-addon-webgl/src/atlas/CharAtlasUtils.ts | 1 + src/browser/ColorManager.ts | 10 ++++++++++ src/browser/Types.d.ts | 1 + src/common/services/Services.ts | 1 + typings/xterm.d.ts | 2 ++ 6 files changed, 18 insertions(+) diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index d060c4d1..dbe9da91 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -398,6 +398,9 @@ export class WebglRenderer extends Disposable implements IRenderer { // Apply the selection color if needed if (this._isCellSelected(x, y)) { bgOverride = this._colors.selectionOpaque.rgba >> 8 & 0xFFFFFF; + if (this._colors.selectionForeground) { + fgOverride = this._colors.selectionForeground.rgba >> 8 && 0xFFFFFF; + } } // Apply decorations on the top layer diff --git a/addons/xterm-addon-webgl/src/atlas/CharAtlasUtils.ts b/addons/xterm-addon-webgl/src/atlas/CharAtlasUtils.ts index 0ce893df..92cfd4a9 100644 --- a/addons/xterm-addon-webgl/src/atlas/CharAtlasUtils.ts +++ b/addons/xterm-addon-webgl/src/atlas/CharAtlasUtils.ts @@ -23,6 +23,7 @@ export function generateConfig(scaledCellWidth: number, scaledCellHeight: number cursorAccent: NULL_COLOR, selectionTransparent: NULL_COLOR, selectionOpaque: NULL_COLOR, + selectionForeground: NULL_COLOR, // For the static char atlas, we only use the first 16 colors, but we need all 256 for the // dynamic character atlas. ansi: colors.ansi.slice(), diff --git a/src/browser/ColorManager.ts b/src/browser/ColorManager.ts index 2d6e4ea5..f11ef8fd 100644 --- a/src/browser/ColorManager.ts +++ b/src/browser/ColorManager.ts @@ -104,6 +104,7 @@ export class ColorManager implements IColorManager { cursorAccent: DEFAULT_CURSOR_ACCENT, selectionTransparent: DEFAULT_SELECTION, selectionOpaque: color.blend(DEFAULT_BACKGROUND, DEFAULT_SELECTION), + selectionForeground: undefined, ansi: DEFAULT_ANSI_COLORS.slice(), contrastCache: this._contrastCache }; @@ -128,6 +129,15 @@ export class ColorManager implements IColorManager { this.colors.cursorAccent = this._parseColor(theme.cursorAccent, DEFAULT_CURSOR_ACCENT, true); this.colors.selectionTransparent = this._parseColor(theme.selection, DEFAULT_SELECTION, true); this.colors.selectionOpaque = color.blend(this.colors.background, this.colors.selectionTransparent); + const nullColor: IColor = { + css: '', + rgba: 0 + }; + this.colors.selectionForeground = theme.selectionForeground ? this._parseColor(theme.selectionForeground, nullColor) : undefined; + if (this.colors.selectionForeground === nullColor) { + this.colors.selectionForeground = undefined; + } + /** * If selection color is opaque, blend it with background with 0.3 opacity * Issue #2737 diff --git a/src/browser/Types.d.ts b/src/browser/Types.d.ts index 0e83c213..129842d5 100644 --- a/src/browser/Types.d.ts +++ b/src/browser/Types.d.ts @@ -120,6 +120,7 @@ export interface IColorSet { selectionTransparent: IColor; /** The selection blended on top of background. */ selectionOpaque: IColor; + selectionForeground: IColor | undefined; ansi: IColor[]; contrastCache: IColorContrastCache; } diff --git a/src/common/services/Services.ts b/src/common/services/Services.ts index c3190210..017eb386 100644 --- a/src/common/services/Services.ts +++ b/src/common/services/Services.ts @@ -259,6 +259,7 @@ export interface ITheme { cursor?: string; cursorAccent?: string; selection?: string; + selectionForeground?: string; black?: string; red?: string; green?: string; diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 15ee4650..ce6c8d3f 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -288,6 +288,8 @@ declare module 'xterm' { cursorAccent?: string; /** The selection background color (can be transparent) */ selection?: string; + /** The selection foreground color */ + selectionForeground?: string; /** ANSI black (eg. `\x1b[30m`) */ black?: string; /** ANSI red (eg. `\x1b[31m`) */ From eb961878012d6ef3c411406e64987061a67d3ca7 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 16 May 2022 10:08:51 -0700 Subject: [PATCH 14/37] Support selectionForeground in dom Part of #3810 --- src/browser/TestUtils.test.ts | 52 +++++++++++++++++++ src/browser/renderer/dom/DomRenderer.ts | 2 + .../dom/DomRendererRowFactory.test.ts | 8 +-- .../renderer/dom/DomRendererRowFactory.ts | 16 ++++-- src/browser/services/SelectionService.ts | 9 ++++ src/browser/services/Services.ts | 1 + 6 files changed, 82 insertions(+), 6 deletions(-) diff --git a/src/browser/TestUtils.test.ts b/src/browser/TestUtils.test.ts index 85e2bb55..f7e6bcb4 100644 --- a/src/browser/TestUtils.test.ts +++ b/src/browser/TestUtils.test.ts @@ -16,6 +16,7 @@ import { Terminal } from 'browser/Terminal'; import { IUnicodeService, IOptionsService, ICoreService, ICoreMouseService } from 'common/services/Services'; import { IFunctionIdentifier, IParams } from 'common/parser/Types'; import { AttributeData } from 'common/buffer/AttributeData'; +import { ISelectionRedrawRequestEvent, ISelectionRequestScrollLinesEvent } from 'browser/selection/Types'; export class TestTerminal extends Terminal { public get curAttrData(): IAttributeData { return (this as any)._inputHandler._curAttrData; } @@ -449,3 +450,54 @@ export class MockCharacterJoinerService implements ICharacterJoinerService { return []; } } + +export class MockSelectionService implements ISelectionService { + public serviceBrand: undefined; + public selectionText: string = ''; + public hasSelection: boolean = false; + public selectionStart: [number, number] | undefined; + public selectionEnd: [number, number] | undefined; + public onLinuxMouseSelection = new EventEmitter().event; + public onRequestRedraw = new EventEmitter().event; + public onRequestScrollLines = new EventEmitter().event; + public onSelectionChange = new EventEmitter().event; + public disable(): void { + throw new Error('Method not implemented.'); + } + public enable(): void { + throw new Error('Method not implemented.'); + } + public reset(): void { + throw new Error('Method not implemented.'); + } + public setSelection(row: number, col: number, length: number): void { + throw new Error('Method not implemented.'); + } + public selectAll(): void { + throw new Error('Method not implemented.'); + } + public selectLines(start: number, end: number): void { + throw new Error('Method not implemented.'); + } + public clearSelection(): void { + throw new Error('Method not implemented.'); + } + public rightClickSelect(event: MouseEvent): void { + throw new Error('Method not implemented.'); + } + public shouldColumnSelect(event: MouseEvent | KeyboardEvent): boolean { + throw new Error('Method not implemented.'); + } + public shouldForceSelection(event: MouseEvent): boolean { + throw new Error('Method not implemented.'); + } + public refresh(isLinuxMouseSelection?: boolean): void { + throw new Error('Method not implemented.'); + } + public onMouseDown(event: MouseEvent): void { + throw new Error('Method not implemented.'); + } + public isCellInSelection(x: number, y: number): boolean { + return false; + } +} diff --git a/src/browser/renderer/dom/DomRenderer.ts b/src/browser/renderer/dom/DomRenderer.ts index d15d7eac..840ef40d 100644 --- a/src/browser/renderer/dom/DomRenderer.ts +++ b/src/browser/renderer/dom/DomRenderer.ts @@ -281,6 +281,8 @@ export class DomRenderer extends Disposable implements IRenderer { this._selectionContainer.removeChild(this._selectionContainer.children[0]); } + this.renderRows(0, this._bufferService.rows - 1); + // Selection does not exist if (!start || !end) { return; diff --git a/src/browser/renderer/dom/DomRendererRowFactory.test.ts b/src/browser/renderer/dom/DomRendererRowFactory.test.ts index bb511a47..ae7a1434 100644 --- a/src/browser/renderer/dom/DomRendererRowFactory.test.ts +++ b/src/browser/renderer/dom/DomRendererRowFactory.test.ts @@ -10,9 +10,9 @@ import { NULL_CELL_CODE, NULL_CELL_WIDTH, NULL_CELL_CHAR, DEFAULT_ATTR, FgFlags, import { BufferLine, DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine'; import { IBufferLine } from 'common/Types'; import { CellData } from 'common/buffer/CellData'; -import { MockCoreService, MockDecorationService, MockOptionsService } from 'common/TestUtils.test'; +import { MockBufferService, MockCoreService, MockDecorationService, MockOptionsService } from 'common/TestUtils.test'; import { css } from 'common/Color'; -import { MockCharacterJoinerService } from 'browser/TestUtils.test'; +import { MockCharacterJoinerService, MockSelectionService } from 'browser/TestUtils.test'; describe('DomRendererRowFactory', () => { let dom: jsdom.JSDOM; @@ -50,7 +50,9 @@ describe('DomRendererRowFactory', () => { new MockCharacterJoinerService(), new MockOptionsService({ drawBoldTextInBrightColors: true }), new MockCoreService(), - new MockDecorationService() + new MockDecorationService(), + new MockBufferService(80, 30), + new MockSelectionService() ); lineData = createEmptyLineData(2); }); diff --git a/src/browser/renderer/dom/DomRendererRowFactory.ts b/src/browser/renderer/dom/DomRendererRowFactory.ts index 4bed48e5..f421859b 100644 --- a/src/browser/renderer/dom/DomRendererRowFactory.ts +++ b/src/browser/renderer/dom/DomRendererRowFactory.ts @@ -7,10 +7,10 @@ import { IBufferLine, ICellData, IColor } from 'common/Types'; import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants'; import { NULL_CELL_CODE, WHITESPACE_CELL_CHAR, Attributes } from 'common/buffer/Constants'; import { CellData } from 'common/buffer/CellData'; -import { ICoreService, IDecorationService, IOptionsService } from 'common/services/Services'; +import { IBufferService, ICoreService, IDecorationService, IOptionsService } from 'common/services/Services'; import { color, rgba } from 'common/Color'; import { IColorSet } from 'browser/Types'; -import { ICharacterJoinerService } from 'browser/services/Services'; +import { ICharacterJoinerService, ISelectionService } from 'browser/services/Services'; import { JoinedCellData } from 'browser/services/CharacterJoinerService'; import { isPowerlineGlyph } from 'browser/renderer/RendererUtils'; @@ -34,7 +34,9 @@ export class DomRendererRowFactory { @ICharacterJoinerService private readonly _characterJoinerService: ICharacterJoinerService, @IOptionsService private readonly _optionsService: IOptionsService, @ICoreService private readonly _coreService: ICoreService, - @IDecorationService private readonly _decorationService: IDecorationService + @IDecorationService private readonly _decorationService: IDecorationService, + @IBufferService private readonly _bufferService: IBufferService, + @ISelectionService private readonly _selectionService: ISelectionService ) { } @@ -195,6 +197,14 @@ export class DomRendererRowFactory { isTop = d.options.layer === 'top'; } + // Apply selection foreground if applicable + if (!isTop) { + if (this._colors.selectionForeground && this._selectionService.isCellInSelection(x, row)) { + fg = this._colors.selectionForeground.rgba >> 8 & 0xFFFFFF; + fgOverride = this._colors.selectionForeground; + } + } + // If it's a top decoration, render above the selection if (isTop) { charElement.classList.add(`xterm-decoration-top`); diff --git a/src/browser/services/SelectionService.ts b/src/browser/services/SelectionService.ts index 4b882e6d..c7b7707a 100644 --- a/src/browser/services/SelectionService.ts +++ b/src/browser/services/SelectionService.ts @@ -308,6 +308,15 @@ export class SelectionService extends Disposable implements ISelectionService { return this._areCoordsInSelection(coords, start, end); } + public isCellInSelection(x: number, y: number): boolean { + const start = this._model.finalSelectionStart; + const end = this._model.finalSelectionEnd; + if (!start || !end) { + return false; + } + return this._areCoordsInSelection([x, y], start, end); + } + protected _areCoordsInSelection(coords: [number, number], start: [number, number], end: [number, number]): boolean { return (coords[1] > start[1] && coords[1] < end[1]) || (start[1] === end[1] && coords[1] === start[1] && coords[0] >= start[0] && coords[0] < end[0]) || diff --git a/src/browser/services/Services.ts b/src/browser/services/Services.ts index 7191d0ed..c5328f76 100644 --- a/src/browser/services/Services.ts +++ b/src/browser/services/Services.ts @@ -101,6 +101,7 @@ export interface ISelectionService { shouldForceSelection(event: MouseEvent): boolean; refresh(isLinuxMouseSelection?: boolean): void; onMouseDown(event: MouseEvent): void; + isCellInSelection(x: number, y: number): boolean; } export const ISoundService = createDecorator('SoundService'); From f2b90d690f97ec72e5ca82d46787975ac9277901 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 16 May 2022 15:17:03 -0700 Subject: [PATCH 15/37] Selection foreground for canvas (circular dep issue) --- src/browser/renderer/BaseRenderLayer.ts | 11 ++++++++++- src/browser/renderer/CursorRenderLayer.ts | 7 ++++--- src/browser/renderer/LinkRenderLayer.ts | 6 ++++-- src/browser/renderer/SelectionRenderLayer.ts | 6 ++++-- src/browser/renderer/TextRenderLayer.ts | 7 ++++--- 5 files changed, 26 insertions(+), 11 deletions(-) diff --git a/src/browser/renderer/BaseRenderLayer.ts b/src/browser/renderer/BaseRenderLayer.ts index 696b793f..7bcc6881 100644 --- a/src/browser/renderer/BaseRenderLayer.ts +++ b/src/browser/renderer/BaseRenderLayer.ts @@ -18,6 +18,7 @@ import { isPowerlineGlyph, throwIfFalsy } from 'browser/renderer/RendererUtils'; import { channels, color, rgba } from 'common/Color'; import { removeElementFromParent } from 'browser/Dom'; import { tryDrawCustomChar } from 'browser/renderer/CustomGlyphs'; +import { ISelectionService } from 'browser/services/Services'; export abstract class BaseRenderLayer implements IRenderLayer { private _canvas: HTMLCanvasElement; @@ -53,7 +54,8 @@ export abstract class BaseRenderLayer implements IRenderLayer { private _rendererId: number, protected readonly _bufferService: IBufferService, protected readonly _optionsService: IOptionsService, - protected readonly _decorationService: IDecorationService + protected readonly _decorationService: IDecorationService, + protected readonly _selectionService: ISelectionService ) { this._canvas = document.createElement('canvas'); this._canvas.classList.add(`xterm-${id}-layer`); @@ -457,6 +459,13 @@ export abstract class BaseRenderLayer implements IRenderLayer { isTop = d.options.layer === 'top'; } + // Apply selection foreground if applicable + if (!isTop) { + if (this._colors.selectionForeground && this._selectionService.isCellInSelection(x, y)) { + fgOverride = this._colors.selectionForeground.rgba; + } + } + if (!bgOverride && !fgOverride && (this._optionsService.rawOptions.minimumContrastRatio === 1 || isPowerlineGlyph(cell.getCode()))) { return undefined; } diff --git a/src/browser/renderer/CursorRenderLayer.ts b/src/browser/renderer/CursorRenderLayer.ts index 3fa576a9..6405390e 100644 --- a/src/browser/renderer/CursorRenderLayer.ts +++ b/src/browser/renderer/CursorRenderLayer.ts @@ -10,7 +10,7 @@ import { CellData } from 'common/buffer/CellData'; import { IColorSet } from 'browser/Types'; import { IBufferService, IOptionsService, ICoreService, IDecorationService } from 'common/services/Services'; import { IEventEmitter } from 'common/EventEmitter'; -import { ICoreBrowserService } from 'browser/services/Services'; +import { ICoreBrowserService, ISelectionService } from 'browser/services/Services'; interface ICursorState { x: number; @@ -41,9 +41,10 @@ export class CursorRenderLayer extends BaseRenderLayer { @IOptionsService optionsService: IOptionsService, @ICoreService private readonly _coreService: ICoreService, @ICoreBrowserService private readonly _coreBrowserService: ICoreBrowserService, - @IDecorationService decorationService: IDecorationService + @IDecorationService decorationService: IDecorationService, + @ISelectionService selectionService: ISelectionService ) { - super(container, 'cursor', zIndex, true, colors, rendererId, bufferService, optionsService, decorationService); + super(container, 'cursor', zIndex, true, colors, rendererId, bufferService, optionsService, decorationService, selectionService); this._state = { x: 0, y: 0, diff --git a/src/browser/renderer/LinkRenderLayer.ts b/src/browser/renderer/LinkRenderLayer.ts index 15086d9a..4d350f4d 100644 --- a/src/browser/renderer/LinkRenderLayer.ts +++ b/src/browser/renderer/LinkRenderLayer.ts @@ -9,6 +9,7 @@ import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants'; import { is256Color } from 'browser/renderer/atlas/CharAtlasUtils'; import { IColorSet, ILinkifierEvent, ILinkifier, ILinkifier2 } from 'browser/Types'; import { IBufferService, IDecorationService, IOptionsService } from 'common/services/Services'; +import { ISelectionService } from 'browser/services/Services'; export class LinkRenderLayer extends BaseRenderLayer { private _state: ILinkifierEvent | undefined; @@ -22,9 +23,10 @@ export class LinkRenderLayer extends BaseRenderLayer { linkifier2: ILinkifier2, @IBufferService bufferService: IBufferService, @IOptionsService optionsService: IOptionsService, - @IDecorationService decorationService: IDecorationService + @IDecorationService decorationService: IDecorationService, + @ISelectionService selectionService: ISelectionService ) { - super(container, 'link', zIndex, true, colors, rendererId, bufferService, optionsService, decorationService); + super(container, 'link', zIndex, true, colors, rendererId, bufferService, optionsService, decorationService, selectionService); linkifier.onShowLinkUnderline(e => this._onShowLinkUnderline(e)); linkifier.onHideLinkUnderline(e => this._onHideLinkUnderline(e)); diff --git a/src/browser/renderer/SelectionRenderLayer.ts b/src/browser/renderer/SelectionRenderLayer.ts index be911eb9..60c53eaf 100644 --- a/src/browser/renderer/SelectionRenderLayer.ts +++ b/src/browser/renderer/SelectionRenderLayer.ts @@ -7,6 +7,7 @@ import { IRenderDimensions } from 'browser/renderer/Types'; import { BaseRenderLayer } from 'browser/renderer/BaseRenderLayer'; import { IColorSet } from 'browser/Types'; import { IBufferService, IDecorationService, IOptionsService } from 'common/services/Services'; +import { ISelectionService } from 'browser/services/Services'; interface ISelectionState { start?: [number, number]; @@ -25,9 +26,10 @@ export class SelectionRenderLayer extends BaseRenderLayer { rendererId: number, @IBufferService bufferService: IBufferService, @IOptionsService optionsService: IOptionsService, - @IDecorationService decorationService: IDecorationService + @IDecorationService decorationService: IDecorationService, + @ISelectionService selectionService: ISelectionService ) { - super(container, 'selection', zIndex, true, colors, rendererId, bufferService, optionsService, decorationService); + super(container, 'selection', zIndex, true, colors, rendererId, bufferService, optionsService, decorationService, selectionService); this._clearState(); } diff --git a/src/browser/renderer/TextRenderLayer.ts b/src/browser/renderer/TextRenderLayer.ts index ef5a9b62..97c7e6f6 100644 --- a/src/browser/renderer/TextRenderLayer.ts +++ b/src/browser/renderer/TextRenderLayer.ts @@ -12,7 +12,7 @@ import { NULL_CELL_CODE, Content } from 'common/buffer/Constants'; import { IColorSet } from 'browser/Types'; import { CellData } from 'common/buffer/CellData'; import { IOptionsService, IBufferService, IDecorationService } from 'common/services/Services'; -import { ICharacterJoinerService } from 'browser/services/Services'; +import { ICharacterJoinerService, ISelectionService } from 'browser/services/Services'; import { JoinedCellData } from 'browser/services/CharacterJoinerService'; /** @@ -38,9 +38,10 @@ export class TextRenderLayer extends BaseRenderLayer { @IBufferService bufferService: IBufferService, @IOptionsService optionsService: IOptionsService, @ICharacterJoinerService private readonly _characterJoinerService: ICharacterJoinerService, - @IDecorationService decorationService: IDecorationService + @IDecorationService decorationService: IDecorationService, + @ISelectionService selectionService: ISelectionService ) { - super(container, 'text', zIndex, alpha, colors, rendererId, bufferService, optionsService, decorationService); + super(container, 'text', zIndex, alpha, colors, rendererId, bufferService, optionsService, decorationService, selectionService); this._state = new GridCache(); } From de5df69333ef97e5d25ae65ec12659c1fd1754a0 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Tue, 17 May 2022 09:52:46 -0700 Subject: [PATCH 16/37] share animation frame with renderService (#3796) --- src/browser/RenderDebouncer.ts | 25 ++++++++++++++++--- src/browser/TestUtils.test.ts | 5 +++- src/browser/Types.d.ts | 4 +++ .../decorations/BufferDecorationRenderer.ts | 2 +- src/browser/services/RenderService.ts | 8 ++++-- src/browser/services/Services.ts | 4 ++- 6 files changed, 40 insertions(+), 8 deletions(-) diff --git a/src/browser/RenderDebouncer.ts b/src/browser/RenderDebouncer.ts index 02521070..ad2d79b4 100644 --- a/src/browser/RenderDebouncer.ts +++ b/src/browser/RenderDebouncer.ts @@ -3,16 +3,17 @@ * @license MIT */ -import { IRenderDebouncer } from 'browser/Types'; +import { IRenderDebouncerWithCallback } from 'browser/Types'; /** * Debounces calls to render terminal rows using animation frames. */ -export class RenderDebouncer implements IRenderDebouncer { +export class RenderDebouncer implements IRenderDebouncerWithCallback { private _rowStart: number | undefined; private _rowEnd: number | undefined; private _rowCount: number | undefined; private _animationFrame: number | undefined; + private _refreshCallbacks: FrameRequestCallback[] = []; constructor( private _renderCallback: (start: number, end: number) => void @@ -26,6 +27,14 @@ export class RenderDebouncer implements IRenderDebouncer { } } + public addRefreshCallback(callback: FrameRequestCallback): number { + this._refreshCallbacks.push(callback); + if (!this._animationFrame) { + this._animationFrame = window.requestAnimationFrame(() => this._innerRefresh()); + } + return this._animationFrame; + } + public refresh(rowStart: number | undefined, rowEnd: number | undefined, rowCount: number): void { this._rowCount = rowCount; // Get the min/max row start/end for the arg values @@ -43,8 +52,11 @@ export class RenderDebouncer implements IRenderDebouncer { } private _innerRefresh(): void { + this._animationFrame = undefined; + // Make sure values are set if (this._rowStart === undefined || this._rowEnd === undefined || this._rowCount === undefined) { + this._runRefreshCallbacks(); return; } @@ -55,9 +67,16 @@ export class RenderDebouncer implements IRenderDebouncer { // Reset debouncer (this happens before render callback as the render could trigger it again) this._rowStart = undefined; this._rowEnd = undefined; - this._animationFrame = undefined; // Run render callback this._renderCallback(start, end); + this._runRefreshCallbacks(); + } + + private _runRefreshCallbacks(): void { + for (const callback of this._refreshCallbacks) { + callback(0); + } + this._refreshCallbacks = []; } } diff --git a/src/browser/TestUtils.test.ts b/src/browser/TestUtils.test.ts index cb487dbc..c3376b21 100644 --- a/src/browser/TestUtils.test.ts +++ b/src/browser/TestUtils.test.ts @@ -7,7 +7,7 @@ import { IDisposable, IMarker, ISelectionPosition, ILinkProvider, IDecorationOpt import { IEvent, EventEmitter } from 'common/EventEmitter'; import { ICharacterJoinerService, ICharSizeService, IMouseService, IRenderService, ISelectionService } from 'browser/services/Services'; import { IRenderDimensions, IRenderer, IRequestRedrawEvent } from 'browser/renderer/Types'; -import { IColorSet, ILinkMatcherOptions, ITerminal, ILinkifier, ILinkifier2, IBrowser, IViewport, IColorManager, ICompositionHelper, CharacterJoinerHandler } from 'browser/Types'; +import { IColorSet, ILinkMatcherOptions, ITerminal, ILinkifier, ILinkifier2, IBrowser, IViewport, IColorManager, ICompositionHelper, CharacterJoinerHandler, IRenderDebouncer } from 'browser/Types'; import { IBuffer, IBufferStringIterator, IBufferSet } from 'common/buffer/Types'; import { IBufferLine, ICellData, IAttributeData, ICircularList, XtermListener, ICharset, ITerminalOptions } from 'common/Types'; import { Buffer } from 'common/buffer/Buffer'; @@ -390,6 +390,9 @@ export class MockRenderService implements IRenderService { public refreshRows(start: number, end: number): void { throw new Error('Method not implemented.'); } + public addRefreshCallback(callback: FrameRequestCallback): number { + throw new Error('Method not implemented.'); + } public clearTextureAtlas(): void { throw new Error('Method not implemented.'); } diff --git a/src/browser/Types.d.ts b/src/browser/Types.d.ts index 0e83c213..a472326a 100644 --- a/src/browser/Types.d.ts +++ b/src/browser/Types.d.ts @@ -309,3 +309,7 @@ export interface ICharacterJoiner { export interface IRenderDebouncer extends IDisposable { refresh(rowStart: number | undefined, rowEnd: number | undefined, rowCount: number): void; } + +export interface IRenderDebouncerWithCallback extends IRenderDebouncer { + addRefreshCallback(callback: FrameRequestCallback): number; +} diff --git a/src/browser/decorations/BufferDecorationRenderer.ts b/src/browser/decorations/BufferDecorationRenderer.ts index 00b2b00b..5c9b1282 100644 --- a/src/browser/decorations/BufferDecorationRenderer.ts +++ b/src/browser/decorations/BufferDecorationRenderer.ts @@ -51,7 +51,7 @@ export class BufferDecorationRenderer extends Disposable { if (this._animationFrame !== undefined) { return; } - this._animationFrame = window.requestAnimationFrame(() => { + this._animationFrame = this._renderService.addRefreshCallback(() => { this.refreshDecorations(); this._animationFrame = undefined; }); diff --git a/src/browser/services/RenderService.ts b/src/browser/services/RenderService.ts index 75c2d3c8..852d8dc4 100644 --- a/src/browser/services/RenderService.ts +++ b/src/browser/services/RenderService.ts @@ -9,7 +9,7 @@ import { EventEmitter, IEvent } from 'common/EventEmitter'; import { Disposable } from 'common/Lifecycle'; import { ScreenDprMonitor } from 'browser/ScreenDprMonitor'; import { addDisposableDomListener } from 'browser/Lifecycle'; -import { IColorSet, IRenderDebouncer } from 'browser/Types'; +import { IColorSet, IRenderDebouncer, IRenderDebouncerWithCallback } from 'browser/Types'; import { IOptionsService, IBufferService, IDecorationService } from 'common/services/Services'; import { ICharSizeService, IRenderService } from 'browser/services/Services'; @@ -22,7 +22,7 @@ interface ISelectionState { export class RenderService extends Disposable implements IRenderService { public serviceBrand: undefined; - private _renderDebouncer: IRenderDebouncer; + private _renderDebouncer: IRenderDebouncerWithCallback; private _screenDprMonitor: ScreenDprMonitor; private _isPaused: boolean = false; @@ -171,6 +171,10 @@ export class RenderService extends Disposable implements IRenderService { this._fullRefresh(); } + public addRefreshCallback(callback: FrameRequestCallback): number { + return this._renderDebouncer.addRefreshCallback(callback); + } + private _fullRefresh(): void { if (this._isPaused) { this._needsFullRefresh = true; diff --git a/src/browser/services/Services.ts b/src/browser/services/Services.ts index a9f76a90..056bf91c 100644 --- a/src/browser/services/Services.ts +++ b/src/browser/services/Services.ts @@ -5,7 +5,7 @@ import { IEvent } from 'common/EventEmitter'; import { IRenderDimensions, IRenderer } from 'browser/renderer/Types'; -import { IColorSet } from 'browser/Types'; +import { IColorSet, IRenderDebouncer } from 'browser/Types'; import { ISelectionRedrawRequestEvent as ISelectionRequestRedrawEvent, ISelectionRequestScrollLinesEvent } from 'browser/selection/Types'; import { createDecorator } from 'common/services/ServiceRegistry'; import { IDisposable } from 'common/Types'; @@ -58,6 +58,8 @@ export interface IRenderService extends IDisposable { dimensions: IRenderDimensions; + addRefreshCallback(callback: FrameRequestCallback): number; + refreshRows(start: number, end: number): void; clearTextureAtlas(): void; resize(cols: number, rows: number): void; From a5f2ea336eff69c784414f371baacde641f03517 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Tue, 17 May 2022 10:00:55 -0700 Subject: [PATCH 17/37] update search results `onBufferContentsChange` (#3811) --- addons/xterm-addon-search/src/SearchAddon.ts | 2 +- addons/xterm-addon-web-links/test/tsconfig.json | 3 +-- src/browser/Linkifier2.ts | 2 +- src/browser/Terminal.ts | 2 +- src/browser/TestUtils.test.ts | 3 ++- src/browser/Types.d.ts | 1 + src/browser/decorations/BufferDecorationRenderer.ts | 2 +- src/browser/decorations/OverviewRulerRenderer.ts | 2 +- src/browser/public/Terminal.ts | 1 + src/browser/services/RenderService.ts | 6 +++--- src/browser/services/Services.ts | 2 +- src/common/CoreTerminal.ts | 3 +++ src/common/input/WriteBuffer.ts | 5 +++++ typings/xterm.d.ts | 11 +++++++++++ 14 files changed, 33 insertions(+), 12 deletions(-) diff --git a/addons/xterm-addon-search/src/SearchAddon.ts b/addons/xterm-addon-search/src/SearchAddon.ts index 1eb126e5..0f356e14 100644 --- a/addons/xterm-addon-search/src/SearchAddon.ts +++ b/addons/xterm-addon-search/src/SearchAddon.ts @@ -77,7 +77,7 @@ export class SearchAddon implements ITerminalAddon { public activate(terminal: Terminal): void { this._terminal = terminal; - this._onDataDisposable = this._terminal.onData(() => this._updateMatches()); + this._onDataDisposable = this._terminal.onWriteParsed(() => this._updateMatches()); this._onResizeDisposable = this._terminal.onResize(() => this._updateMatches()); } diff --git a/addons/xterm-addon-web-links/test/tsconfig.json b/addons/xterm-addon-web-links/test/tsconfig.json index 1c772984..9f4d23df 100644 --- a/addons/xterm-addon-web-links/test/tsconfig.json +++ b/addons/xterm-addon-web-links/test/tsconfig.json @@ -12,8 +12,7 @@ "strict": true, "types": [ "../../../node_modules/@types/mocha", - "../../../node_modules/@types/node", - "../../../out-test/api/TestUtils" + "../../../node_modules/@types/node" ] }, "include": [ diff --git a/src/browser/Linkifier2.ts b/src/browser/Linkifier2.ts index 89542936..acbf1c93 100644 --- a/src/browser/Linkifier2.ts +++ b/src/browser/Linkifier2.ts @@ -303,7 +303,7 @@ export class Linkifier2 extends Disposable implements ILinkifier2 { // Add listener for rerendering if (this._renderService) { - this._linkCacheDisposables.push(this._renderService.onRenderedBufferChange(e => { + this._linkCacheDisposables.push(this._renderService.onRenderedViewportChange(e => { // When start is 0 a scroll most likely occurred, make sure links above the fold also get // cleared. const start = e.start === 0 ? 0 : e.start + 1 + this._bufferService.buffer.ydisp; diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index de3fff90..9ab3087c 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -526,7 +526,7 @@ export class Terminal extends CoreTerminal implements ITerminal { const renderer = this._createRenderer(); this._renderService = this.register(this._instantiationService.createInstance(RenderService, renderer, this.rows, this.screenElement)); this._instantiationService.setService(IRenderService, this._renderService); - this.register(this._renderService.onRenderedBufferChange(e => this._onRender.fire(e))); + this.register(this._renderService.onRenderedViewportChange(e => this._onRender.fire(e))); this.onResize(e => this._renderService!.resize(e.cols, e.rows)); this._compositionView = document.createElement('div'); diff --git a/src/browser/TestUtils.test.ts b/src/browser/TestUtils.test.ts index c3376b21..573720fc 100644 --- a/src/browser/TestUtils.test.ts +++ b/src/browser/TestUtils.test.ts @@ -30,6 +30,7 @@ export class MockTerminal implements ITerminal { public onBlur!: IEvent; public onFocus!: IEvent; public onA11yChar!: IEvent; + public onWriteParsed!: IEvent; public onA11yTab!: IEvent; public onCursorMove!: IEvent; public onLineFeed!: IEvent; @@ -370,7 +371,7 @@ export class MockMouseService implements IMouseService { export class MockRenderService implements IRenderService { public serviceBrand: undefined; public onDimensionsChange: IEvent = new EventEmitter().event; - public onRenderedBufferChange: IEvent<{ start: number, end: number }, void> = new EventEmitter<{ start: number, end: number }>().event; + public onRenderedViewportChange: IEvent<{ start: number, end: number }, void> = new EventEmitter<{ start: number, end: number }>().event; public onRender: IEvent<{ start: number, end: number }, void> = new EventEmitter<{ start: number, end: number }>().event; public onRefreshRequest: IEvent<{ start: number, end: number}, void> = new EventEmitter<{ start: number, end: number }>().event; public dimensions: IRenderDimensions = { diff --git a/src/browser/Types.d.ts b/src/browser/Types.d.ts index a472326a..f8ca03df 100644 --- a/src/browser/Types.d.ts +++ b/src/browser/Types.d.ts @@ -44,6 +44,7 @@ export interface IPublicTerminal extends IDisposable { onSelectionChange: IEvent; onRender: IEvent<{ start: number, end: number }>; onResize: IEvent<{ cols: number, rows: number }>; + onWriteParsed: IEvent; onTitleChange: IEvent; onBell: IEvent; blur(): void; diff --git a/src/browser/decorations/BufferDecorationRenderer.ts b/src/browser/decorations/BufferDecorationRenderer.ts index 5c9b1282..632a2864 100644 --- a/src/browser/decorations/BufferDecorationRenderer.ts +++ b/src/browser/decorations/BufferDecorationRenderer.ts @@ -28,7 +28,7 @@ export class BufferDecorationRenderer extends Disposable { this._container.classList.add('xterm-decoration-container'); this._screenElement.appendChild(this._container); - this.register(this._renderService.onRenderedBufferChange(() => this._queueRefresh())); + this.register(this._renderService.onRenderedViewportChange(() => this._queueRefresh())); this.register(this._renderService.onDimensionsChange(() => { this._dimensionsChanged = true; this._queueRefresh(); diff --git a/src/browser/decorations/OverviewRulerRenderer.ts b/src/browser/decorations/OverviewRulerRenderer.ts index 39480ca2..f31409ca 100644 --- a/src/browser/decorations/OverviewRulerRenderer.ts +++ b/src/browser/decorations/OverviewRulerRenderer.ts @@ -82,7 +82,7 @@ export class OverviewRulerRenderer extends Disposable { * and hide the canvas if the alt buffer is active */ private _registerBufferChangeListeners(): void { - this.register(this._renderService.onRenderedBufferChange(() => this._queueRefresh())); + this.register(this._renderService.onRenderedViewportChange(() => this._queueRefresh())); this.register(this._bufferService.buffers.onBufferActivate(() => { this._canvas!.style.display = this._bufferService.buffer === this._bufferService.buffers.alt ? 'none' : 'block'; })); diff --git a/src/browser/public/Terminal.ts b/src/browser/public/Terminal.ts index 1acde934..187bd3b5 100644 --- a/src/browser/public/Terminal.ts +++ b/src/browser/public/Terminal.ts @@ -74,6 +74,7 @@ export class Terminal implements ITerminalApi { public get onScroll(): IEvent { return this._core.onScroll; } public get onSelectionChange(): IEvent { return this._core.onSelectionChange; } public get onTitleChange(): IEvent { return this._core.onTitleChange; } + public get onWriteParsed(): IEvent { return this._core.onWriteParsed; } public get element(): HTMLElement | undefined { return this._core.element; } public get parser(): IParser { diff --git a/src/browser/services/RenderService.ts b/src/browser/services/RenderService.ts index 852d8dc4..78bd7f56 100644 --- a/src/browser/services/RenderService.ts +++ b/src/browser/services/RenderService.ts @@ -39,8 +39,8 @@ export class RenderService extends Disposable implements IRenderService { private _onDimensionsChange = new EventEmitter(); public get onDimensionsChange(): IEvent { return this._onDimensionsChange.event; } - private _onRenderedBufferChange = new EventEmitter<{ start: number, end: number }>(); - public get onRenderedBufferChange(): IEvent<{ start: number, end: number }> { return this._onRenderedBufferChange.event; } + private _onRenderedViewportChange = new EventEmitter<{ start: number, end: number }>(); + public get onRenderedViewportChange(): IEvent<{ start: number, end: number }> { return this._onRenderedViewportChange.event; } private _onRender = new EventEmitter<{ start: number, end: number }>(); public get onRender(): IEvent<{ start: number, end: number }> { return this._onRender.event; } private _onRefreshRequest = new EventEmitter<{ start: number, end: number }>(); @@ -131,7 +131,7 @@ export class RenderService extends Disposable implements IRenderService { // Fire render event only if it was not a redraw if (!this._isNextRenderRedrawOnly) { - this._onRenderedBufferChange.fire({ start, end }); + this._onRenderedViewportChange.fire({ start, end }); } this._onRender.fire({ start, end }); this._isNextRenderRedrawOnly = true; diff --git a/src/browser/services/Services.ts b/src/browser/services/Services.ts index 056bf91c..00534707 100644 --- a/src/browser/services/Services.ts +++ b/src/browser/services/Services.ts @@ -49,7 +49,7 @@ export interface IRenderService extends IDisposable { * Fires when buffer changes are rendered. This does not fire when only cursor * or selections are rendered. */ - onRenderedBufferChange: IEvent<{ start: number, end: number }>; + onRenderedViewportChange: IEvent<{ start: number, end: number }>; /** * Fires on render */ diff --git a/src/common/CoreTerminal.ts b/src/common/CoreTerminal.ts index 12b374c8..af9ec3f9 100644 --- a/src/common/CoreTerminal.ts +++ b/src/common/CoreTerminal.ts @@ -68,6 +68,8 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal { private _onResize = new EventEmitter<{ cols: number, rows: number }>(); public get onResize(): IEvent<{ cols: number, rows: number }> { return this._onResize.event; } protected _onScroll = new EventEmitter(); + public get onWriteParsed(): IEvent { return this._onWriteParsed.event; } + protected _onWriteParsed = new EventEmitter(); /** * Internally we track the source of the scroll but this is meaningless outside the library so * it's filtered out. @@ -138,6 +140,7 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal { // Setup WriteBuffer this._writeBuffer = new WriteBuffer((data, promiseResult) => this._inputHandler.parse(data, promiseResult)); + this.register(forwardEvent(this._writeBuffer.onWriteParsed, this._onWriteParsed)); } public dispose(): void { diff --git a/src/common/input/WriteBuffer.ts b/src/common/input/WriteBuffer.ts index cc84c9ab..67fd751e 100644 --- a/src/common/input/WriteBuffer.ts +++ b/src/common/input/WriteBuffer.ts @@ -4,6 +4,8 @@ * @license MIT */ +import { EventEmitter, IEvent } from 'common/EventEmitter'; + declare const setTimeout: (handler: () => void, timeout?: number) => void; /** @@ -44,6 +46,8 @@ export class WriteBuffer { private _bufferOffset = 0; private _isSyncWriting = false; private _syncCalls = 0; + public get onWriteParsed(): IEvent { return this._onWriteParsed.event; } + private _onWriteParsed = new EventEmitter(); constructor(private _action: (data: string | Uint8Array, promiseResult?: boolean) => void | Promise) { } @@ -220,5 +224,6 @@ export class WriteBuffer { this._pendingData = 0; this._bufferOffset = 0; } + this._onWriteParsed.fire(); } } diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 15ee4650..4ebd0254 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -839,6 +839,17 @@ declare module 'xterm' { */ onRender: IEvent<{ start: number, end: number }>; + /** + * Adds an event listener for when data has been parsed by the terminal, + * after {@link write} is called. This event is useful to listen for any + * changes in the buffer. + * + * This fires at most once per frame, after data parsing completes. Note + * that this can fire when there are still writes pending if there is a lot + * of data. + */ + onWriteParsed: IEvent; + /** * Adds an event listener for when the terminal is resized. The event value * contains the new size. From d7fdf17eabbb60b4671038b9da8a5c072d764f45 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 17 May 2022 11:20:20 -0700 Subject: [PATCH 18/37] Ensure color mode is set for selection foreground overrides --- src/browser/renderer/dom/DomRendererRowFactory.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/browser/renderer/dom/DomRendererRowFactory.ts b/src/browser/renderer/dom/DomRendererRowFactory.ts index f421859b..5ce61113 100644 --- a/src/browser/renderer/dom/DomRendererRowFactory.ts +++ b/src/browser/renderer/dom/DomRendererRowFactory.ts @@ -200,6 +200,7 @@ export class DomRendererRowFactory { // Apply selection foreground if applicable if (!isTop) { if (this._colors.selectionForeground && this._selectionService.isCellInSelection(x, row)) { + fgColorMode = Attributes.CM_RGB; fg = this._colors.selectionForeground.rgba >> 8 & 0xFFFFFF; fgOverride = this._colors.selectionForeground; } From ec26820ea6e543aa69a09af8285d91387e8820bb Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 17 May 2022 11:23:33 -0700 Subject: [PATCH 19/37] Fix circular dependency mouseservice <-> renderservice --- src/browser/renderer/BaseRenderLayer.ts | 29 ++++++++++++++++---- src/browser/renderer/CursorRenderLayer.ts | 7 ++--- src/browser/renderer/LinkRenderLayer.ts | 6 ++-- src/browser/renderer/Renderer.ts | 4 +++ src/browser/renderer/SelectionRenderLayer.ts | 8 +++--- src/browser/renderer/TextRenderLayer.ts | 7 ++--- 6 files changed, 40 insertions(+), 21 deletions(-) diff --git a/src/browser/renderer/BaseRenderLayer.ts b/src/browser/renderer/BaseRenderLayer.ts index 7bcc6881..447f5ade 100644 --- a/src/browser/renderer/BaseRenderLayer.ts +++ b/src/browser/renderer/BaseRenderLayer.ts @@ -18,7 +18,6 @@ import { isPowerlineGlyph, throwIfFalsy } from 'browser/renderer/RendererUtils'; import { channels, color, rgba } from 'common/Color'; import { removeElementFromParent } from 'browser/Dom'; import { tryDrawCustomChar } from 'browser/renderer/CustomGlyphs'; -import { ISelectionService } from 'browser/services/Services'; export abstract class BaseRenderLayer implements IRenderLayer { private _canvas: HTMLCanvasElement; @@ -30,6 +29,10 @@ export abstract class BaseRenderLayer implements IRenderLayer { private _scaledCharLeft: number = 0; private _scaledCharTop: number = 0; + private _selectionStart: [number, number] | undefined; + private _selectionEnd: [number, number] | undefined; + private _columnSelectMode: boolean = false; + protected _charAtlas: BaseCharAtlas | undefined; /** @@ -54,8 +57,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { private _rendererId: number, protected readonly _bufferService: IBufferService, protected readonly _optionsService: IOptionsService, - protected readonly _decorationService: IDecorationService, - protected readonly _selectionService: ISelectionService + protected readonly _decorationService: IDecorationService ) { this._canvas = document.createElement('canvas'); this._canvas.classList.add(`xterm-${id}-layer`); @@ -82,7 +84,12 @@ export abstract class BaseRenderLayer implements IRenderLayer { public onFocus(): void {} public onCursorMove(): void {} public onGridChanged(startRow: number, endRow: number): void {} - public onSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean = false): void {} + + public onSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean = false): void { + this._selectionStart = start; + this._selectionEnd = end; + this._columnSelectMode = columnSelectMode; + } public setColors(colorSet: IColorSet): void { this._refreshCharAtlas(colorSet); @@ -461,7 +468,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { // Apply selection foreground if applicable if (!isTop) { - if (this._colors.selectionForeground && this._selectionService.isCellInSelection(x, y)) { + if (this._colors.selectionForeground && this._isCellInSelection(x, y)) { fgOverride = this._colors.selectionForeground.rgba; } } @@ -555,5 +562,17 @@ export abstract class BaseRenderLayer implements IRenderLayer { return this._colors.foreground.rgba; } } + + private _isCellInSelection(x: number, y: number): boolean { + const start = this._selectionStart; + const end = this._selectionEnd; + if (!start || !end) { + return false; + } + return (y > start[1] && y < end[1]) || + (start[1] === end[1] && y === start[1] && x >= start[0] && x < end[0]) || + (start[1] < end[1] && y === end[1] && x < end[0]) || + (start[1] < end[1] && y === start[1] && x >= start[0]); + } } diff --git a/src/browser/renderer/CursorRenderLayer.ts b/src/browser/renderer/CursorRenderLayer.ts index 6405390e..3fa576a9 100644 --- a/src/browser/renderer/CursorRenderLayer.ts +++ b/src/browser/renderer/CursorRenderLayer.ts @@ -10,7 +10,7 @@ import { CellData } from 'common/buffer/CellData'; import { IColorSet } from 'browser/Types'; import { IBufferService, IOptionsService, ICoreService, IDecorationService } from 'common/services/Services'; import { IEventEmitter } from 'common/EventEmitter'; -import { ICoreBrowserService, ISelectionService } from 'browser/services/Services'; +import { ICoreBrowserService } from 'browser/services/Services'; interface ICursorState { x: number; @@ -41,10 +41,9 @@ export class CursorRenderLayer extends BaseRenderLayer { @IOptionsService optionsService: IOptionsService, @ICoreService private readonly _coreService: ICoreService, @ICoreBrowserService private readonly _coreBrowserService: ICoreBrowserService, - @IDecorationService decorationService: IDecorationService, - @ISelectionService selectionService: ISelectionService + @IDecorationService decorationService: IDecorationService ) { - super(container, 'cursor', zIndex, true, colors, rendererId, bufferService, optionsService, decorationService, selectionService); + super(container, 'cursor', zIndex, true, colors, rendererId, bufferService, optionsService, decorationService); this._state = { x: 0, y: 0, diff --git a/src/browser/renderer/LinkRenderLayer.ts b/src/browser/renderer/LinkRenderLayer.ts index 4d350f4d..15086d9a 100644 --- a/src/browser/renderer/LinkRenderLayer.ts +++ b/src/browser/renderer/LinkRenderLayer.ts @@ -9,7 +9,6 @@ import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants'; import { is256Color } from 'browser/renderer/atlas/CharAtlasUtils'; import { IColorSet, ILinkifierEvent, ILinkifier, ILinkifier2 } from 'browser/Types'; import { IBufferService, IDecorationService, IOptionsService } from 'common/services/Services'; -import { ISelectionService } from 'browser/services/Services'; export class LinkRenderLayer extends BaseRenderLayer { private _state: ILinkifierEvent | undefined; @@ -23,10 +22,9 @@ export class LinkRenderLayer extends BaseRenderLayer { linkifier2: ILinkifier2, @IBufferService bufferService: IBufferService, @IOptionsService optionsService: IOptionsService, - @IDecorationService decorationService: IDecorationService, - @ISelectionService selectionService: ISelectionService + @IDecorationService decorationService: IDecorationService ) { - super(container, 'link', zIndex, true, colors, rendererId, bufferService, optionsService, decorationService, selectionService); + super(container, 'link', zIndex, true, colors, rendererId, bufferService, optionsService, decorationService); linkifier.onShowLinkUnderline(e => this._onShowLinkUnderline(e)); linkifier.onHideLinkUnderline(e => this._onHideLinkUnderline(e)); diff --git a/src/browser/renderer/Renderer.ts b/src/browser/renderer/Renderer.ts index 8dfe09c9..8bc32278 100644 --- a/src/browser/renderer/Renderer.ts +++ b/src/browser/renderer/Renderer.ts @@ -119,6 +119,10 @@ export class Renderer extends Disposable implements IRenderer { public onSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean = false): void { this._runOperation(l => l.onSelectionChanged(start, end, columnSelectMode)); + // Selection foreground requires a full re-render + if (this._colors.selectionForeground) { + this._onRequestRedraw.fire({ start: 0, end: this._bufferService.rows - 1 }); + } } public onCursorMove(): void { diff --git a/src/browser/renderer/SelectionRenderLayer.ts b/src/browser/renderer/SelectionRenderLayer.ts index 60c53eaf..ce4fe071 100644 --- a/src/browser/renderer/SelectionRenderLayer.ts +++ b/src/browser/renderer/SelectionRenderLayer.ts @@ -7,7 +7,6 @@ import { IRenderDimensions } from 'browser/renderer/Types'; import { BaseRenderLayer } from 'browser/renderer/BaseRenderLayer'; import { IColorSet } from 'browser/Types'; import { IBufferService, IDecorationService, IOptionsService } from 'common/services/Services'; -import { ISelectionService } from 'browser/services/Services'; interface ISelectionState { start?: [number, number]; @@ -26,10 +25,9 @@ export class SelectionRenderLayer extends BaseRenderLayer { rendererId: number, @IBufferService bufferService: IBufferService, @IOptionsService optionsService: IOptionsService, - @IDecorationService decorationService: IDecorationService, - @ISelectionService selectionService: ISelectionService + @IDecorationService decorationService: IDecorationService ) { - super(container, 'selection', zIndex, true, colors, rendererId, bufferService, optionsService, decorationService, selectionService); + super(container, 'selection', zIndex, true, colors, rendererId, bufferService, optionsService, decorationService); this._clearState(); } @@ -56,6 +54,8 @@ export class SelectionRenderLayer extends BaseRenderLayer { } public onSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void { + super.onSelectionChanged(start, end, columnSelectMode); + // Selection has not changed if (!this._didStateChange(start, end, columnSelectMode, this._bufferService.buffer.ydisp)) { return; diff --git a/src/browser/renderer/TextRenderLayer.ts b/src/browser/renderer/TextRenderLayer.ts index 97c7e6f6..ef5a9b62 100644 --- a/src/browser/renderer/TextRenderLayer.ts +++ b/src/browser/renderer/TextRenderLayer.ts @@ -12,7 +12,7 @@ import { NULL_CELL_CODE, Content } from 'common/buffer/Constants'; import { IColorSet } from 'browser/Types'; import { CellData } from 'common/buffer/CellData'; import { IOptionsService, IBufferService, IDecorationService } from 'common/services/Services'; -import { ICharacterJoinerService, ISelectionService } from 'browser/services/Services'; +import { ICharacterJoinerService } from 'browser/services/Services'; import { JoinedCellData } from 'browser/services/CharacterJoinerService'; /** @@ -38,10 +38,9 @@ export class TextRenderLayer extends BaseRenderLayer { @IBufferService bufferService: IBufferService, @IOptionsService optionsService: IOptionsService, @ICharacterJoinerService private readonly _characterJoinerService: ICharacterJoinerService, - @IDecorationService decorationService: IDecorationService, - @ISelectionService selectionService: ISelectionService + @IDecorationService decorationService: IDecorationService ) { - super(container, 'text', zIndex, alpha, colors, rendererId, bufferService, optionsService, decorationService, selectionService); + super(container, 'text', zIndex, alpha, colors, rendererId, bufferService, optionsService, decorationService); this._state = new GridCache(); } From f9bd0b8fed8046f06ba287d27e5bd0d587c5f28c Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 17 May 2022 11:39:14 -0700 Subject: [PATCH 20/37] Fix setting selection foreground on webgl --- addons/xterm-addon-webgl/src/WebglRenderer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index dbe9da91..e437ca90 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -399,7 +399,7 @@ export class WebglRenderer extends Disposable implements IRenderer { if (this._isCellSelected(x, y)) { bgOverride = this._colors.selectionOpaque.rgba >> 8 & 0xFFFFFF; if (this._colors.selectionForeground) { - fgOverride = this._colors.selectionForeground.rgba >> 8 && 0xFFFFFF; + fgOverride = this._colors.selectionForeground.rgba >> 8 & 0xFFFFFF; } } From d6ad4a4fc336225f0393a3b2557b997b3a79ecf5 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 17 May 2022 11:41:13 -0700 Subject: [PATCH 21/37] Remove unneeded dep --- src/browser/renderer/dom/DomRendererRowFactory.test.ts | 3 +-- src/browser/renderer/dom/DomRendererRowFactory.ts | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/browser/renderer/dom/DomRendererRowFactory.test.ts b/src/browser/renderer/dom/DomRendererRowFactory.test.ts index ae7a1434..d50765f0 100644 --- a/src/browser/renderer/dom/DomRendererRowFactory.test.ts +++ b/src/browser/renderer/dom/DomRendererRowFactory.test.ts @@ -10,7 +10,7 @@ import { NULL_CELL_CODE, NULL_CELL_WIDTH, NULL_CELL_CHAR, DEFAULT_ATTR, FgFlags, import { BufferLine, DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine'; import { IBufferLine } from 'common/Types'; import { CellData } from 'common/buffer/CellData'; -import { MockBufferService, MockCoreService, MockDecorationService, MockOptionsService } from 'common/TestUtils.test'; +import { MockCoreService, MockDecorationService, MockOptionsService } from 'common/TestUtils.test'; import { css } from 'common/Color'; import { MockCharacterJoinerService, MockSelectionService } from 'browser/TestUtils.test'; @@ -51,7 +51,6 @@ describe('DomRendererRowFactory', () => { new MockOptionsService({ drawBoldTextInBrightColors: true }), new MockCoreService(), new MockDecorationService(), - new MockBufferService(80, 30), new MockSelectionService() ); lineData = createEmptyLineData(2); diff --git a/src/browser/renderer/dom/DomRendererRowFactory.ts b/src/browser/renderer/dom/DomRendererRowFactory.ts index 5ce61113..fc21e638 100644 --- a/src/browser/renderer/dom/DomRendererRowFactory.ts +++ b/src/browser/renderer/dom/DomRendererRowFactory.ts @@ -35,7 +35,6 @@ export class DomRendererRowFactory { @IOptionsService private readonly _optionsService: IOptionsService, @ICoreService private readonly _coreService: ICoreService, @IDecorationService private readonly _decorationService: IDecorationService, - @IBufferService private readonly _bufferService: IBufferService, @ISelectionService private readonly _selectionService: ISelectionService ) { } From 18a275147174c5e0a9a2e378016e6ae5faf02b77 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 17 May 2022 11:55:03 -0700 Subject: [PATCH 22/37] Fix selectionForeground handling in test --- src/browser/ColorManager.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/browser/ColorManager.test.ts b/src/browser/ColorManager.test.ts index 926a7df3..96bc82e4 100644 --- a/src/browser/ColorManager.test.ts +++ b/src/browser/ColorManager.test.ts @@ -34,7 +34,7 @@ describe('ColorManager', () => { describe('constructor', () => { it('should fill all colors with values', () => { for (const key of Object.keys(cm.colors)) { - if (key !== 'ansi' && key !== 'contrastCache') { + if (key !== 'ansi' && key !== 'contrastCache' && key !== 'selectionForeground') { // A #rrggbb or rgba(...) assert.ok((cm.colors as any)[key].css.length >= 7); } From d1a87facfd7c7142da4f77b623e876da105a3ff5 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 17 May 2022 12:10:52 -0700 Subject: [PATCH 23/37] Fix selectionForeground column select mode on dom/canvas --- src/browser/renderer/BaseRenderLayer.ts | 4 +++ src/browser/renderer/dom/DomRenderer.ts | 1 + .../dom/DomRendererRowFactory.test.ts | 3 +- .../renderer/dom/DomRendererRowFactory.ts | 31 +++++++++++++++++-- 4 files changed, 34 insertions(+), 5 deletions(-) diff --git a/src/browser/renderer/BaseRenderLayer.ts b/src/browser/renderer/BaseRenderLayer.ts index 447f5ade..ead0844f 100644 --- a/src/browser/renderer/BaseRenderLayer.ts +++ b/src/browser/renderer/BaseRenderLayer.ts @@ -569,6 +569,10 @@ export abstract class BaseRenderLayer implements IRenderLayer { if (!start || !end) { return false; } + if (this._columnSelectMode) { + return x >= start[0] && y >= start[1] && + x < end[0] && y < end[1]; + } return (y > start[1] && y < end[1]) || (start[1] === end[1] && y === start[1] && x >= start[0] && x < end[0]) || (start[1] < end[1] && y === end[1] && x < end[0]) || diff --git a/src/browser/renderer/dom/DomRenderer.ts b/src/browser/renderer/dom/DomRenderer.ts index 840ef40d..fdeef91c 100644 --- a/src/browser/renderer/dom/DomRenderer.ts +++ b/src/browser/renderer/dom/DomRenderer.ts @@ -281,6 +281,7 @@ export class DomRenderer extends Disposable implements IRenderer { this._selectionContainer.removeChild(this._selectionContainer.children[0]); } + this._rowFactory.onSelectionChanged(start, end, columnSelectMode); this.renderRows(0, this._bufferService.rows - 1); // Selection does not exist diff --git a/src/browser/renderer/dom/DomRendererRowFactory.test.ts b/src/browser/renderer/dom/DomRendererRowFactory.test.ts index d50765f0..5a374b14 100644 --- a/src/browser/renderer/dom/DomRendererRowFactory.test.ts +++ b/src/browser/renderer/dom/DomRendererRowFactory.test.ts @@ -50,8 +50,7 @@ describe('DomRendererRowFactory', () => { new MockCharacterJoinerService(), new MockOptionsService({ drawBoldTextInBrightColors: true }), new MockCoreService(), - new MockDecorationService(), - new MockSelectionService() + new MockDecorationService() ); lineData = createEmptyLineData(2); }); diff --git a/src/browser/renderer/dom/DomRendererRowFactory.ts b/src/browser/renderer/dom/DomRendererRowFactory.ts index fc21e638..266d7587 100644 --- a/src/browser/renderer/dom/DomRendererRowFactory.ts +++ b/src/browser/renderer/dom/DomRendererRowFactory.ts @@ -28,14 +28,17 @@ export const CURSOR_STYLE_UNDERLINE_CLASS = 'xterm-cursor-underline'; export class DomRendererRowFactory { private _workCell: CellData = new CellData(); + private _selectionStart: [number, number] | undefined; + private _selectionEnd: [number, number] | undefined; + private _columnSelectMode: boolean = false; + constructor( private readonly _document: Document, private _colors: IColorSet, @ICharacterJoinerService private readonly _characterJoinerService: ICharacterJoinerService, @IOptionsService private readonly _optionsService: IOptionsService, @ICoreService private readonly _coreService: ICoreService, - @IDecorationService private readonly _decorationService: IDecorationService, - @ISelectionService private readonly _selectionService: ISelectionService + @IDecorationService private readonly _decorationService: IDecorationService ) { } @@ -43,6 +46,12 @@ export class DomRendererRowFactory { this._colors = colors; } + public onSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void { + this._selectionStart = start; + this._selectionEnd = end; + this._columnSelectMode = columnSelectMode; + } + public createRow(lineData: IBufferLine, row: number, isCursorRow: boolean, cursorStyle: string | undefined, cursorX: number, cursorBlink: boolean, cellWidth: number, cols: number): DocumentFragment { const fragment = this._document.createDocumentFragment(); @@ -198,7 +207,7 @@ export class DomRendererRowFactory { // Apply selection foreground if applicable if (!isTop) { - if (this._colors.selectionForeground && this._selectionService.isCellInSelection(x, row)) { + if (this._colors.selectionForeground && this._isCellInSelection(x, row)) { fgColorMode = Attributes.CM_RGB; fg = this._colors.selectionForeground.rgba >> 8 & 0xFFFFFF; fgOverride = this._colors.selectionForeground; @@ -293,6 +302,22 @@ export class DomRendererRowFactory { private _addStyle(element: HTMLElement, style: string): void { element.setAttribute('style', `${element.getAttribute('style') || ''}${style};`); } + + private _isCellInSelection(x: number, y: number): boolean { + const start = this._selectionStart; + const end = this._selectionEnd; + if (!start || !end) { + return false; + } + if (this._columnSelectMode) { + return x >= start[0] && y >= start[1] && + x < end[0] && y < end[1]; + } + return (y > start[1] && y < end[1]) || + (start[1] === end[1] && y === start[1] && x >= start[0] && x < end[0]) || + (start[1] < end[1] && y === end[1] && x < end[0]) || + (start[1] < end[1] && y === start[1] && x >= start[0]); + } } function padStart(text: string, padChar: string, length: number): string { From 53f05de3e7f3644b5f378fe3414dcb519a7f4fd7 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 17 May 2022 12:11:51 -0700 Subject: [PATCH 24/37] Add selectionForeground webgl unit test --- .../test/WebglRenderer.api.ts | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/addons/xterm-addon-webgl/test/WebglRenderer.api.ts b/addons/xterm-addon-webgl/test/WebglRenderer.api.ts index cec1e3b1..d51990a7 100644 --- a/addons/xterm-addon-webgl/test/WebglRenderer.api.ts +++ b/addons/xterm-addon-webgl/test/WebglRenderer.api.ts @@ -875,6 +875,25 @@ describe('WebGL Renderer Integration Tests', async () => { }); }); + describe('selectionForeground', () => { + if (areTestsEnabled) { + before(async () => setupBrowser({ rendererType: 'dom' })); + after(async () => browser.close()); + beforeEach(async () => page.evaluate(`window.term.reset()`)); + } + + itWebgl('transparent background inverse', async () => { + const theme: ITheme = { + selectionForeground: '#ff0000' + }; + await page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`); + const data = `\\x1b[7m█\x1b[0m`; + await writeSync(page, data); + await page.evaluate(`window.term.selectAll()`); + await pollFor(page, () => getCellColor(1, 1), [255, 0, 0, 255]); + }); + }); + describe('decoration color overrides', async () => { if (areTestsEnabled) { before(async () => setupBrowser({ rendererType: 'dom' })); From ebe55e5b5f5453203478cf51db9a083721d59ce6 Mon Sep 17 00:00:00 2001 From: ChaseKnowlden Date: Wed, 18 May 2022 20:28:59 -0400 Subject: [PATCH 25/37] Add rgba format --- src/common/Color.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/common/Color.ts b/src/common/Color.ts index a2a3cbad..590304c9 100644 --- a/src/common/Color.ts +++ b/src/common/Color.ts @@ -95,6 +95,16 @@ export namespace color { export namespace css { export function toColor(css: string): IColor { switch (css.length) { + case 5: // #rgba + return { + css, + rgba: channels.toRgba( + parseInt(css.substr(1, 2), 16), + parseInt(css.substr(3, 2), 16), + parseInt(css.substr(5, 2), 16), + parseInt(css.substr(7, 2), 16) + ) + }; case 7: // #rrggbb return { css, From 7edc11bd045021b1e83b9308a253db0b69120964 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 19 May 2022 09:00:50 -0700 Subject: [PATCH 26/37] Correct #rgba, add #rgb and tests --- src/common/Color.test.ts | 38 ++++++++++++++++++++++++++++++++++++++ src/common/Color.ts | 17 +++++++++++++---- 2 files changed, 51 insertions(+), 4 deletions(-) diff --git a/src/common/Color.test.ts b/src/common/Color.test.ts index f16e6ffb..8fd2e393 100644 --- a/src/common/Color.test.ts +++ b/src/common/Color.test.ts @@ -153,6 +153,44 @@ describe('Color', () => { describe('css', () => { describe('toColor', () => { + it('should convert the #rgb format to an IColor', () => { + assert.deepEqual(css.toColor('#000'), { css: '#000', rgba: 0x000000FF }); + assert.deepEqual(css.toColor('#111'), { css: '#111', rgba: 0x111111FF }); + assert.deepEqual(css.toColor('#222'), { css: '#222', rgba: 0x222222FF }); + assert.deepEqual(css.toColor('#333'), { css: '#333', rgba: 0x333333FF }); + assert.deepEqual(css.toColor('#444'), { css: '#444', rgba: 0x444444FF }); + assert.deepEqual(css.toColor('#555'), { css: '#555', rgba: 0x555555FF }); + assert.deepEqual(css.toColor('#666'), { css: '#666', rgba: 0x666666FF }); + assert.deepEqual(css.toColor('#777'), { css: '#777', rgba: 0x777777FF }); + assert.deepEqual(css.toColor('#888'), { css: '#888', rgba: 0x888888FF }); + assert.deepEqual(css.toColor('#999'), { css: '#999', rgba: 0x999999FF }); + assert.deepEqual(css.toColor('#aaa'), { css: '#aaa', rgba: 0xaaaaaaFF }); + assert.deepEqual(css.toColor('#bbb'), { css: '#bbb', rgba: 0xbbbbbbFF }); + assert.deepEqual(css.toColor('#ccc'), { css: '#ccc', rgba: 0xccccccFF }); + assert.deepEqual(css.toColor('#ddd'), { css: '#ddd', rgba: 0xddddddFF }); + assert.deepEqual(css.toColor('#eee'), { css: '#eee', rgba: 0xeeeeeeFF }); + assert.deepEqual(css.toColor('#fff'), { css: '#fff', rgba: 0xffffffFF }); + assert.deepEqual(css.toColor('#fff'), { css: '#fff', rgba: 0xffffffFF }); + }); + it('should convert the #rgb format to an IColor', () => { + assert.deepEqual(css.toColor('#0000'), { css: '#0000', rgba: 0x00000000 }); + assert.deepEqual(css.toColor('#1111'), { css: '#1111', rgba: 0x11111111 }); + assert.deepEqual(css.toColor('#2222'), { css: '#2222', rgba: 0x22222222 }); + assert.deepEqual(css.toColor('#3333'), { css: '#3333', rgba: 0x33333333 }); + assert.deepEqual(css.toColor('#4444'), { css: '#4444', rgba: 0x44444444 }); + assert.deepEqual(css.toColor('#5555'), { css: '#5555', rgba: 0x55555555 }); + assert.deepEqual(css.toColor('#6666'), { css: '#6666', rgba: 0x66666666 }); + assert.deepEqual(css.toColor('#7777'), { css: '#7777', rgba: 0x77777777 }); + assert.deepEqual(css.toColor('#8888'), { css: '#8888', rgba: 0x88888888 }); + assert.deepEqual(css.toColor('#9999'), { css: '#9999', rgba: 0x99999999 }); + assert.deepEqual(css.toColor('#aaaa'), { css: '#aaaa', rgba: 0xaaaaaaaa }); + assert.deepEqual(css.toColor('#bbbb'), { css: '#bbbb', rgba: 0xbbbbbbbb }); + assert.deepEqual(css.toColor('#cccc'), { css: '#cccc', rgba: 0xcccccccc }); + assert.deepEqual(css.toColor('#dddd'), { css: '#dddd', rgba: 0xdddddddd }); + assert.deepEqual(css.toColor('#eeee'), { css: '#eeee', rgba: 0xeeeeeeee }); + assert.deepEqual(css.toColor('#ffff'), { css: '#ffff', rgba: 0xffffffff }); + assert.deepEqual(css.toColor('#ffff'), { css: '#ffff', rgba: 0xffffffff }); + }); it('should convert the #rrggbb format to an IColor', () => { assert.deepEqual(css.toColor('#000000'), { css: '#000000', rgba: 0x000000FF }); assert.deepEqual(css.toColor('#101010'), { css: '#101010', rgba: 0x101010FF }); diff --git a/src/common/Color.ts b/src/common/Color.ts index 590304c9..2b9fca3a 100644 --- a/src/common/Color.ts +++ b/src/common/Color.ts @@ -95,14 +95,23 @@ export namespace color { export namespace css { export function toColor(css: string): IColor { switch (css.length) { + case 4: // #rgb + return { + css, + rgba: channels.toRgba( + parseInt(css.slice(1, 2).repeat(2), 16), + parseInt(css.slice(2, 3).repeat(2), 16), + parseInt(css.slice(3, 4).repeat(2), 16) + ) + }; case 5: // #rgba return { css, rgba: channels.toRgba( - parseInt(css.substr(1, 2), 16), - parseInt(css.substr(3, 2), 16), - parseInt(css.substr(5, 2), 16), - parseInt(css.substr(7, 2), 16) + parseInt(css.slice(1, 2).repeat(2), 16), + parseInt(css.slice(2, 3).repeat(2), 16), + parseInt(css.slice(3, 4).repeat(2), 16), + parseInt(css.slice(4, 5).repeat(2), 16) ) }; case 7: // #rrggbb From e3c361327c871c8f6ac9af2d7066cc7acdb4677a Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 19 May 2022 09:22:47 -0700 Subject: [PATCH 27/37] Support rgb() and rgba() This is used for default theme colors and decoration bg/fg. Fixes #3814 --- src/common/Color.test.ts | 14 ++++++++ src/common/Color.ts | 74 ++++++++++++++++++++++++---------------- 2 files changed, 58 insertions(+), 30 deletions(-) diff --git a/src/common/Color.test.ts b/src/common/Color.test.ts index 8fd2e393..a608908d 100644 --- a/src/common/Color.test.ts +++ b/src/common/Color.test.ts @@ -229,6 +229,20 @@ describe('Color', () => { assert.deepEqual(css.toColor('#f0f0f0f0'), { css: '#f0f0f0f0', rgba: 0xf0f0f0f0 }); assert.deepEqual(css.toColor('#ffffffff'), { css: '#ffffffff', rgba: 0xffffffff }); }); + it('should convert the rgb() format to an IColor', () => { + assert.deepEqual(css.toColor('rgb(0, 0, 0)'), { css: 'rgb(0, 0, 0)', rgba: 0x000000ff }); + assert.deepEqual(css.toColor('rgb(80, 0, 0)'), { css: 'rgb(80, 0, 0)', rgba: 0x500000ff }); + assert.deepEqual(css.toColor('rgb(0, 80, 0)'), { css: 'rgb(0, 80, 0)', rgba: 0x005000ff }); + assert.deepEqual(css.toColor('rgb(0, 0, 80)'), { css: 'rgb(0, 0, 80)', rgba: 0x000050ff }); + assert.deepEqual(css.toColor('rgb(255, 255, 255)'), { css: 'rgb(255, 255, 255)', rgba: 0xffffffff }); + }); + it('should convert the rgba() format to an IColor', () => { + assert.deepEqual(css.toColor('rgba(0, 0, 0, 0)'), { css: 'rgba(0, 0, 0, 0)', rgba: 0x00000000 }); + assert.deepEqual(css.toColor('rgba(80, 0, 0, 80)'), { css: 'rgba(80, 0, 0, 80)', rgba: 0x50000050 }); + assert.deepEqual(css.toColor('rgba(0, 80, 0, 80)'), { css: 'rgba(0, 80, 0, 80)', rgba: 0x00500050 }); + assert.deepEqual(css.toColor('rgba(0, 0, 80, 80)'), { css: 'rgba(0, 0, 80, 80)', rgba: 0x00005050 }); + assert.deepEqual(css.toColor('rgba(255, 255, 255, 255)'), { css: 'rgba(255, 255, 255, 255)', rgba: 0xffffffff }); + }); }); }); diff --git a/src/common/Color.ts b/src/common/Color.ts index 2b9fca3a..3f09601f 100644 --- a/src/common/Color.ts +++ b/src/common/Color.ts @@ -94,36 +94,50 @@ export namespace color { */ export namespace css { export function toColor(css: string): IColor { - switch (css.length) { - case 4: // #rgb - return { - css, - rgba: channels.toRgba( - parseInt(css.slice(1, 2).repeat(2), 16), - parseInt(css.slice(2, 3).repeat(2), 16), - parseInt(css.slice(3, 4).repeat(2), 16) - ) - }; - case 5: // #rgba - return { - css, - rgba: channels.toRgba( - parseInt(css.slice(1, 2).repeat(2), 16), - parseInt(css.slice(2, 3).repeat(2), 16), - parseInt(css.slice(3, 4).repeat(2), 16), - parseInt(css.slice(4, 5).repeat(2), 16) - ) - }; - case 7: // #rrggbb - return { - css, - rgba: (parseInt(css.slice(1), 16) << 8 | 0xFF) >>> 0 - }; - case 9: // #rrggbbaa - return { - css, - rgba: parseInt(css.slice(1), 16) >>> 0 - }; + if (css.match(/#[0-9a-f]{3,8}/i)) { + switch (css.length) { + case 4: // #rgb + return { + css, + rgba: channels.toRgba( + parseInt(css.slice(1, 2).repeat(2), 16), + parseInt(css.slice(2, 3).repeat(2), 16), + parseInt(css.slice(3, 4).repeat(2), 16) + ) + }; + case 5: // #rgba + return { + css, + rgba: channels.toRgba( + parseInt(css.slice(1, 2).repeat(2), 16), + parseInt(css.slice(2, 3).repeat(2), 16), + parseInt(css.slice(3, 4).repeat(2), 16), + parseInt(css.slice(4, 5).repeat(2), 16) + ) + }; + case 7: // #rrggbb + return { + css, + rgba: (parseInt(css.slice(1), 16) << 8 | 0xFF) >>> 0 + }; + case 9: // #rrggbbaa + return { + css, + rgba: parseInt(css.slice(1), 16) >>> 0 + }; + } + } + const rgbaMatch = css.match(/rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(,\s*(\d{1,3})\s*)?\)/); + if (rgbaMatch) { // rgb() or rgba() + return { + css, + rgba: channels.toRgba( + parseInt(rgbaMatch[1]), // r + parseInt(rgbaMatch[2]), // g + parseInt(rgbaMatch[3]), // b + rgbaMatch[5] === undefined ? 0xFF : parseInt(rgbaMatch[5]) // a? + ) + }; } throw new Error('css.toColor: Unsupported css format'); } From bcb71e854f905bf2cd554ed7640ba7196ad2a6d4 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 19 May 2022 09:32:57 -0700 Subject: [PATCH 28/37] Fix decoration positioning In _createElement the element wasn't set on the decoration yet. Fixes 3818 --- src/browser/decorations/BufferDecorationRenderer.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/browser/decorations/BufferDecorationRenderer.ts b/src/browser/decorations/BufferDecorationRenderer.ts index 632a2864..7fcc5ea9 100644 --- a/src/browser/decorations/BufferDecorationRenderer.ts +++ b/src/browser/decorations/BufferDecorationRenderer.ts @@ -84,7 +84,7 @@ export class BufferDecorationRenderer extends Disposable { // exceeded the container width, so hide element.style.display = 'none'; } - this._refreshXPosition(decoration); + this._refreshXPosition(decoration, element); return element; } @@ -112,15 +112,15 @@ export class BufferDecorationRenderer extends Disposable { } } - private _refreshXPosition(decoration: IInternalDecoration): void { - if (!decoration.element) { + private _refreshXPosition(decoration: IInternalDecoration, element: HTMLElement | undefined = decoration.element): void { + if (!element) { return; } const x = decoration.options.x ?? 0; if ((decoration.options.anchor || 'left') === 'right') { - decoration.element.style.right = x ? `${x * this._renderService.dimensions.actualCellWidth}px` : ''; + element.style.right = x ? `${x * this._renderService.dimensions.actualCellWidth}px` : ''; } else { - decoration.element.style.left = x ? `${x * this._renderService.dimensions.actualCellWidth}px` : ''; + element.style.left = x ? `${x * this._renderService.dimensions.actualCellWidth}px` : ''; } } From 53538084379b4825168b68c4b6a7120b863d4479 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 19 May 2022 15:26:52 -0700 Subject: [PATCH 29/37] Update playwright --- package.json | 6 +- yarn.lock | 193 ++++----------------------------------------------- 2 files changed, 16 insertions(+), 183 deletions(-) diff --git a/package.json b/package.json index c3e29be5..336e32c5 100644 --- a/package.json +++ b/package.json @@ -33,8 +33,8 @@ "test": "npm run test-unit", "posttest": "npm run lint", "test-api": "npm run test-api-chromium", - "test-api-chromium": "node ./bin/test_api.js --browser=chromium --timeout=20000", - "test-api-firefox": "node ./bin/test_api.js --browser=firefox --timeout=20000", + "test-api-chromium": "node ./bin/test_api.js --browser=chromium --timeout=200000", + "test-api-firefox": "node ./bin/test_api.js --browser=firefox --timeout=200000", "test-api-webkit": "node ./bin/test_api.js --browser=webkit --timeout=20000", "test-unit": "node ./bin/test.js", "test-unit-coverage": "node ./bin/test.js --coverage", @@ -76,7 +76,7 @@ "mustache": "^4.2.0", "node-pty": "^0.10.1", "nyc": "^15.1.0", - "playwright": "^1.16.2", + "playwright": "^1.22.1", "source-map-loader": "^3.0.0", "source-map-support": "^0.5.20", "ts-loader": "^9.1.2", diff --git a/yarn.lock b/yarn.lock index 2523d316..7cb970bb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -429,13 +429,6 @@ dependencies: "@types/node" "*" -"@types/yauzl@^2.9.1": - version "2.9.2" - resolved "https://registry.yarnpkg.com/@types/yauzl/-/yauzl-2.9.2.tgz#c48e5d56aff1444409e39fa164b0b4d4552a7b7a" - integrity sha512-8uALY5LTvSuHgloDVUvWP3pIauILm+8/0pDMokuDYIoNsOkSwd5AiHBTSEJjKTDcZr5z8UpgOWZkxBF4iJftoA== - dependencies: - "@types/node" "*" - "@typescript-eslint/eslint-plugin@^5.3.0": version "5.3.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.3.0.tgz#a55ae72d28ffeb6badd817fe4566c9cced1f5e29" @@ -831,7 +824,7 @@ acorn@^8.4.1, acorn@^8.5.0: resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.5.0.tgz#4512ccb99b3698c752591e9bb4472e38ad43cee2" integrity sha512-yXbYeFy+jUuYd3/CDcg2NkIYE991XYX/bje7LmjJigUciaeO1JR4XxXgCIV1/Zc/dRuFEyw1L0pbA+qynJkW5Q== -agent-base@6, agent-base@^6.0.2: +agent-base@6: version "6.0.2" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== @@ -1027,11 +1020,6 @@ browserslist@^4.14.5: escalade "^3.1.1" node-releases "^1.1.71" -buffer-crc32@~0.2.3: - version "0.2.13" - resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" - integrity sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI= - buffer-from@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" @@ -1255,11 +1243,6 @@ commander@^7.0.0: resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7" integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw== -commander@^8.2.0: - version "8.3.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66" - integrity sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww== - commondir@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" @@ -1367,7 +1350,7 @@ debug@4.3.3: dependencies: ms "2.1.2" -debug@^4.3.1, debug@^4.3.2: +debug@^4.3.2: version "4.3.2" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.2.tgz#f0a49c18ac8779e31d4a0c6029dfb76873c7428b" integrity sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw== @@ -1504,13 +1487,6 @@ encodeurl@~1.0.2: resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= -end-of-stream@^1.1.0: - version "1.4.4" - resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" - integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== - dependencies: - once "^1.4.0" - enhanced-resolve@^5.0.0, enhanced-resolve@^5.8.3: version "5.8.3" resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.8.3.tgz#6d552d465cce0423f5b3d718511ea53826a7b2f0" @@ -1624,11 +1600,6 @@ escape-string-regexp@^1.0.5: resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= -escape-string-regexp@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" - integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== - escodegen@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-2.0.0.tgz#5e32b12833e8aa8fa35e1bf0befa89380484c7dd" @@ -1834,17 +1805,6 @@ express@^4.17.1: utils-merge "1.0.1" vary "~1.1.2" -extract-zip@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-2.0.1.tgz#663dca56fe46df890d5f131ef4a06d22bb8ba13a" - integrity sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg== - dependencies: - debug "^4.1.1" - get-stream "^5.1.0" - yauzl "^2.10.0" - optionalDependencies: - "@types/yauzl" "^2.9.1" - fast-deep-equal@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz#545145077c501491e33b15ec408c294376e94ae4" @@ -1888,13 +1848,6 @@ fastq@^1.6.0: dependencies: reusify "^1.0.4" -fd-slicer@~1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e" - integrity sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4= - dependencies: - pend "~1.2.0" - file-entry-cache@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" @@ -2056,13 +2009,6 @@ get-package-type@^0.1.0: resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== -get-stream@^5.1.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3" - integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA== - dependencies: - pump "^3.0.0" - get-stream@^6.0.0: version "6.0.1" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" @@ -2319,11 +2265,6 @@ interpret@^2.2.0: resolved "https://registry.yarnpkg.com/interpret/-/interpret-2.2.0.tgz#1a78a0b5965c40a5416d007ad6f50ad27c417df9" integrity sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw== -ip@^1.1.5: - version "1.1.5" - resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a" - integrity sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo= - ipaddr.js@1.9.1: version "1.9.1" resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" @@ -2601,11 +2542,6 @@ jest-worker@^27.0.6: merge-stream "^2.0.0" supports-color "^8.0.0" -jpeg-js@^0.4.2: - version "0.4.3" - resolved "https://registry.yarnpkg.com/jpeg-js/-/jpeg-js-0.4.3.tgz#6158e09f1983ad773813704be80680550eff977b" - integrity sha512-ru1HWKek8octvUHFHvE5ZzQ1yAsJmIvRdGWvSoKV52XKyuyYA437QWDttXT8eZXDSbuMpHlLzPDZUPd6idIz+Q== - js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" @@ -2852,11 +2788,6 @@ mime@1.6.0: resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== -mime@^2.4.6: - version "2.5.2" - resolved "https://registry.yarnpkg.com/mime/-/mime-2.5.2.tgz#6e3dc6cc2b9510643830e5f19d5cb753da5eeabe" - integrity sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg== - mimic-fn@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" @@ -3058,7 +2989,7 @@ on-finished@~2.3.0: dependencies: ee-first "1.1.1" -once@^1.3.0, once@^1.3.1, once@^1.4.0: +once@^1.3.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= @@ -3205,11 +3136,6 @@ pathval@^1.1.1: resolved "https://registry.yarnpkg.com/pathval/-/pathval-1.1.1.tgz#8534e77a77ce7ac5a2512ea21e0fdb8fcf6c3d8d" integrity sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ== -pend@~1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" - integrity sha1-elfrVQpng/kRUzH89GY9XI4AelA= - picomatch@^2.0.4, picomatch@^2.2.1: version "2.2.2" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad" @@ -3227,39 +3153,17 @@ pkg-dir@^4.1.0, pkg-dir@^4.2.0: dependencies: find-up "^4.0.0" -playwright-core@=1.16.2: - version "1.16.2" - resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.16.2.tgz#13c4c352a41e431ba167dbadb80e8c628e1e1b79" - integrity sha512-8WkoP5OfZAYrRxtW/PCVACn9bNgqrTxVVPlc+MoxvJ48knNsZ+skrPjfno/XF3SgTUY9DyYX0g5fVOB7lkPtGg== - dependencies: - commander "^8.2.0" - debug "^4.1.1" - extract-zip "^2.0.1" - https-proxy-agent "^5.0.0" - jpeg-js "^0.4.2" - mime "^2.4.6" - pngjs "^5.0.0" - progress "^2.0.3" - proper-lockfile "^4.1.1" - proxy-from-env "^1.1.0" - rimraf "^3.0.2" - socks-proxy-agent "^6.1.0" - stack-utils "^2.0.3" - ws "^7.4.6" - yauzl "^2.10.0" - yazl "^2.5.1" +playwright-core@1.22.1: + version "1.22.1" + resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.22.1.tgz#59ddf903546171fdfd9c3dc189630c883619667c" + integrity sha512-H+ZUVYnceWNXrRf3oxTEKAr81QzFsCKu5Fp//fEjQvqgKkfA1iX3E9DBrPJpPNOrgVzcE+IqeI0fDmYJe6Ynnw== -playwright@^1.16.2: - version "1.16.2" - resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.16.2.tgz#92e1c48a74cab778970facca4094ce79fb57cae4" - integrity sha512-lqndwy5rDIp3tOLex5lnLSrltomqxgVkRkRi547rXoTl2qy7PY2rtHbUouQl7KY2XoVSeSeECYVq/bLJ+bbJNg== +playwright@^1.22.1: + version "1.22.1" + resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.22.1.tgz#a41bf55065953a6b31c151433e0f00c9e23c54dd" + integrity sha512-pn4dvphQdL3zTgI+xfqkTL3HK1xbtQz+zjJxzyhwKLDxMNuAH7/NCm75auWbYLyssdnvApznujwrQuJvqXTKUw== dependencies: - playwright-core "=1.16.2" - -pngjs@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-5.0.0.tgz#e79dd2b215767fd9c04561c01236df960bce7fbb" - integrity sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw== + playwright-core "1.22.1" prelude-ls@^1.2.1: version "1.2.1" @@ -3278,20 +3182,11 @@ process-on-spawn@^1.0.0: dependencies: fromentries "^1.2.0" -progress@^2.0.0, progress@^2.0.3: +progress@^2.0.0: version "2.0.3" resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== -proper-lockfile@^4.1.1: - version "4.1.2" - resolved "https://registry.yarnpkg.com/proper-lockfile/-/proper-lockfile-4.1.2.tgz#c8b9de2af6b2f1601067f98e01ac66baa223141f" - integrity sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA== - dependencies: - graceful-fs "^4.2.4" - retry "^0.12.0" - signal-exit "^3.0.2" - proxy-addr@~2.0.5: version "2.0.6" resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.6.tgz#fdc2336505447d3f2f2c638ed272caf614bbb2bf" @@ -3300,24 +3195,11 @@ proxy-addr@~2.0.5: forwarded "~0.1.2" ipaddr.js "1.9.1" -proxy-from-env@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" - integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== - psl@^1.1.33: version "1.8.0" resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24" integrity sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ== -pump@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" - integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== - dependencies: - end-of-stream "^1.1.0" - once "^1.3.1" - punycode@^2.1.0, punycode@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" @@ -3436,11 +3318,6 @@ resolve@^1.9.0: is-core-module "^2.2.0" path-parse "^1.0.6" -retry@^0.12.0: - version "0.12.0" - resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b" - integrity sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs= - reusify@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" @@ -3618,28 +3495,6 @@ slash@^3.0.0: resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== -smart-buffer@^4.1.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.2.0.tgz#6e1d71fa4f18c05f7d0ff216dd16a481d0e8d9ae" - integrity sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg== - -socks-proxy-agent@^6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-6.1.0.tgz#869cf2d7bd10fea96c7ad3111e81726855e285c3" - integrity sha512-57e7lwCN4Tzt3mXz25VxOErJKXlPfXmkMLnk310v/jwW20jWRVcgsOit+xNkN3eIEdB47GwnfAEBLacZ/wVIKg== - dependencies: - agent-base "^6.0.2" - debug "^4.3.1" - socks "^2.6.1" - -socks@^2.6.1: - version "2.6.1" - resolved "https://registry.yarnpkg.com/socks/-/socks-2.6.1.tgz#989e6534a07cf337deb1b1c94aaa44296520d30e" - integrity sha512-kLQ9N5ucj8uIcxrDwjm0Jsqk06xdpBjGNQtpXy4Q8/QY2k+fY7nZH8CARy+hkbG+SGAovmzzuauCpBlb8FrnBA== - dependencies: - ip "^1.1.5" - smart-buffer "^4.1.0" - source-list-map@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34" @@ -3707,13 +3562,6 @@ sprintf-js@~1.0.2: resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= -stack-utils@^2.0.3: - version "2.0.5" - resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.5.tgz#d25265fca995154659dbbfba3b49254778d2fdd5" - integrity sha512-xrQcmYhOsn/1kX+Vraq+7j4oE2j/6BFscZ0etmYg81xuM8Gq0022Pxb8+IqgOFUIaxHs0KaSb7T1+OegiNrNFA== - dependencies: - escape-string-regexp "^2.0.0" - "statuses@>= 1.5.0 < 2", statuses@~1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" @@ -4396,21 +4244,6 @@ yargs@^15.0.2: y18n "^4.0.0" yargs-parser "^18.1.1" -yauzl@^2.10.0: - version "2.10.0" - resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9" - integrity sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk= - dependencies: - buffer-crc32 "~0.2.3" - fd-slicer "~1.1.0" - -yazl@^2.5.1: - version "2.5.1" - resolved "https://registry.yarnpkg.com/yazl/-/yazl-2.5.1.tgz#a3d65d3dd659a5b0937850e8609f22fffa2b5c35" - integrity sha512-phENi2PLiHnHb6QBVot+dJnaAZ0xosj7p3fWl+znIjBDlnMI2PsZCJZ306BPTFOaHf5qdDEI8x5qFrSOBN5vrw== - dependencies: - buffer-crc32 "~0.2.3" - yocto-queue@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" From 8f97a5a78635560a01f44098bc9f1f036a77159a Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 19 May 2022 15:30:12 -0700 Subject: [PATCH 30/37] Deflake registerLinkProvider tests The root cause of this flakiness problem was related to the click event handler. For some reason it would not go off on some links but it would when clicking elsewhere in the terminal. I tried a bunch of things to keep the click handler to no avail, instead opting to move to using mousedown and mouseup events for activating links which comes with the nice benefit of ensuring that the link on mouseup is the same one on mousedown. Fixes #3821 --- src/browser/Linkifier2.ts | 17 +++++++++++------ test/api/Terminal.api.ts | 28 +++++++++++++++++++--------- 2 files changed, 30 insertions(+), 15 deletions(-) diff --git a/src/browser/Linkifier2.ts b/src/browser/Linkifier2.ts index acbf1c93..dae9acfa 100644 --- a/src/browser/Linkifier2.ts +++ b/src/browser/Linkifier2.ts @@ -18,6 +18,7 @@ export class Linkifier2 extends Disposable implements ILinkifier2 { private _linkProviders: ILinkProvider[] = []; public get currentLink(): ILinkWithState | undefined { return this._currentLink; } protected _currentLink: ILinkWithState | undefined; + private _mouseDownLink: ILinkWithState | undefined; private _lastMouseEvent: MouseEvent | undefined; private _linkCacheDisposables: IDisposable[] = []; private _lastBufferCell: IBufferCellPosition | undefined; @@ -61,7 +62,8 @@ export class Linkifier2 extends Disposable implements ILinkifier2 { this._clearCurrentLink(); })); this.register(addDisposableDomListener(this._element, 'mousemove', this._onMouseMove.bind(this))); - this.register(addDisposableDomListener(this._element, 'click', this._onClick.bind(this))); + this.register(addDisposableDomListener(this._element, 'mousedown', this._handleMouseDown.bind(this))); + this.register(addDisposableDomListener(this._element, 'mouseup', this._handleMouseUp.bind(this))); } private _onMouseMove(event: MouseEvent): void { @@ -129,7 +131,7 @@ export class Linkifier2 extends Disposable implements ILinkifier2 { let linkProvided = false; // There is no link cached, so ask for one - this._linkProviders.forEach((linkProvider, i) => { + for (const [i, linkProvider] of this._linkProviders.entries()) { if (useLineCache) { const existingReply = this._activeProviderReplies?.get(i); // If there isn't a reply, the provider hasn't responded yet. @@ -156,7 +158,7 @@ export class Linkifier2 extends Disposable implements ILinkifier2 { } }); } - }); + } } private _removeIntersectingLinks(y: number, replies: Map): void { @@ -222,18 +224,21 @@ export class Linkifier2 extends Disposable implements ILinkifier2 { return linkProvided; } - private _onClick(event: MouseEvent): void { + private _handleMouseDown(): void { + this._mouseDownLink = this._currentLink; + } + + private _handleMouseUp(event: MouseEvent): void { if (!this._element || !this._mouseService || !this._currentLink) { return; } const position = this._positionFromMouseEvent(event, this._element, this._mouseService); - if (!position) { return; } - if (this._linkAtPosition(this._currentLink.link, position)) { + if (this._mouseDownLink === this._currentLink && this._linkAtPosition(this._currentLink.link, position)) { this._currentLink.link.activate(event, this._currentLink.link.text); } } diff --git a/test/api/Terminal.api.ts b/test/api/Terminal.api.ts index b1582be8..3a023d4a 100644 --- a/test/api/Terminal.api.ts +++ b/test/api/Terminal.api.ts @@ -792,6 +792,9 @@ describe('API Integration Tests', function(): void { describe('registerLinkProvider', () => { it('should fire provideLinks when hovering cells', async () => { await openTerminal(page, { rendererType: 'dom' }); + // Focus the terminal as the cursor will show and trigger a rerender, which can clear the + // active link + await page.evaluate('window.term.focus()'); await page.evaluate(` window.calls = []; window.disposable = window.term.registerLinkProvider({ @@ -811,6 +814,9 @@ describe('API Integration Tests', function(): void { it('should fire hover and leave events on the link', async () => { await openTerminal(page, { rendererType: 'dom' }); + // Focus the terminal as the cursor will show and trigger a rerender, which can clear the + // active link + await page.evaluate('window.term.focus()'); await writeSync(page, 'foo bar baz'); // Wait for renderer to catch up as links are cleared on render await pollFor(page, `document.querySelector('.xterm-rows').textContent`, 'foo bar baz '); @@ -846,6 +852,9 @@ describe('API Integration Tests', function(): void { it('should work fine when hover and leave callbacks are not provided', async () => { await openTerminal(page, { rendererType: 'dom' }); + // Focus the terminal as the cursor will show and trigger a rerender, which can clear the + // active link + await page.evaluate('window.term.focus()'); await writeSync(page, 'foo bar baz'); // Wait for renderer to catch up as links are cleared on render await pollFor(page, `document.querySelector('.xterm-rows').textContent`, 'foo bar baz '); @@ -886,18 +895,12 @@ describe('API Integration Tests', function(): void { it('should fire activate events when clicking the link', async () => { await openTerminal(page, { rendererType: 'dom' }); + // Focus the terminal as the cursor will show and trigger a rerender, which can clear the + // active link + await page.evaluate('window.term.focus()'); await writeSync(page, 'a b c'); - // Wait for renderer to catch up as links are cleared on render await pollFor(page, `document.querySelector('.xterm-rows').textContent`, 'a b c '); - - // Focus terminal to avoid a render event clearing the active link - const dims = await getDimensions(); - await moveMouseCell(page, dims, 5, 5); - await page.mouse.down(); - await page.mouse.up(); - await timeout(200); // Not sure how to avoid this timeout, checking for xterm-focus doesn't help - await page.evaluate(` window.calls = []; window.disposable = window.term.registerLinkProvider({ @@ -913,6 +916,7 @@ describe('API Integration Tests', function(): void { } }); `); + const dims = await getDimensions(); await moveMouseCell(page, dims, 3, 1); await pollFor(page, `window.calls`, ['provide 1', 'hover 1']); await page.mouse.down(); @@ -933,6 +937,9 @@ describe('API Integration Tests', function(): void { it('should work when multiple links are provided on the same line', async () => { await openTerminal(page, { rendererType: 'dom' }); + // Focus the terminal as the cursor will show and trigger a rerender, which can clear the + // active link + await page.evaluate('window.term.focus()'); await writeSync(page, 'foo bar baz'); // Wait for renderer to catch up as links are cleared on render await pollFor(page, `document.querySelector('.xterm-rows').textContent`, 'foo bar baz '); @@ -979,6 +986,9 @@ describe('API Integration Tests', function(): void { it('should dispose links when hovering away', async () => { await openTerminal(page, { rendererType: 'dom' }); + // Focus the terminal as the cursor will show and trigger a rerender, which can clear the + // active link + await page.evaluate('window.term.focus()'); await writeSync(page, 'foo bar baz'); // Wait for renderer to catch up as links are cleared on render await pollFor(page, `document.querySelector('.xterm-rows').textContent`, 'foo bar baz '); From 7f14bfc7d9bc2ffa2a3804ae04c06304bb158a1c Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 19 May 2022 15:51:28 -0700 Subject: [PATCH 31/37] Update package.json Co-authored-by: Megan Rogge --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 336e32c5..f7bcb5d6 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "test": "npm run test-unit", "posttest": "npm run lint", "test-api": "npm run test-api-chromium", - "test-api-chromium": "node ./bin/test_api.js --browser=chromium --timeout=200000", + "test-api-chromium": "node ./bin/test_api.js --browser=chromium --timeout=20000", "test-api-firefox": "node ./bin/test_api.js --browser=firefox --timeout=200000", "test-api-webkit": "node ./bin/test_api.js --browser=webkit --timeout=20000", "test-unit": "node ./bin/test.js", From e5fb255b7972a77d4dc79eed2406b79b059fb5b1 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 19 May 2022 15:51:31 -0700 Subject: [PATCH 32/37] Update package.json Co-authored-by: Megan Rogge --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f7bcb5d6..b6a18f8b 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "posttest": "npm run lint", "test-api": "npm run test-api-chromium", "test-api-chromium": "node ./bin/test_api.js --browser=chromium --timeout=20000", - "test-api-firefox": "node ./bin/test_api.js --browser=firefox --timeout=200000", + "test-api-firefox": "node ./bin/test_api.js --browser=firefox --timeout=20000", "test-api-webkit": "node ./bin/test_api.js --browser=webkit --timeout=20000", "test-unit": "node ./bin/test.js", "test-unit-coverage": "node ./bin/test.js --coverage", From ddd43e0f09cfad60331f8fc7eb48709a5c7b81d5 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 20 May 2022 06:34:17 -0700 Subject: [PATCH 33/37] Correct rgba() parsing to use float alpha channel Fixes #3814 --- src/common/Color.test.ts | 8 ++++---- src/common/Color.ts | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/common/Color.test.ts b/src/common/Color.test.ts index a608908d..2f46d8c1 100644 --- a/src/common/Color.test.ts +++ b/src/common/Color.test.ts @@ -238,10 +238,10 @@ describe('Color', () => { }); it('should convert the rgba() format to an IColor', () => { assert.deepEqual(css.toColor('rgba(0, 0, 0, 0)'), { css: 'rgba(0, 0, 0, 0)', rgba: 0x00000000 }); - assert.deepEqual(css.toColor('rgba(80, 0, 0, 80)'), { css: 'rgba(80, 0, 0, 80)', rgba: 0x50000050 }); - assert.deepEqual(css.toColor('rgba(0, 80, 0, 80)'), { css: 'rgba(0, 80, 0, 80)', rgba: 0x00500050 }); - assert.deepEqual(css.toColor('rgba(0, 0, 80, 80)'), { css: 'rgba(0, 0, 80, 80)', rgba: 0x00005050 }); - assert.deepEqual(css.toColor('rgba(255, 255, 255, 255)'), { css: 'rgba(255, 255, 255, 255)', rgba: 0xffffffff }); + assert.deepEqual(css.toColor('rgba(80, 0, 0, 0.5)'), { css: 'rgba(80, 0, 0, 0.5)', rgba: 0x50000080 }); + assert.deepEqual(css.toColor('rgba(0, 80, 0, 0.5)'), { css: 'rgba(0, 80, 0, 0.5)', rgba: 0x00500080 }); + assert.deepEqual(css.toColor('rgba(0, 0, 80, 0.5)'), { css: 'rgba(0, 0, 80, 0.5)', rgba: 0x00005080 }); + assert.deepEqual(css.toColor('rgba(255, 255, 255, 1)'), { css: 'rgba(255, 255, 255, 1)', rgba: 0xffffffff }); }); }); }); diff --git a/src/common/Color.ts b/src/common/Color.ts index 3f09601f..4b834053 100644 --- a/src/common/Color.ts +++ b/src/common/Color.ts @@ -127,7 +127,7 @@ export namespace css { }; } } - const rgbaMatch = css.match(/rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(,\s*(\d{1,3})\s*)?\)/); + const rgbaMatch = css.match(/rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(,\s*(0|1|\d?\.(\d+))\s*)?\)/); if (rgbaMatch) { // rgb() or rgba() return { css, @@ -135,7 +135,7 @@ export namespace css { parseInt(rgbaMatch[1]), // r parseInt(rgbaMatch[2]), // g parseInt(rgbaMatch[3]), // b - rgbaMatch[5] === undefined ? 0xFF : parseInt(rgbaMatch[5]) // a? + Math.round((rgbaMatch[5] === undefined ? 1 : parseFloat(rgbaMatch[5])) * 0xFF) // a? ) }; } From 384cd9db300e1d88c430a7df2238b7d80b229089 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 20 May 2022 11:06:19 -0700 Subject: [PATCH 34/37] Standardize css.toColor output to #rrggbb[aa] This will help reduce the chance of runtime exceptions that would break the render loop. --- src/common/Color.test.ts | 90 ++++++++++++++++++++-------------------- src/common/Color.ts | 52 ++++++++++------------- 2 files changed, 66 insertions(+), 76 deletions(-) diff --git a/src/common/Color.test.ts b/src/common/Color.test.ts index 2f46d8c1..97cd89fe 100644 --- a/src/common/Color.test.ts +++ b/src/common/Color.test.ts @@ -151,45 +151,45 @@ describe('Color', () => { }); }); - describe('css', () => { + describe.only('css', () => { describe('toColor', () => { it('should convert the #rgb format to an IColor', () => { - assert.deepEqual(css.toColor('#000'), { css: '#000', rgba: 0x000000FF }); - assert.deepEqual(css.toColor('#111'), { css: '#111', rgba: 0x111111FF }); - assert.deepEqual(css.toColor('#222'), { css: '#222', rgba: 0x222222FF }); - assert.deepEqual(css.toColor('#333'), { css: '#333', rgba: 0x333333FF }); - assert.deepEqual(css.toColor('#444'), { css: '#444', rgba: 0x444444FF }); - assert.deepEqual(css.toColor('#555'), { css: '#555', rgba: 0x555555FF }); - assert.deepEqual(css.toColor('#666'), { css: '#666', rgba: 0x666666FF }); - assert.deepEqual(css.toColor('#777'), { css: '#777', rgba: 0x777777FF }); - assert.deepEqual(css.toColor('#888'), { css: '#888', rgba: 0x888888FF }); - assert.deepEqual(css.toColor('#999'), { css: '#999', rgba: 0x999999FF }); - assert.deepEqual(css.toColor('#aaa'), { css: '#aaa', rgba: 0xaaaaaaFF }); - assert.deepEqual(css.toColor('#bbb'), { css: '#bbb', rgba: 0xbbbbbbFF }); - assert.deepEqual(css.toColor('#ccc'), { css: '#ccc', rgba: 0xccccccFF }); - assert.deepEqual(css.toColor('#ddd'), { css: '#ddd', rgba: 0xddddddFF }); - assert.deepEqual(css.toColor('#eee'), { css: '#eee', rgba: 0xeeeeeeFF }); - assert.deepEqual(css.toColor('#fff'), { css: '#fff', rgba: 0xffffffFF }); - assert.deepEqual(css.toColor('#fff'), { css: '#fff', rgba: 0xffffffFF }); + assert.deepEqual(css.toColor('#000'), { css: '#000000', rgba: 0x000000FF }); + assert.deepEqual(css.toColor('#111'), { css: '#111111', rgba: 0x111111FF }); + assert.deepEqual(css.toColor('#222'), { css: '#222222', rgba: 0x222222FF }); + assert.deepEqual(css.toColor('#333'), { css: '#333333', rgba: 0x333333FF }); + assert.deepEqual(css.toColor('#444'), { css: '#444444', rgba: 0x444444FF }); + assert.deepEqual(css.toColor('#555'), { css: '#555555', rgba: 0x555555FF }); + assert.deepEqual(css.toColor('#666'), { css: '#666666', rgba: 0x666666FF }); + assert.deepEqual(css.toColor('#777'), { css: '#777777', rgba: 0x777777FF }); + assert.deepEqual(css.toColor('#888'), { css: '#888888', rgba: 0x888888FF }); + assert.deepEqual(css.toColor('#999'), { css: '#999999', rgba: 0x999999FF }); + assert.deepEqual(css.toColor('#aaa'), { css: '#aaaaaa', rgba: 0xaaaaaaFF }); + assert.deepEqual(css.toColor('#bbb'), { css: '#bbbbbb', rgba: 0xbbbbbbFF }); + assert.deepEqual(css.toColor('#ccc'), { css: '#cccccc', rgba: 0xccccccFF }); + assert.deepEqual(css.toColor('#ddd'), { css: '#dddddd', rgba: 0xddddddFF }); + assert.deepEqual(css.toColor('#eee'), { css: '#eeeeee', rgba: 0xeeeeeeFF }); + assert.deepEqual(css.toColor('#fff'), { css: '#ffffff', rgba: 0xffffffFF }); + assert.deepEqual(css.toColor('#fff'), { css: '#ffffff', rgba: 0xffffffFF }); }); it('should convert the #rgb format to an IColor', () => { - assert.deepEqual(css.toColor('#0000'), { css: '#0000', rgba: 0x00000000 }); - assert.deepEqual(css.toColor('#1111'), { css: '#1111', rgba: 0x11111111 }); - assert.deepEqual(css.toColor('#2222'), { css: '#2222', rgba: 0x22222222 }); - assert.deepEqual(css.toColor('#3333'), { css: '#3333', rgba: 0x33333333 }); - assert.deepEqual(css.toColor('#4444'), { css: '#4444', rgba: 0x44444444 }); - assert.deepEqual(css.toColor('#5555'), { css: '#5555', rgba: 0x55555555 }); - assert.deepEqual(css.toColor('#6666'), { css: '#6666', rgba: 0x66666666 }); - assert.deepEqual(css.toColor('#7777'), { css: '#7777', rgba: 0x77777777 }); - assert.deepEqual(css.toColor('#8888'), { css: '#8888', rgba: 0x88888888 }); - assert.deepEqual(css.toColor('#9999'), { css: '#9999', rgba: 0x99999999 }); - assert.deepEqual(css.toColor('#aaaa'), { css: '#aaaa', rgba: 0xaaaaaaaa }); - assert.deepEqual(css.toColor('#bbbb'), { css: '#bbbb', rgba: 0xbbbbbbbb }); - assert.deepEqual(css.toColor('#cccc'), { css: '#cccc', rgba: 0xcccccccc }); - assert.deepEqual(css.toColor('#dddd'), { css: '#dddd', rgba: 0xdddddddd }); - assert.deepEqual(css.toColor('#eeee'), { css: '#eeee', rgba: 0xeeeeeeee }); - assert.deepEqual(css.toColor('#ffff'), { css: '#ffff', rgba: 0xffffffff }); - assert.deepEqual(css.toColor('#ffff'), { css: '#ffff', rgba: 0xffffffff }); + assert.deepEqual(css.toColor('#0000'), { css: '#00000000', rgba: 0x00000000 }); + assert.deepEqual(css.toColor('#1111'), { css: '#11111111', rgba: 0x11111111 }); + assert.deepEqual(css.toColor('#2222'), { css: '#22222222', rgba: 0x22222222 }); + assert.deepEqual(css.toColor('#3333'), { css: '#33333333', rgba: 0x33333333 }); + assert.deepEqual(css.toColor('#4444'), { css: '#44444444', rgba: 0x44444444 }); + assert.deepEqual(css.toColor('#5555'), { css: '#55555555', rgba: 0x55555555 }); + assert.deepEqual(css.toColor('#6666'), { css: '#66666666', rgba: 0x66666666 }); + assert.deepEqual(css.toColor('#7777'), { css: '#77777777', rgba: 0x77777777 }); + assert.deepEqual(css.toColor('#8888'), { css: '#88888888', rgba: 0x88888888 }); + assert.deepEqual(css.toColor('#9999'), { css: '#99999999', rgba: 0x99999999 }); + assert.deepEqual(css.toColor('#aaaa'), { css: '#aaaaaaaa', rgba: 0xaaaaaaaa }); + assert.deepEqual(css.toColor('#bbbb'), { css: '#bbbbbbbb', rgba: 0xbbbbbbbb }); + assert.deepEqual(css.toColor('#cccc'), { css: '#cccccccc', rgba: 0xcccccccc }); + assert.deepEqual(css.toColor('#dddd'), { css: '#dddddddd', rgba: 0xdddddddd }); + assert.deepEqual(css.toColor('#eeee'), { css: '#eeeeeeee', rgba: 0xeeeeeeee }); + assert.deepEqual(css.toColor('#ffff'), { css: '#ffffffff', rgba: 0xffffffff }); + assert.deepEqual(css.toColor('#ffff'), { css: '#ffffffff', rgba: 0xffffffff }); }); it('should convert the #rrggbb format to an IColor', () => { assert.deepEqual(css.toColor('#000000'), { css: '#000000', rgba: 0x000000FF }); @@ -230,18 +230,18 @@ describe('Color', () => { assert.deepEqual(css.toColor('#ffffffff'), { css: '#ffffffff', rgba: 0xffffffff }); }); it('should convert the rgb() format to an IColor', () => { - assert.deepEqual(css.toColor('rgb(0, 0, 0)'), { css: 'rgb(0, 0, 0)', rgba: 0x000000ff }); - assert.deepEqual(css.toColor('rgb(80, 0, 0)'), { css: 'rgb(80, 0, 0)', rgba: 0x500000ff }); - assert.deepEqual(css.toColor('rgb(0, 80, 0)'), { css: 'rgb(0, 80, 0)', rgba: 0x005000ff }); - assert.deepEqual(css.toColor('rgb(0, 0, 80)'), { css: 'rgb(0, 0, 80)', rgba: 0x000050ff }); - assert.deepEqual(css.toColor('rgb(255, 255, 255)'), { css: 'rgb(255, 255, 255)', rgba: 0xffffffff }); + assert.deepEqual(css.toColor('rgb(0, 0, 0)'), { css: '#000000ff', rgba: 0x000000ff }); + assert.deepEqual(css.toColor('rgb(80, 0, 0)'), { css: '#500000ff', rgba: 0x500000ff }); + assert.deepEqual(css.toColor('rgb(0, 80, 0)'), { css: '#005000ff', rgba: 0x005000ff }); + assert.deepEqual(css.toColor('rgb(0, 0, 80)'), { css: '#000050ff', rgba: 0x000050ff }); + assert.deepEqual(css.toColor('rgb(255, 255, 255)'), { css: '#ffffffff', rgba: 0xffffffff }); }); it('should convert the rgba() format to an IColor', () => { - assert.deepEqual(css.toColor('rgba(0, 0, 0, 0)'), { css: 'rgba(0, 0, 0, 0)', rgba: 0x00000000 }); - assert.deepEqual(css.toColor('rgba(80, 0, 0, 0.5)'), { css: 'rgba(80, 0, 0, 0.5)', rgba: 0x50000080 }); - assert.deepEqual(css.toColor('rgba(0, 80, 0, 0.5)'), { css: 'rgba(0, 80, 0, 0.5)', rgba: 0x00500080 }); - assert.deepEqual(css.toColor('rgba(0, 0, 80, 0.5)'), { css: 'rgba(0, 0, 80, 0.5)', rgba: 0x00005080 }); - assert.deepEqual(css.toColor('rgba(255, 255, 255, 1)'), { css: 'rgba(255, 255, 255, 1)', rgba: 0xffffffff }); + assert.deepEqual(css.toColor('rgba(0, 0, 0, 0)'), { css: '#00000000', rgba: 0x00000000 }); + assert.deepEqual(css.toColor('rgba(80, 0, 0, 0.5)'), { css: '#50000080', rgba: 0x50000080 }); + assert.deepEqual(css.toColor('rgba(0, 80, 0, 0.5)'), { css: '#00500080', rgba: 0x00500080 }); + assert.deepEqual(css.toColor('rgba(0, 0, 80, 0.5)'), { css: '#00005080', rgba: 0x00005080 }); + assert.deepEqual(css.toColor('rgba(255, 255, 255, 1)'), { css: '#ffffffff', rgba: 0xffffffff }); }); }); }); diff --git a/src/common/Color.ts b/src/common/Color.ts index 4b834053..e5c7e3eb 100644 --- a/src/common/Color.ts +++ b/src/common/Color.ts @@ -96,25 +96,19 @@ export namespace css { export function toColor(css: string): IColor { if (css.match(/#[0-9a-f]{3,8}/i)) { switch (css.length) { - case 4: // #rgb - return { - css, - rgba: channels.toRgba( - parseInt(css.slice(1, 2).repeat(2), 16), - parseInt(css.slice(2, 3).repeat(2), 16), - parseInt(css.slice(3, 4).repeat(2), 16) - ) - }; - case 5: // #rgba - return { - css, - rgba: channels.toRgba( - parseInt(css.slice(1, 2).repeat(2), 16), - parseInt(css.slice(2, 3).repeat(2), 16), - parseInt(css.slice(3, 4).repeat(2), 16), - parseInt(css.slice(4, 5).repeat(2), 16) - ) - }; + case 4: { // #rgb + const r = parseInt(css.slice(1, 2).repeat(2), 16); + const g = parseInt(css.slice(2, 3).repeat(2), 16); + const b = parseInt(css.slice(3, 4).repeat(2), 16); + return rgba.toColor(r, g, b); + } + case 5: { // #rgba + const r = parseInt(css.slice(1, 2).repeat(2), 16); + const g = parseInt(css.slice(2, 3).repeat(2), 16); + const b = parseInt(css.slice(3, 4).repeat(2), 16); + const a = parseInt(css.slice(4, 5).repeat(2), 16); + return rgba.toColor(r, g, b, a); + } case 7: // #rrggbb return { css, @@ -129,15 +123,11 @@ export namespace css { } const rgbaMatch = css.match(/rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(,\s*(0|1|\d?\.(\d+))\s*)?\)/); if (rgbaMatch) { // rgb() or rgba() - return { - css, - rgba: channels.toRgba( - parseInt(rgbaMatch[1]), // r - parseInt(rgbaMatch[2]), // g - parseInt(rgbaMatch[3]), // b - Math.round((rgbaMatch[5] === undefined ? 1 : parseFloat(rgbaMatch[5])) * 0xFF) // a? - ) - }; + const r = parseInt(rgbaMatch[1]); + const g = parseInt(rgbaMatch[2]); + const b = parseInt(rgbaMatch[3]); + const a = Math.round((rgbaMatch[5] === undefined ? 1 : parseFloat(rgbaMatch[5])) * 0xFF); + return rgba.toColor(r, g, b, a); } throw new Error('css.toColor: Unsupported css format'); } @@ -268,10 +258,10 @@ export namespace rgba { return [(value >> 24) & 0xFF, (value >> 16) & 0xFF, (value >> 8) & 0xFF, value & 0xFF]; } - export function toColor(r: number, g: number, b: number): IColor { + export function toColor(r: number, g: number, b: number, a?: number): IColor { return { - css: channels.toCss(r, g, b), - rgba: channels.toRgba(r, g, b) + css: channels.toCss(r, g, b, a), + rgba: channels.toRgba(r, g, b, a) }; } } From 4ededf5b06768f148ed56d91b2599e95f4325ce0 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 20 May 2022 12:35:51 -0700 Subject: [PATCH 35/37] Update src/common/Color.test.ts Co-authored-by: Megan Rogge --- src/common/Color.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/Color.test.ts b/src/common/Color.test.ts index 97cd89fe..082c81c3 100644 --- a/src/common/Color.test.ts +++ b/src/common/Color.test.ts @@ -151,7 +151,7 @@ describe('Color', () => { }); }); - describe.only('css', () => { + describe('css', () => { describe('toColor', () => { it('should convert the #rgb format to an IColor', () => { assert.deepEqual(css.toColor('#000'), { css: '#000000', rgba: 0x000000FF }); From 8b53b8c2559ce976726152c8a7d043f024935529 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 23 May 2022 06:49:52 -0700 Subject: [PATCH 36/37] Take padding into account when converting mouse coord to cell Part of microsoft/vscode#148061 --- src/browser/input/Mouse.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/browser/input/Mouse.ts b/src/browser/input/Mouse.ts index 2986fb3c..31a6af0a 100644 --- a/src/browser/input/Mouse.ts +++ b/src/browser/input/Mouse.ts @@ -5,7 +5,13 @@ export function getCoordsRelativeToElement(event: {clientX: number, clientY: number}, element: HTMLElement): [number, number] { const rect = element.getBoundingClientRect(); - return [event.clientX - rect.left, event.clientY - rect.top]; + const elementStyle = window.getComputedStyle(element); + const leftPadding = parseInt(elementStyle.getPropertyValue('padding-left')); + const topPadding = parseInt(elementStyle.getPropertyValue('padding-top')); + return [ + event.clientX - rect.left - leftPadding, + event.clientY - rect.top - topPadding + ]; } /** From ca083f257cd1136746c382996dbc77280b48c5e8 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 23 May 2022 07:15:18 -0700 Subject: [PATCH 37/37] Pass window in to fix tests --- src/browser/input/Mouse.test.ts | 20 ++++++++++++++------ src/browser/input/Mouse.ts | 6 +++--- src/browser/services/MouseService.ts | 1 + src/browser/services/SelectionService.ts | 2 +- 4 files changed, 19 insertions(+), 10 deletions(-) diff --git a/src/browser/input/Mouse.test.ts b/src/browser/input/Mouse.test.ts index 6a908499..00260361 100644 --- a/src/browser/input/Mouse.test.ts +++ b/src/browser/input/Mouse.test.ts @@ -11,30 +11,38 @@ const CHAR_WIDTH = 10; const CHAR_HEIGHT = 20; describe('Mouse getCoords', () => { + let windowOverride: Pick; let document: Document; beforeEach(() => { + windowOverride = { + getComputedStyle(): any { + return { + getPropertyValue: () => '0px' + } as Pick; + } + }; document = new jsdom.JSDOM('').window.document; }); it('should return the cell that was clicked', () => { let coords: [number, number] | undefined; - coords = getCoords({ clientX: CHAR_WIDTH / 2, clientY: CHAR_HEIGHT / 2 }, document.createElement('div'), 10, 10, true, CHAR_WIDTH, CHAR_HEIGHT); + coords = getCoords(windowOverride, { clientX: CHAR_WIDTH / 2, clientY: CHAR_HEIGHT / 2 }, document.createElement('div'), 10, 10, true, CHAR_WIDTH, CHAR_HEIGHT); assert.deepEqual(coords, [1, 1]); - coords = getCoords({ clientX: CHAR_WIDTH, clientY: CHAR_HEIGHT }, document.createElement('div'), 10, 10, true, CHAR_WIDTH, CHAR_HEIGHT); + coords = getCoords(windowOverride, { clientX: CHAR_WIDTH, clientY: CHAR_HEIGHT }, document.createElement('div'), 10, 10, true, CHAR_WIDTH, CHAR_HEIGHT); assert.deepEqual(coords, [1, 1]); - coords = getCoords({ clientX: CHAR_WIDTH, clientY: CHAR_HEIGHT + 1 }, document.createElement('div'), 10, 10, true, CHAR_WIDTH, CHAR_HEIGHT); + coords = getCoords(windowOverride, { clientX: CHAR_WIDTH, clientY: CHAR_HEIGHT + 1 }, document.createElement('div'), 10, 10, true, CHAR_WIDTH, CHAR_HEIGHT); assert.deepEqual(coords, [1, 2]); - coords = getCoords({ clientX: CHAR_WIDTH + 1, clientY: CHAR_HEIGHT }, document.createElement('div'), 10, 10, true, CHAR_WIDTH, CHAR_HEIGHT); + coords = getCoords(windowOverride, { clientX: CHAR_WIDTH + 1, clientY: CHAR_HEIGHT }, document.createElement('div'), 10, 10, true, CHAR_WIDTH, CHAR_HEIGHT); assert.deepEqual(coords, [2, 1]); }); it('should ensure the coordinates are returned within the terminal bounds', () => { let coords: [number, number] | undefined; - coords = getCoords({ clientX: -1, clientY: -1 }, document.createElement('div'), 10, 10, true, CHAR_WIDTH, CHAR_HEIGHT); + coords = getCoords(windowOverride, { clientX: -1, clientY: -1 }, document.createElement('div'), 10, 10, true, CHAR_WIDTH, CHAR_HEIGHT); assert.deepEqual(coords, [1, 1]); // Event are double the cols/rows - coords = getCoords({ clientX: CHAR_WIDTH * 20, clientY: CHAR_HEIGHT * 20 }, document.createElement('div'), 10, 10, true, CHAR_WIDTH, CHAR_HEIGHT); + coords = getCoords(windowOverride, { clientX: CHAR_WIDTH * 20, clientY: CHAR_HEIGHT * 20 }, document.createElement('div'), 10, 10, true, CHAR_WIDTH, CHAR_HEIGHT); assert.deepEqual(coords, [10, 10], 'coordinates should never come back as larger than the terminal'); }); }); diff --git a/src/browser/input/Mouse.ts b/src/browser/input/Mouse.ts index 31a6af0a..6c377edb 100644 --- a/src/browser/input/Mouse.ts +++ b/src/browser/input/Mouse.ts @@ -3,7 +3,7 @@ * @license MIT */ -export function getCoordsRelativeToElement(event: {clientX: number, clientY: number}, element: HTMLElement): [number, number] { +export function getCoordsRelativeToElement(window: Pick, event: {clientX: number, clientY: number}, element: HTMLElement): [number, number] { const rect = element.getBoundingClientRect(); const elementStyle = window.getComputedStyle(element); const leftPadding = parseInt(elementStyle.getPropertyValue('padding-left')); @@ -26,13 +26,13 @@ export function getCoordsRelativeToElement(event: {clientX: number, clientY: num * apply an offset to the x value such that the left half of the cell will * select that cell and the right half will select the next cell. */ -export function getCoords(event: {clientX: number, clientY: number}, element: HTMLElement, colCount: number, rowCount: number, hasValidCharSize: boolean, actualCellWidth: number, actualCellHeight: number, isSelection?: boolean): [number, number] | undefined { +export function getCoords(window: Pick, event: {clientX: number, clientY: number}, element: HTMLElement, colCount: number, rowCount: number, hasValidCharSize: boolean, actualCellWidth: number, actualCellHeight: number, isSelection?: boolean): [number, number] | undefined { // Coordinates cannot be measured if there are no valid if (!hasValidCharSize) { return undefined; } - const coords = getCoordsRelativeToElement(event, element); + const coords = getCoordsRelativeToElement(window, event, element); if (!coords) { return undefined; } diff --git a/src/browser/services/MouseService.ts b/src/browser/services/MouseService.ts index 348ba64e..69123ba3 100644 --- a/src/browser/services/MouseService.ts +++ b/src/browser/services/MouseService.ts @@ -17,6 +17,7 @@ export class MouseService implements IMouseService { public getCoords(event: {clientX: number, clientY: number}, element: HTMLElement, colCount: number, rowCount: number, isSelection?: boolean): [number, number] | undefined { return getCoords( + window, event, element, colCount, diff --git a/src/browser/services/SelectionService.ts b/src/browser/services/SelectionService.ts index c7b7707a..57ba048f 100644 --- a/src/browser/services/SelectionService.ts +++ b/src/browser/services/SelectionService.ts @@ -402,7 +402,7 @@ export class SelectionService extends Disposable implements ISelectionService { * @param event The mouse event. */ private _getMouseEventScrollAmount(event: MouseEvent): number { - let offset = getCoordsRelativeToElement(event, this._screenElement)[1]; + let offset = getCoordsRelativeToElement(window, event, this._screenElement)[1]; const terminalHeight = this._renderService.dimensions.canvasHeight; if (offset >= 0 && offset <= terminalHeight) { return 0;