diff --git a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts index 88d929f1..1d1936e7 100644 --- a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts @@ -379,7 +379,17 @@ export abstract class BaseRenderLayer extends Disposable implements IRenderLayer } this._ctx.save(); this._clipRow(y); + // Draw the image, use the bitmap if it's available + + // HACK: If the canvas doesn't match, delete the generator. It's not clear how this happens but + // something is wrong with either the lifecycle of _bitmapGenerator or the page canvases are + // swapped out unexpectedly + if (this._bitmapGenerator[glyph.texturePage] && this._charAtlas.pages[glyph.texturePage].canvas !== this._bitmapGenerator[glyph.texturePage]!.canvas) { + this._bitmapGenerator[glyph.texturePage]?.bitmap?.close(); + delete this._bitmapGenerator[glyph.texturePage]; + } + if (this._charAtlas.pages[glyph.texturePage].version !== this._bitmapGenerator[glyph.texturePage]?.version) { if (!this._bitmapGenerator[glyph.texturePage]) { this._bitmapGenerator[glyph.texturePage] = new BitmapGenerator(this._charAtlas.pages[glyph.texturePage].canvas); @@ -446,11 +456,12 @@ class BitmapGenerator { public get bitmap(): ImageBitmap | undefined { return this._bitmap; } public version: number = -1; - constructor(private readonly _canvas: HTMLCanvasElement) { + constructor(public readonly canvas: HTMLCanvasElement) { } public refresh(): void { // Clear the bitmap immediately as it's stale + this._bitmap?.close(); this._bitmap = undefined; // Disable ImageBitmaps on Safari because of https://bugs.webkit.org/show_bug.cgi?id=149990 if (isSafari) { @@ -466,9 +477,10 @@ class BitmapGenerator { private _generate(): void { if (this._state === BitmapGeneratorState.IDLE) { + this._bitmap?.close(); this._bitmap = undefined; this._state = BitmapGeneratorState.GENERATING; - window.createImageBitmap(this._canvas).then(bitmap => { + window.createImageBitmap(this.canvas).then(bitmap => { if (this._state === BitmapGeneratorState.GENERATING_INVALID) { this.refresh(); } else { diff --git a/addons/xterm-addon-canvas/src/CanvasAddon.ts b/addons/xterm-addon-canvas/src/CanvasAddon.ts index b96ce21d..d6136174 100644 --- a/addons/xterm-addon-canvas/src/CanvasAddon.ts +++ b/addons/xterm-addon-canvas/src/CanvasAddon.ts @@ -4,7 +4,7 @@ */ import { ICharacterJoinerService, ICharSizeService, ICoreBrowserService, IRenderService, ISelectionService, IThemeService } from 'browser/services/Services'; -import { IColorSet, ITerminal } from 'browser/Types'; +import { ITerminal } from 'browser/Types'; import { CanvasRenderer } from './CanvasRenderer'; import { IBufferService, ICoreService, IDecorationService, IOptionsService } from 'common/services/Services'; import { ITerminalAddon, Terminal } from 'xterm'; diff --git a/addons/xterm-addon-canvas/src/TextRenderLayer.ts b/addons/xterm-addon-canvas/src/TextRenderLayer.ts index 4ee7c548..0066cc7d 100644 --- a/addons/xterm-addon-canvas/src/TextRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/TextRenderLayer.ts @@ -188,12 +188,6 @@ export class TextRenderLayer extends BaseRenderLayer { nextFillStyle = this._themeService.colors.ansi[cell.getBgColor()].css; } - // Apply dim to the background, this is relatively slow as the CSS is re-parsed but dim is - // rarely used - if (nextFillStyle && cell.isDim()) { - nextFillStyle = color.multiplyOpacity(css.toColor(nextFillStyle), 0.5).css; - } - // Get any decoration foreground/background overrides, this must be fetched before the early // exist but applied after inverse let isTop = false; diff --git a/addons/xterm-addon-serialize/src/SerializeAddon.ts b/addons/xterm-addon-serialize/src/SerializeAddon.ts index b83ff160..0279fee8 100644 --- a/addons/xterm-addon-serialize/src/SerializeAddon.ts +++ b/addons/xterm-addon-serialize/src/SerializeAddon.ts @@ -77,6 +77,7 @@ function equalFlags(cell1: IBufferCell | IAttributeData, cell2: IBufferCell): bo return cell1.isInverse() === cell2.isInverse() && cell1.isBold() === cell2.isBold() && cell1.isUnderline() === cell2.isUnderline() + && cell1.isOverline() === cell2.isOverline() && cell1.isBlink() === cell2.isBlink() && cell1.isInvisible() === cell2.isInvisible() && cell1.isItalic() === cell2.isItalic() @@ -264,6 +265,7 @@ class StringSerializeHandler extends BaseSerializeHandler { if (cell.isInverse() !== oldCell.isInverse()) { sgrSeq.push(cell.isInverse() ? 7 : 27); } if (cell.isBold() !== oldCell.isBold()) { sgrSeq.push(cell.isBold() ? 1 : 22); } if (cell.isUnderline() !== oldCell.isUnderline()) { sgrSeq.push(cell.isUnderline() ? 4 : 24); } + if (cell.isOverline() !== oldCell.isOverline()) { sgrSeq.push(cell.isOverline() ? 53 : 55); } if (cell.isBlink() !== oldCell.isBlink()) { sgrSeq.push(cell.isBlink() ? 5 : 25); } if (cell.isInvisible() !== oldCell.isInvisible()) { sgrSeq.push(cell.isInvisible() ? 8 : 28); } if (cell.isItalic() !== oldCell.isItalic()) { sgrSeq.push(cell.isItalic() ? 3 : 23); } @@ -625,7 +627,9 @@ export class HTMLSerializeHandler extends BaseSerializeHandler { if (cell.isInverse()) { content.push('color: #000000; background-color: #BFBFBF;'); } if (cell.isBold()) { content.push('font-weight: bold;'); } - if (cell.isUnderline()) { content.push('text-decoration: underline;'); } + if (cell.isUnderline() && cell.isOverline()) { content.push('text-decoration: overline underline;'); } + else if (cell.isUnderline()) { content.push('text-decoration: underline;'); } + else if (cell.isOverline()) { content.push('text-decoration: overline;'); } if (cell.isBlink()) { content.push('text-decoration: blink;'); } if (cell.isInvisible()) { content.push('visibility: hidden;'); } if (cell.isItalic()) { content.push('font-style: italic;'); } diff --git a/addons/xterm-addon-serialize/test/SerializeAddon.api.ts b/addons/xterm-addon-serialize/test/SerializeAddon.api.ts index 157b7072..5128910a 100644 --- a/addons/xterm-addon-serialize/test/SerializeAddon.api.ts +++ b/addons/xterm-addon-serialize/test/SerializeAddon.api.ts @@ -220,6 +220,18 @@ describe('SerializeAddon', () => { assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n')); }); + it('serialize all rows of content with overline', async () => { + const cols = 10; + const line = '+'.repeat(cols); + const lines: string[] = [ + sgr(OVERLINED) + line, // Overlined + sgr(UNDERLINED) + line, // Overlined, Underlined + sgr(NORMAL) + line // Normal + ]; + await writeSync(page, lines.join('\\r\\n')); + assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n')); + }); + it('serialize all rows of content with color16 and style separately', async function(): Promise { const cols = 10; const line = '+'.repeat(cols); @@ -601,6 +613,7 @@ const BLINK = '5'; const INVERSE = '7'; const INVISIBLE = '8'; const STRIKETHROUGH = '9'; +const OVERLINED = '53'; const NO_BOLD = '22'; const NO_DIM = '22'; @@ -610,3 +623,4 @@ const NO_BLINK = '25'; const NO_INVERSE = '27'; const NO_INVISIBLE = '28'; const NO_STRIKETHROUGH = '29'; +const NO_OVERLINED = '55'; diff --git a/addons/xterm-addon-webgl/src/RectangleRenderer.ts b/addons/xterm-addon-webgl/src/RectangleRenderer.ts index f45ae3df..fb5a0762 100644 --- a/addons/xterm-addon-webgl/src/RectangleRenderer.ts +++ b/addons/xterm-addon-webgl/src/RectangleRenderer.ts @@ -270,7 +270,7 @@ export class RectangleRenderer extends Disposable { $r = (($rgba >> 24) & 0xFF) / 255; $g = (($rgba >> 16) & 0xFF) / 255; $b = (($rgba >> 8 ) & 0xFF) / 255; - $a = (!$isDefault && bg & BgFlags.DIM) ? DIM_OPACITY : 1; + $a = 1; this._addRectangle(vertices.attributes, offset, $x1, $y1, (endX - startX) * this._dimensions.device.cell.width, this._dimensions.device.cell.height, $r, $g, $b, $a); } diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index fcb0dd80..4fc4427f 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -336,13 +336,16 @@ export class WebglRenderer extends Disposable implements IRenderer { } // Tell renderer the frame is beginning + // upon a model clear also refresh the full viewport model + // (also triggered by an atlas page merge, part of #4480) if (this._glyphRenderer.beginFrame()) { this._clearModel(true); + this._updateModel(0, this._terminal.rows - 1); + } else { + // just update changed lines to draw + this._updateModel(start, end); } - // Update model to reflect what's drawn - this._updateModel(start, end); - // Render this._rectangleRenderer?.render(); this._glyphRenderer?.render(this._model); diff --git a/addons/xterm-addon-webgl/test/WebglRenderer.api.ts b/addons/xterm-addon-webgl/test/WebglRenderer.api.ts index 5e34aede..3edfdc33 100644 --- a/addons/xterm-addon-webgl/test/WebglRenderer.api.ts +++ b/addons/xterm-addon-webgl/test/WebglRenderer.api.ts @@ -364,6 +364,55 @@ describe('WebGL Renderer Integration Tests', async () => { } }); + itWebgl('foreground 16-255 dim', async () => { + let data = ''; + for (let y = 0; y < 240 / 16; y++) { + for (let x = 0; x < 16; x++) { + data += `\\x1b[2;38;5;${16 + y * 16 + x}m█\x1b[0m`; + } + data += '\\r\\n'; + } + await writeSync(page, data); + for (let y = 0; y < 240 / 16; y++) { + for (let x = 0; x < 16; x++) { + const cssColor = COLORS_16_TO_255[y * 16 + x]; + const r = parseInt(cssColor.slice(1, 3), 16); + const g = parseInt(cssColor.slice(3, 5), 16); + const b = parseInt(cssColor.slice(5, 7), 16); + // It's difficult to assert the exact color due to rounding, just ensure the color differs + // to the regular color + await pollFor(page, async () => { + const c = await getCellColor(x + 1, y + 1); + return ( + (c[0] === 0 || c[0] !== r) && + (c[1] === 0 || c[1] !== g) && + (c[2] === 0 || c[2] !== b) + ); + }, true); + } + } + }); + + itWebgl('background 16-255 dim', async () => { + let data = ''; + for (let y = 0; y < 240 / 16; y++) { + for (let x = 0; x < 16; x++) { + data += `\\x1b[2;48;5;${16 + y * 16 + x}m \\x1b[0m`; + } + data += '\\r\\n'; + } + await writeSync(page, data); + for (let y = 0; y < 240 / 16; y++) { + for (let x = 0; x < 16; x++) { + const cssColor = COLORS_16_TO_255[y * 16 + x]; + const r = parseInt(cssColor.slice(1, 3), 16); + const g = parseInt(cssColor.slice(3, 5), 16); + const b = parseInt(cssColor.slice(5, 7), 16); + await pollFor(page, () => getCellColor(x + 1, y + 1), [r, g, b, 255]); + } + } + }); + itWebgl('foreground true color red', async () => { let data = ''; for (let y = 0; y < 16; y++) { diff --git a/css/xterm.css b/css/xterm.css index b14a6cf5..74acc267 100644 --- a/css/xterm.css +++ b/css/xterm.css @@ -161,7 +161,9 @@ } .xterm-dim { - opacity: 0.5; + /* Dim should not apply to background, so the opacity of the foreground color is applied + * explicitly in the generated class and reset to 1 here */ + opacity: 1 !important; } .xterm-underline-1 { text-decoration: underline; } @@ -170,6 +172,16 @@ .xterm-underline-4 { text-decoration: dotted underline; } .xterm-underline-5 { text-decoration: dashed underline; } +.xterm-overline { + text-decoration: overline; +} + +.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; } +.xterm-overline.xterm-underline-4 { text-decoration: overline dotted underline; } +.xterm-overline.xterm-underline-5 { text-decoration: overline dashed underline; } + .xterm-strikethrough { text-decoration: line-through; } diff --git a/demo/client.ts b/demo/client.ts index 179a6ff1..4852ddf2 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -991,7 +991,9 @@ function sgrTest(): void { { ps: 45, name: 'Background Magenta' }, { ps: 46, name: 'Background Cyan' }, { ps: 47, name: 'Background White' }, - { ps: 49, name: 'Background default' } + { ps: 49, name: 'Background default' }, + { ps: 53, name: 'Overlined' }, + { ps: 55, name: 'Not overlined' } ]; const maxNameLength = entries.reduce((p, c) => Math.max(c.name.length, p), 0); for (const e of entries) { @@ -1003,7 +1005,8 @@ function sgrTest(): void { } const comboEntries: { ps: number[] }[] = [ { ps: [1, 2, 3, 4, 5, 6, 7, 9] }, - { ps: [2, 41] } + { ps: [2, 41] }, + { ps: [4, 53] } ]; term.write('\n\n\r'); term.writeln(`Combinations`); diff --git a/demo/server.js b/demo/server.js index 92b82e98..f477ae79 100644 --- a/demo/server.js +++ b/demo/server.js @@ -111,35 +111,32 @@ function startServer() { } // binary message buffering function bufferUtf8(socket, timeout, maxSize) { - const dataBuffer = new Uint8Array(maxSize); - let sender = null; + const chunks = []; let length = 0; + let sender = null; return (data) => { - function flush() { - socket.send(Buffer.from(dataBuffer.buffer, 0, length)); + chunks.push(data); + length += data.length; + if (length > maxSize || userInput) { + userInput = false; + socket.send(Buffer.concat(chunks)); + chunks.length = 0; length = 0; if (sender) { clearTimeout(sender); sender = null; } - } - if (length + data.length > maxSize) { - flush(); - } - dataBuffer.set(data, length); - length += data.length; - if (length > maxSize || userInput) { - userInput = false; - flush(); } else if (!sender) { sender = setTimeout(() => { + socket.send(Buffer.concat(chunks)); + chunks.length = 0; + length = 0; sender = null; - flush(); }, timeout); } }; } - const send = (USE_BINARY ? bufferUtf8 : buffer)(ws, 5, 262144); + const send = (USE_BINARY ? bufferUtf8 : buffer)(ws, 3, 262144); // WARNING: This is a naive implementation that will not throttle the flow of data. This means // it could flood the communication channel and make the terminal unresponsive. Learn more about diff --git a/src/browser/renderer/dom/DomRenderer.ts b/src/browser/renderer/dom/DomRenderer.ts index 3135e9d2..9cee75a8 100644 --- a/src/browser/renderer/dom/DomRenderer.ts +++ b/src/browser/renderer/dom/DomRenderer.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { BOLD_CLASS, CURSOR_BLINK_CLASS, CURSOR_CLASS, CURSOR_STYLE_BAR_CLASS, CURSOR_STYLE_BLOCK_CLASS, CURSOR_STYLE_UNDERLINE_CLASS, DomRendererRowFactory, ITALIC_CLASS } from 'browser/renderer/dom/DomRendererRowFactory'; +import { BOLD_CLASS, CURSOR_BLINK_CLASS, CURSOR_CLASS, CURSOR_STYLE_BAR_CLASS, CURSOR_STYLE_BLOCK_CLASS, CURSOR_STYLE_UNDERLINE_CLASS, DIM_CLASS, DomRendererRowFactory, ITALIC_CLASS } from 'browser/renderer/dom/DomRendererRowFactory'; import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/shared/Constants'; import { createRenderDimensions } from 'browser/renderer/shared/RendererUtils'; import { IRenderDimensions, IRenderer, IRequestRedrawEvent } from 'browser/renderer/shared/Types'; @@ -149,6 +149,10 @@ export class DomRenderer extends Disposable implements IRenderer { ` font-family: ${this._optionsService.rawOptions.fontFamily};` + ` font-size: ${this._optionsService.rawOptions.fontSize}px;` + `}`; + styles += + `${this._terminalSelector} .${ROW_CONTAINER_CLASS} .xterm-dim {` + + ` color: ${color.multiplyOpacity(colors.foreground, 0.5).css};` + + `}`; // Text styles styles += `${this._terminalSelector} span:not(.${BOLD_CLASS}) {` + @@ -224,10 +228,12 @@ 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}.${DIM_CLASS} { color: ${color.multiplyOpacity(c, 0.5).css}; }` + `${this._terminalSelector} .${BG_CLASS_PREFIX}${i} { background-color: ${c.css}; }`; } styles += `${this._terminalSelector} .${FG_CLASS_PREFIX}${INVERTED_DEFAULT_COLOR} { color: ${color.opaque(colors.background).css}; }` + + `${this._terminalSelector} .${FG_CLASS_PREFIX}${INVERTED_DEFAULT_COLOR}.${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}; }`; this._themeStyleElement.textContent = styles; diff --git a/src/browser/renderer/dom/DomRendererRowFactory.test.ts b/src/browser/renderer/dom/DomRendererRowFactory.test.ts index 5969abff..0c2fb79d 100644 --- a/src/browser/renderer/dom/DomRendererRowFactory.test.ts +++ b/src/browser/renderer/dom/DomRendererRowFactory.test.ts @@ -167,6 +167,16 @@ describe('DomRendererRowFactory', () => { }); }); + it('should add class for overline', () => { + const cell = CellData.fromCharData([0, 'a', 1, 'a'.charCodeAt(0)]); + cell.bg = DEFAULT_ATTR_DATA.bg | BgFlags.OVERLINE; + lineData.setCell(0, cell); + const fragment = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, 20, EMPTY_ELEM_MAPPING); + assert.equal(getFragmentHtml(fragment), + 'a' + ); + }); + it('should add class for strikethrough', () => { const cell = CellData.fromCharData([0, 'a', 1, 'a'.charCodeAt(0)]); cell.fg = DEFAULT_ATTR_DATA.fg | FgFlags.STRIKETHROUGH; diff --git a/src/browser/renderer/dom/DomRendererRowFactory.ts b/src/browser/renderer/dom/DomRendererRowFactory.ts index cc64a438..a0b44d2c 100644 --- a/src/browser/renderer/dom/DomRendererRowFactory.ts +++ b/src/browser/renderer/dom/DomRendererRowFactory.ts @@ -9,7 +9,6 @@ import { NULL_CELL_CODE, WHITESPACE_CELL_CHAR, Attributes } from 'common/buffer/ import { CellData } from 'common/buffer/CellData'; import { ICoreService, IDecorationService, IOptionsService } from 'common/services/Services'; import { color, rgba } from 'common/Color'; -import { IColorSet, ReadonlyColorSet } from 'browser/Types'; import { ICharacterJoinerService, ICoreBrowserService, IThemeService } from 'browser/services/Services'; import { JoinedCellData } from 'browser/services/CharacterJoinerService'; import { excludeFromContrastRatioDemands } from 'browser/renderer/shared/RendererUtils'; @@ -19,6 +18,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 OVERLINE_CLASS = 'xterm-overline'; export const STRIKETHROUGH_CLASS = 'xterm-strikethrough'; export const CURSOR_CLASS = 'xterm-cursor'; export const CURSOR_BLINK_CLASS = 'xterm-cursor-blink'; @@ -186,6 +186,13 @@ export class DomRendererRowFactory { } } + if (cell.isOverline()) { + charElement.classList.add(OVERLINE_CLASS); + if (charElement.textContent === ' ') { + charElement.textContent = '\xa0'; // =   + } + } + if (cell.isStrikethrough()) { charElement.classList.add(STRIKETHROUGH_CLASS); } diff --git a/src/browser/renderer/shared/TextureAtlas.ts b/src/browser/renderer/shared/TextureAtlas.ts index 0850e268..d6bf1581 100644 --- a/src/browser/renderer/shared/TextureAtlas.ts +++ b/src/browser/renderer/shared/TextureAtlas.ts @@ -304,12 +304,6 @@ export class TextureAtlas implements ITextureAtlas { break; } - if (dim) { - // Blend here instead of using opacity because transparent colors mess with clipping the - // glyph's bounding box - result = color.blend(this._config.colors.background, color.multiplyOpacity(result, DIM_OPACITY)); - } - return result; } @@ -455,6 +449,7 @@ export class TextureAtlas implements ITextureAtlas { const italic = !!this._workAttributeData.isItalic(); const underline = !!this._workAttributeData.isUnderline(); const strikethrough = !!this._workAttributeData.isStrikethrough(); + const overline = !!this._workAttributeData.isOverline(); let fgColor = this._workAttributeData.getFgColor(); let fgColorMode = this._workAttributeData.getFgColorMode(); let bgColor = this._workAttributeData.getBgColor(); @@ -638,12 +633,24 @@ export class TextureAtlas implements ITextureAtlas { } } + // Overline + if (overline) { + const lineWidth = Math.max(1, Math.floor(this._config.fontSize * this._config.devicePixelRatio / 15)); + const yOffset = lineWidth % 2 === 1 ? 0.5 : 0; + this._tmpCtx.lineWidth = lineWidth; + this._tmpCtx.strokeStyle = this._tmpCtx.fillStyle; + this._tmpCtx.beginPath(); + this._tmpCtx.moveTo(padding, padding + yOffset); + this._tmpCtx.lineTo(padding + this._config.deviceCharWidth * chWidth, padding + yOffset); + this._tmpCtx.stroke(); + } + // Draw the character if (!customGlyph) { this._tmpCtx.fillText(chars, padding, padding + this._config.deviceCharHeight); } - // If this charcater is underscore and beyond the cell bounds, shift it up until it is visible + // If this character is underscore and beyond the cell bounds, shift it up until it is visible // even on the bottom row, try for a maximum of 5 pixels. if (chars === '_' && !this._config.allowTransparency) { let isBeyondCellBounds = clearColor(this._tmpCtx.getImageData(padding, padding, this._config.deviceCellWidth, this._config.deviceCellHeight), backgroundColor, foregroundColor, enableClearThresholdCheck); diff --git a/src/common/InputHandler.ts b/src/common/InputHandler.ts index fac20474..bb8f9e51 100644 --- a/src/common/InputHandler.ts +++ b/src/common/InputHandler.ts @@ -2436,6 +2436,8 @@ export class InputHandler extends Disposable implements IInputHandler { * | 47 | Background color: White. | #Y | * | 48 | Background color: Extended color. | #P[Support for RGB and indexed colors, see below.] | * | 49 | Background color: Default (original). | #Y | + * | 53 | Overlined. | #Y | + * | 55 | Not Overlined. | #Y | * | 58 | Underline color: Extended color. | #P[Support for RGB and indexed colors, see below.] | * | 90 - 97 | Bright foreground color (analogous to 30 - 37). | #Y | * | 100 - 107 | Bright background color (analogous to 40 - 47). | #Y | @@ -2562,6 +2564,12 @@ export class InputHandler extends Disposable implements IInputHandler { } else if (p === 38 || p === 48 || p === 58) { // fg color 256 and RGB i += this._extractColor(params, i, attr); + } else if (p === 53) { + // overline + attr.bg |= BgFlags.OVERLINE; + } else if (p === 55) { + // not overline + attr.bg &= ~BgFlags.OVERLINE; } else if (p === 59) { attr.extended = attr.extended.clone(); attr.extended.underlineColor = -1; diff --git a/src/common/Types.d.ts b/src/common/Types.d.ts index a558e494..20d833e2 100644 --- a/src/common/Types.d.ts +++ b/src/common/Types.d.ts @@ -165,6 +165,7 @@ export interface IAttributeData { isDim(): number; isStrikethrough(): number; isProtected(): number; + isOverline(): number; /** * The color mode of the foreground color which determines how to decode {@link getFgColor}, diff --git a/src/common/buffer/AttributeData.ts b/src/common/buffer/AttributeData.ts index c9f4cd61..f4d12c2b 100644 --- a/src/common/buffer/AttributeData.ts +++ b/src/common/buffer/AttributeData.ts @@ -47,6 +47,7 @@ export class AttributeData implements IAttributeData { public isDim(): number { return this.bg & BgFlags.DIM; } public isStrikethrough(): number { return this.fg & FgFlags.STRIKETHROUGH; } public isProtected(): number { return this.bg & BgFlags.PROTECTED; } + public isOverline(): number { return this.bg & BgFlags.OVERLINE; } // 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 da455794..f6a31be7 100644 --- a/src/common/buffer/Constants.ts +++ b/src/common/buffer/Constants.ts @@ -128,7 +128,8 @@ export const enum BgFlags { ITALIC = 0x4000000, DIM = 0x8000000, HAS_EXTENDED = 0x10000000, - PROTECTED = 0x20000000 + PROTECTED = 0x20000000, + OVERLINE = 0x40000000 } export const enum ExtFlags { diff --git a/typings/xterm-headless.d.ts b/typings/xterm-headless.d.ts index 013998d0..eab3bb13 100644 --- a/typings/xterm-headless.d.ts +++ b/typings/xterm-headless.d.ts @@ -999,6 +999,8 @@ declare module 'xterm-headless' { isInvisible(): number; /** Whether the cell has the strikethrough attribute (CSI 9 m). */ isStrikethrough(): number; + /** Whether the cell has the overline attribute (CSI 53 m). */ + isOverline(): number; /** Whether the cell is using the RGB foreground color mode. */ isFgRGB(): boolean; diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index d22bc61e..03e4f1d0 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -1516,6 +1516,8 @@ declare module 'xterm' { isInvisible(): number; /** Whether the cell has the strikethrough attribute (CSI 9 m). */ isStrikethrough(): number; + /** Whether the cell has the overline attribute (CSI 53 m). */ + isOverline(): number; /** Whether the cell is using the RGB foreground color mode. */ isFgRGB(): boolean;