From 03cfce7087fb3e15dadbed7e5252c173c1eea80e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B0=A2=E4=BD=B3=E9=BE=99?= Date: Mon, 13 May 2024 11:25:11 +0800 Subject: [PATCH 1/3] Implementing underline through pseudo elements. --- css/xterm.css | 19 ++++++++++++++++++- src/browser/renderer/dom/DomRenderer.ts | 8 ++++++++ .../renderer/dom/DomRendererRowFactory.ts | 3 ++- 3 files changed, 28 insertions(+), 2 deletions(-) diff --git a/css/xterm.css b/css/xterm.css index e97b643905..671cb478bd 100644 --- a/css/xterm.css +++ b/css/xterm.css @@ -179,12 +179,29 @@ .xterm-underline-2 { text-decoration: double underline; } .xterm-underline-3 { text-decoration: wavy underline; } .xterm-underline-4 { text-decoration: dotted underline; } -.xterm-underline-5 { text-decoration: dashed underline; } +.xterm-underline-5 { position: relative; /* text-decoration: dashed underline; */ } +.xterm-underline-5::after { + content: ""; + position: absolute; + left: 0; + right: 0; + bottom: 1px; + border-bottom: 2px dashed #fff; +} .xterm-overline { text-decoration: overline; } +.xterm-hoverline::before { + content: ""; + position: absolute; + left: 0; + right: 0; + bottom: 3px; + border-bottom: 1px solid #fff; +} + .xterm-overline.xterm-underline-1 { text-decoration: overline underline; } .xterm-overline.xterm-underline-2 { text-decoration: overline double underline; } .xterm-overline.xterm-underline-3 { text-decoration: overline wavy underline; } diff --git a/src/browser/renderer/dom/DomRenderer.ts b/src/browser/renderer/dom/DomRenderer.ts index 92d152f0d0..2f5da028e9 100644 --- a/src/browser/renderer/dom/DomRenderer.ts +++ b/src/browser/renderer/dom/DomRenderer.ts @@ -37,6 +37,7 @@ export class DomRenderer extends Disposable implements IRenderer { private _themeStyleElement!: HTMLStyleElement; private _dimensionsStyleElement!: HTMLStyleElement; + private _underlineStyleElement!: HTMLStyleElement; private _rowContainer: HTMLElement; private _rowElements: HTMLElement[] = []; private _selectionContainer: HTMLElement; @@ -98,6 +99,7 @@ export class DomRenderer extends Disposable implements IRenderer { this._widthCache.dispose(); this._themeStyleElement.remove(); this._dimensionsStyleElement.remove(); + this._underlineStyleElement.remove(); })); this._widthCache = new WidthCache(this._document, this._helperContainer); @@ -158,6 +160,11 @@ export class DomRenderer extends Disposable implements IRenderer { this._screenElement.appendChild(this._themeStyleElement); } + if (!this._underlineStyleElement) { + this._underlineStyleElement = this._document.createElement('style'); + this._screenElement.appendChild(this._underlineStyleElement); + } + // Base CSS let styles = `${this._terminalSelector} .${ROW_CONTAINER_CLASS} {` + @@ -264,6 +271,7 @@ export class DomRenderer extends Disposable implements IRenderer { for (const [i, c] of colors.ansi.entries()) { styles += `${this._terminalSelector} .${FG_CLASS_PREFIX}${i} { color: ${c.css}; }` + + `${this._terminalSelector} .${FG_CLASS_PREFIX}${i}::after { content: "";border-bottom-color: ${c.css}; }` + `${this._terminalSelector} .${FG_CLASS_PREFIX}${i}.${RowCss.DIM_CLASS} { color: ${color.multiplyOpacity(c, 0.5).css}; }` + `${this._terminalSelector} .${BG_CLASS_PREFIX}${i} { background-color: ${c.css}; }`; } diff --git a/src/browser/renderer/dom/DomRendererRowFactory.ts b/src/browser/renderer/dom/DomRendererRowFactory.ts index d71edeb96f..6913770e91 100644 --- a/src/browser/renderer/dom/DomRendererRowFactory.ts +++ b/src/browser/renderer/dom/DomRendererRowFactory.ts @@ -303,7 +303,8 @@ export class DomRendererRowFactory { // apply link hover underline late, effectively overrides any previous text-decoration // settings if (isLinkHover) { - charElement.style.textDecoration = 'underline'; + classes.push('xterm-hoverline'); + // charElement.style.textDecoration = 'underline'; } let fg = cell.getFgColor(); From 085f937d0e7045c849e3d06ec6b0eec41b71a74d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B0=A2=E4=BD=B3=E9=BE=99?= Date: Mon, 13 May 2024 14:44:59 +0800 Subject: [PATCH 2/3] handle selection hover --- css/xterm.css | 45 +++++++++++++++---- src/browser/renderer/dom/DomRenderer.ts | 18 ++++++-- .../renderer/dom/DomRendererRowFactory.ts | 10 +++-- 3 files changed, 58 insertions(+), 15 deletions(-) diff --git a/css/xterm.css b/css/xterm.css index 671cb478bd..5f38a2e93e 100644 --- a/css/xterm.css +++ b/css/xterm.css @@ -175,31 +175,60 @@ opacity: 1 !important; } -.xterm-underline-1 { text-decoration: underline; } -.xterm-underline-2 { text-decoration: double underline; } +.xterm-underline-1 { position: relative; } +.xterm-underline-1::after { + content: ""; + position: absolute; + left: 0; + right: 0; + bottom: 1px; + border-bottom: 2px solid; +} + +.xterm-underline-2 { position: relative; } +.xterm-underline-2::after { + content: ""; + position: absolute; + left: 0; + right: 0; + bottom: 1px; + border-bottom: 3px double; +} + .xterm-underline-3 { text-decoration: wavy underline; } -.xterm-underline-4 { text-decoration: dotted underline; } -.xterm-underline-5 { position: relative; /* text-decoration: dashed underline; */ } +.xterm-underline-4 { position: relative; } +.xterm-underline-4::after { + content: ""; + position: absolute; + left: 0; + right: 0; + bottom: 1px; + border-bottom: 2px dotted; +} + +.xterm-underline-5 { position: relative; } .xterm-underline-5::after { content: ""; position: absolute; left: 0; right: 0; bottom: 1px; - border-bottom: 2px dashed #fff; + border-bottom: 2px dashed; } .xterm-overline { text-decoration: overline; } -.xterm-hoverline::before { +.xterm-fg-hoverline { + position: relative; +} +.xterm-fg-hoverline::before { content: ""; position: absolute; left: 0; right: 0; - bottom: 3px; - border-bottom: 1px solid #fff; + bottom: 3px } .xterm-overline.xterm-underline-1 { text-decoration: overline underline; } diff --git a/src/browser/renderer/dom/DomRenderer.ts b/src/browser/renderer/dom/DomRenderer.ts index 2f5da028e9..602ca92fa0 100644 --- a/src/browser/renderer/dom/DomRenderer.ts +++ b/src/browser/renderer/dom/DomRenderer.ts @@ -46,6 +46,8 @@ export class DomRenderer extends Disposable implements IRenderer { public dimensions: IRenderDimensions; + private _currentLink: ILinkifierEvent | undefined; + public readonly onRequestRedraw = this.register(new EventEmitter()).event; constructor( @@ -272,6 +274,7 @@ export class DomRenderer extends Disposable implements IRenderer { styles += `${this._terminalSelector} .${FG_CLASS_PREFIX}${i} { color: ${c.css}; }` + `${this._terminalSelector} .${FG_CLASS_PREFIX}${i}::after { content: "";border-bottom-color: ${c.css}; }` + + `${this._terminalSelector} .${FG_CLASS_PREFIX}${i}-underline::after { content: "";border-bottom-color: ${c.css}; }` + `${this._terminalSelector} .${FG_CLASS_PREFIX}${i}.${RowCss.DIM_CLASS} { color: ${color.multiplyOpacity(c, 0.5).css}; }` + `${this._terminalSelector} .${BG_CLASS_PREFIX}${i} { background-color: ${c.css}; }`; } @@ -280,6 +283,13 @@ export class DomRenderer extends Disposable implements IRenderer { `${this._terminalSelector} .${FG_CLASS_PREFIX}${INVERTED_DEFAULT_COLOR}.${RowCss.DIM_CLASS} { color: ${color.multiplyOpacity(color.opaque(colors.background), 0.5).css}; }` + `${this._terminalSelector} .${BG_CLASS_PREFIX}${INVERTED_DEFAULT_COLOR} { background-color: ${colors.foreground.css}; }`; + // Underline + styles += + `.${FG_CLASS_PREFIX}underline::after { border-bottom-color: ${colors.foreground.css}; }`; + + styles += + `.${FG_CLASS_PREFIX}hoverline::before { border-bottom: 1px solid ${colors.foreground.css}; }`; + this._themeStyleElement.textContent = styles; } @@ -448,7 +458,7 @@ export class DomRenderer extends Disposable implements IRenderer { 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; const rowElement = this._rowElements[y]; @@ -467,8 +477,8 @@ export class DomRenderer extends Disposable implements IRenderer { cursorBlink, this.dimensions.css.cell.width, this._widthCache, - -1, - -1 + this._currentLink ? this._currentLink.y1 === y ? this._currentLink.x1 : -1 : -1, + this._currentLink ? this._currentLink.y2 === y ? this._currentLink.x2 : -1 : -1 ) ); } @@ -479,10 +489,12 @@ export class DomRenderer extends Disposable implements IRenderer { } private _handleLinkHover(e: ILinkifierEvent): void { + this._currentLink = e; this._setCellUnderline(e.x1, e.x2, e.y1, e.y2, e.cols, true); } private _handleLinkLeave(e: ILinkifierEvent): void { + this._currentLink = undefined; this._setCellUnderline(e.x1, e.x2, e.y1, e.y2, e.cols, false); } diff --git a/src/browser/renderer/dom/DomRendererRowFactory.ts b/src/browser/renderer/dom/DomRendererRowFactory.ts index 6913770e91..fd4a4f9639 100644 --- a/src/browser/renderer/dom/DomRendererRowFactory.ts +++ b/src/browser/renderer/dom/DomRendererRowFactory.ts @@ -29,7 +29,8 @@ export const enum RowCss { 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_STYLE_UNDERLINE_CLASS = 'xterm-cursor-underline', + FG_UNDERLINE_CLASS = 'xterm-fg-underline' } @@ -284,8 +285,10 @@ export class DomRendererRowFactory { if (this._optionsService.rawOptions.drawBoldTextInBrightColors && cell.isBold() && fg < 8) { fg += 8; } - charElement.style.textDecorationColor = colors.ansi[fg].css; + classes.push(`xterm-fg-${fg}-underline`); } + } else { + classes.push(RowCss.FG_UNDERLINE_CLASS); } } @@ -303,8 +306,7 @@ export class DomRendererRowFactory { // apply link hover underline late, effectively overrides any previous text-decoration // settings if (isLinkHover) { - classes.push('xterm-hoverline'); - // charElement.style.textDecoration = 'underline'; + classes.push('xterm-fg-hoverline'); } let fg = cell.getFgColor(); From 4d86cf3a56b173556516fad93fdb42a9900ed6c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B0=A2=E4=BD=B3=E9=BE=99?= Date: Mon, 13 May 2024 14:51:28 +0800 Subject: [PATCH 3/3] Remove unused var. --- src/browser/renderer/dom/DomRenderer.ts | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/browser/renderer/dom/DomRenderer.ts b/src/browser/renderer/dom/DomRenderer.ts index 602ca92fa0..71a93104c5 100644 --- a/src/browser/renderer/dom/DomRenderer.ts +++ b/src/browser/renderer/dom/DomRenderer.ts @@ -37,7 +37,6 @@ export class DomRenderer extends Disposable implements IRenderer { private _themeStyleElement!: HTMLStyleElement; private _dimensionsStyleElement!: HTMLStyleElement; - private _underlineStyleElement!: HTMLStyleElement; private _rowContainer: HTMLElement; private _rowElements: HTMLElement[] = []; private _selectionContainer: HTMLElement; @@ -101,7 +100,6 @@ export class DomRenderer extends Disposable implements IRenderer { this._widthCache.dispose(); this._themeStyleElement.remove(); this._dimensionsStyleElement.remove(); - this._underlineStyleElement.remove(); })); this._widthCache = new WidthCache(this._document, this._helperContainer); @@ -162,11 +160,6 @@ export class DomRenderer extends Disposable implements IRenderer { this._screenElement.appendChild(this._themeStyleElement); } - if (!this._underlineStyleElement) { - this._underlineStyleElement = this._document.createElement('style'); - this._screenElement.appendChild(this._underlineStyleElement); - } - // Base CSS let styles = `${this._terminalSelector} .${ROW_CONTAINER_CLASS} {` +