From d19a740825449ef13560109e739e1f7de89ecdad Mon Sep 17 00:00:00 2001 From: Simon Lamon Date: Sat, 15 May 2021 17:35:23 +0200 Subject: [PATCH 1/3] Support strikethrough text style --- css/xterm.css | 4 ++++ src/browser/renderer/BaseRenderLayer.ts | 15 +++++++++++++++ src/browser/renderer/TextRenderLayer.ts | 9 +++++++-- .../renderer/dom/DomRendererRowFactory.test.ts | 10 ++++++++++ src/browser/renderer/dom/DomRendererRowFactory.ts | 5 +++++ src/common/InputHandler.test.ts | 6 ++++++ src/common/InputHandler.ts | 6 ++++++ src/common/Types.d.ts | 1 + src/common/buffer/AttributeData.ts | 15 ++++++++------- src/common/buffer/Constants.ts | 5 +++-- 10 files changed, 65 insertions(+), 11 deletions(-) diff --git a/css/xterm.css b/css/xterm.css index 831a89c6..3fab18bd 100644 --- a/css/xterm.css +++ b/css/xterm.css @@ -168,3 +168,7 @@ .xterm-underline { text-decoration: underline; } + +.xterm-strikethrough { + text-decoration: line-through; +} diff --git a/src/browser/renderer/BaseRenderLayer.ts b/src/browser/renderer/BaseRenderLayer.ts index ef869ef3..7986e510 100644 --- a/src/browser/renderer/BaseRenderLayer.ts +++ b/src/browser/renderer/BaseRenderLayer.ts @@ -152,6 +152,21 @@ export abstract class BaseRenderLayer implements IRenderLayer { height * this._scaledCellHeight); } + /** + * Fills a 1px line (2px on HDPI) at the middle of the cell. This uses the + * existing fillStyle on the context. + * @param x The column to fill. + * @param y The row to fill. + */ + protected _fillMiddleLineAtCells(x: number, y: number, width: number = 1): void { + const cellOffset = Math.ceil(this._scaledCellHeight * 0.5); + this._ctx.fillRect( + x * this._scaledCellWidth, + (y + 1) * this._scaledCellHeight - cellOffset - window.devicePixelRatio, + width * this._scaledCellWidth, + window.devicePixelRatio); + } + /** * Fills a 1px line (2px on HDPI) at the bottom of the cell. This uses the * existing fillStyle on the context. diff --git a/src/browser/renderer/TextRenderLayer.ts b/src/browser/renderer/TextRenderLayer.ts index ded6c9c6..59fbb7b1 100644 --- a/src/browser/renderer/TextRenderLayer.ts +++ b/src/browser/renderer/TextRenderLayer.ts @@ -215,7 +215,7 @@ export class TextRenderLayer extends BaseRenderLayer { return; } this._drawChars(cell, x, y); - if (cell.isUnderline()) { + if (cell.isUnderline() || cell.isStrikethrough()) { this._ctx.save(); if (cell.isInverse()) { @@ -244,7 +244,12 @@ export class TextRenderLayer extends BaseRenderLayer { } } - this._fillBottomLineAtCells(x, y, cell.getWidth()); + if (cell.isStrikethrough()) { + this._fillMiddleLineAtCells(x, y, cell.getWidth()); + } + if (cell.isUnderline()) { + this._fillBottomLineAtCells(x, y, cell.getWidth()); + } this._ctx.restore(); } }); diff --git a/src/browser/renderer/dom/DomRendererRowFactory.test.ts b/src/browser/renderer/dom/DomRendererRowFactory.test.ts index 9eacb97a..2f8d264a 100644 --- a/src/browser/renderer/dom/DomRendererRowFactory.test.ts +++ b/src/browser/renderer/dom/DomRendererRowFactory.test.ts @@ -132,6 +132,16 @@ describe('DomRendererRowFactory', () => { ); }); + it('should add class for strikethrough', () => { + const cell = CellData.fromCharData([0, 'a', 1, 'a'.charCodeAt(0)]); + cell.fg = DEFAULT_ATTR_DATA.fg | FgFlags.STRIKETHROUGH; + lineData.setCell(0, cell); + const fragment = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, 20); + assert.equal(getFragmentHtml(fragment), + 'a' + ); + }); + it('should add classes for 256 foreground colors', () => { const cell = CellData.fromCharData([0, 'a', 1, 'a'.charCodeAt(0)]); cell.fg |= Attributes.CM_P256; diff --git a/src/browser/renderer/dom/DomRendererRowFactory.ts b/src/browser/renderer/dom/DomRendererRowFactory.ts index eb2dd1fc..a61ebd73 100644 --- a/src/browser/renderer/dom/DomRendererRowFactory.ts +++ b/src/browser/renderer/dom/DomRendererRowFactory.ts @@ -17,6 +17,7 @@ export const BOLD_CLASS = 'xterm-bold'; export const DIM_CLASS = 'xterm-dim'; export const ITALIC_CLASS = 'xterm-italic'; export const UNDERLINE_CLASS = 'xterm-underline'; +export const STRIKETHROUGH_CLASS = 'xterm-strikethrough'; export const CURSOR_CLASS = 'xterm-cursor'; export const CURSOR_BLINK_CLASS = 'xterm-cursor-blink'; export const CURSOR_STYLE_BLOCK_CLASS = 'xterm-cursor-block'; @@ -151,6 +152,10 @@ export class DomRendererRowFactory { charElement.textContent = cell.getChars() || WHITESPACE_CELL_CHAR; } + if (cell.isStrikethrough()) { + charElement.classList.add(STRIKETHROUGH_CLASS); + } + let fg = cell.getFgColor(); let fgColorMode = cell.getFgColorMode(); let bg = cell.getBgColor(); diff --git a/src/common/InputHandler.test.ts b/src/common/InputHandler.test.ts index cb60aec3..25b602ea 100644 --- a/src/common/InputHandler.test.ts +++ b/src/common/InputHandler.test.ts @@ -636,6 +636,12 @@ describe('InputHandler', () => { await inputHandler.parseP('\x1b[28m'); assert.equal(!!inputHandler.curAttrData.isInvisible(), false); }); + it('strikethrough', async () => { + await inputHandler.parseP('\x1b[9m'); + assert.equal(!!inputHandler.curAttrData.isStrikethrough(), true); + await inputHandler.parseP('\x1b[29m'); + assert.equal(!!inputHandler.curAttrData.isStrikethrough(), false); + }); it('colormode palette 16', async () => { assert.equal(inputHandler.curAttrData.getFgColorMode(), 0); // DEFAULT assert.equal(inputHandler.curAttrData.getBgColorMode(), 0); // DEFAULT diff --git a/src/common/InputHandler.ts b/src/common/InputHandler.ts index a2b5a782..06f4ac9f 100644 --- a/src/common/InputHandler.ts +++ b/src/common/InputHandler.ts @@ -2478,6 +2478,9 @@ export class InputHandler extends Disposable implements IInputHandler { } else if (p === 8) { // invisible attr.fg |= FgFlags.INVISIBLE; + } else if (p === 9) { + // strikethrough + attr.fg |= FgFlags.STRIKETHROUGH; } else if (p === 2) { // dimmed text attr.bg |= BgFlags.DIM; @@ -2503,6 +2506,9 @@ export class InputHandler extends Disposable implements IInputHandler { } else if (p === 28) { // not invisible attr.fg &= ~FgFlags.INVISIBLE; + } else if (p === 29) { + // not strikethrough + attr.fg &= ~FgFlags.STRIKETHROUGH; } else if (p === 39) { // reset fg attr.fg &= ~(Attributes.CM_MASK | Attributes.RGB_MASK); diff --git a/src/common/Types.d.ts b/src/common/Types.d.ts index df299195..51f7e172 100644 --- a/src/common/Types.d.ts +++ b/src/common/Types.d.ts @@ -117,6 +117,7 @@ export interface IAttributeData { isInvisible(): number; isItalic(): number; isDim(): number; + isStrikethrough(): number; // color modes getFgColorMode(): number; diff --git a/src/common/buffer/AttributeData.ts b/src/common/buffer/AttributeData.ts index c7217a2a..43d378ea 100644 --- a/src/common/buffer/AttributeData.ts +++ b/src/common/buffer/AttributeData.ts @@ -33,13 +33,14 @@ export class AttributeData implements IAttributeData { public extended = new ExtendedAttrs(); // flags - public isInverse(): number { return this.fg & FgFlags.INVERSE; } - public isBold(): number { return this.fg & FgFlags.BOLD; } - public isUnderline(): number { return this.fg & FgFlags.UNDERLINE; } - public isBlink(): number { return this.fg & FgFlags.BLINK; } - public isInvisible(): number { return this.fg & FgFlags.INVISIBLE; } - public isItalic(): number { return this.bg & BgFlags.ITALIC; } - public isDim(): number { return this.bg & BgFlags.DIM; } + public isInverse(): number { return this.fg & FgFlags.INVERSE; } + public isBold(): number { return this.fg & FgFlags.BOLD; } + public isUnderline(): number { return this.fg & FgFlags.UNDERLINE; } + public isBlink(): number { return this.fg & FgFlags.BLINK; } + public isInvisible(): number { return this.fg & FgFlags.INVISIBLE; } + public isItalic(): number { return this.bg & BgFlags.ITALIC; } + public isDim(): number { return this.bg & BgFlags.DIM; } + public isStrikethrough(): number { return this.fg & FgFlags.STRIKETHROUGH; } // color modes public getFgColorMode(): number { return this.fg & Attributes.CM_MASK; } diff --git a/src/common/buffer/Constants.ts b/src/common/buffer/Constants.ts index ee86a804..a2c1b884 100644 --- a/src/common/buffer/Constants.ts +++ b/src/common/buffer/Constants.ts @@ -110,13 +110,14 @@ export const enum Attributes { export const enum FgFlags { /** - * bit 27..31 (32th bit unused) + * bit 27..32 */ INVERSE = 0x4000000, BOLD = 0x8000000, UNDERLINE = 0x10000000, BLINK = 0x20000000, - INVISIBLE = 0x40000000 + INVISIBLE = 0x40000000, + STRIKETHROUGH = 0x80000000, } export const enum BgFlags { From a1f65a2f77d95fd2f604e2c488e0b7165813d18d Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 9 Jul 2021 06:52:28 -0700 Subject: [PATCH 2/3] Update crossed out characters support to partial --- src/common/InputHandler.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/common/InputHandler.ts b/src/common/InputHandler.ts index 06f4ac9f..e7d4f130 100644 --- a/src/common/InputHandler.ts +++ b/src/common/InputHandler.ts @@ -2365,7 +2365,7 @@ export class InputHandler extends Disposable implements IInputHandler { * | 6 | Rapidly blinking. | #N | * | 7 | Inverse. Flips foreground and background color. | #Y | * | 8 | Invisible (hidden). | #Y | - * | 9 | Crossed-out characters. | #N | + * | 9 | Crossed-out characters. | #P[Support in DOM and Canvas renderers, not WebGL] | * | 21 | Doubly underlined. | #P[Currently outputs a single underline.] | * | 22 | Normal (neither bold nor faint). | #Y | * | 23 | No italic. | #Y | @@ -2373,7 +2373,7 @@ export class InputHandler extends Disposable implements IInputHandler { * | 25 | Steady (not blinking). | #Y | * | 27 | Positive (not inverse). | #Y | * | 28 | Visible (not hidden). | #Y | - * | 29 | Not Crossed-out. | #N | + * | 29 | Not Crossed-out. | #Y | * | 30 | Foreground color: Black. | #Y | * | 31 | Foreground color: Red. | #Y | * | 32 | Foreground color: Green. | #Y | From fa8a4853f72d88907586e6241a2429b40ee97e73 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 9 Jul 2021 06:52:54 -0700 Subject: [PATCH 3/3] Clarify crossed out = strikethrough --- src/common/InputHandler.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/common/InputHandler.ts b/src/common/InputHandler.ts index e7d4f130..30c8fbe9 100644 --- a/src/common/InputHandler.ts +++ b/src/common/InputHandler.ts @@ -2365,7 +2365,7 @@ export class InputHandler extends Disposable implements IInputHandler { * | 6 | Rapidly blinking. | #N | * | 7 | Inverse. Flips foreground and background color. | #Y | * | 8 | Invisible (hidden). | #Y | - * | 9 | Crossed-out characters. | #P[Support in DOM and Canvas renderers, not WebGL] | + * | 9 | Crossed-out characters (strikethrough). | #P[Support in DOM and Canvas renderers, not WebGL] | * | 21 | Doubly underlined. | #P[Currently outputs a single underline.] | * | 22 | Normal (neither bold nor faint). | #Y | * | 23 | No italic. | #Y | @@ -2373,7 +2373,7 @@ export class InputHandler extends Disposable implements IInputHandler { * | 25 | Steady (not blinking). | #Y | * | 27 | Positive (not inverse). | #Y | * | 28 | Visible (not hidden). | #Y | - * | 29 | Not Crossed-out. | #Y | + * | 29 | Not Crossed-out (strikethrough). | #Y | * | 30 | Foreground color: Black. | #Y | * | 31 | Foreground color: Red. | #Y | * | 32 | Foreground color: Green. | #Y |