From 482dad3036e35d39f6a1a6d3765ab03005b5a31f Mon Sep 17 00:00:00 2001 From: tisilent Date: Thu, 10 Aug 2023 16:11:04 +0800 Subject: [PATCH] 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