From 482dad3036e35d39f6a1a6d3765ab03005b5a31f Mon Sep 17 00:00:00 2001 From: tisilent Date: Thu, 10 Aug 2023 16:11:04 +0800 Subject: [PATCH 01/22] Add cursorInactiveStyle option --- .../src/CursorRenderLayer.ts | 9 +- addons/xterm-addon-webgl/src/WebglRenderer.ts | 15 +++- demo/client.ts | 1 + src/browser/renderer/dom/DomRenderer.ts | 17 +++- .../dom/DomRendererRowFactory.test.ts | 85 ++++++++++--------- .../renderer/dom/DomRendererRowFactory.ts | 23 ++++- src/common/Types.d.ts | 2 + src/common/services/OptionsService.ts | 1 + src/common/services/Services.ts | 3 +- typings/xterm.d.ts | 5 ++ 10 files changed, 115 insertions(+), 46 deletions(-) diff --git a/addons/xterm-addon-canvas/src/CursorRenderLayer.ts b/addons/xterm-addon-canvas/src/CursorRenderLayer.ts index 19b07b5f..b35cdbbc 100644 --- a/addons/xterm-addon-canvas/src/CursorRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/CursorRenderLayer.ts @@ -150,7 +150,14 @@ export class CursorRenderLayer extends BaseRenderLayer { this._ctx.save(); this._ctx.fillStyle = this._themeService.colors.cursor.css; const cursorStyle = this._optionsService.rawOptions.cursorStyle; - this._renderBlurCursor(cursorX, viewportRelativeCursorY, this._cell); + if (this._optionsService.rawOptions.cursorInactiveStyle === 'outline') { + this._renderBlurCursor(cursorX, viewportRelativeCursorY, this._cell); + } else if (this._optionsService.rawOptions.cursorInactiveStyle === 'line') { + this._cursorRenderers['bar'](cursorX, viewportRelativeCursorY, this._cell); + } else if (this._optionsService.rawOptions.cursorInactiveStyle === 'underline') { + this._cursorRenderers['underline'](cursorX, viewportRelativeCursorY, this._cell); + } else { + } this._ctx.restore(); this._state.x = cursorX; this._state.y = viewportRelativeCursorY; diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index b74aada9..f7d190ee 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -461,7 +461,7 @@ export class WebglRenderer extends Disposable implements IRenderer { y: this._terminal.buffer.active.cursorY, width: cell.getWidth(), style: this._coreBrowserService.isFocused ? - (terminal.options.cursorStyle || 'block') : 'blur', + (terminal.options.cursorStyle || 'block') : this._getInactiveCursorStyle(terminal.options.cursorInactiveStyle), cursorWidth: terminal.options.cursorWidth, dpr: this._devicePixelRatio }; @@ -600,6 +600,19 @@ export class WebglRenderer extends Disposable implements IRenderer { const cursorY = this._terminal.buffer.active.cursorY; this._onRequestRedraw.fire({ start: cursorY, end: cursorY }); } + + private _getInactiveCursorStyle(cursorInactiveStyle: 'outline' | 'line' | 'underline' | 'none'): string { + if (cursorInactiveStyle === 'outline') { + return 'blur'; + } + if (cursorInactiveStyle === 'line') { + return 'bar'; + } + if (cursorInactiveStyle === 'underline'){ + return 'underline'; + } + return 'block'; + } } // TODO: Share impl with core diff --git a/demo/client.ts b/demo/client.ts index a7c70e5b..b5c67208 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -419,6 +419,7 @@ function initOptions(term: TerminalType): void { ]; const stringOptions = { cursorStyle: ['block', 'underline', 'bar'], + cursorInactiveStyle: ['outline', 'line', 'underline', 'none'], fastScrollModifier: ['none', 'alt', 'ctrl', 'shift'], fontFamily: null, fontWeight: ['normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900'], diff --git a/src/browser/renderer/dom/DomRenderer.ts b/src/browser/renderer/dom/DomRenderer.ts index cd854696..be703ebe 100644 --- a/src/browser/renderer/dom/DomRenderer.ts +++ b/src/browser/renderer/dom/DomRenderer.ts @@ -197,13 +197,18 @@ export class DomRenderer extends Disposable implements IRenderer { `}`; // Cursor styles += - `${this._terminalSelector} .${ROW_CONTAINER_CLASS}:not(.${FOCUS_CLASS}) .${RowCss.CURSOR_CLASS}.${RowCss.CURSOR_STYLE_BLOCK_CLASS} ,` + - `${this._terminalSelector} .${ROW_CONTAINER_CLASS}:not(.${FOCUS_CLASS}) .${RowCss.CURSOR_CLASS}.${RowCss.CURSOR_STYLE_BAR_CLASS} ,` + - `${this._terminalSelector} .${ROW_CONTAINER_CLASS}:not(.${FOCUS_CLASS}) .${RowCss.CURSOR_CLASS}.${RowCss.CURSOR_STYLE_UNDERLINE_CLASS} ` + - `{` + + `${this._terminalSelector} .${ROW_CONTAINER_CLASS}:not(.${FOCUS_CLASS}) .${RowCss.CURSOR_CLASS}.${RowCss.CURSOR_INACTIVE_STYLE_OUTLINE_CLASS} {` + ` outline: 1px solid ${colors.cursor.css};` + ` outline-offset: -1px;` + `}` + + `${this._terminalSelector} .${ROW_CONTAINER_CLASS}:not(.${FOCUS_CLASS}) .${RowCss.CURSOR_CLASS}.${RowCss.CURSOR_INACTIVE_STYLE_LINE_CLASS} {` + + ` box-shadow: ${this._optionsService.rawOptions.cursorWidth}px 0 0 ${colors.cursor.css} inset;` + + `}` + + `${this._terminalSelector} .${ROW_CONTAINER_CLASS}:not(.${FOCUS_CLASS}) .${RowCss.CURSOR_CLASS}.${RowCss.CURSOR_INACTIVE_STYLE_UNDERLINE_CLASS} {` + + ` border-bottom: 1px ${colors.cursor.css};` + + ` border-bottom-style: solid;` + + ` height: calc(100% - 1px);` + + `}` + `${this._terminalSelector} .${ROW_CONTAINER_CLASS}.${FOCUS_CLASS} .${RowCss.CURSOR_CLASS}.${RowCss.CURSOR_BLINK_CLASS}:not(.${RowCss.CURSOR_STYLE_BLOCK_CLASS}) {` + ` animation: blink_box_shadow` + `_` + this._terminalClass + ` 1s step-end infinite;` + `}` + @@ -408,6 +413,7 @@ export class DomRenderer extends Disposable implements IRenderer { const cursorX = Math.min(buffer.x, this._bufferService.cols - 1); const cursorBlink = this._optionsService.rawOptions.cursorBlink; const cursorStyle = this._optionsService.rawOptions.cursorStyle; + const cursorInactiveStyle = this._optionsService.rawOptions.cursorInactiveStyle; for (let y = start; y <= end; y++) { const row = y + buffer.ydisp; @@ -422,6 +428,7 @@ export class DomRenderer extends Disposable implements IRenderer { row, row === cursorAbsoluteY, cursorStyle, + cursorInactiveStyle, cursorX, cursorBlink, this.dimensions.css.cell.width, @@ -474,6 +481,7 @@ export class DomRenderer extends Disposable implements IRenderer { const cursorX = Math.min(buffer.x, cols - 1); const cursorBlink = this._optionsService.rawOptions.cursorBlink; const cursorStyle = this._optionsService.rawOptions.cursorStyle; + const cursorInactiveStyle = this._optionsService.rawOptions.cursorInactiveStyle; // refresh rows within link range for (let i = y; i <= y2; ++i) { @@ -489,6 +497,7 @@ export class DomRenderer extends Disposable implements IRenderer { row, row === cursorAbsoluteY, cursorStyle, + cursorInactiveStyle, cursorX, cursorBlink, this.dimensions.css.cell.width, diff --git a/src/browser/renderer/dom/DomRendererRowFactory.test.ts b/src/browser/renderer/dom/DomRendererRowFactory.test.ts index 455a401d..e95952f2 100644 --- a/src/browser/renderer/dom/DomRendererRowFactory.test.ts +++ b/src/browser/renderer/dom/DomRendererRowFactory.test.ts @@ -39,7 +39,7 @@ describe('DomRendererRowFactory', () => { describe('createRow', () => { it('should not create anything for an empty row', () => { - const spans = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1); + const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1); assert.equal(extractHtml(spans), '' ); @@ -50,7 +50,7 @@ describe('DomRendererRowFactory', () => { lineData.setCell(0, CellData.fromCharData([DEFAULT_ATTR, '語', 2, '語'.charCodeAt(0)])); // There should be no element for the following "empty" cell lineData.setCell(1, CellData.fromCharData([DEFAULT_ATTR, '', 0, 0])); - const spans = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1); + const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1); assert.equal(extractHtml(spans), '' ); @@ -58,7 +58,7 @@ describe('DomRendererRowFactory', () => { it('should add class for cursor and cursor style', () => { for (const style of ['block', 'bar', 'underline']) { - const spans = rowFactory.createRow(lineData, 0, true, style, 0, false, 5, EMPTY_WIDTH, -1, -1); + const spans = rowFactory.createRow(lineData, 0, true, style, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1); assert.equal(extractHtml(spans), ` ` ); @@ -66,18 +66,27 @@ describe('DomRendererRowFactory', () => { }); it('should add class for cursor blink', () => { - const spans = rowFactory.createRow(lineData, 0, true, 'block', 0, true, 5, EMPTY_WIDTH, -1, -1); + const spans = rowFactory.createRow(lineData, 0, true, 'block', undefined, 0, true, 5, EMPTY_WIDTH, -1, -1); assert.equal(extractHtml(spans), ` ` ); }); + it('should add class for inactive cursor', () => { + for (const inactiveStyle of ['outline', 'line', 'underline', 'none']){ + const spans = rowFactory.createRow(lineData, 0, true, 'block', inactiveStyle, 0, false, 5, EMPTY_WIDTH, -1, -1); + assert.equal(extractHtml(spans), + ` ` + ); + } + }); + describe('attributes', () => { it('should add class for bold', () => { const cell = CellData.fromCharData([0, 'a', 1, 'a'.charCodeAt(0)]); cell.fg = DEFAULT_ATTR_DATA.fg | FgFlags.BOLD; lineData.setCell(0, cell); - const spans = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1); + const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1); assert.equal(extractHtml(spans), 'a' ); @@ -87,7 +96,7 @@ describe('DomRendererRowFactory', () => { const cell = CellData.fromCharData([0, 'a', 1, 'a'.charCodeAt(0)]); cell.bg = DEFAULT_ATTR_DATA.bg | BgFlags.ITALIC; lineData.setCell(0, cell); - const spans = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1); + const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1); assert.equal(extractHtml(spans), 'a' ); @@ -97,7 +106,7 @@ describe('DomRendererRowFactory', () => { const cell = CellData.fromCharData([0, 'a', 1, 'a'.charCodeAt(0)]); cell.bg = DEFAULT_ATTR_DATA.bg | BgFlags.DIM; lineData.setCell(0, cell); - const spans = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1); + const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1); assert.equal(extractHtml(spans), 'a' ); @@ -110,7 +119,7 @@ describe('DomRendererRowFactory', () => { cell.bg = DEFAULT_ATTR_DATA.bg | BgFlags.HAS_EXTENDED; cell.extended.underlineStyle = UnderlineStyle.SINGLE; lineData.setCell(0, cell); - const spans = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1); + const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1); assert.equal(extractHtml(spans), 'a' ); @@ -121,7 +130,7 @@ describe('DomRendererRowFactory', () => { cell.bg = DEFAULT_ATTR_DATA.bg | BgFlags.HAS_EXTENDED; cell.extended.underlineStyle = UnderlineStyle.DOUBLE; lineData.setCell(0, cell); - const spans = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1); + const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1); assert.equal(extractHtml(spans), 'a' ); @@ -132,7 +141,7 @@ describe('DomRendererRowFactory', () => { cell.bg = DEFAULT_ATTR_DATA.bg | BgFlags.HAS_EXTENDED; cell.extended.underlineStyle = UnderlineStyle.CURLY; lineData.setCell(0, cell); - const spans = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1); + const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1); assert.equal(extractHtml(spans), 'a' ); @@ -143,7 +152,7 @@ describe('DomRendererRowFactory', () => { cell.bg = DEFAULT_ATTR_DATA.bg | BgFlags.HAS_EXTENDED; cell.extended.underlineStyle = UnderlineStyle.DOTTED; lineData.setCell(0, cell); - const spans = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1); + const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1); assert.equal(extractHtml(spans), 'a' ); @@ -154,7 +163,7 @@ describe('DomRendererRowFactory', () => { cell.bg = DEFAULT_ATTR_DATA.bg | BgFlags.HAS_EXTENDED; cell.extended.underlineStyle = UnderlineStyle.DASHED; lineData.setCell(0, cell); - const spans = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1); + const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1); assert.equal(extractHtml(spans), 'a' ); @@ -165,7 +174,7 @@ describe('DomRendererRowFactory', () => { const cell = CellData.fromCharData([0, 'a', 1, 'a'.charCodeAt(0)]); cell.bg = DEFAULT_ATTR_DATA.bg | BgFlags.OVERLINE; lineData.setCell(0, cell); - const spans = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1); + const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1); assert.equal(extractHtml(spans), 'a' ); @@ -175,7 +184,7 @@ describe('DomRendererRowFactory', () => { const cell = CellData.fromCharData([0, 'a', 1, 'a'.charCodeAt(0)]); cell.fg = DEFAULT_ATTR_DATA.fg | FgFlags.STRIKETHROUGH; lineData.setCell(0, cell); - const spans = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1); + const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1); assert.equal(extractHtml(spans), 'a' ); @@ -188,7 +197,7 @@ describe('DomRendererRowFactory', () => { cell.fg &= ~Attributes.PCOLOR_MASK; cell.fg |= i; lineData.setCell(0, cell); - const spans = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1); + const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1); assert.equal(extractHtml(spans), `a` ); @@ -202,7 +211,7 @@ describe('DomRendererRowFactory', () => { cell.bg &= ~Attributes.PCOLOR_MASK; cell.bg |= i; lineData.setCell(0, cell); - const spans = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1); + const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1); assert.equal(extractHtml(spans), `a` ); @@ -214,7 +223,7 @@ describe('DomRendererRowFactory', () => { cell.fg |= Attributes.CM_P16 | 2 | FgFlags.INVERSE; cell.bg |= Attributes.CM_P16 | 1; lineData.setCell(0, cell); - const spans = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1); + const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1); assert.equal(extractHtml(spans), 'a' ); @@ -225,7 +234,7 @@ describe('DomRendererRowFactory', () => { cell.fg |= FgFlags.INVERSE; cell.bg |= Attributes.CM_P16 | 1; lineData.setCell(0, cell); - const spans = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1); + const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1); assert.equal(extractHtml(spans), 'a' ); @@ -235,7 +244,7 @@ describe('DomRendererRowFactory', () => { const cell = CellData.fromCharData([0, 'a', 1, 'a'.charCodeAt(0)]); cell.fg |= Attributes.CM_P16 | 1 | FgFlags.INVERSE; lineData.setCell(0, cell); - const spans = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1); + const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1); assert.equal(extractHtml(spans), 'a' ); @@ -248,7 +257,7 @@ describe('DomRendererRowFactory', () => { cell.fg &= ~Attributes.PCOLOR_MASK; cell.fg |= i; lineData.setCell(0, cell); - const spans = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1); + const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1); assert.equal(extractHtml(spans), `a` ); @@ -260,7 +269,7 @@ describe('DomRendererRowFactory', () => { cell.fg |= Attributes.CM_RGB | 1 << 16 | 2 << 8 | 3; cell.bg |= Attributes.CM_RGB | 4 << 16 | 5 << 8 | 6; lineData.setCell(0, cell); - const spans = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1); + const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1); assert.equal(extractHtml(spans), 'a' ); @@ -271,7 +280,7 @@ describe('DomRendererRowFactory', () => { cell.fg |= Attributes.CM_RGB | 1 << 16 | 2 << 8 | 3 | FgFlags.INVERSE; cell.bg |= Attributes.CM_RGB | 4 << 16 | 5 << 8 | 6; lineData.setCell(0, cell); - const spans = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1); + const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1); assert.equal(extractHtml(spans), 'a' ); @@ -283,7 +292,7 @@ describe('DomRendererRowFactory', () => { lineData.setCell(0, CellData.fromCharData([DEFAULT_ATTR, 'a', 1, 'a'.charCodeAt(0)])); lineData.setCell(1, CellData.fromCharData([DEFAULT_ATTR, 'b', 1, 'b'.charCodeAt(0)])); rowFactory.handleSelectionChanged([1, 0], [2, 0], false); - const spans = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1); + const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1); assert.equal(extractHtml(spans), 'ab' ); @@ -291,7 +300,7 @@ describe('DomRendererRowFactory', () => { it('should force whitespace cells to be rendered above the background', () => { lineData.setCell(1, CellData.fromCharData([DEFAULT_ATTR, 'a', 1, 'a'.charCodeAt(0)])); rowFactory.handleSelectionChanged([0, 0], [2, 0], false); - const spans = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1); + const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1); assert.equal(extractHtml(spans), ' a' ); @@ -308,7 +317,7 @@ describe('DomRendererRowFactory', () => { }); it('should not create anything for an empty row', () => { - const spans = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1); + const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1); assert.equal(extractHtml(spans), '' ); @@ -318,7 +327,7 @@ describe('DomRendererRowFactory', () => { lineData.setCell(0, CellData.fromCharData([DEFAULT_ATTR, 'a', 1, 'a'.charCodeAt(0)])); lineData.setCell(1, CellData.fromCharData([DEFAULT_ATTR, 'b', 1, 'b'.charCodeAt(0)])); lineData.setCell(2, CellData.fromCharData([DEFAULT_ATTR, 'c', 1, 'c'.charCodeAt(0)])); - const spans = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1); + const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1); assert.equal(extractHtml(spans), 'abc' ); @@ -329,7 +338,7 @@ describe('DomRendererRowFactory', () => { lineData.setCell(0, CellData.fromCharData([DEFAULT_ATTR, 'a', 1, 'a'.charCodeAt(0)])); lineData.setCell(1, CellData.fromCharData([DEFAULT_ATTR, '€', 1, '€'.charCodeAt(0)])); lineData.setCell(2, CellData.fromCharData([DEFAULT_ATTR, 'c', 1, 'c'.charCodeAt(0)])); - const spans = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1); + const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1); assert.equal(extractHtml(spans), 'ac' ); @@ -344,7 +353,7 @@ describe('DomRendererRowFactory', () => { lineData.setCell(1, aColor1); lineData.setCell(2, bColor2); lineData.setCell(3, bColor2); - const spans = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1); + const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1); assert.equal(extractHtml(spans), 'aabb' ); @@ -356,7 +365,7 @@ describe('DomRendererRowFactory', () => { lineData.setCell(2, CellData.fromCharData([DEFAULT_ATTR, 'X', 1, 'X'.charCodeAt(0)])); lineData.setCell(3, CellData.fromCharData([DEFAULT_ATTR, 'b', 1, 'b'.charCodeAt(0)])); lineData.setCell(4, CellData.fromCharData([DEFAULT_ATTR, 'b', 1, 'b'.charCodeAt(0)])); - const spans = rowFactory.createRow(lineData, 0, true, undefined, 2, false, 5, EMPTY_WIDTH, -1, -1); + const spans = rowFactory.createRow(lineData, 0, true, undefined, undefined, 2, false, 5, EMPTY_WIDTH, -1, -1); assert.equal(extractHtml(spans), 'aaXbb' ); @@ -369,7 +378,7 @@ describe('DomRendererRowFactory', () => { nullCell.bg = Attributes.CM_P16 | 2; lineData.setCell(3, nullCell); lineData.setCell(4, nullCell); - const spans = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1); + const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1); assert.equal(extractHtml(spans), ' ' ); @@ -379,23 +388,23 @@ describe('DomRendererRowFactory', () => { const nullCell = lineData.loadCell(0, new CellData()); nullCell.bg = Attributes.CM_P16 | 1; lineData.setCell(0, nullCell); - let spans = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1); + let spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1); assert.equal(extractHtml(spans), ' ' ); lineData.setCell(1, nullCell); - spans = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1); + spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1); assert.equal(extractHtml(spans), ' ' ); lineData.setCell(2, nullCell); lineData.setCell(3, nullCell); - spans = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1); + spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1); assert.equal(extractHtml(spans), ' ' ); lineData.setCell(4, CellData.fromCharData([DEFAULT_ATTR, 'a', 1, 'a'.charCodeAt(0)])); - spans = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1); + spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1); assert.equal(extractHtml(spans), ' a' ); @@ -410,7 +419,7 @@ describe('DomRendererRowFactory', () => { lineData.setCell(2, CellData.fromCharData([DEFAULT_ATTR, 'c', 1, 'c'.charCodeAt(0)])); lineData.setCell(3, CellData.fromCharData([DEFAULT_ATTR, '語', 2, 'c'.charCodeAt(0)])); lineData.setCell(4, CellData.fromCharData([DEFAULT_ATTR, '𝄞', 1, 'c'.charCodeAt(0)])); - const spans = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1); + const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1); assert.equal(extractHtml(spans), 'ac語𝄞' ); @@ -424,7 +433,7 @@ describe('DomRendererRowFactory', () => { lineData.setCell(4, CellData.fromCharData([DEFAULT_ATTR, 'x', 1, 'x'.charCodeAt(0)])); lineData.setCell(5, CellData.fromCharData([DEFAULT_ATTR, 'b', 1, 'b'.charCodeAt(0)])); lineData.setCell(6, CellData.fromCharData([DEFAULT_ATTR, 'b', 1, 'b'.charCodeAt(0)])); - const spans = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, EMPTY_WIDTH, 2, 4); + const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, EMPTY_WIDTH, 2, 4); assert.equal(extractHtml(spans), 'aaxxxbb' ); @@ -435,7 +444,7 @@ describe('DomRendererRowFactory', () => { lineData.setCell(1, CellData.fromCharData([DEFAULT_ATTR, 'a', 1, 'a'.charCodeAt(0)])); lineData.setCell(2, CellData.fromCharData([DEFAULT_ATTR, 'x', 1, 'x'.charCodeAt(0)])); lineData.setCell(4, CellData.fromCharData([DEFAULT_ATTR, 'x', 1, 'x'.charCodeAt(0)])); - const spans = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, EMPTY_WIDTH, 2, 4); + const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, EMPTY_WIDTH, 2, 4); assert.equal(extractHtml(spans), 'aax x' ); @@ -445,7 +454,7 @@ describe('DomRendererRowFactory', () => { for (let i = 0; i < 10; ++i) { lineData.setCell(i, CellData.fromCharData([DEFAULT_ATTR, 'a', 1, 'a'.charCodeAt(0)])); } - const spans = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, EMPTY_WIDTH, -100, 100); + const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, EMPTY_WIDTH, -100, 100); assert.equal(extractHtml(spans), 'aaaaaaaaaa' ); diff --git a/src/browser/renderer/dom/DomRendererRowFactory.ts b/src/browser/renderer/dom/DomRendererRowFactory.ts index 3f9765f0..4c643720 100644 --- a/src/browser/renderer/dom/DomRendererRowFactory.ts +++ b/src/browser/renderer/dom/DomRendererRowFactory.ts @@ -27,7 +27,11 @@ export const enum RowCss { CURSOR_BLINK_CLASS = 'xterm-cursor-blink', CURSOR_STYLE_BLOCK_CLASS = 'xterm-cursor-block', CURSOR_STYLE_BAR_CLASS = 'xterm-cursor-bar', - CURSOR_STYLE_UNDERLINE_CLASS = 'xterm-cursor-underline' + CURSOR_STYLE_UNDERLINE_CLASS = 'xterm-cursor-underline', + CURSOR_INACTIVE_STYLE_OUTLINE_CLASS = 'xterm-cursor-inactive-outline', + CURSOR_INACTIVE_STYLE_LINE_CLASS = 'xterm-cursor-inactive-line', + CURSOR_INACTIVE_STYLE_UNDERLINE_CLASS = 'xterm-cursor-inactive-underline', + CURSOR_INACTIVE_STYLE_NONE_CLASS = 'xterm-cursor-inactive-none' } @@ -61,6 +65,7 @@ export class DomRendererRowFactory { row: number, isCursorRow: boolean, cursorStyle: string | undefined, + cursorInactiveStyle: string | undefined, cursorX: number, cursorBlink: boolean, cellWidth: number, @@ -207,6 +212,22 @@ export class DomRendererRowFactory { ? RowCss.CURSOR_STYLE_UNDERLINE_CLASS : RowCss.CURSOR_STYLE_BLOCK_CLASS ); + if (cursorInactiveStyle) { + switch (cursorInactiveStyle) { + case 'outline': + classes.push(RowCss.CURSOR_INACTIVE_STYLE_OUTLINE_CLASS); + break; + case 'line': + classes.push(RowCss.CURSOR_INACTIVE_STYLE_LINE_CLASS); + break; + case 'underline': + classes.push(RowCss.CURSOR_INACTIVE_STYLE_UNDERLINE_CLASS); + break; + default: + classes.push(RowCss.CURSOR_INACTIVE_STYLE_NONE_CLASS); + break; + } + } } if (cell.isBold()) { diff --git a/src/common/Types.d.ts b/src/common/Types.d.ts index 8b978673..c605e5fa 100644 --- a/src/common/Types.d.ts +++ b/src/common/Types.d.ts @@ -38,6 +38,8 @@ export interface ITerminalOptions extends IPublicTerminalOptions { export type CursorStyle = 'block' | 'underline' | 'bar'; +export type CursorInactiveStyle = 'outline' | 'line' | 'underline' | 'none'; + export type XtermListener = (...args: any[]) => void; /** diff --git a/src/common/services/OptionsService.ts b/src/common/services/OptionsService.ts index cbeb6188..fb4251d9 100644 --- a/src/common/services/OptionsService.ts +++ b/src/common/services/OptionsService.ts @@ -15,6 +15,7 @@ export const DEFAULT_OPTIONS: Readonly> = { cursorBlink: false, cursorStyle: 'block', cursorWidth: 1, + cursorInactiveStyle: 'outline', customGlyphs: true, drawBoldTextInBrightColors: true, fastScrollModifier: 'alt', diff --git a/src/common/services/Services.ts b/src/common/services/Services.ts index 09b11083..f31b6be0 100644 --- a/src/common/services/Services.ts +++ b/src/common/services/Services.ts @@ -5,7 +5,7 @@ import { IEvent, IEventEmitter } from 'common/EventEmitter'; import { IBuffer, IBufferSet } from 'common/buffer/Types'; -import { IDecPrivateModes, ICoreMouseEvent, CoreMouseEncoding, ICoreMouseProtocol, CoreMouseEventType, ICharset, IWindowOptions, IModes, IAttributeData, ScrollSource, IDisposable, IColor, CursorStyle, IOscLinkData } from 'common/Types'; +import { IDecPrivateModes, ICoreMouseEvent, CoreMouseEncoding, ICoreMouseProtocol, CoreMouseEventType, ICharset, IWindowOptions, IModes, IAttributeData, ScrollSource, IDisposable, IColor, CursorStyle, CursorInactiveStyle, IOscLinkData } from 'common/Types'; import { createDecorator } from 'common/services/ServiceRegistry'; import { IDecorationOptions, IDecoration, ILinkHandler, IWindowsPty, ILogger } from 'xterm'; @@ -212,6 +212,7 @@ export interface ITerminalOptions { cursorBlink?: boolean; cursorStyle?: CursorStyle; cursorWidth?: number; + cursorInactiveStyle?: CursorInactiveStyle; customGlyphs?: boolean; disableStdin?: boolean; drawBoldTextInBrightColors?: boolean; diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 954970dc..78c3bf71 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -69,6 +69,11 @@ declare module 'xterm' { */ cursorWidth?: number; + /** + * The style of the inactive cursor. + */ + cursorInactiveStyle?: 'outline' | 'line' | 'underline' | 'none'; + /** * Whether to draw custom glyphs for block element and box drawing characters instead of using * the font. This should typically result in better rendering with continuous lines, even when From 0321bcb715578c76eb731a4a92a9a53630672a36 Mon Sep 17 00:00:00 2001 From: tisilent Date: Fri, 11 Aug 2023 09:40:06 +0800 Subject: [PATCH 02/22] Remove special classes. Add xterm-cursor-outline class. --- src/browser/renderer/dom/DomRenderer.ts | 6 +- .../renderer/dom/DomRendererRowFactory.ts | 57 +++++++++---------- 2 files changed, 31 insertions(+), 32 deletions(-) diff --git a/src/browser/renderer/dom/DomRenderer.ts b/src/browser/renderer/dom/DomRenderer.ts index be703ebe..5a83427a 100644 --- a/src/browser/renderer/dom/DomRenderer.ts +++ b/src/browser/renderer/dom/DomRenderer.ts @@ -197,14 +197,14 @@ export class DomRenderer extends Disposable implements IRenderer { `}`; // Cursor styles += - `${this._terminalSelector} .${ROW_CONTAINER_CLASS}:not(.${FOCUS_CLASS}) .${RowCss.CURSOR_CLASS}.${RowCss.CURSOR_INACTIVE_STYLE_OUTLINE_CLASS} {` + + `${this._terminalSelector} .${ROW_CONTAINER_CLASS}:not(.${FOCUS_CLASS}) .${RowCss.CURSOR_CLASS}.${RowCss.CURSOR_STYLE_OUTLINE_CLASS} {` + ` outline: 1px solid ${colors.cursor.css};` + ` outline-offset: -1px;` + `}` + - `${this._terminalSelector} .${ROW_CONTAINER_CLASS}:not(.${FOCUS_CLASS}) .${RowCss.CURSOR_CLASS}.${RowCss.CURSOR_INACTIVE_STYLE_LINE_CLASS} {` + + `${this._terminalSelector} .${ROW_CONTAINER_CLASS}:not(.${FOCUS_CLASS}) .${RowCss.CURSOR_CLASS}.${RowCss.CURSOR_STYLE_BAR_CLASS} {` + ` box-shadow: ${this._optionsService.rawOptions.cursorWidth}px 0 0 ${colors.cursor.css} inset;` + `}` + - `${this._terminalSelector} .${ROW_CONTAINER_CLASS}:not(.${FOCUS_CLASS}) .${RowCss.CURSOR_CLASS}.${RowCss.CURSOR_INACTIVE_STYLE_UNDERLINE_CLASS} {` + + `${this._terminalSelector} .${ROW_CONTAINER_CLASS}:not(.${FOCUS_CLASS}) .${RowCss.CURSOR_CLASS}.${RowCss.CURSOR_STYLE_UNDERLINE_CLASS} {` + ` border-bottom: 1px ${colors.cursor.css};` + ` border-bottom-style: solid;` + ` height: calc(100% - 1px);` + diff --git a/src/browser/renderer/dom/DomRendererRowFactory.ts b/src/browser/renderer/dom/DomRendererRowFactory.ts index 4c643720..4471b34e 100644 --- a/src/browser/renderer/dom/DomRendererRowFactory.ts +++ b/src/browser/renderer/dom/DomRendererRowFactory.ts @@ -26,12 +26,9 @@ export const enum RowCss { CURSOR_CLASS = 'xterm-cursor', CURSOR_BLINK_CLASS = 'xterm-cursor-blink', CURSOR_STYLE_BLOCK_CLASS = 'xterm-cursor-block', + CURSOR_STYLE_OUTLINE_CLASS = 'xterm-cursor-outline', CURSOR_STYLE_BAR_CLASS = 'xterm-cursor-bar', - CURSOR_STYLE_UNDERLINE_CLASS = 'xterm-cursor-underline', - CURSOR_INACTIVE_STYLE_OUTLINE_CLASS = 'xterm-cursor-inactive-outline', - CURSOR_INACTIVE_STYLE_LINE_CLASS = 'xterm-cursor-inactive-line', - CURSOR_INACTIVE_STYLE_UNDERLINE_CLASS = 'xterm-cursor-inactive-underline', - CURSOR_INACTIVE_STYLE_NONE_CLASS = 'xterm-cursor-inactive-none' + CURSOR_STYLE_UNDERLINE_CLASS = 'xterm-cursor-underline' } @@ -202,30 +199,32 @@ export class DomRendererRowFactory { if (!this._coreService.isCursorHidden && isCursorCell) { classes.push(RowCss.CURSOR_CLASS); - if (cursorBlink) { - classes.push(RowCss.CURSOR_BLINK_CLASS); - } - classes.push( - cursorStyle === 'bar' - ? RowCss.CURSOR_STYLE_BAR_CLASS - : cursorStyle === 'underline' - ? RowCss.CURSOR_STYLE_UNDERLINE_CLASS - : RowCss.CURSOR_STYLE_BLOCK_CLASS - ); - if (cursorInactiveStyle) { - switch (cursorInactiveStyle) { - case 'outline': - classes.push(RowCss.CURSOR_INACTIVE_STYLE_OUTLINE_CLASS); - break; - case 'line': - classes.push(RowCss.CURSOR_INACTIVE_STYLE_LINE_CLASS); - break; - case 'underline': - classes.push(RowCss.CURSOR_INACTIVE_STYLE_UNDERLINE_CLASS); - break; - default: - classes.push(RowCss.CURSOR_INACTIVE_STYLE_NONE_CLASS); - break; + if (this._coreBrowserService.isFocused) { + if (cursorBlink) { + classes.push(RowCss.CURSOR_BLINK_CLASS); + } + classes.push( + cursorStyle === 'bar' + ? RowCss.CURSOR_STYLE_BAR_CLASS + : cursorStyle === 'underline' + ? RowCss.CURSOR_STYLE_UNDERLINE_CLASS + : RowCss.CURSOR_STYLE_BLOCK_CLASS + ); + } else { + if (cursorInactiveStyle) { + switch (cursorInactiveStyle) { + case 'outline': + classes.push(RowCss.CURSOR_STYLE_OUTLINE_CLASS); + break; + case 'line': + classes.push(RowCss.CURSOR_STYLE_BAR_CLASS); + break; + case 'underline': + classes.push(RowCss.CURSOR_STYLE_UNDERLINE_CLASS); + break; + default: + break; + } } } } From 5c1e6dce998e1b153bc3db32a230cefa0714b0cf Mon Sep 17 00:00:00 2001 From: tisilent Date: Fri, 11 Aug 2023 11:36:40 +0800 Subject: [PATCH 03/22] Add block to cursorInactiveStyle, change line to bar --- .../src/CursorRenderLayer.ts | 15 ++++++-------- addons/xterm-addon-webgl/src/WebglRenderer.ts | 20 ++++++++++++------- demo/client.ts | 2 +- src/browser/renderer/dom/DomRenderer.ts | 18 +++++------------ .../renderer/dom/DomRendererRowFactory.ts | 4 +++- src/common/Types.d.ts | 2 +- typings/xterm.d.ts | 2 +- 7 files changed, 30 insertions(+), 33 deletions(-) diff --git a/addons/xterm-addon-canvas/src/CursorRenderLayer.ts b/addons/xterm-addon-canvas/src/CursorRenderLayer.ts index b35cdbbc..2ef1d072 100644 --- a/addons/xterm-addon-canvas/src/CursorRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/CursorRenderLayer.ts @@ -58,7 +58,8 @@ export class CursorRenderLayer extends BaseRenderLayer { this._cursorRenderers = { 'bar': this._renderBarCursor.bind(this), 'block': this._renderBlockCursor.bind(this), - 'underline': this._renderUnderlineCursor.bind(this) + 'underline': this._renderUnderlineCursor.bind(this), + 'outline': this._renderOutlineCursor.bind(this) }; this.register(optionsService.onOptionChange(() => this._handleOptionsChanged())); this._handleOptionsChanged(); @@ -150,13 +151,9 @@ export class CursorRenderLayer extends BaseRenderLayer { this._ctx.save(); this._ctx.fillStyle = this._themeService.colors.cursor.css; const cursorStyle = this._optionsService.rawOptions.cursorStyle; - if (this._optionsService.rawOptions.cursorInactiveStyle === 'outline') { - this._renderBlurCursor(cursorX, viewportRelativeCursorY, this._cell); - } else if (this._optionsService.rawOptions.cursorInactiveStyle === 'line') { - this._cursorRenderers['bar'](cursorX, viewportRelativeCursorY, this._cell); - } else if (this._optionsService.rawOptions.cursorInactiveStyle === 'underline') { - this._cursorRenderers['underline'](cursorX, viewportRelativeCursorY, this._cell); - } else { + const cursorInactiveStyle = this._optionsService.rawOptions.cursorInactiveStyle; + if (cursorInactiveStyle && cursorInactiveStyle !== 'none') { + this._cursorRenderers[cursorInactiveStyle](cursorX, viewportRelativeCursorY, this._cell); } this._ctx.restore(); this._state.x = cursorX; @@ -238,7 +235,7 @@ export class CursorRenderLayer extends BaseRenderLayer { this._ctx.restore(); } - private _renderBlurCursor(x: number, y: number, cell: ICellData): void { + private _renderOutlineCursor(x: number, y: number, cell: ICellData): void { this._ctx.save(); this._ctx.strokeStyle = this._themeService.colors.cursor.css; this._strokeRectAtCell(x, y, cell.getWidth(), 1); diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index f7d190ee..9bd30cb5 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -455,21 +455,24 @@ export class WebglRenderer extends Disposable implements IRenderer { // Override colors for cursor cell if (isCursorVisible && row === cursorY) { + const inactiveCursorStyle = this._getInactiveCursorStyle(terminal.options.cursorInactiveStyle); if (x === cursorX) { this._model.cursor = { x: cursorX, y: this._terminal.buffer.active.cursorY, width: cell.getWidth(), style: this._coreBrowserService.isFocused ? - (terminal.options.cursorStyle || 'block') : this._getInactiveCursorStyle(terminal.options.cursorInactiveStyle), + (terminal.options.cursorStyle || 'block') : inactiveCursorStyle, cursorWidth: terminal.options.cursorWidth, dpr: this._devicePixelRatio }; lastCursorX = cursorX + cell.getWidth() - 1; } if (x >= cursorX && x <= lastCursorX && - this._coreBrowserService.isFocused && - (terminal.options.cursorStyle || 'block') === 'block') { + ((this._coreBrowserService.isFocused && + (terminal.options.cursorStyle || 'block') === 'block') || + (this._coreBrowserService.isFocused === false && + inactiveCursorStyle === 'block'))) { this._cellColorResolver.result.fg = Attributes.CM_RGB | (this._themeService.colors.cursorAccent.rgba >> 8 & Attributes.RGB_MASK); this._cellColorResolver.result.bg = @@ -601,17 +604,20 @@ export class WebglRenderer extends Disposable implements IRenderer { this._onRequestRedraw.fire({ start: cursorY, end: cursorY }); } - private _getInactiveCursorStyle(cursorInactiveStyle: 'outline' | 'line' | 'underline' | 'none'): string { + private _getInactiveCursorStyle(cursorInactiveStyle: 'outline' | 'block' | 'bar' | 'underline' | 'none'): string { if (cursorInactiveStyle === 'outline') { return 'blur'; } - if (cursorInactiveStyle === 'line') { + if (cursorInactiveStyle === 'block') { + return 'block'; + } + if (cursorInactiveStyle === 'bar') { return 'bar'; } - if (cursorInactiveStyle === 'underline'){ + if (cursorInactiveStyle === 'underline') { return 'underline'; } - return 'block'; + return ''; } } diff --git a/demo/client.ts b/demo/client.ts index b5c67208..c9111fec 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -419,7 +419,7 @@ function initOptions(term: TerminalType): void { ]; const stringOptions = { cursorStyle: ['block', 'underline', 'bar'], - cursorInactiveStyle: ['outline', 'line', 'underline', 'none'], + cursorInactiveStyle: ['outline', 'block', 'bar', 'underline', 'none'], fastScrollModifier: ['none', 'alt', 'ctrl', 'shift'], fontFamily: null, fontWeight: ['normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900'], diff --git a/src/browser/renderer/dom/DomRenderer.ts b/src/browser/renderer/dom/DomRenderer.ts index 5a83427a..e607ae3e 100644 --- a/src/browser/renderer/dom/DomRenderer.ts +++ b/src/browser/renderer/dom/DomRenderer.ts @@ -197,28 +197,20 @@ export class DomRenderer extends Disposable implements IRenderer { `}`; // Cursor styles += - `${this._terminalSelector} .${ROW_CONTAINER_CLASS}:not(.${FOCUS_CLASS}) .${RowCss.CURSOR_CLASS}.${RowCss.CURSOR_STYLE_OUTLINE_CLASS} {` + - ` outline: 1px solid ${colors.cursor.css};` + - ` outline-offset: -1px;` + - `}` + - `${this._terminalSelector} .${ROW_CONTAINER_CLASS}:not(.${FOCUS_CLASS}) .${RowCss.CURSOR_CLASS}.${RowCss.CURSOR_STYLE_BAR_CLASS} {` + - ` box-shadow: ${this._optionsService.rawOptions.cursorWidth}px 0 0 ${colors.cursor.css} inset;` + - `}` + - `${this._terminalSelector} .${ROW_CONTAINER_CLASS}:not(.${FOCUS_CLASS}) .${RowCss.CURSOR_CLASS}.${RowCss.CURSOR_STYLE_UNDERLINE_CLASS} {` + - ` border-bottom: 1px ${colors.cursor.css};` + - ` border-bottom-style: solid;` + - ` height: calc(100% - 1px);` + - `}` + `${this._terminalSelector} .${ROW_CONTAINER_CLASS}.${FOCUS_CLASS} .${RowCss.CURSOR_CLASS}.${RowCss.CURSOR_BLINK_CLASS}:not(.${RowCss.CURSOR_STYLE_BLOCK_CLASS}) {` + ` animation: blink_box_shadow` + `_` + this._terminalClass + ` 1s step-end infinite;` + `}` + `${this._terminalSelector} .${ROW_CONTAINER_CLASS}.${FOCUS_CLASS} .${RowCss.CURSOR_CLASS}.${RowCss.CURSOR_BLINK_CLASS}.${RowCss.CURSOR_STYLE_BLOCK_CLASS} {` + ` animation: blink_block` + `_` + this._terminalClass + ` 1s step-end infinite;` + `}` + - `${this._terminalSelector} .${ROW_CONTAINER_CLASS}.${FOCUS_CLASS} .${RowCss.CURSOR_CLASS}.${RowCss.CURSOR_STYLE_BLOCK_CLASS} {` + + `${this._terminalSelector} .${ROW_CONTAINER_CLASS} .${RowCss.CURSOR_CLASS}.${RowCss.CURSOR_STYLE_BLOCK_CLASS} {` + ` background-color: ${colors.cursor.css};` + ` color: ${colors.cursorAccent.css};` + `}` + + `${this._terminalSelector} .${ROW_CONTAINER_CLASS} .${RowCss.CURSOR_CLASS}.${RowCss.CURSOR_STYLE_OUTLINE_CLASS} {` + + ` outline: 1px solid ${colors.cursor.css};` + + ` outline-offset: -1px;` + + `}` + `${this._terminalSelector} .${ROW_CONTAINER_CLASS} .${RowCss.CURSOR_CLASS}.${RowCss.CURSOR_STYLE_BAR_CLASS} {` + ` box-shadow: ${this._optionsService.rawOptions.cursorWidth}px 0 0 ${colors.cursor.css} inset;` + `}` + diff --git a/src/browser/renderer/dom/DomRendererRowFactory.ts b/src/browser/renderer/dom/DomRendererRowFactory.ts index 4471b34e..89468dd4 100644 --- a/src/browser/renderer/dom/DomRendererRowFactory.ts +++ b/src/browser/renderer/dom/DomRendererRowFactory.ts @@ -216,7 +216,9 @@ export class DomRendererRowFactory { case 'outline': classes.push(RowCss.CURSOR_STYLE_OUTLINE_CLASS); break; - case 'line': + case 'block': + classes.push(RowCss.CURSOR_STYLE_BLOCK_CLASS); + case 'bar': classes.push(RowCss.CURSOR_STYLE_BAR_CLASS); break; case 'underline': diff --git a/src/common/Types.d.ts b/src/common/Types.d.ts index c605e5fa..fceb4e8e 100644 --- a/src/common/Types.d.ts +++ b/src/common/Types.d.ts @@ -38,7 +38,7 @@ export interface ITerminalOptions extends IPublicTerminalOptions { export type CursorStyle = 'block' | 'underline' | 'bar'; -export type CursorInactiveStyle = 'outline' | 'line' | 'underline' | 'none'; +export type CursorInactiveStyle = 'outline' | 'block' | 'bar' | 'underline' | 'none'; export type XtermListener = (...args: any[]) => void; diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 78c3bf71..23aab9b5 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -72,7 +72,7 @@ declare module 'xterm' { /** * The style of the inactive cursor. */ - cursorInactiveStyle?: 'outline' | 'line' | 'underline' | 'none'; + cursorInactiveStyle?: 'outline' | 'block' | 'bar' | 'underline' | 'none'; /** * Whether to draw custom glyphs for block element and box drawing characters instead of using From c3d8c2a919bef1c21f20c77f5196c87cc2422813 Mon Sep 17 00:00:00 2001 From: tisilent Date: Fri, 11 Aug 2023 15:47:50 +0800 Subject: [PATCH 04/22] Update test and fix --- .../dom/DomRendererRowFactory.test.ts | 23 +++++++++++++++---- .../renderer/dom/DomRendererRowFactory.ts | 1 + 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/src/browser/renderer/dom/DomRendererRowFactory.test.ts b/src/browser/renderer/dom/DomRendererRowFactory.test.ts index e95952f2..609a5b4a 100644 --- a/src/browser/renderer/dom/DomRendererRowFactory.test.ts +++ b/src/browser/renderer/dom/DomRendererRowFactory.test.ts @@ -73,11 +73,26 @@ describe('DomRendererRowFactory', () => { }); it('should add class for inactive cursor', () => { - for (const inactiveStyle of ['outline', 'line', 'underline', 'none']){ + const coreBrowserService = new MockCoreBrowserService(); + coreBrowserService.isFocused = false; + const rowFactory = new DomRendererRowFactory( + dom.window.document, + new MockCharacterJoinerService(), + new MockOptionsService({ drawBoldTextInBrightColors: true }), + coreBrowserService, + new MockCoreService(), + new MockDecorationService(), + new MockThemeService() + ); + for (const inactiveStyle of ['outline', 'block', 'bar', 'underline', 'none']){ const spans = rowFactory.createRow(lineData, 0, true, 'block', inactiveStyle, 0, false, 5, EMPTY_WIDTH, -1, -1); - assert.equal(extractHtml(spans), - ` ` - ); + if (inactiveStyle === 'none') { + assert.equal(extractHtml(spans), + ` `); + } else { + assert.equal(extractHtml(spans), + ` `); + } } }); diff --git a/src/browser/renderer/dom/DomRendererRowFactory.ts b/src/browser/renderer/dom/DomRendererRowFactory.ts index 89468dd4..41e66b96 100644 --- a/src/browser/renderer/dom/DomRendererRowFactory.ts +++ b/src/browser/renderer/dom/DomRendererRowFactory.ts @@ -218,6 +218,7 @@ export class DomRendererRowFactory { break; case 'block': classes.push(RowCss.CURSOR_STYLE_BLOCK_CLASS); + break; case 'bar': classes.push(RowCss.CURSOR_STYLE_BAR_CLASS); break; From f317b962629e897b5ea3e54231be5f8fd054b86e Mon Sep 17 00:00:00 2001 From: tisilent Date: Fri, 11 Aug 2023 17:58:29 +0800 Subject: [PATCH 05/22] Fix canvas underline is cut off. --- src/browser/renderer/shared/TextureAtlas.ts | 24 ++++++++------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/src/browser/renderer/shared/TextureAtlas.ts b/src/browser/renderer/shared/TextureAtlas.ts index e77e595d..f0f5d864 100644 --- a/src/browser/renderer/shared/TextureAtlas.ts +++ b/src/browser/renderer/shared/TextureAtlas.ts @@ -532,10 +532,11 @@ export class TextureAtlas implements ITextureAtlas { // Underline style/stroke this._tmpCtx.beginPath(); const xLeft = padding; - const yTop = Math.ceil(padding + this._config.deviceCharHeight) - yOffset; - const yMid = padding + this._config.deviceCharHeight + lineWidth - yOffset; - const yBot = Math.ceil(padding + this._config.deviceCharHeight + lineWidth * 2) - yOffset; - const ySpace = lineWidth * 2; + const yTop = restrictToCellHeight ? + Math.ceil(padding + this._config.deviceCharHeight) - yOffset - lineWidth * 2 : + Math.ceil(padding + this._config.deviceCharHeight) - yOffset; + const yMid = yTop + lineWidth; + const yBot = yTop + lineWidth * 2; for (let i = 0; i < chWidth; i++) { this._tmpCtx.save(); @@ -544,17 +545,10 @@ export class TextureAtlas implements ITextureAtlas { const xChMid = xChLeft + this._config.deviceCellWidth / 2; switch (this._workAttributeData.extended.underlineStyle) { case UnderlineStyle.DOUBLE: - if (restrictToCellHeight) { - this._tmpCtx.moveTo(xChLeft, yTop - ySpace); - this._tmpCtx.lineTo(xChRight, yTop - ySpace); - this._tmpCtx.moveTo(xChLeft, yTop); - this._tmpCtx.lineTo(xChRight, yTop); - } else { - this._tmpCtx.moveTo(xChLeft, yTop); - this._tmpCtx.lineTo(xChRight, yTop); - this._tmpCtx.moveTo(xChLeft, yBot); - this._tmpCtx.lineTo(xChRight, yBot); - } + this._tmpCtx.moveTo(xChLeft, yTop); + this._tmpCtx.lineTo(xChRight, yTop); + this._tmpCtx.moveTo(xChLeft, yBot); + this._tmpCtx.lineTo(xChRight, yBot); break; case UnderlineStyle.CURLY: // Choose the bezier top and bottom based on the device pixel ratio, the curly line is From 18125dad847be79b80e269a5f5537b47598a88e7 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 11 Aug 2023 04:58:07 -0700 Subject: [PATCH 06/22] Add functional yarn wasm This is what I tried after seeing the error so it will improve the #4646 experience until a real fix is in place Part of #4646 --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 5ebc7762..b315d347 100644 --- a/package.json +++ b/package.json @@ -43,9 +43,10 @@ "prepare": "npm run setup", "setup": "npm run build", "presetup": "node ./bin/install-addons.js", - "postsetup": "cd addons/xterm-addon-image && npm run inwasm -- -S", + "postsetup": "npm run inwasm", "prepublishOnly": "npm run package", "watch": "tsc -b -w ./tsconfig.all.json --preserveWatchOutput", + "inwasm": "cd addons/xterm-addon-image && npm run inwasm -- -S", "benchmark": "NODE_PATH=./out xterm-benchmark -r 5 -c test/benchmark/benchmark.json", "benchmark-baseline": "NODE_PATH=./out xterm-benchmark -r 5 -c test/benchmark/benchmark.json --baseline out-test/benchmark/test/benchmark/*benchmark.js", "benchmark-eval": "NODE_PATH=./out xterm-benchmark -r 5 -c test/benchmark/benchmark.json --eval out-test/benchmark/test/benchmark/*benchmark.js", From 07d187151e1855a7c9d42f6b99bd4909efb9c07a Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 11 Aug 2023 09:50:22 -0700 Subject: [PATCH 07/22] Update src/browser/renderer/shared/TextureAtlas.ts --- src/browser/renderer/shared/TextureAtlas.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/browser/renderer/shared/TextureAtlas.ts b/src/browser/renderer/shared/TextureAtlas.ts index f0f5d864..b73e7009 100644 --- a/src/browser/renderer/shared/TextureAtlas.ts +++ b/src/browser/renderer/shared/TextureAtlas.ts @@ -532,9 +532,7 @@ export class TextureAtlas implements ITextureAtlas { // Underline style/stroke this._tmpCtx.beginPath(); const xLeft = padding; - const yTop = restrictToCellHeight ? - Math.ceil(padding + this._config.deviceCharHeight) - yOffset - lineWidth * 2 : - Math.ceil(padding + this._config.deviceCharHeight) - yOffset; + const yTop = Math.ceil(padding + this._config.deviceCharHeight) - yOffset - (restrictToCellHeight ? lineWidth * 2 : 0); const yMid = yTop + lineWidth; const yBot = yTop + lineWidth * 2; From 0a4151fbfa8a12653f7f9c56455a13bc3c569941 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 11 Aug 2023 09:52:15 -0700 Subject: [PATCH 08/22] Add nvmrc using node 16 node-pty doesn't seem to work 18 yet and VS Code has moved over to be based on 18 so that's my main (don't ask me why it's working there but not in this repo) --- .nvmrc | 1 + 1 file changed, 1 insertion(+) create mode 100644 .nvmrc diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 00000000..b6a7d89c --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +16 From 136e2f2afebace27f56435dae368577dce9a20f9 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 11 Aug 2023 13:07:04 -0700 Subject: [PATCH 09/22] Improve API wording --- typings/xterm.d.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 23aab9b5..345c100b 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -60,7 +60,7 @@ declare module 'xterm' { cursorBlink?: boolean; /** - * The style of the cursor. + * The style of the cursor when the terminal is focused. */ cursorStyle?: 'block' | 'underline' | 'bar'; @@ -70,7 +70,7 @@ declare module 'xterm' { cursorWidth?: number; /** - * The style of the inactive cursor. + * The style of the cursor when the terminal is not focused. */ cursorInactiveStyle?: 'outline' | 'block' | 'bar' | 'underline' | 'none'; From 6f6cc6a686dc91284212ee28820072c7ab2422ab Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 11 Aug 2023 13:09:31 -0700 Subject: [PATCH 10/22] Remove string mapping, improve types, standardize on 'outline' --- .../src/RectangleRenderer.ts | 6 +++--- addons/xterm-addon-webgl/src/Types.d.ts | 4 +++- addons/xterm-addon-webgl/src/WebglRenderer.ts | 21 ++----------------- 3 files changed, 8 insertions(+), 23 deletions(-) diff --git a/addons/xterm-addon-webgl/src/RectangleRenderer.ts b/addons/xterm-addon-webgl/src/RectangleRenderer.ts index 4706b442..b2e5d0fa 100644 --- a/addons/xterm-addon-webgl/src/RectangleRenderer.ts +++ b/addons/xterm-addon-webgl/src/RectangleRenderer.ts @@ -255,7 +255,7 @@ export class RectangleRenderer extends Disposable { let offset: number; let rectangleCount = 0; - if (cursor.style === 'bar' || cursor.style === 'blur') { + if (cursor.style === 'bar' || cursor.style === 'outline') { // Left edge offset = rectangleCount++ * INDICES_PER_RECTANGLE; this._addRectangleFloat( @@ -268,7 +268,7 @@ export class RectangleRenderer extends Disposable { this._cursorFloat ); } - if (cursor.style === 'underline' || cursor.style === 'blur') { + if (cursor.style === 'underline' || cursor.style === 'outline') { // Bottom edge offset = rectangleCount++ * INDICES_PER_RECTANGLE; this._addRectangleFloat( @@ -281,7 +281,7 @@ export class RectangleRenderer extends Disposable { this._cursorFloat ); } - if (cursor.style === 'blur') { + if (cursor.style === 'outline') { // Top edge offset = rectangleCount++ * INDICES_PER_RECTANGLE; this._addRectangleFloat( diff --git a/addons/xterm-addon-webgl/src/Types.d.ts b/addons/xterm-addon-webgl/src/Types.d.ts index 73b15bc7..3eb3b300 100644 --- a/addons/xterm-addon-webgl/src/Types.d.ts +++ b/addons/xterm-addon-webgl/src/Types.d.ts @@ -16,11 +16,13 @@ export interface ICursorRenderModel { x: number; y: number; width: number; - style: string; + style: CursorStyle; cursorWidth: number; dpr: number; } +export type CursorStyle = 'outline' | 'block' | 'bar' | 'underline' | 'none'; + export interface IWebGL2RenderingContext extends WebGLRenderingContext { vertexAttribDivisor(index: number, divisor: number): void; createVertexArray(): IWebGLVertexArrayObject; diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index 9bd30cb5..5e5e3aaa 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -455,14 +455,13 @@ export class WebglRenderer extends Disposable implements IRenderer { // Override colors for cursor cell if (isCursorVisible && row === cursorY) { - const inactiveCursorStyle = this._getInactiveCursorStyle(terminal.options.cursorInactiveStyle); if (x === cursorX) { this._model.cursor = { x: cursorX, y: this._terminal.buffer.active.cursorY, width: cell.getWidth(), style: this._coreBrowserService.isFocused ? - (terminal.options.cursorStyle || 'block') : inactiveCursorStyle, + (terminal.options.cursorStyle || 'block') : terminal.options.cursorInactiveStyle, cursorWidth: terminal.options.cursorWidth, dpr: this._devicePixelRatio }; @@ -472,7 +471,7 @@ export class WebglRenderer extends Disposable implements IRenderer { ((this._coreBrowserService.isFocused && (terminal.options.cursorStyle || 'block') === 'block') || (this._coreBrowserService.isFocused === false && - inactiveCursorStyle === 'block'))) { + terminal.options.cursorInactiveStyle === 'block'))) { this._cellColorResolver.result.fg = Attributes.CM_RGB | (this._themeService.colors.cursorAccent.rgba >> 8 & Attributes.RGB_MASK); this._cellColorResolver.result.bg = @@ -603,22 +602,6 @@ export class WebglRenderer extends Disposable implements IRenderer { const cursorY = this._terminal.buffer.active.cursorY; this._onRequestRedraw.fire({ start: cursorY, end: cursorY }); } - - private _getInactiveCursorStyle(cursorInactiveStyle: 'outline' | 'block' | 'bar' | 'underline' | 'none'): string { - if (cursorInactiveStyle === 'outline') { - return 'blur'; - } - if (cursorInactiveStyle === 'block') { - return 'block'; - } - if (cursorInactiveStyle === 'bar') { - return 'bar'; - } - if (cursorInactiveStyle === 'underline') { - return 'underline'; - } - return ''; - } } // TODO: Share impl with core From fb7349bc876bff1af46405f96cc02a3d61f0b524 Mon Sep 17 00:00:00 2001 From: dennnnny Date: Sat, 12 Aug 2023 08:39:39 +0800 Subject: [PATCH 11/22] Fix transparent foreground color --- src/browser/renderer/shared/TextureAtlas.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/browser/renderer/shared/TextureAtlas.ts b/src/browser/renderer/shared/TextureAtlas.ts index b73e7009..c50bda23 100644 --- a/src/browser/renderer/shared/TextureAtlas.ts +++ b/src/browser/renderer/shared/TextureAtlas.ts @@ -277,7 +277,7 @@ export class TextureAtlas implements ITextureAtlas { } private _getBackgroundColor(bgColorMode: number, bgColor: number, inverse: boolean, dim: boolean): IColor { - if (this._config.allowTransparency) { + if (this._config.allowTransparency || (inverse && !color.isOpaque(this._config.colors.foreground))) { // The background color might have some transparency, so we need to render it as fully // transparent in the atlas. Otherwise we'd end up drawing the transparent background twice // around the anti-aliased edges of the glyph, and it would look too dark. From 530f758905c7fc4b41404bf1906cba5bc22b692f Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 12 Aug 2023 09:04:41 -0700 Subject: [PATCH 12/22] Restrict comments to 100 characters This is a rule I try enforce in PR but it's a bit of a hassle, this will give a lint warning when it happens resulting in: - Yellow underline in supported editors when eslint is installed - Compilation will succeed since it's a warning not an error - CI will fail as it does not allow lint warnings Special case comments are ignored which includes @vt comments, table comments and commented out code. --- .eslintrc.json | 13 +++- .../xterm-addon-image/src/IIPHeaderParser.ts | 3 +- addons/xterm-addon-search/src/SearchAddon.ts | 17 +++-- .../src/SerializeAddon.ts | 6 +- src/browser/Linkifier2.ts | 3 +- src/browser/OscLinkProvider.ts | 3 +- src/browser/Viewport.ts | 4 +- src/browser/renderer/dom/DomRenderer.ts | 3 +- .../renderer/dom/DomRendererRowFactory.ts | 3 +- src/browser/renderer/shared/Constants.ts | 4 +- src/browser/renderer/shared/CustomGlyphs.ts | 3 +- src/browser/renderer/shared/TextureAtlas.ts | 7 +- src/browser/services/CharSizeService.ts | 3 +- src/common/Color.ts | 3 +- src/common/InputHandler.ts | 72 ++++++++++--------- src/common/buffer/Buffer.ts | 4 +- src/common/input/Keyboard.ts | 3 +- src/common/input/TextDecoder.ts | 4 +- src/common/input/WriteBuffer.ts | 7 +- src/common/parser/EscapeSequenceParser.ts | 12 ++-- src/common/services/BufferService.ts | 3 +- 21 files changed, 108 insertions(+), 72 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index b8b3b4e7..9b675856 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -159,6 +159,16 @@ "jsdoc/check-param-names": 1, "jsdoc/no-multi-asterisks": 1, "keyword-spacing": "warn", + "max-len": [ + "warn", + { + "code": 1000, // Don't enforce for code + "comments": 100, + "ignoreTrailingComments": true, + "ignoreUrls": true, + "ignorePattern": "^ *((?(//|\\*) @vt)|(?\\* \\| )|(?// ))" + } + ], "new-parens": "warn", "no-duplicate-imports": "warn", "no-else-return": [ @@ -225,7 +235,8 @@ { "files": ["**/*.test.ts"], "rules": { - "object-curly-spacing": "off" + "object-curly-spacing": "off", + "max-len": "off" } } ] diff --git a/addons/xterm-addon-image/src/IIPHeaderParser.ts b/addons/xterm-addon-image/src/IIPHeaderParser.ts index 21dc1937..05a350c1 100644 --- a/addons/xterm-addon-image/src/IIPHeaderParser.ts +++ b/addons/xterm-addon-image/src/IIPHeaderParser.ts @@ -23,7 +23,8 @@ export interface IHeaderFields { height?: string; // Optional, defaults to 1 respecting aspect ratio (width takes precedence). preserveAspectRatio?: number; - // Optional, defaults to 0. If set to 1, the file will be displayed inline, else downloaded (download not supported). + // Optional, defaults to 0. If set to 1, the file will be displayed inline, else downloaded + // (download not supported). inline?: number; } diff --git a/addons/xterm-addon-search/src/SearchAddon.ts b/addons/xterm-addon-search/src/SearchAddon.ts index a029fc24..8568cba8 100644 --- a/addons/xterm-addon-search/src/SearchAddon.ts +++ b/addons/xterm-addon-search/src/SearchAddon.ts @@ -440,7 +440,8 @@ export class SearchAddon extends Disposable implements ITerminalAddon { } /** - * A found substring is a whole word if it doesn't have an alphanumeric character directly adjacent to it. + * A found substring is a whole word if it doesn't have an alphanumeric character directly + * adjacent to it. * @param searchIndex starting indext of the potential whole word substring * @param line entire string in which the potential whole word was found * @param term the substring that starts at searchIndex @@ -451,14 +452,15 @@ export class SearchAddon extends Disposable implements ITerminalAddon { } /** - * Searches a line for a search term. Takes the provided terminal line and searches the text line, which may contain - * subsequent terminal lines if the text is wrapped. If the provided line number is part of a wrapped text line that - * started on an earlier line then it is skipped since it will be properly searched when the terminal line that the - * text starts on is searched. + * Searches a line for a search term. Takes the provided terminal line and searches the text line, + * which may contain subsequent terminal lines if the text is wrapped. If the provided line number + * is part of a wrapped text line that started on an earlier line then it is skipped since it will + * be properly searched when the terminal line that the text starts on is searched. * @param term The search term. * @param searchPosition The position to start the search. * @param searchOptions Search options. - * @param isReverseSearch Whether the search should start from the right side of the terminal and search to the left. + * @param isReverseSearch Whether the search should start from the right side of the terminal and + * search to the left. * @returns The search result if it was found. */ protected _findInLine(term: string, searchPosition: ISearchPosition, searchOptions: ISearchOptions = {}, isReverseSearch: boolean = false): ISearchResult | undefined { @@ -526,7 +528,8 @@ export class SearchAddon extends Disposable implements ITerminalAddon { return; } - // Adjust the row number and search index if needed since a "line" of text can span multiple rows + // Adjust the row number and search index if needed since a "line" of text can span multiple + // rows let startRowOffset = 0; while (startRowOffset < offsets.length - 1 && resultIndex >= offsets[startRowOffset + 1]) { startRowOffset++; diff --git a/addons/xterm-addon-serialize/src/SerializeAddon.ts b/addons/xterm-addon-serialize/src/SerializeAddon.ts index ceee48bf..737d529a 100644 --- a/addons/xterm-addon-serialize/src/SerializeAddon.ts +++ b/addons/xterm-addon-serialize/src/SerializeAddon.ts @@ -131,7 +131,8 @@ class StringSerializeHandler extends BaseSerializeHandler { private _thisRowLastSecondChar: IBufferCell = this._buffer.getNullCell(); private _nextRowFirstChar: IBufferCell = this._buffer.getNullCell(); protected _rowEnd(row: number, isLastRow: boolean): void { - // if there is colorful empty cell at line end, whe must pad it back, or the the color block will missing + // if there is colorful empty cell at line end, whe must pad it back, or the the color block + // will missing if (this._nullCellCount > 0 && !equalBg(this._cursorStyle, this._backgroundCell)) { // use clear right to set background. this._currentRow += `\u001b[${this._nullCellCount}X`; @@ -292,7 +293,8 @@ class StringSerializeHandler extends BaseSerializeHandler { const sgrSeq = this._diffStyle(cell, this._cursorStyle); - // the empty cell style is only assumed to be changed when background changed, because foreground is always 0. + // the empty cell style is only assumed to be changed when background changed, because + // foreground is always 0. const styleChanged = isEmptyCell ? !equalBg(this._cursorStyle, cell) : sgrSeq.length > 0; /** diff --git a/src/browser/Linkifier2.ts b/src/browser/Linkifier2.ts index c1d1d282..64467426 100644 --- a/src/browser/Linkifier2.ts +++ b/src/browser/Linkifier2.ts @@ -109,7 +109,8 @@ export class Linkifier2 extends Disposable implements ILinkifier2 { } private _handleHover(position: IBufferCellPosition): void { - // TODO: This currently does not cache link provider results across wrapped lines, activeLine should be something like `activeRange: {startY, endY}` + // TODO: This currently does not cache link provider results across wrapped lines, activeLine + // should be something like `activeRange: {startY, endY}` // Check if we need to clear the link if (this._activeLine !== position.y || this._wasResized) { this._clearCurrentLink(); diff --git a/src/browser/OscLinkProvider.ts b/src/browser/OscLinkProvider.ts index 648ffa44..fee1ae7c 100644 --- a/src/browser/OscLinkProvider.ts +++ b/src/browser/OscLinkProvider.ts @@ -104,7 +104,8 @@ export class OscLinkProvider implements ILinkProvider { } } - // TODO: Handle fetching and returning other link ranges to underline other links with the same id + // TODO: Handle fetching and returning other link ranges to underline other links with the same + // id callback(result); } } diff --git a/src/browser/Viewport.ts b/src/browser/Viewport.ts index 48e97341..7c1ae945 100644 --- a/src/browser/Viewport.ts +++ b/src/browser/Viewport.ts @@ -65,8 +65,8 @@ export class Viewport extends Disposable implements IViewport { super(); // Measure the width of the scrollbar. If it is 0 we can assume it's an OSX overlay scrollbar. - // 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 + // 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.register(addDisposableDomListener(this._viewportElement, 'scroll', this._handleScroll.bind(this))); diff --git a/src/browser/renderer/dom/DomRenderer.ts b/src/browser/renderer/dom/DomRenderer.ts index e607ae3e..1449aee6 100644 --- a/src/browser/renderer/dom/DomRenderer.ts +++ b/src/browser/renderer/dom/DomRenderer.ts @@ -389,7 +389,8 @@ export class DomRenderer extends Disposable implements IRenderer { public clear(): void { for (const e of this._rowElements) { /** - * NOTE: This used to be `e.innerText = '';` but that doesn't work when using `jsdom` and `@testing-library/react` + * NOTE: This used to be `e.innerText = '';` but that doesn't work when using `jsdom` and + * `@testing-library/react` * * references: * - https://github.com/testing-library/react-testing-library/issues/1146 diff --git a/src/browser/renderer/dom/DomRendererRowFactory.ts b/src/browser/renderer/dom/DomRendererRowFactory.ts index 2faa9cb7..8a428c96 100644 --- a/src/browser/renderer/dom/DomRendererRowFactory.ts +++ b/src/browser/renderer/dom/DomRendererRowFactory.ts @@ -285,7 +285,8 @@ export class DomRendererRowFactory { classes.push(RowCss.STRIKETHROUGH_CLASS); } - // apply link hover underline late, effectively overrides any previous text-decoration settings + // apply link hover underline late, effectively overrides any previous text-decoration + // settings if (isLinkHover) { charElement.style.textDecoration = 'underline'; } diff --git a/src/browser/renderer/shared/Constants.ts b/src/browser/renderer/shared/Constants.ts index ac698b85..b5105ec7 100644 --- a/src/browser/renderer/shared/Constants.ts +++ b/src/browser/renderer/shared/Constants.ts @@ -8,7 +8,7 @@ import { isFirefox, isLegacyEdge } from 'common/Platform'; export const INVERTED_DEFAULT_COLOR = 257; export const DIM_OPACITY = 0.5; -// The text baseline is set conditionally by browser. Using 'ideographic' for Firefox or Legacy Edge would -// result in truncated text (Issue 3353). Using 'bottom' for Chrome would result in slightly +// The text baseline is set conditionally by browser. Using 'ideographic' for Firefox or Legacy Edge +// would result in truncated text (Issue 3353). Using 'bottom' for Chrome would result in slightly // unaligned Powerline fonts (PR 3356#issuecomment-850928179). export const TEXT_BASELINE: CanvasTextBaseline = isFirefox || isLegacyEdge ? 'bottom' : 'ideographic'; diff --git a/src/browser/renderer/shared/CustomGlyphs.ts b/src/browser/renderer/shared/CustomGlyphs.ts index fc36e96c..cface515 100644 --- a/src/browser/renderer/shared/CustomGlyphs.ts +++ b/src/browser/renderer/shared/CustomGlyphs.ts @@ -349,7 +349,8 @@ const enum VectorType { * not been patched with powerline characters and also to get pixel perfect rendering as rendering * issues can occur around AA/SPAA. * - * The line variants draw beyond the cell and get clipped to ensure the end of the line is not visible. + * The line variants draw beyond the cell and get clipped to ensure the end of the line is not + * visible. * * Original symbols defined in https://github.com/powerline/fontpatcher */ diff --git a/src/browser/renderer/shared/TextureAtlas.ts b/src/browser/renderer/shared/TextureAtlas.ts index b73e7009..d73d4ba6 100644 --- a/src/browser/renderer/shared/TextureAtlas.ts +++ b/src/browser/renderer/shared/TextureAtlas.ts @@ -466,8 +466,8 @@ export class TextureAtlas implements ITextureAtlas { // draw the background const backgroundColor = this._getBackgroundColor(bgColorMode, bgColor, inverse, dim); - // Use a 'copy' composite operation to clear any existing glyph out of _tmpCtxWithAlpha, regardless of - // transparency in backgroundColor + // Use a 'copy' composite operation to clear any existing glyph out of _tmpCtxWithAlpha, + // regardless of transparency in backgroundColor this._tmpCtx.globalCompositeOperation = 'copy'; this._tmpCtx.fillStyle = backgroundColor.css; this._tmpCtx.fillRect(0, 0, this._tmpCanvas.width, this._tmpCanvas.height); @@ -559,7 +559,8 @@ export class TextureAtlas implements ITextureAtlas { const clipRegion = new Path2D(); clipRegion.rect(xChLeft, yTop, this._config.deviceCellWidth, yBot - yTop); this._tmpCtx.clip(clipRegion); - // Start 1/2 cell before and end 1/2 cells after to ensure a smooth curve with other cells + // Start 1/2 cell before and end 1/2 cells after to ensure a smooth curve with other + // cells this._tmpCtx.moveTo(xChLeft - this._config.deviceCellWidth / 2, yMid); this._tmpCtx.bezierCurveTo( xChLeft - this._config.deviceCellWidth / 2, yCurlyTop, diff --git a/src/browser/services/CharSizeService.ts b/src/browser/services/CharSizeService.ts index 8e2a7019..614b9b30 100644 --- a/src/browser/services/CharSizeService.ts +++ b/src/browser/services/CharSizeService.ts @@ -60,7 +60,8 @@ interface IMeasureResult { height: number; } -// TODO: For supporting browsers we should also provide a CanvasCharDimensionsProvider that uses ctx.measureText +// TODO: For supporting browsers we should also provide a CanvasCharDimensionsProvider that uses +// ctx.measureText class DomMeasureStrategy implements IMeasureStrategy { private _result: IMeasureResult = { width: 0, height: 0 }; private _measureElement: HTMLElement; diff --git a/src/common/Color.ts b/src/common/Color.ts index 0d7bffe0..108d72f3 100644 --- a/src/common/Color.ts +++ b/src/common/Color.ts @@ -106,7 +106,8 @@ export namespace color { } /** - * Helper functions where the source type is "css" (string: '#rgb', '#rgba', '#rrggbb', '#rrggbbaa'). + * Helper functions where the source type is "css" (string: '#rgb', '#rgba', '#rrggbb', + * '#rrggbbaa'). */ export namespace css { let $ctx: CanvasRenderingContext2D | undefined; diff --git a/src/common/InputHandler.ts b/src/common/InputHandler.ts index 46db6110..bbc9256c 100644 --- a/src/common/InputHandler.ts +++ b/src/common/InputHandler.ts @@ -416,11 +416,11 @@ export class InputHandler extends Disposable implements IInputHandler { * - undefined (void): * all handlers were sync, no stack save, continue normally with next chunk * - Promise\: - * execution stopped at async handler, stack saved, continue with - * same chunk and the promise resolve value as `promiseResult` until the method returns `undefined` + * execution stopped at async handler, stack saved, continue with same chunk and the promise + * resolve value as `promiseResult` until the method returns `undefined` * - * Note: This method should only be called by `Terminal.write` to ensure correct execution order and - * proper continuation of async parser handlers. + * Note: This method should only be called by `Terminal.write` to ensure correct execution order + * and proper continuation of async parser handlers. */ public parse(data: string | Uint8Array, promiseResult?: boolean): void | Promise { let result: void | Promise; @@ -787,12 +787,13 @@ export class InputHandler extends Disposable implements IInputHandler { // find last taken cell - last cell can have 3 different states: // - hasContent(true) + hasWidth(1): narrow char - we are done // - hasWidth(0): second part of wide char - we are done - // - hasContent(false) + hasWidth(1): empty cell due to early wrapping wide char, go one cell further back + // - hasContent(false) + hasWidth(1): empty cell due to early wrapping wide char, go one + // cell further back const line = this._activeBuffer.lines.get(this._activeBuffer.ybase + this._activeBuffer.y)!; if (line.hasWidth(this._activeBuffer.x) && !line.hasContent(this._activeBuffer.x)) { this._activeBuffer.x--; - // We do this only once, since width=1 + hasContent=false currently happens only once before - // early wrapping of a wide char. + // We do this only once, since width=1 + hasContent=false currently happens only once + // before early wrapping of a wide char. // This needs to be fixed once we support graphemes taking more than 2 cells. } } @@ -1147,8 +1148,8 @@ export class InputHandler extends Disposable implements IInputHandler { } /** - * Helper method to reset cells in a terminal row. - * The cell gets replaced with the eraseChar of the terminal and the isWrapped property is set to false. + * Helper method to reset cells in a terminal row. The cell gets replaced with the eraseChar of + * the terminal and the isWrapped property is set to false. * @param y row index */ private _resetBufferLine(y: number, respectProtect: boolean = false): void { @@ -1345,8 +1346,9 @@ export class InputHandler extends Disposable implements IInputHandler { * Insert Ps (Blank) Character(s) (default = 1) (ICH). * * @vt: #Y CSI ICH "Insert Characters" "CSI Ps @" "Insert `Ps` (blank) characters (default = 1)." - * The ICH sequence inserts `Ps` blank characters. The cursor remains at the beginning of the blank characters. - * Text between the cursor and right margin moves to the right. Characters moved past the right margin are lost. + * The ICH sequence inserts `Ps` blank characters. The cursor remains at the beginning of the + * blank characters. Text between the cursor and right margin moves to the right. Characters moved + * past the right margin are lost. * * * FIXME: check against xterm - should not work outside of scroll margins (see VT520 manual) @@ -1371,8 +1373,9 @@ export class InputHandler extends Disposable implements IInputHandler { * Delete Ps Character(s) (default = 1) (DCH). * * @vt: #Y CSI DCH "Delete Character" "CSI Ps P" "Delete `Ps` characters (default=1)." - * As characters are deleted, the remaining characters between the cursor and right margin move to the left. - * Character attributes move with the characters. The terminal adds blank characters at the right margin. + * As characters are deleted, the remaining characters between the cursor and right margin move to + * the left. Character attributes move with the characters. The terminal adds blank characters at + * the right margin. * * * FIXME: check against xterm - should not work outside of scroll margins (see VT520 manual) @@ -1497,9 +1500,9 @@ export class InputHandler extends Disposable implements IInputHandler { * Insert Ps Column(s) (default = 1) (DECIC), VT420 and up. * * @vt: #Y CSI DECIC "Insert Columns" "CSI Ps ' }" "Insert `Ps` columns at cursor position." - * DECIC inserts `Ps` times blank columns at the cursor position for all lines with the scroll margins, - * moving content to the right. Content at the right margin is lost. - * DECIC has no effect outside the scrolling margins. + * DECIC inserts `Ps` times blank columns at the cursor position for all lines with the scroll + * margins, moving content to the right. Content at the right margin is lost. DECIC has no effect + * outside the scrolling margins. */ public insertColumns(params: IParams): boolean { if (this._activeBuffer.y > this._activeBuffer.scrollBottom || this._activeBuffer.y < this._activeBuffer.scrollTop) { @@ -1580,12 +1583,12 @@ export class InputHandler extends Disposable implements IInputHandler { * - wrap around is respected * - any valid sequence resets the carried forward char * - * Note: To get reset on a valid sequence working correctly without much runtime penalty, - * the preceding codepoint is stored on the parser in `this.print` and reset during `parser.parse`. + * Note: To get reset on a valid sequence working correctly without much runtime penalty, the + * preceding codepoint is stored on the parser in `this.print` and reset during `parser.parse`. * * @vt: #Y CSI REP "Repeat Preceding Character" "CSI Ps b" "Repeat preceding character `Ps` times (default=1)." - * REP repeats the previous character `Ps` times advancing the cursor, also wrapping if DECAWM is set. - * REP has no effect if the sequence does not follow a printable ASCII character + * REP repeats the previous character `Ps` times advancing the cursor, also wrapping if DECAWM is + * set. REP has no effect if the sequence does not follow a printable ASCII character * (NOOP for any other sequence in between or NON ASCII characters). */ public repeatPrecedingCharacter(params: IParams): boolean { @@ -2446,7 +2449,8 @@ export class InputHandler extends Disposable implements IInputHandler { * | 5 | Dashed underline. | #Y | * | other | Single underline. Same as `SGR 4 m`. | #Y | * - * Extended colors are supported for foreground (Ps=38), background (Ps=48) and underline (Ps=58) as follows: + * Extended colors are supported for foreground (Ps=38), background (Ps=48) and underline (Ps=58) + * as follows: * * | Ps + 1 | Meaning | Support | * | ------ | ------------------------------------------------------------- | ------- | @@ -2656,8 +2660,9 @@ export class InputHandler extends Disposable implements IInputHandler { * http://vt100.net/docs/vt220-rm/table4-10.html * * @vt: #Y CSI DECSTR "Soft Terminal Reset" "CSI ! p" "Reset several terminal attributes to initial state." - * There are two terminal reset sequences - RIS and DECSTR. While RIS performs almost a full terminal bootstrap, - * DECSTR only resets certain attributes. For most needs DECSTR should be sufficient. + * There are two terminal reset sequences - RIS and DECSTR. While RIS performs almost a full + * terminal bootstrap, DECSTR only resets certain attributes. For most needs DECSTR should be + * sufficient. * * The following terminal attributes are reset to default values: * - IRM is reset (dafault = false) @@ -2882,7 +2887,8 @@ export class InputHandler extends Disposable implements IInputHandler { * Icon name is not supported. For Window Title see below. * * @vt: #Y OSC 2 "Set Windows Title" "OSC 2 ; Pt BEL" "Set window title." - * xterm.js does not manipulate the title directly, instead exposes changes via the event `Terminal.onTitleChange`. + * xterm.js does not manipulate the title directly, instead exposes changes via the event + * `Terminal.onTitleChange`. */ public setTitle(data: string): boolean { this._windowTitle = data; @@ -2903,9 +2909,10 @@ export class InputHandler extends Disposable implements IInputHandler { * OSC 4; ; ST (set ANSI color to ) * * @vt: #Y OSC 4 "Set ANSI color" "OSC 4 ; c ; spec BEL" "Change color number `c` to the color specified by `spec`." - * `c` is the color index between 0 and 255. The color format of `spec` is derived from `XParseColor` (see OSC 10 for supported formats). - * There may be multipe `c ; spec` pairs present in the same instruction. - * If `spec` contains `?` the terminal returns a sequence with the currently set color. + * `c` is the color index between 0 and 255. The color format of `spec` is derived from + * `XParseColor` (see OSC 10 for supported formats). There may be multipe `c ; spec` pairs present + * in the same instruction. If `spec` contains `?` the terminal returns a sequence with the + * currently set color. */ public setOrReportIndexedColor(data: string): boolean { const event: IColorEvent = []; @@ -2945,9 +2952,10 @@ export class InputHandler extends Disposable implements IInputHandler { * * @vt: #Y OSC 8 "Create hyperlink" "OSC 8 ; params ; uri BEL" "Create a hyperlink to `uri` using `params`." * `uri` is a hyperlink starting with `http://`, `https://`, `ftp://`, `file://` or `mailto://`. `params` is an - * optional list of key=value assignments, separated by the : character. Example: `id=xyz123:foo=bar:baz=quux`. - * Currently only the id key is defined. Cells that share the same ID and URI share hover feedback. - * Use `OSC 8 ; ; BEL` to finish the current hyperlink. + * optional list of key=value assignments, separated by the : character. + * Example: `id=xyz123:foo=bar:baz=quux`. + * Currently only the id key is defined. Cells that share the same ID and URI share hover + * feedback. Use `OSC 8 ; ; BEL` to finish the current hyperlink. */ public setHyperlink(data: string): boolean { const args = data.split(';'); @@ -3334,8 +3342,8 @@ export class InputHandler extends Disposable implements IInputHandler { * Response: DECRPSS (https://vt100.net/docs/vt510-rm/DECRPSS.html) * * @vt: #P[Limited support, see below.] DCS DECRQSS "Request Selection or Setting" "DCS $ q Pt ST" "Request several terminal settings." - * Response is in the form `ESC P 1 $ r Pt ST` for valid requests, where `Pt` contains the corresponding CSI string, - * `ESC P 0 ST` for invalid requests. + * Response is in the form `ESC P 1 $ r Pt ST` for valid requests, where `Pt` contains the + * corresponding CSI string, `ESC P 0 ST` for invalid requests. * * Supported requests and responses: * diff --git a/src/common/buffer/Buffer.ts b/src/common/buffer/Buffer.ts index f32ce385..a82a4569 100644 --- a/src/common/buffer/Buffer.ts +++ b/src/common/buffer/Buffer.ts @@ -270,8 +270,8 @@ export class Buffer implements IBuffer { private _batchedMemoryCleanup(): boolean { let normalRun = true; if (this._memoryCleanupPosition >= this.lines.length) { - // cleanup made it once through all lines, thus rescan in loop below to also catch shifted lines, - // which should finish rather quick if there are no more cleanups pending + // cleanup made it once through all lines, thus rescan in loop below to also catch shifted + // lines, which should finish rather quick if there are no more cleanups pending this._memoryCleanupPosition = 0; normalRun = false; } diff --git a/src/common/input/Keyboard.ts b/src/common/input/Keyboard.ts index 225c914e..9420a974 100644 --- a/src/common/input/Keyboard.ts +++ b/src/common/input/Keyboard.ts @@ -380,7 +380,8 @@ export function evaluateKeyboardEvent( result.type = KeyboardResultType.SELECT_ALL; } } else if (ev.key && !ev.ctrlKey && !ev.altKey && !ev.metaKey && ev.keyCode >= 48 && ev.key.length === 1) { - // Include only keys that that result in a _single_ character; don't include num lock, volume up, etc. + // Include only keys that that result in a _single_ character; don't include num lock, + // volume up, etc. result.key = ev.key; } else if (ev.key && ev.ctrlKey) { if (ev.key === '_') { // ^_ diff --git a/src/common/input/TextDecoder.ts b/src/common/input/TextDecoder.ts index 715e9197..7ec9c7cd 100644 --- a/src/common/input/TextDecoder.ts +++ b/src/common/input/TextDecoder.ts @@ -28,8 +28,8 @@ export function utf32ToString(data: Uint32Array, start: number = 0, end: number for (let i = start; i < end; ++i) { let codepoint = data[i]; if (codepoint > 0xFFFF) { - // JS strings are encoded as UTF16, thus a non BMP codepoint gets converted into a surrogate pair - // conversion rules: + // JS strings are encoded as UTF16, thus a non BMP codepoint gets converted into a surrogate + // pair conversion rules: // - subtract 0x10000 from code point, leaving a 20 bit number // - add high 10 bits to 0xD800 --> first surrogate // - add low 10 bits to 0xDC00 --> second surrogate diff --git a/src/common/input/WriteBuffer.ts b/src/common/input/WriteBuffer.ts index 68dbc6f7..6c3dbf64 100644 --- a/src/common/input/WriteBuffer.ts +++ b/src/common/input/WriteBuffer.ts @@ -181,9 +181,10 @@ export class WriteBuffer extends Disposable { /** * If a promise takes long to resolve, we should schedule continuation behind setTimeout. - * This might already be too late, if our .then enters really late (executor + prev thens took very long). - * This cannot be solved here for the handler itself (it is the handlers responsibility to slice hard work), - * but we can at least schedule a screen update as we gain control. + * This might already be too late, if our .then enters really late (executor + prev thens + * took very long). This cannot be solved here for the handler itself (it is the handlers + * responsibility to slice hard work), but we can at least schedule a screen update as we + * gain control. */ const continuation: (r: boolean) => void = (r: boolean) => Date.now() - startTime >= WRITE_TIMEOUT_MS ? setTimeout(() => this._innerWrite(0, r)) diff --git a/src/common/parser/EscapeSequenceParser.ts b/src/common/parser/EscapeSequenceParser.ts index 2f3ddd92..de206322 100644 --- a/src/common/parser/EscapeSequenceParser.ts +++ b/src/common/parser/EscapeSequenceParser.ts @@ -532,9 +532,9 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP } else { if (promiseResult === undefined || this._parseStack.state === ParserStackType.FAIL) { /** - * Reject further parsing on improper continuation after pausing. - * This is a really bad condition with screwed up execution order and prolly messed up - * terminal state, therefore we exit hard with an exception and reject any further parsing. + * Reject further parsing on improper continuation after pausing. This is a really bad + * condition with screwed up execution order and prolly messed up terminal state, + * therefore we exit hard with an exception and reject any further parsing. * * Note: With `Terminal.write` usage this exception should never occur, as the top level * calls are guaranteed to handle async conditions properly. If you ever encounter this @@ -542,9 +542,9 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP * `InputHandler.parse` or `EscapeSequenceParser.parse` synchronously without waiting for * continuation of a running async handler. * - * It is possible to get rid of this error by calling `reset`. But dont rely on that, - * as the pending async handler still might mess up the terminal later. Instead fix the faulty - * async handling, so this error will not be thrown anymore. + * It is possible to get rid of this error by calling `reset`. But dont rely on that, as + * the pending async handler still might mess up the terminal later. Instead fix the + * faulty async handling, so this error will not be thrown anymore. */ this._parseStack.state = ParserStackType.FAIL; throw new Error('improper continuation due to previous async handler, giving up parsing'); diff --git a/src/common/services/BufferService.ts b/src/common/services/BufferService.ts index 7b02cb7d..7d8e2846 100644 --- a/src/common/services/BufferService.ts +++ b/src/common/services/BufferService.ts @@ -43,7 +43,8 @@ export class BufferService extends Disposable implements IBufferService { this.cols = cols; this.rows = rows; this.buffers.resize(cols, rows); - // TODO: This doesn't fire when scrollback changes - add a resize event to BufferSet and forward event + // TODO: This doesn't fire when scrollback changes - add a resize event to BufferSet and forward + // event this._onResize.fire({ cols, rows }); } From 1f4fe0a8c0baaab53b180db19cb275210b78e9be Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 12 Aug 2023 09:33:35 -0700 Subject: [PATCH 13/22] Lint API .d.ts files Similar to #4668, this brings linting to the API files with a cut down set of rules. One of the bigger ones is comment length is restricted to 80, this was done as opposed to 100 for regular code to reduce the chance of wrapping or API going off screen regardless of resolution or window/browser size --- .eslintrc.json.typings | 104 +++++++++++++++++ azure-pipelines.yml | 12 +- package.json | 1 + typings/xterm-headless.d.ts | 92 +++++++++------ typings/xterm.d.ts | 227 +++++++++++++++++++----------------- 5 files changed, 289 insertions(+), 147 deletions(-) create mode 100644 .eslintrc.json.typings diff --git a/.eslintrc.json.typings b/.eslintrc.json.typings new file mode 100644 index 00000000..5af028d3 --- /dev/null +++ b/.eslintrc.json.typings @@ -0,0 +1,104 @@ +{ + "env": { + "browser": true, + "es6": true, + "node": true + }, + "parser": "@typescript-eslint/parser", + "plugins": [ + "@typescript-eslint", + "jsdoc" + ], + "rules": { + "no-extra-semi": "error", + "@typescript-eslint/array-type": [ + "warn", + { + "default": "array", + "readonly": "generic" + } + ], + "@typescript-eslint/explicit-function-return-type": [ + "warn", + { + "allowExpressions": true + } + ], + "@typescript-eslint/indent": [ + "warn", + 2 + ], + "@typescript-eslint/member-delimiter-style": [ + "warn", + { + "multiline": { + "delimiter": "semi", + "requireLast": true + }, + "singleline": { + "delimiter": "comma", + "requireLast": false + } + } + ], + "@typescript-eslint/naming-convention": [ + "warn", + { "selector": "typeLike", "format": ["PascalCase"] }, + { "selector": "interface", "format": ["PascalCase"], "prefix": ["I"] } + ], + "@typescript-eslint/prefer-namespace-keyword": "warn", + "@typescript-eslint/type-annotation-spacing": "warn", + "@typescript-eslint/quotes": [ + "warn", + "single", + { "allowTemplateLiterals": true } + ], + "@typescript-eslint/semi": [ + "warn", + "always" + ], + "comma-dangle": [ + "warn", + { + "objects": "never", + "arrays": "never", + "functions": "never" + } + ], + "curly": [ + "warn", + "multi-line" + ], + "eol-last": "warn", + "eqeqeq": [ + "warn", + "always" + ], + "jsdoc/check-alignment": 1, + "jsdoc/check-param-names": 1, + "keyword-spacing": "warn", + "max-len": [ + "warn", + { + "code": 1000, // Don't enforce for code + "comments": 80, + "ignoreUrls": true, + "ignorePattern": "^ *(?\\* Ps=)" + } + ], + "no-irregular-whitespace": "warn", + "no-trailing-spaces": "warn", + "object-curly-spacing": [ + "warn", + "always" + ], + "spaced-comment": [ + "warn", + "always", + { + "markers": ["/"], + "exceptions": ["-"] + } + ] + } +} diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 354f72c2..f96a0e8a 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -33,7 +33,9 @@ jobs: exit $EXIT_CODE displayName: 'Unit tests' - script: yarn lint - displayName: 'Lint' + displayName: 'Lint code' + - script: yarn lint-api + displayName: 'Lint API' - task: PublishCodeCoverageResults@1 inputs: codeCoverageTool: Cobertura @@ -58,7 +60,9 @@ jobs: - script: yarn test-unit --forbid-only displayName: 'Unit tests' - script: yarn lint - displayName: 'Lint' + displayName: 'Lint code' + - script: yarn lint-api + displayName: 'Lint API' - job: Windows pool: @@ -78,7 +82,9 @@ jobs: - script: yarn test-unit --forbid-only displayName: 'Unit tests' - script: yarn lint - displayName: 'Lint' + displayName: 'Lint code' + - script: yarn lint-api + displayName: 'Lint API' - job: Linux_IntegrationTests pool: diff --git a/package.json b/package.json index b315d347..5fc4e558 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "start": "node demo/start", "start-debug": "node --inspect-brk demo/start", "lint": "eslint -c .eslintrc.json --max-warnings 0 --ext .ts src/ addons/", + "lint-api": "eslint --no-eslintrc -c .eslintrc.json.typings --max-warnings 0 --no-ignore --ext .d.ts typings/", "test": "npm run test-unit", "posttest": "npm run lint", "test-api": "npm run test-api-chromium", diff --git a/typings/xterm-headless.d.ts b/typings/xterm-headless.d.ts index 719975bf..7beb848a 100644 --- a/typings/xterm-headless.d.ts +++ b/typings/xterm-headless.d.ts @@ -19,7 +19,8 @@ declare module 'xterm-headless' { export interface ITerminalOptions { /** * Whether to allow the use of proposed API. When false, any usage of APIs - * marked as experimental/proposed will throw an error. The default is false. + * marked as experimental/proposed will throw an error. The default is + * false. */ allowProposedApi?: boolean; @@ -63,13 +64,13 @@ declare module 'xterm-headless' { cursorWidth?: number; /** - * Whether to draw custom glyphs for block element and box drawing characters instead of using - * the font. This should typically result in better rendering with continuous lines, even when - * line height and letter spacing is used. Note that this doesn't work with the DOM renderer - * which renders all characters using the font. The default is true. + * Whether to draw custom glyphs for block element and box drawing + * characters instead of using the font. This should typically result in + * better rendering with continuous lines, even when line height and letter + * spacing is used. Note that this doesn't work with the DOM renderer which + * renders all characters using the font. The default is true. */ customGlyphs?: boolean; - /** * Whether input should be disabled. */ @@ -217,9 +218,9 @@ declare module 'xterm-headless' { windowsPty?: IWindowsPty; /** - * A string containing all characters that are considered word separated by the - * double click to select work logic. - */ + * A string containing all characters that are considered word separated by + * the double click to select work logic. + */ wordSeparator?: string; /** @@ -314,23 +315,23 @@ declare module 'xterm-headless' { */ export interface ILogger { /** - * Log a debug message, this will only be called if {@link ITerminalOptions.logLevel} is set to - * debug. + * Log a debug message, this will only be called if + * {@link ITerminalOptions.logLevel} is set to debug. */ debug(message: string, ...args: any[]): void; /** - * Log a debug message, this will only be called if {@link ITerminalOptions.logLevel} is set to - * info or below. + * Log a debug message, this will only be called if + * {@link ITerminalOptions.logLevel} is set to info or below. */ info(message: string, ...args: any[]): void; /** - * Log a debug message, this will only be called if {@link ITerminalOptions.logLevel} is set to - * warn or below. + * Log a debug message, this will only be called if + * {@link ITerminalOptions.logLevel} is set to warn or below. */ warn(message: string, ...args: any[]): void; /** - * Log a debug message, this will only be called if {@link ITerminalOptions.logLevel} is set to - * error or below. + * Log a debug message, this will only be called if + * {@link ITerminalOptions.logLevel} is set to error or below. */ error(message: string | Error, ...args: any[]): void; } @@ -361,23 +362,26 @@ declare module 'xterm-headless' { */ readonly id: number; - /** - * Whether this marker is disposed. - */ - readonly isDisposed: boolean; - /** * The actual line index in the buffer at this point in time. This is set to * -1 if the marker has been disposed. */ readonly line: number; + } + /** + * Represents a disposable that tracks is disposed state. + */ + export interface IDisposableWithEvent extends IDisposable { /** - * Event listener to get notified when the marker gets disposed. Automatic disposal - * might happen for a marker, that got invalidated by scrolling out or removal of - * a line from the buffer. + * Event listener to get notified when this gets disposed. */ onDispose: IEvent; + + /** + * Whether this is disposed. + */ + readonly isDisposed: boolean; } /** @@ -397,7 +401,8 @@ declare module 'xterm-headless' { } /** - * Enable various window manipulation and report features (CSI Ps ; Ps ; Ps t). + * Enable various window manipulation and report features + * (`CSI Ps ; Ps ; Ps t`). * * Most settings have no default implementation, as they heavily rely on * the embedding environment. @@ -417,10 +422,10 @@ declare module 'xterm-headless' { * * Note on security: * Most features are meant to deal with some information of the host machine - * where the terminal runs on. This is seen as a security risk possibly leaking - * sensitive data of the host to the program in the terminal. Therefore all options - * (even those without a default implementation) are guarded by the boolean flag - * and disabled by default. + * where the terminal runs on. This is seen as a security risk possibly + * leaking sensitive data of the host to the program in the terminal. + * Therefore all options (even those without a default implementation) are + * guarded by the boolean flag and disabled by default. */ export interface IWindowOptions { /** @@ -601,23 +606,36 @@ declare module 'xterm-headless' { readonly modes: IModes; /** - * Gets or sets the terminal options. This supports setting multiple options. + * Gets or sets the terminal options. This supports setting multiple + * options. * * @example Get a single option - * ```typescript + * ```ts * console.log(terminal.options.fontSize); * ``` * - * @example Set a single option - * ```typescript + * @example Set a single option: + * ```ts * terminal.options.fontSize = 12; * ``` + * Note that for options that are object, a new object must be used in order + * to take effect as a reference comparison will be done: + * ```ts + * const newValue = terminal.options.theme; + * newValue.background = '#000000'; + * + * // This won't work + * terminal.options.theme = newValue; + * + * // This will work + * terminal.options.theme = { ...newValue }; + * ``` * * @example Set multiple options - * ```typescript + * ```ts * terminal.options = { * fontSize: 12, - * fontFamily: 'Courier New', + * fontFamily: 'Courier New' * }; * ``` */ @@ -1282,6 +1300,6 @@ declare module 'xterm-headless' { /** * Auto-Wrap Mode (DECAWM): `CSI ? 7 h` */ - readonly wraparoundMode: boolean + readonly wraparoundMode: boolean; } } diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 345c100b..dccc18f6 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -26,7 +26,8 @@ declare module 'xterm' { export interface ITerminalOptions { /** * Whether to allow the use of proposed API. When false, any usage of APIs - * marked as experimental/proposed will throw an error. The default is false. + * marked as experimental/proposed will throw an error. The default is + * false. */ allowProposedApi?: boolean; @@ -75,10 +76,11 @@ declare module 'xterm' { cursorInactiveStyle?: 'outline' | 'block' | 'bar' | 'underline' | 'none'; /** - * Whether to draw custom glyphs for block element and box drawing characters instead of using - * the font. This should typically result in better rendering with continuous lines, even when - * line height and letter spacing is used. Note that this doesn't work with the DOM renderer - * which renders all characters using the font. The default is true. + * Whether to draw custom glyphs for block element and box drawing + * characters instead of using the font. This should typically result in + * better rendering with continuous lines, even when line height and letter + * spacing is used. Note that this doesn't work with the DOM renderer which + * renders all characters using the font. The default is true. */ customGlyphs?: boolean; @@ -280,9 +282,9 @@ declare module 'xterm' { windowsPty?: IWindowsPty; /** - * A string containing all characters that are considered word separated by the - * double click to select work logic. - */ + * A string containing all characters that are considered word separated by + * the double click to select work logic. + */ wordSeparator?: string; /** @@ -330,7 +332,10 @@ declare module 'xterm' { selectionBackground?: string; /** The selection foreground color */ selectionForeground?: string; - /** The selection background color when the terminal does not have focus (can be transparent) */ + /** + * The selection background color when the terminal does not have focus (can + * be transparent) + */ selectionInactiveBackground?: string; /** ANSI black (eg. `\x1b[30m`) */ black?: string; @@ -387,23 +392,23 @@ declare module 'xterm' { */ export interface ILogger { /** - * Log a debug message, this will only be called if {@link ITerminalOptions.logLevel} is set to - * debug. + * Log a debug message, this will only be called if + * {@link ITerminalOptions.logLevel} is set to debug. */ debug(message: string, ...args: any[]): void; /** - * Log a debug message, this will only be called if {@link ITerminalOptions.logLevel} is set to - * info or below. + * Log a debug message, this will only be called if + * {@link ITerminalOptions.logLevel} is set to info or below. */ info(message: string, ...args: any[]): void; /** - * Log a debug message, this will only be called if {@link ITerminalOptions.logLevel} is set to - * warn or below. + * Log a debug message, this will only be called if + * {@link ITerminalOptions.logLevel} is set to warn or below. */ warn(message: string, ...args: any[]): void; /** - * Log a debug message, this will only be called if {@link ITerminalOptions.logLevel} is set to - * error or below. + * Log a debug message, this will only be called if + * {@link ITerminalOptions.logLevel} is set to error or below. */ error(message: string | Error, ...args: any[]): void; } @@ -443,8 +448,6 @@ declare module 'xterm' { /** * Represents a disposable that tracks is disposed state. - * @param onDispose event listener and - * @param isDisposed property. */ export interface IDisposableWithEvent extends IDisposable { /** @@ -459,7 +462,8 @@ declare module 'xterm' { } /** - * Represents a decoration in the terminal that is associated with a particular marker and DOM element. + * Represents a decoration in the terminal that is associated with a + * particular marker and DOM element. */ export interface IDecoration extends IDisposableWithEvent { /* @@ -482,9 +486,9 @@ declare module 'xterm' { element: HTMLElement | undefined; /** - * The options for the overview ruler that can be updated. - * This will only take effect when {@link IDecorationOptions.overviewRulerOptions} - * were provided initially. + * The options for the overview ruler that can be updated. This will only + * take effect when {@link IDecorationOptions.overviewRulerOptions} were + * provided initially. */ options: Pick; } @@ -531,24 +535,26 @@ declare module 'xterm' { readonly height?: number; /** - * The background color of the cell(s). When 2 decorations both set the foreground color the - * last registered decoration will be used. Only the `#RRGGBB` format is supported. + * The background color of the cell(s). When 2 decorations both set the + * foreground color the last registered decoration will be used. Only the + * `#RRGGBB` format is supported. */ readonly backgroundColor?: string; /** - * The foreground color of the cell(s). When 2 decorations both set the foreground color the - * last registered decoration will be used. Only the `#RRGGBB` format is supported. + * The foreground color of the cell(s). When 2 decorations both set the + * foreground color the last registered decoration will be used. Only the + * `#RRGGBB` format is supported. */ readonly foregroundColor?: string; /** * What layer to render the decoration at when {@link backgroundColor} or - * {@link foregroundColor} are used. `'bottom'` will render under the selection, `'top`' will - * render above the selection\*. + * {@link foregroundColor} are used. `'bottom'` will render under the + * selection, `'top`' will render above the selection\*. * - * *\* The selection will render on top regardless of layer on the canvas renderer due to how - * it renders selection separately.* + * *\* The selection will render on top regardless of layer on the canvas + * renderer due to how it renders selection separately.* */ readonly layer?: 'bottom' | 'top'; @@ -559,7 +565,7 @@ declare module 'xterm' { * @param color The color of the decoration. * @param position The position of the decoration. */ - overviewRulerOptions?: IDecorationOverviewRulerOptions + overviewRulerOptions?: IDecorationOverviewRulerOptions; } /** @@ -579,7 +585,8 @@ declare module 'xterm' { } /** - * Enable various window manipulation and report features (CSI Ps ; Ps ; Ps t). + * Enable various window manipulation and report features + * (`CSI Ps ; Ps ; Ps t`). * * Most settings have no default implementation, as they heavily rely on * the embedding environment. @@ -599,10 +606,10 @@ declare module 'xterm' { * * Note on security: * Most features are meant to deal with some information of the host machine - * where the terminal runs on. This is seen as a security risk possibly leaking - * sensitive data of the host to the program in the terminal. Therefore all options - * (even those without a default implementation) are guarded by the boolean flag - * and disabled by default. + * where the terminal runs on. This is seen as a security risk possibly + * leaking sensitive data of the host to the program in the terminal. + * Therefore all options (even those without a default implementation) are + * guarded by the boolean flag and disabled by default. */ export interface IWindowOptions { /** @@ -870,9 +877,9 @@ declare module 'xterm' { onData: IEvent; /** - * Adds an event listener for when a key is pressed. The event value contains the - * string that will be sent in the data event as well as the DOM event that - * triggered it. + * Adds an event listener for when a key is pressed. The event value + * contains the string that will be sent in the data event as well as the + * DOM event that triggered it. * @returns an `IDisposable` to stop listening. */ onKey: IEvent<{ key: string, domEvent: KeyboardEvent }>; @@ -1040,10 +1047,11 @@ declare module 'xterm' { /** * (EXPERIMENTAL) Adds a decoration to the terminal using - * @param decorationOptions, which takes a marker and an optional anchor, - * width, height, and x offset from the anchor. Returns the decoration or - * undefined if the alt buffer is active or the marker has already been disposed of. - * @throws when options include a negative x offset. + * @param decorationOptions, which takes a marker and an optional anchor, + * width, height, and x offset from the anchor. Returns the decoration or + * undefined if the alt buffer is active or the marker has already been + * disposed of. + * @throws when options include a negative x offset. */ registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined; @@ -1149,7 +1157,8 @@ declare module 'xterm' { writeln(data: string | Uint8Array, callback?: () => void): void; /** - * Writes text to the terminal, performing the necessary transformations for pasted text. + * Writes text to the terminal, performing the necessary transformations for + * pasted text. * @param data The text to write to the terminal. */ paste(data: string): void; @@ -1163,10 +1172,10 @@ declare module 'xterm' { refresh(start: number, end: number): void; /** - * Clears the texture atlas of the canvas renderer if it's active. Doing this will force a - * redraw of all glyphs which can workaround issues causing the texture to become corrupt, for - * example Chromium/Nvidia has an issue where the texture gets messed up when resuming the OS - * from sleep. + * Clears the texture atlas of the canvas renderer if it's active. Doing + * this will force a redraw of all glyphs which can workaround issues + * causing the texture to become corrupt, for example Chromium/Nvidia has an + * issue where the texture gets messed up when resuming the OS from sleep. */ clearTextureAtlas(): void; @@ -1237,32 +1246,34 @@ declare module 'xterm' { * @param text The text of the link. * @param range The buffer range of the link. */ - activate(event: MouseEvent, text: string, range: IBufferRange): void; + activate(event: MouseEvent, text: string, range: IBufferRange): void; - /** - * Called when the mouse hovers the link. To use this to create a DOM-based hover tooltip, - * create the hover element within `Terminal.element` and add the `xterm-hover` class to it, - * that will cause mouse events to not fall through and activate other links. - * @param event The mouse event triggering the callback. - * @param text The text of the link. - * @param range The buffer range of the link. - */ - hover?(event: MouseEvent, text: string, range: IBufferRange): void; + /** + * Called when the mouse hovers the link. To use this to create a DOM-based + * hover tooltip, create the hover element within `Terminal.element` and + * add the `xterm-hover` class to it, that will cause mouse events to not + * fall through and activate other links. + * @param event The mouse event triggering the callback. + * @param text The text of the link. + * @param range The buffer range of the link. + */ + hover?(event: MouseEvent, text: string, range: IBufferRange): void; - /** - * Called when the mouse leaves the link. - * @param event The mouse event triggering the callback. - * @param text The text of the link. - * @param range The buffer range of the link. - */ - leave?(event: MouseEvent, text: string, range: IBufferRange): void; + /** + * Called when the mouse leaves the link. + * @param event The mouse event triggering the callback. + * @param text The text of the link. + * @param range The buffer range of the link. + */ + leave?(event: MouseEvent, text: string, range: IBufferRange): void; - /** - * Whether to receive non-HTTP URLs from LinkProvider. When false, any usage of non-HTTP URLs - * will be ignored. Enabling this option without proper protection in `activate` function - * may cause security issues such as XSS. - */ - allowNonHttpProtocols?: boolean; + /** + * Whether to receive non-HTTP URLs from LinkProvider. When false, any + * usage of non-HTTP URLs will be ignored. Enabling this option without + * proper protection in `activate` function may cause security issues such + * as XSS. + */ + allowNonHttpProtocols?: boolean; } /** @@ -1294,9 +1305,9 @@ declare module 'xterm' { text: string; /** - * What link decorations to show when hovering the link, this property is tracked and changes - * made after the link is provided will trigger changes. If not set, all decroations will be - * enabled. + * What link decorations to show when hovering the link, this property is + * tracked and changes made after the link is provided will trigger changes. + * If not set, all decroations will be enabled. */ decorations?: ILinkDecorations; @@ -1308,9 +1319,10 @@ declare module 'xterm' { activate(event: MouseEvent, text: string): void; /** - * Called when the mouse hovers the link. To use this to create a DOM-based hover tooltip, - * create the hover element within `Terminal.element` and add the `xterm-hover` class to it, - * that will cause mouse events to not fall through and activate other links. + * Called when the mouse hovers the link. To use this to create a DOM-based + * hover tooltip, create the hover element within `Terminal.element` and add + * the `xterm-hover` class to it, that will cause mouse events to not fall + * through and activate other links. * @param event The mouse event triggering the callback. * @param text The text of the link. */ @@ -1434,7 +1446,8 @@ declare module 'xterm' { export interface IBufferElementProvider { /** - * Provides a document fragment or HTMLElement containing the buffer elements. + * Provides a document fragment or HTMLElement containing the buffer + * elements. */ provideBufferElements(): DocumentFragment | HTMLElement; } @@ -1681,43 +1694,43 @@ declare module 'xterm' { export interface IParser { /** * Adds a handler for CSI escape sequences. - * @param id Specifies the function identifier under which the callback - * gets registered, e.g. {final: 'm'} for SGR. + * @param id Specifies the function identifier under which the callback gets + * registered, e.g. {final: 'm'} for SGR. * @param callback The function to handle the sequence. The callback is - * called with the numerical params. If the sequence has subparams the - * array will contain subarrays with their numercial values. - * Return `true` if the sequence was handled, `false` if the parser should try - * a previous handler. The most recently added handler is tried first. + * called with the numerical params. If the sequence has subparams the array + * will contain subarrays with their numercial values. Return `true` if the + * sequence was handled, `false` if the parser should try a previous + * handler. The most recently added handler is tried first. * @returns An IDisposable you can call to remove this handler. */ registerCsiHandler(id: IFunctionIdentifier, callback: (params: (number | number[])[]) => boolean | Promise): IDisposable; /** * Adds a handler for DCS escape sequences. - * @param id Specifies the function identifier under which the callback - * gets registered, e.g. {intermediates: '$' final: 'q'} for DECRQSS. + * @param id Specifies the function identifier under which the callback gets + * registered, e.g. {intermediates: '$' final: 'q'} for DECRQSS. * @param callback The function to handle the sequence. Note that the * function will only be called once if the sequence finished sucessfully. * There is currently no way to intercept smaller data chunks, data chunks - * will be stored up until the sequence is finished. Since DCS sequences - * are not limited by the amount of data this might impose a problem for - * big payloads. Currently xterm.js limits DCS payload to 10 MB - * which should give enough room for most use cases. - * The function gets the payload and numerical parameters as arguments. - * Return `true` if the sequence was handled, `false` if the parser should try - * a previous handler. The most recently added handler is tried first. + * will be stored up until the sequence is finished. Since DCS sequences are + * not limited by the amount of data this might impose a problem for big + * payloads. Currently xterm.js limits DCS payload to 10 MB which should + * give enough room for most use cases. The function gets the payload and + * numerical parameters as arguments. Return `true` if the sequence was + * handled, `false` if the parser should try a previous handler. The most + * recently added handler is tried first. * @returns An IDisposable you can call to remove this handler. */ registerDcsHandler(id: IFunctionIdentifier, callback: (data: string, param: (number | number[])[]) => boolean | Promise): IDisposable; /** * Adds a handler for ESC escape sequences. - * @param id Specifies the function identifier under which the callback - * gets registered, e.g. {intermediates: '%' final: 'G'} for - * default charset selection. + * @param id Specifies the function identifier under which the callback gets + * registered, e.g. {intermediates: '%' final: 'G'} for default charset + * selection. * @param callback The function to handle the sequence. - * Return `true` if the sequence was handled, `false` if the parser should try - * a previous handler. The most recently added handler is tried first. + * Return `true` if the sequence was handled, `false` if the parser should + * try a previous handler. The most recently added handler is tried first. * @returns An IDisposable you can call to remove this handler. */ registerEscHandler(id: IFunctionIdentifier, handler: () => boolean | Promise): IDisposable; @@ -1728,13 +1741,13 @@ declare module 'xterm' { * @param callback The function to handle the sequence. Note that the * function will only be called once if the sequence finished sucessfully. * There is currently no way to intercept smaller data chunks, data chunks - * will be stored up until the sequence is finished. Since OSC sequences - * are not limited by the amount of data this might impose a problem for - * big payloads. Currently xterm.js limits OSC payload to 10 MB - * which should give enough room for most use cases. - * The callback is called with OSC data string. - * Return `true` if the sequence was handled, `false` if the parser should try - * a previous handler. The most recently added handler is tried first. + * will be stored up until the sequence is finished. Since OSC sequences are + * not limited by the amount of data this might impose a problem for big + * payloads. Currently xterm.js limits OSC payload to 10 MB which should + * give enough room for most use cases. The callback is called with OSC data + * string. Return `true` if the sequence was handled, `false` if the parser + * should try a previous handler. The most recently added handler is tried + * first. * @returns An IDisposable you can call to remove this handler. */ registerOscHandler(ident: number, callback: (data: string) => boolean | Promise): IDisposable; @@ -1820,6 +1833,6 @@ declare module 'xterm' { /** * Auto-Wrap Mode (DECAWM): `CSI ? 7 h` */ - readonly wraparoundMode: boolean + readonly wraparoundMode: boolean; } } From bef87403db69831529bd7c55d494367c3298cdab Mon Sep 17 00:00:00 2001 From: dennnnny Date: Sun, 13 Aug 2023 17:52:49 +0800 Subject: [PATCH 14/22] Fix force transparent foreground colors to be opaque --- src/browser/renderer/shared/TextureAtlas.ts | 2 +- src/browser/services/ThemeService.ts | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/browser/renderer/shared/TextureAtlas.ts b/src/browser/renderer/shared/TextureAtlas.ts index c50bda23..b73e7009 100644 --- a/src/browser/renderer/shared/TextureAtlas.ts +++ b/src/browser/renderer/shared/TextureAtlas.ts @@ -277,7 +277,7 @@ export class TextureAtlas implements ITextureAtlas { } private _getBackgroundColor(bgColorMode: number, bgColor: number, inverse: boolean, dim: boolean): IColor { - if (this._config.allowTransparency || (inverse && !color.isOpaque(this._config.colors.foreground))) { + if (this._config.allowTransparency) { // The background color might have some transparency, so we need to render it as fully // transparent in the atlas. Otherwise we'd end up drawing the transparent background twice // around the anti-aliased edges of the glyph, and it would look too dark. diff --git a/src/browser/services/ThemeService.ts b/src/browser/services/ThemeService.ts index 2411ce85..91a72f5e 100644 --- a/src/browser/services/ThemeService.ts +++ b/src/browser/services/ThemeService.ts @@ -122,7 +122,8 @@ export class ThemeService extends Disposable implements IThemeService { */ private _setTheme(theme: ITheme = {}): void { const colors = this._colors; - colors.foreground = parseColor(theme.foreground, DEFAULT_FOREGROUND); + colors.foreground = color.opaque(parseColor(theme.foreground, DEFAULT_FOREGROUND)); + console.warn("xterm.js is not fully support foreground colors with transparent, so it will the foreground colors alpha channel to 255.") colors.background = parseColor(theme.background, DEFAULT_BACKGROUND); colors.cursor = parseColor(theme.cursor, DEFAULT_CURSOR); colors.cursorAccent = parseColor(theme.cursorAccent, DEFAULT_CURSOR_ACCENT); From 70f6fa2bc91e27500900d8633d7f1279feb26e24 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sun, 13 Aug 2023 10:42:45 -0700 Subject: [PATCH 15/22] Fix SortedList.values iteration and general dec lifecycle fixes Fixes #4652 --- addons/xterm-addon-search/src/SearchAddon.ts | 12 +++--------- .../decorations/BufferDecorationRenderer.ts | 4 ++++ src/common/SortedList.test.ts | 14 ++++++++++++++ src/common/SortedList.ts | 3 ++- src/common/buffer/Marker.ts | 4 ++-- src/common/services/DecorationService.ts | 14 +------------- 6 files changed, 26 insertions(+), 25 deletions(-) diff --git a/addons/xterm-addon-search/src/SearchAddon.ts b/addons/xterm-addon-search/src/SearchAddon.ts index 8568cba8..cbde1a1b 100644 --- a/addons/xterm-addon-search/src/SearchAddon.ts +++ b/addons/xterm-addon-search/src/SearchAddon.ts @@ -68,8 +68,6 @@ export class SearchAddon extends Disposable implements ITerminalAddon { private _highlightDecorations: IHighlight[] = []; private _selectedDecoration: IHighlight | undefined; private _highlightLimit: number; - private _onDataDisposable: IDisposable | undefined; - private _onResizeDisposable: IDisposable | undefined; private _lastSearchOptions: ISearchOptions | undefined; private _highlightTimeout: number | undefined; /** @@ -93,13 +91,9 @@ export class SearchAddon extends Disposable implements ITerminalAddon { public activate(terminal: Terminal): void { this._terminal = terminal; - this._onDataDisposable = this.register(this._terminal.onWriteParsed(() => this._updateMatches())); - this._onResizeDisposable = this.register(this._terminal.onResize(() => this._updateMatches())); - this.register(toDisposable(() => { - this.clearDecorations(); - this._onDataDisposable?.dispose(); - this._onResizeDisposable?.dispose(); - })); + this.register(this._terminal.onWriteParsed(() => this._updateMatches())); + this.register(this._terminal.onResize(() => this._updateMatches())); + this.register(toDisposable(() => this.clearDecorations())); } private _updateMatches(): void { diff --git a/src/browser/decorations/BufferDecorationRenderer.ts b/src/browser/decorations/BufferDecorationRenderer.ts index fb77ce9c..ba4f6ca8 100644 --- a/src/browser/decorations/BufferDecorationRenderer.ts +++ b/src/browser/decorations/BufferDecorationRenderer.ts @@ -103,6 +103,10 @@ export class BufferDecorationRenderer extends Disposable { decoration.element = element; this._decorationElements.set(decoration, element); this._container.appendChild(element); + decoration.onDispose(() => { + this._decorationElements.delete(decoration); + element!.remove(); + }); } element.style.top = `${line * this._renderService.dimensions.css.cell.height}px`; element.style.display = this._altBufferIsActive ? 'none' : 'block'; diff --git a/src/common/SortedList.test.ts b/src/common/SortedList.test.ts index ecafdb8f..d2e01ba8 100644 --- a/src/common/SortedList.test.ts +++ b/src/common/SortedList.test.ts @@ -104,4 +104,18 @@ describe('SortedList', () => { { key: 10 } ]); }); + describe('values', () => { + it('should iterate correctly when list items change during iteration', () => { + list.insert(1); + list.insert(2); + list.insert(3); + list.insert(4); + const visited: number[] = []; + for (const item of list.values()) { + visited.push(item); + list.delete(item); + } + deepStrictEqual(visited, [1, 2, 3, 4]); + }); + }); }); diff --git a/src/common/SortedList.ts b/src/common/SortedList.ts index c5e7bc36..c3250091 100644 --- a/src/common/SortedList.ts +++ b/src/common/SortedList.ts @@ -89,7 +89,8 @@ export class SortedList { } public values(): IterableIterator { - return this._array.values(); + // Duplicate the array to avoid issues when _array changes while iterating + return [...this._array].values(); } private _search(key: number): number { diff --git a/src/common/buffer/Marker.ts b/src/common/buffer/Marker.ts index 0629e26a..96df6366 100644 --- a/src/common/buffer/Marker.ts +++ b/src/common/buffer/Marker.ts @@ -11,9 +11,9 @@ export class Marker implements IMarker { private static _nextId = 1; public isDisposed: boolean = false; - private _disposables: IDisposable[] = []; + private readonly _disposables: IDisposable[] = []; - private _id: number = Marker._nextId++; + private readonly _id: number = Marker._nextId++; public get id(): number { return this._id; } private readonly _onDispose = this.register(new EventEmitter()); diff --git a/src/common/services/DecorationService.ts b/src/common/services/DecorationService.ts index 1ccc712c..ed96fb35 100644 --- a/src/common/services/DecorationService.ts +++ b/src/common/services/DecorationService.ts @@ -35,12 +35,7 @@ export class DecorationService extends Disposable implements IDecorationService constructor() { super(); - this.register(toDisposable(() => { - for (const d of this._decorations.values()) { - this._onDecorationRemoved.fire(d); - } - this.reset(); - })); + this.register(toDisposable(() => this.reset())); } public registerDecoration(options: IDecorationOptions): IDecoration | undefined { @@ -92,13 +87,6 @@ export class DecorationService extends Disposable implements IDecorationService } }); } - - public dispose(): void { - for (const d of this._decorations.values()) { - this._onDecorationRemoved.fire(d); - } - this.reset(); - } } class Decoration extends Disposable implements IInternalDecoration { From 1cef30cca0171eaae7e2de231587360431af2ee1 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sun, 13 Aug 2023 11:27:57 -0700 Subject: [PATCH 16/22] Enforce half minimum contrast ratio for dim text Provided MCR is not too high, this ensures dim text differs from normal text and has a reasonable difference in contrast. Fixes #4262 --- .../test/WebglRenderer.api.ts | 73 ++++++++++++++++++- src/browser/Types.d.ts | 3 + .../renderer/dom/DomRendererRowFactory.ts | 18 ++++- src/browser/renderer/shared/CharAtlasUtils.ts | 3 +- src/browser/renderer/shared/TextureAtlas.ts | 24 ++++-- src/browser/services/ThemeService.ts | 8 +- 6 files changed, 113 insertions(+), 16 deletions(-) diff --git a/addons/xterm-addon-webgl/test/WebglRenderer.api.ts b/addons/xterm-addon-webgl/test/WebglRenderer.api.ts index cca37e0a..8d84b200 100644 --- a/addons/xterm-addon-webgl/test/WebglRenderer.api.ts +++ b/addons/xterm-addon-webgl/test/WebglRenderer.api.ts @@ -789,7 +789,7 @@ describe('WebGL Renderer Integration Tests', async () => { await pollFor(page, () => getCellColor(6, 2), [0xad, 0x7f, 0xa8, 255]); await pollFor(page, () => getCellColor(7, 2), [0x34, 0xe2, 0xe2, 255]); await pollFor(page, () => getCellColor(8, 2), [0xee, 0xee, 0xec, 255]); - // Setting and check for minimum contrast values, note that these are note + // Setting and check for minimum contrast values, note that these are not // exact to the contrast ratio, if the increase luminance algorithm // changes then these will probably fail await page.evaluate(`window.term.options.minimumContrastRatio = 10;`); @@ -858,7 +858,7 @@ describe('WebGL Renderer Integration Tests', async () => { await pollFor(page, () => getCellColor(6, 2), [0xad, 0x7f, 0xa8, 255]); await pollFor(page, () => getCellColor(7, 2), [0x34, 0xe2, 0xe2, 255]); await pollFor(page, () => getCellColor(8, 2), [0xee, 0xee, 0xec, 255]); - // Setting and check for minimum contrast values, note that these are note + // Setting and check for minimum contrast values, note that these are not // exact to the contrast ratio, if the increase luminance algorithm // changes then these will probably fail await page.evaluate(`window.term.options.minimumContrastRatio = 10;`); @@ -879,6 +879,75 @@ describe('WebGL Renderer Integration Tests', async () => { await pollFor(page, () => getCellColor(7, 2), [13, 67, 67, 255]); await pollFor(page, () => getCellColor(8, 2), [64, 64, 64, 255]); }); + + itWebgl('should enforce half the contrast for dim cells', async () => { + const theme: ITheme = { + background: '#ffffff', + black: '#2e3436', + red: '#cc0000', + green: '#4e9a06', + yellow: '#c4a000', + blue: '#3465a4', + magenta: '#75507b', + cyan: '#06989a', + white: '#d3d7cf', + brightBlack: '#555753', + brightRed: '#ef2929', + brightGreen: '#8ae234', + brightYellow: '#fce94f', + brightBlue: '#729fcf', + brightMagenta: '#ad7fa8', + brightCyan: '#34e2e2', + brightWhite: '#eeeeec' + }; + await page.evaluate(` + window.term.options.theme = ${JSON.stringify(theme)}; + window.term.options.minimumContrastRatio = 1; + `); + // Block characters ignore block elements so a different char is used here + await writeSync(page, + '\\x1b[2m' + + `\\x1b[30m■\\x1b[31m■\\x1b[32m■\\x1b[33m■\\x1b[34m■\\x1b[35m■\\x1b[36m■\\x1b[37m■\\r\\n` + + `\\x1b[90m■\\x1b[91m■\\x1b[92m■\\x1b[93m■\\x1b[94m■\\x1b[95m■\\x1b[96m■\\x1b[97m■` + ); + // Validate before minimumContrastRatio is applied + await pollFor(page, () => getCellColor(1, 1), [Math.floor((255 + 0x2e) / 2), Math.floor((255 + 0x34) / 2), Math.floor((255 + 0x36) / 2), 255]); + await pollFor(page, () => getCellColor(2, 1), [Math.floor((255 + 0xcc) / 2), Math.floor((255 + 0x00) / 2), Math.floor((255 + 0x00) / 2), 255]); + await pollFor(page, () => getCellColor(3, 1), [Math.floor((255 + 0x4e) / 2), Math.floor((255 + 0x9a) / 2), Math.floor((255 + 0x06) / 2), 255]); + await pollFor(page, () => getCellColor(4, 1), [Math.floor((255 + 0xc4) / 2), Math.floor((255 + 0xa0) / 2), Math.floor((255 + 0x00) / 2), 255]); + await pollFor(page, () => getCellColor(5, 1), [Math.floor((255 + 0x34) / 2), Math.floor((255 + 0x65) / 2), Math.floor((255 + 0xa4) / 2), 255]); + await pollFor(page, () => getCellColor(6, 1), [Math.floor((255 + 0x75) / 2), Math.floor((255 + 0x50) / 2), Math.floor((255 + 0x7b) / 2), 255]); + await pollFor(page, () => getCellColor(7, 1), [Math.floor((255 + 0x06) / 2), Math.floor((255 + 0x98) / 2), Math.floor((255 + 0x9a) / 2), 255]); + await pollFor(page, () => getCellColor(8, 1), [Math.floor((255 + 0xd3) / 2), Math.floor((255 + 0xd7) / 2), Math.floor((255 + 0xcf) / 2), 255]); + await pollFor(page, () => getCellColor(1, 2), [Math.floor((255 + 0x55) / 2), Math.floor((255 + 0x57) / 2), Math.floor((255 + 0x53) / 2), 255]); + await pollFor(page, () => getCellColor(2, 2), [Math.floor((255 + 0xef) / 2), Math.floor((255 + 0x29) / 2), Math.floor((255 + 0x29) / 2), 255]); + await pollFor(page, () => getCellColor(3, 2), [Math.floor((255 + 0x8a) / 2), Math.floor((255 + 0xe2) / 2), Math.floor((255 + 0x34) / 2), 255]); + await pollFor(page, () => getCellColor(4, 2), [Math.floor((255 + 0xfc) / 2), Math.floor((255 + 0xe9) / 2), Math.floor((255 + 0x4f) / 2), 255]); + await pollFor(page, () => getCellColor(5, 2), [Math.floor((255 + 0x72) / 2), Math.floor((255 + 0x9f) / 2), Math.floor((255 + 0xcf) / 2), 255]); + await pollFor(page, () => getCellColor(6, 2), [Math.floor((255 + 0xad) / 2), Math.floor((255 + 0x7f) / 2), Math.floor((255 + 0xa8) / 2), 255]); + await pollFor(page, () => getCellColor(7, 2), [Math.floor((255 + 0x34) / 2), Math.floor((255 + 0xe2) / 2), Math.floor((255 + 0xe2) / 2), 255]); + await pollFor(page, () => getCellColor(8, 2), [Math.floor((255 + 0xee) / 2), Math.floor((255 + 0xee) / 2), Math.floor((255 + 0xec) / 2), 255]); + // Setting and check for minimum contrast values, note that these are not + // exact to the contrast ratio, if the increase luminance algorithm + // changes then these will probably fail + await page.evaluate(`window.term.options.minimumContrastRatio = 10;`); + await pollFor(page, () => getCellColor(1, 1), [150, 153, 154, 255]); + await pollFor(page, () => getCellColor(2, 1), [229, 127, 127, 255]); + await pollFor(page, () => getCellColor(3, 1), [63, 124, 4, 255]); + await pollFor(page, () => getCellColor(4, 1), [127, 104, 0, 255]); + await pollFor(page, () => getCellColor(5, 1), [153, 178, 209, 255]); + await pollFor(page, () => getCellColor(6, 1), [186, 167, 189, 255]); + await pollFor(page, () => getCellColor(7, 1), [4, 122, 124, 255]); + await pollFor(page, () => getCellColor(8, 1), [110, 112, 108, 255]); + await pollFor(page, () => getCellColor(1, 2), [170, 171, 169, 255]); + await pollFor(page, () => getCellColor(2, 2), [215, 36, 36, 255]); + await pollFor(page, () => getCellColor(3, 2), [72, 117, 25, 255]); + await pollFor(page, () => getCellColor(4, 2), [117, 109, 36, 255]); + await pollFor(page, () => getCellColor(5, 2), [72, 103, 135, 255]); + await pollFor(page, () => getCellColor(6, 2), [125, 91, 121, 255]); + await pollFor(page, () => getCellColor(7, 2), [25, 117, 117, 255]); + await pollFor(page, () => getCellColor(8, 2), [111, 111, 110, 255]); + }); }); describe('selectionBackground', async () => { diff --git a/src/browser/Types.d.ts b/src/browser/Types.d.ts index 5337a169..d32b6099 100644 --- a/src/browser/Types.d.ts +++ b/src/browser/Types.d.ts @@ -136,7 +136,10 @@ export interface IColorSet { selectionInactiveBackgroundTransparent: IColor; selectionInactiveBackgroundOpaque: IColor; ansi: IColor[]; + /** Maps original colors to colors that respect minimum contrast ratio. */ contrastCache: IColorContrastCache; + /** Maps original colors to colors that respect _half_ of the minimum contrast ratio. */ + halfContrastCache: IColorContrastCache; } export type ReadonlyColorSet = Readonly> & { ansi: Readonly['ansi']> }; diff --git a/src/browser/renderer/dom/DomRendererRowFactory.ts b/src/browser/renderer/dom/DomRendererRowFactory.ts index 8a428c96..8ecaad42 100644 --- a/src/browser/renderer/dom/DomRendererRowFactory.ts +++ b/src/browser/renderer/dom/DomRendererRowFactory.ts @@ -14,6 +14,7 @@ import { JoinedCellData } from 'browser/services/CharacterJoinerService'; import { excludeFromContrastRatioDemands } from 'browser/renderer/shared/RendererUtils'; import { AttributeData } from 'common/buffer/AttributeData'; import { WidthCache } from 'browser/renderer/dom/WidthCache'; +import { IColorContrastCache } from 'browser/Types'; export const enum RowCss { @@ -444,15 +445,19 @@ export class DomRendererRowFactory { } // Try get from cache first, only use the cache when there are no decoration overrides + const cache = this._getContrastCache(cell); let adjustedColor: IColor | undefined | null = undefined; if (!bgOverride && !fgOverride) { - adjustedColor = this._themeService.colors.contrastCache.getColor(bg.rgba, fg.rgba); + adjustedColor = cache.getColor(bg.rgba, fg.rgba); } // Calculate and store in cache if (adjustedColor === undefined) { - adjustedColor = color.ensureContrastRatio(bgOverride || bg, fgOverride || fg, this._optionsService.rawOptions.minimumContrastRatio); - this._themeService.colors.contrastCache.setColor((bgOverride || bg).rgba, (fgOverride || fg).rgba, adjustedColor ?? null); + // Dim cells only require half the contrast, otherwise they wouldn't be distinguishable from + // non-dim cells + const ratio = this._optionsService.rawOptions.minimumContrastRatio / (cell.isDim() ? 2 : 1); + adjustedColor = color.ensureContrastRatio(bgOverride || bg, fgOverride || fg, ratio); + cache.setColor((bgOverride || bg).rgba, (fgOverride || fg).rgba, adjustedColor ?? null); } if (adjustedColor) { @@ -463,6 +468,13 @@ export class DomRendererRowFactory { return false; } + private _getContrastCache(cell: ICellData): IColorContrastCache { + if (cell.isDim()) { + return this._themeService.colors.halfContrastCache; + } + return this._themeService.colors.contrastCache; + } + private _addStyle(element: HTMLElement, style: string): void { element.setAttribute('style', `${element.getAttribute('style') || ''}${style};`); } diff --git a/src/browser/renderer/shared/CharAtlasUtils.ts b/src/browser/renderer/shared/CharAtlasUtils.ts index 89b21dbc..955bd4e6 100644 --- a/src/browser/renderer/shared/CharAtlasUtils.ts +++ b/src/browser/renderer/shared/CharAtlasUtils.ts @@ -24,7 +24,8 @@ export function generateConfig(deviceCellWidth: number, deviceCellHeight: number // 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(), - contrastCache: colors.contrastCache + contrastCache: colors.contrastCache, + halfContrastCache: colors.halfContrastCache }; return { customGlyphs: options.customGlyphs, diff --git a/src/browser/renderer/shared/TextureAtlas.ts b/src/browser/renderer/shared/TextureAtlas.ts index d73d4ba6..6a572ce0 100644 --- a/src/browser/renderer/shared/TextureAtlas.ts +++ b/src/browser/renderer/shared/TextureAtlas.ts @@ -15,6 +15,7 @@ import { FourKeyMap } from 'common/MultiKeyMap'; import { IdleTaskQueue } from 'common/TaskQueue'; import { IBoundingBox, ICharAtlasConfig, IRasterizedGlyph, IRequestRedrawEvent, ITextureAtlas } from 'browser/renderer/shared/Types'; import { EventEmitter } from 'common/EventEmitter'; +import { IColorContrastCache } from 'browser/Types'; /** * A shared object which is used to draw nothing for a particular cell. @@ -309,8 +310,7 @@ export class TextureAtlas implements ITextureAtlas { } private _getForegroundColor(bg: number, bgColorMode: number, bgColor: number, fg: number, fgColorMode: number, fgColor: number, inverse: boolean, dim: boolean, bold: boolean, excludeFromContrastRatioDemands: boolean): IColor { - // TODO: Pass dim along to get min contrast? - const minimumContrastColor = this._getMinimumContrastColor(bg, bgColorMode, bgColor, fg, fgColorMode, fgColor, false, bold, excludeFromContrastRatioDemands); + const minimumContrastColor = this._getMinimumContrastColor(bg, bgColorMode, bgColor, fg, fgColorMode, fgColor, false, bold, dim, excludeFromContrastRatioDemands); if (minimumContrastColor) { return minimumContrastColor; } @@ -385,23 +385,26 @@ export class TextureAtlas implements ITextureAtlas { } } - private _getMinimumContrastColor(bg: number, bgColorMode: number, bgColor: number, fg: number, fgColorMode: number, fgColor: number, inverse: boolean, bold: boolean, excludeFromContrastRatioDemands: boolean): IColor | undefined { + private _getMinimumContrastColor(bg: number, bgColorMode: number, bgColor: number, fg: number, fgColorMode: number, fgColor: number, inverse: boolean, bold: boolean, dim: boolean, excludeFromContrastRatioDemands: boolean): IColor | undefined { if (this._config.minimumContrastRatio === 1 || excludeFromContrastRatioDemands) { return undefined; } // Try get from cache first - const adjustedColor = this._config.colors.contrastCache.getColor(bg, fg); + const cache = this._getContrastCache(dim); + const adjustedColor = cache.getColor(bg, fg); if (adjustedColor !== undefined) { return adjustedColor || undefined; } const bgRgba = this._resolveBackgroundRgba(bgColorMode, bgColor, inverse); const fgRgba = this._resolveForegroundRgba(fgColorMode, fgColor, inverse, bold); - const result = rgba.ensureContrastRatio(bgRgba, fgRgba, this._config.minimumContrastRatio); + // Dim cells only require half the contrast, otherwise they wouldn't be distinguishable from + // non-dim cells + const result = rgba.ensureContrastRatio(bgRgba, fgRgba, this._config.minimumContrastRatio / (dim ? 2 : 1)); if (!result) { - this._config.colors.contrastCache.setColor(bg, fg, null); + cache.setColor(bg, fg, null); return undefined; } @@ -410,11 +413,18 @@ export class TextureAtlas implements ITextureAtlas { (result >> 16) & 0xFF, (result >> 8) & 0xFF ); - this._config.colors.contrastCache.setColor(bg, fg, color); + cache.setColor(bg, fg, color); return color; } + private _getContrastCache(dim: boolean): IColorContrastCache { + if (dim) { + return this._config.colors.halfContrastCache; + } + return this._config.colors.contrastCache; + } + private _drawToCache(codeOrChars: number | string, bg: number, fg: number, ext: number, restrictToCellHeight: boolean = false): IRasterizedGlyph { const chars = typeof codeOrChars === 'number' ? String.fromCharCode(codeOrChars) : codeOrChars; diff --git a/src/browser/services/ThemeService.ts b/src/browser/services/ThemeService.ts index 2411ce85..58c9f354 100644 --- a/src/browser/services/ThemeService.ts +++ b/src/browser/services/ThemeService.ts @@ -81,7 +81,8 @@ export class ThemeService extends Disposable implements IThemeService { public serviceBrand: undefined; private _colors: IColorSet; - private _contrastCache: IColorContrastCache; + private _contrastCache: IColorContrastCache = new ColorContrastCache(); + private _halfContrastCache: IColorContrastCache = new ColorContrastCache(); private _restoreColors!: IRestoreColorSet; public get colors(): ReadonlyColorSet { return this._colors; } @@ -94,7 +95,6 @@ export class ThemeService extends Disposable implements IThemeService { ) { super(); - this._contrastCache = new ColorContrastCache(); this._colors = { foreground: DEFAULT_FOREGROUND, background: DEFAULT_BACKGROUND, @@ -106,7 +106,8 @@ export class ThemeService extends Disposable implements IThemeService { selectionInactiveBackgroundTransparent: DEFAULT_SELECTION, selectionInactiveBackgroundOpaque: color.blend(DEFAULT_BACKGROUND, DEFAULT_SELECTION), ansi: DEFAULT_ANSI_COLORS.slice(), - contrastCache: this._contrastCache + contrastCache: this._contrastCache, + halfContrastCache: this._halfContrastCache }; this._updateRestoreColors(); this._setTheme(this._optionsService.rawOptions.theme); @@ -172,6 +173,7 @@ export class ThemeService extends Disposable implements IThemeService { } // Clear our the cache this._contrastCache.clear(); + this._halfContrastCache.clear(); this._updateRestoreColors(); this._onChangeColors.fire(this.colors); } From 28c4732abcab95637a92f1be27658deaf15e4f73 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sun, 13 Aug 2023 13:04:40 -0700 Subject: [PATCH 17/22] Fix ThemeService.ctor test --- src/browser/services/ThemeService.test.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/browser/services/ThemeService.test.ts b/src/browser/services/ThemeService.test.ts index f2b2def3..cdfa87df 100644 --- a/src/browser/services/ThemeService.test.ts +++ b/src/browser/services/ThemeService.test.ts @@ -35,7 +35,12 @@ describe('ThemeService', () => { describe('constructor', () => { it('should fill all colors with values', () => { for (const key of Object.keys(themeService.colors)) { - if (key !== 'ansi' && key !== 'contrastCache' && key !== 'selectionForeground') { + if (![ + 'ansi', + 'contrastCache', + 'halfContrastCache', + 'selectionForeground' + ].includes(key)) { // A #rrggbb or rgba(...) assert.ok((themeService.colors as any)[key].css.length >= 7); } From 73a77cda3d4352e88ee4684edbe530938bcbac30 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sun, 13 Aug 2023 13:47:50 -0700 Subject: [PATCH 18/22] DOM: Render selection color instead of cell bg Summary: - Selection cell reuse has been added to the DOM renderer - Selection bg override is now correctly set correctly - Selection elements 'pretend' to be a decoration in order to render bg Fixes #4097 --- .../renderer/dom/DomRendererRowFactory.ts | 44 ++++++++++++------- 1 file changed, 29 insertions(+), 15 deletions(-) diff --git a/src/browser/renderer/dom/DomRendererRowFactory.ts b/src/browser/renderer/dom/DomRendererRowFactory.ts index 8ecaad42..998a11eb 100644 --- a/src/browser/renderer/dom/DomRendererRowFactory.ts +++ b/src/browser/renderer/dom/DomRendererRowFactory.ts @@ -89,6 +89,7 @@ export class DomRendererRowFactory { let oldExt = 0; let oldLinkHover: number | boolean = false; let oldSpacing = 0; + let oldIsInSelection: boolean = false; let spacing = 0; const classes: string[] = []; @@ -154,16 +155,24 @@ export class DomRendererRowFactory { /** * chars can only be merged on existing span if: * - existing span only contains mergeable chars (cellAmount != 0) - * - fg/bg/ul did not change - * - char not part of a selection + * - bg did not change (or both are in selection) + * - fg did not change (or both are in selection and selection fg is set) + * - ext did not change * - underline from hover state did not change * - cell content renders to same letter-spacing * - cell is not cursor */ if ( cellAmount - && cell.bg === oldBg && cell.fg === oldFg && cell.extended.ext === oldExt - && !isInSelection + && ( + (isInSelection && oldIsInSelection) + || (!isInSelection && cell.bg === oldBg) + ) + && ( + (isInSelection && oldIsInSelection && colors.selectionForeground) + || (!(isInSelection && oldIsInSelection && colors.selectionForeground) && cell.fg === oldFg) + ) + && cell.extended.ext === oldExt && isLinkHover === oldLinkHover && spacing === oldSpacing && !isCursorCell @@ -194,6 +203,7 @@ export class DomRendererRowFactory { oldExt = cell.extended.ext; oldLinkHover = isLinkHover; oldSpacing = spacing; + oldIsInSelection = isInSelection; if (isJoined) { // The DOM renderer colors the background of the cursor but for ligatures all cells are @@ -328,22 +338,26 @@ export class DomRendererRowFactory { isTop = d.options.layer === 'top'; }); - // Apply selection foreground if applicable - if (!isTop) { - if (colors.selectionForeground && isInSelection) { + // Apply selection + if (!isTop && isInSelection) { + // If in the selection, force the element to be above the selection to improve contrast and + // support opaque selections. The applies background is not actually needed here as + // selection is drawn in a seperate container, the main purpose of this to ensuring minimum + // contrast ratio + bgOverride = this._coreBrowserService.isFocused ? colors.selectionBackgroundOpaque : colors.selectionInactiveBackgroundOpaque; + bg = bgOverride.rgba >> 8 & 0xFFFFFF; + bgColorMode = Attributes.CM_RGB; + // Since an opaque selection is being rendered, the selection pretends to be a decoration to + // ensure text is drawn above the selection. + isTop = true; + // Apply selection foreground if applicable + if (colors.selectionForeground) { fgColorMode = Attributes.CM_RGB; fg = colors.selectionForeground.rgba >> 8 & 0xFFFFFF; fgOverride = colors.selectionForeground; } } - // If in the selection, force the element to be above the selection to improve contrast and - // support opaque selections - if (isInSelection) { - bgOverride = this._coreBrowserService.isFocused ? colors.selectionBackgroundOpaque : colors.selectionInactiveBackgroundOpaque; - isTop = true; - } - // If it's a top decoration, render above the selection if (isTop) { classes.push('xterm-decoration-top'); @@ -417,7 +431,7 @@ export class DomRendererRowFactory { } // exclude conditions for cell merging - never merge these - if (!isCursorCell && !isInSelection && !isJoined && !isDecorated) { + if (!isCursorCell && !isJoined && !isDecorated && isInSelection === oldIsInSelection) { cellAmount++; } else { charElement.textContent = text; From 855e9113983bb6bfa60e4316042f727beb32ce5c Mon Sep 17 00:00:00 2001 From: dennnnny Date: Mon, 14 Aug 2023 09:49:18 +0800 Subject: [PATCH 19/22] Fix transparent foreground color --- src/browser/renderer/shared/TextureAtlas.ts | 2 +- src/browser/services/ThemeService.ts | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/browser/renderer/shared/TextureAtlas.ts b/src/browser/renderer/shared/TextureAtlas.ts index b73e7009..54171008 100644 --- a/src/browser/renderer/shared/TextureAtlas.ts +++ b/src/browser/renderer/shared/TextureAtlas.ts @@ -298,7 +298,7 @@ export class TextureAtlas implements ITextureAtlas { case Attributes.CM_DEFAULT: default: if (inverse) { - result = this._config.colors.foreground; + result = color.opaque(this._config.colors.foreground); } else { result = this._config.colors.background; } diff --git a/src/browser/services/ThemeService.ts b/src/browser/services/ThemeService.ts index 91a72f5e..2411ce85 100644 --- a/src/browser/services/ThemeService.ts +++ b/src/browser/services/ThemeService.ts @@ -122,8 +122,7 @@ export class ThemeService extends Disposable implements IThemeService { */ private _setTheme(theme: ITheme = {}): void { const colors = this._colors; - colors.foreground = color.opaque(parseColor(theme.foreground, DEFAULT_FOREGROUND)); - console.warn("xterm.js is not fully support foreground colors with transparent, so it will the foreground colors alpha channel to 255.") + colors.foreground = parseColor(theme.foreground, DEFAULT_FOREGROUND); colors.background = parseColor(theme.background, DEFAULT_BACKGROUND); colors.cursor = parseColor(theme.cursor, DEFAULT_CURSOR); colors.cursorAccent = parseColor(theme.cursorAccent, DEFAULT_CURSOR_ACCENT); From 39ae0f45fdee2195382e36db0f32cbeb7c747679 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 14 Aug 2023 04:43:55 -0700 Subject: [PATCH 20/22] Fix selectionForeground dom tests --- src/browser/TestUtils.test.ts | 4 +++- src/browser/renderer/dom/DomRendererRowFactory.test.ts | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/browser/TestUtils.test.ts b/src/browser/TestUtils.test.ts index 2015d046..5981a6b5 100644 --- a/src/browser/TestUtils.test.ts +++ b/src/browser/TestUtils.test.ts @@ -529,6 +529,8 @@ export class MockThemeService implements IThemeService{ css.toColor('#ad7fa8'), css.toColor('#34e2e2'), css.toColor('#eeeeec') - ] + ], + selectionBackgroundOpaque: css.toColor('#ff0000'), + selectionInactiveBackgroundOpaque: css.toColor('#00ff00') } as any; } diff --git a/src/browser/renderer/dom/DomRendererRowFactory.test.ts b/src/browser/renderer/dom/DomRendererRowFactory.test.ts index 609a5b4a..36923dbb 100644 --- a/src/browser/renderer/dom/DomRendererRowFactory.test.ts +++ b/src/browser/renderer/dom/DomRendererRowFactory.test.ts @@ -309,7 +309,7 @@ describe('DomRendererRowFactory', () => { rowFactory.handleSelectionChanged([1, 0], [2, 0], false); const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1); assert.equal(extractHtml(spans), - 'ab' + 'ab' ); }); it('should force whitespace cells to be rendered above the background', () => { @@ -317,7 +317,7 @@ describe('DomRendererRowFactory', () => { rowFactory.handleSelectionChanged([0, 0], [2, 0], false); const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, EMPTY_WIDTH, -1, -1); assert.equal(extractHtml(spans), - ' a' + ' a' ); }); }); From 09fc798e8053b1410bd8a9aa3125b9bb003e7446 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 14 Aug 2023 05:47:25 -0700 Subject: [PATCH 21/22] Remove redundant condition Part of #4097 --- 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 998a11eb..dfb17a1d 100644 --- a/src/browser/renderer/dom/DomRendererRowFactory.ts +++ b/src/browser/renderer/dom/DomRendererRowFactory.ts @@ -431,7 +431,7 @@ export class DomRendererRowFactory { } // exclude conditions for cell merging - never merge these - if (!isCursorCell && !isJoined && !isDecorated && isInSelection === oldIsInSelection) { + if (!isCursorCell && !isJoined && !isDecorated) { cellAmount++; } else { charElement.textContent = text; From 62cf3d39e1a32fc1e4d575e6de2c8da09e59a340 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 14 Aug 2023 07:15:20 -0700 Subject: [PATCH 22/22] Fix headless types regression From the API lint PR --- typings/xterm-headless.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/typings/xterm-headless.d.ts b/typings/xterm-headless.d.ts index 7beb848a..835cc553 100644 --- a/typings/xterm-headless.d.ts +++ b/typings/xterm-headless.d.ts @@ -356,7 +356,7 @@ declare module 'xterm-headless' { * is trimmed and lines are added or removed. This is a single line that may * be part of a larger wrapped line. */ - export interface IMarker extends IDisposable { + export interface IMarker extends IDisposableWithEvent { /** * A unique identifier for this marker. */