diff --git a/css/xterm.css b/css/xterm.css index e97b643905..5f38a2e93e 100644 --- a/css/xterm.css +++ b/css/xterm.css @@ -175,16 +175,62 @@ 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 { 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; +} .xterm-overline { text-decoration: overline; } +.xterm-fg-hoverline { + position: relative; +} +.xterm-fg-hoverline::before { + content: ""; + position: absolute; + left: 0; + right: 0; + bottom: 3px +} + .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..71a93104c5 100644 --- a/src/browser/renderer/dom/DomRenderer.ts +++ b/src/browser/renderer/dom/DomRenderer.ts @@ -45,6 +45,8 @@ export class DomRenderer extends Disposable implements IRenderer { public dimensions: IRenderDimensions; + private _currentLink: ILinkifierEvent | undefined; + public readonly onRequestRedraw = this.register(new EventEmitter()).event; constructor( @@ -264,6 +266,8 @@ 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}-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}; }`; } @@ -272,6 +276,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; } @@ -440,7 +451,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]; @@ -459,8 +470,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 ) ); } @@ -471,10 +482,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 d71edeb96f..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,7 +306,7 @@ export class DomRendererRowFactory { // apply link hover underline late, effectively overrides any previous text-decoration // settings if (isLinkHover) { - charElement.style.textDecoration = 'underline'; + classes.push('xterm-fg-hoverline'); } let fg = cell.getFgColor();