From 1e8901630cd38ed4e4c97d32b34d177b948f1b10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=84=BF=E6=97=B6?= <58261676+childrentime@users.noreply.github.com> Date: Wed, 2 Mar 2022 11:25:20 +0800 Subject: [PATCH 001/245] Replace all getOption/setOption usage with options --- .../xterm-addon-ligatures/src/index.test.ts | 36 +++++++++---------- addons/xterm-addon-ligatures/src/index.ts | 8 ++--- addons/xterm-addon-webgl/src/WebglRenderer.ts | 8 ++--- .../src/atlas/CharAtlasUtils.ts | 20 +++++------ .../src/renderLayer/BaseRenderLayer.ts | 4 +-- .../src/renderLayer/CursorRenderLayer.ts | 14 ++++---- .../test/WebglRenderer.api.ts | 36 +++++++++---------- demo/index.html | 2 +- test/api/InputHandler.api.ts | 4 +-- test/api/MouseTracking.api.ts | 2 +- test/api/Terminal.api.ts | 7 ---- 11 files changed, 67 insertions(+), 74 deletions(-) diff --git a/addons/xterm-addon-ligatures/src/index.test.ts b/addons/xterm-addon-ligatures/src/index.test.ts index 838955c0..f9f10d0b 100644 --- a/addons/xterm-addon-ligatures/src/index.test.ts +++ b/addons/xterm-addon-ligatures/src/index.test.ts @@ -78,7 +78,7 @@ describe('xterm-addon-ligatures', () => { }); it('handles quoted font names', done => { - term.setOption('fontFamily', '"Fira Code", monospace'); + term.options.fontFamily = '"Fira Code", monospace'; assert.deepEqual(term.joiner!(input), []); onRefresh.callsFake(() => { assert.deepEqual(term.joiner!(input), [[2, 4], [7, 10]]); @@ -87,7 +87,7 @@ describe('xterm-addon-ligatures', () => { }); it('falls back to later fonts if earlier ones are not present', done => { - term.setOption('fontFamily', 'notinstalled, Fira Code, monospace'); + term.options.fontFamily = 'notinstalled, Fira Code, monospace'; assert.deepEqual(term.joiner!(input), []); onRefresh.callsFake(() => { assert.deepEqual(term.joiner!(input), [[2, 4], [7, 10]]); @@ -98,17 +98,17 @@ describe('xterm-addon-ligatures', () => { it('uses the current font value', done => { // The first three calls are all synchronous so that we don't allow time for // any fonts to load while we're switching things around - term.setOption('fontFamily', 'Fira Code'); + term.options.fontFamily = 'Fira Code'; assert.deepEqual(term.joiner!(input), []); - term.setOption('fontFamily', 'notinstalled'); + term.options.fontFamily = 'notinstalled'; assert.deepEqual(term.joiner!(input), []); - term.setOption('fontFamily', 'Iosevka'); + term.options.fontFamily = 'Iosevka'; assert.deepEqual(term.joiner!(input), []); onRefresh.callsFake(() => { assert.deepEqual(term.joiner!(input), [[2, 4]]); // And switch it back to Fira Code for good measure - term.setOption('fontFamily', 'Fira Code'); + term.options.fontFamily = 'Fira Code'; // At this point, we haven't loaded the new font, so the result reverts // back to empty until that happens @@ -124,7 +124,7 @@ describe('xterm-addon-ligatures', () => { it('allows multiple terminal instances that use different fonts', done => { const onRefresh2 = sinon.stub(); const term2 = new MockTerminal(onRefresh2); - term2.setOption('fontFamily', 'Iosevka'); + term2.options.fontFamily = 'Iosevka'; ligatureSupport.enableLigatures(term2 as any); assert.deepEqual(term.joiner!(input), []); @@ -140,7 +140,7 @@ describe('xterm-addon-ligatures', () => { }); it('fails if it finds but cannot load the font', async () => { - term.setOption('fontFamily', 'Nonexistant Font, monospace'); + term.options.fontFamily = 'Nonexistant Font, monospace'; assert.deepEqual(term.joiner!(input), []); await delay(500); assert.isTrue(onRefresh.notCalled); @@ -148,7 +148,7 @@ describe('xterm-addon-ligatures', () => { }); it('returns nothing if the font is not present on the system', async () => { - term.setOption('fontFamily', 'notinstalled'); + term.options.fontFamily = 'notinstalled'; assert.deepEqual(term.joiner!(input), []); await delay(500); assert.isTrue(onRefresh.notCalled); @@ -156,7 +156,7 @@ describe('xterm-addon-ligatures', () => { }); it('returns nothing if no specific font is specified', async () => { - term.setOption('fontFamily', 'monospace'); + term.options.fontFamily = 'monospace'; assert.deepEqual(term.joiner!(input), []); await delay(500); assert.isTrue(onRefresh.notCalled); @@ -164,7 +164,7 @@ describe('xterm-addon-ligatures', () => { }); it('returns nothing if no fonts are provided', async () => { - term.setOption('fontFamily', ''); + term.options.fontFamily = ''; assert.deepEqual(term.joiner!(input), []); await delay(500); assert.isTrue(onRefresh.notCalled); @@ -172,7 +172,7 @@ describe('xterm-addon-ligatures', () => { }); it('fails when given malformed inputs', async () => { - term.setOption('fontFamily', {} as any); + term.options.fontFamily = {} as any; assert.deepEqual(term.joiner!(input), []); await delay(500); assert.isTrue(onRefresh.notCalled); @@ -181,7 +181,7 @@ describe('xterm-addon-ligatures', () => { it('ensures no empty errors are thrown', async () => { sinon.stub(fontLigatures, 'loadFile').callsFake(async () => { throw undefined; }); - term.setOption('fontFamily', 'Iosevka'); + term.options.fontFamily = 'Iosevka'; assert.deepEqual(term.joiner!(input), []); await delay(500); assert.isTrue(onRefresh.notCalled); @@ -209,11 +209,11 @@ class MockTerminal { public deregisterCharacterJoiner(id: number): void { this.joiner = undefined; } - public setOption(name: string, value: string | number): void { - this._options[name] = value; - } - public getOption(name: string): string | number { - return this._options[name]; + public get options(): { [name: string]: string | number } { return this._options; } + public set options(options: { [name: string]: string | number }) { + for (const key in this._options) { + this._options[key] = options[key]; + } } } diff --git a/addons/xterm-addon-ligatures/src/index.ts b/addons/xterm-addon-ligatures/src/index.ts index c867369d..c54f3b50 100644 --- a/addons/xterm-addon-ligatures/src/index.ts +++ b/addons/xterm-addon-ligatures/src/index.ts @@ -34,7 +34,7 @@ export function enableLigatures(term: Terminal): void { term.registerCharacterJoiner((text: string): [number, number][] => { // If the font hasn't been loaded yet, load it and return an empty result - const termFont = term.getOption('fontFamily'); + const termFont = term.options.fontFamily; if ( termFont && (loadingState === LoadingState.UNLOADED || currentFontName !== termFont) @@ -48,20 +48,20 @@ export function enableLigatures(term: Terminal): void { .then(f => { // Another request may have come in while we were waiting, so make // sure our font is still vaild. - if (currentCallFontName === term.getOption('fontFamily')) { + if (currentCallFontName === term.options.fontFamily) { loadingState = LoadingState.LOADED; font = f; // Only refresh things if we actually found a font if (f) { - term.refresh(0, term.getOption('rows') - 1); + term.refresh(0, term.options.rows! - 1); } } }) .catch(e => { // Another request may have come in while we were waiting, so make // sure our font is still vaild. - if (currentCallFontName === term.getOption('fontFamily')) { + if (currentCallFontName === term.options.fontFamily) { loadingState = LoadingState.FAILED; font = undefined; loadError = e; diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index 24e55fed..a256b9da 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -440,18 +440,18 @@ export class WebglRenderer extends Disposable implements IRenderer { // will be floored because since lineHeight can never be lower then 1, there // is a guarentee that the scaled line height will always be larger than // scaled char height. - this.dimensions.scaledCellHeight = Math.floor(this.dimensions.scaledCharHeight * this._terminal.getOption('lineHeight')); + this.dimensions.scaledCellHeight = Math.floor(this.dimensions.scaledCharHeight * this._terminal.options.lineHeight!); // Calculate the y coordinate within a cell that text should draw from in // order to draw in the center of a cell. - this.dimensions.scaledCharTop = this._terminal.getOption('lineHeight') === 1 ? 0 : Math.round((this.dimensions.scaledCellHeight - this.dimensions.scaledCharHeight) / 2); + this.dimensions.scaledCharTop = this._terminal.options.lineHeight === 1 ? 0 : Math.round((this.dimensions.scaledCellHeight - this.dimensions.scaledCharHeight) / 2); // Calculate the scaled cell width, taking the letterSpacing into account. - this.dimensions.scaledCellWidth = this.dimensions.scaledCharWidth + Math.round(this._terminal.getOption('letterSpacing')); + this.dimensions.scaledCellWidth = this.dimensions.scaledCharWidth + Math.round(this._terminal.options.letterSpacing!); // Calculate the x coordinate with a cell that text should draw from in // order to draw in the center of a cell. - this.dimensions.scaledCharLeft = Math.floor(this._terminal.getOption('letterSpacing') / 2); + this.dimensions.scaledCharLeft = Math.floor(this._terminal.options.letterSpacing! / 2); // Recalculate the canvas dimensions; scaled* define the actual number of // pixel in the canvas diff --git a/addons/xterm-addon-webgl/src/atlas/CharAtlasUtils.ts b/addons/xterm-addon-webgl/src/atlas/CharAtlasUtils.ts index 962eb7b3..4705796a 100644 --- a/addons/xterm-addon-webgl/src/atlas/CharAtlasUtils.ts +++ b/addons/xterm-addon-webgl/src/atlas/CharAtlasUtils.ts @@ -28,21 +28,21 @@ export function generateConfig(scaledCellWidth: number, scaledCellHeight: number contrastCache: colors.contrastCache }; return { - customGlyphs: terminal.getOption('customGlyphs'), + customGlyphs: terminal.options.customGlyphs!, devicePixelRatio: window.devicePixelRatio, - letterSpacing: terminal.getOption('letterSpacing'), - lineHeight: terminal.getOption('lineHeight'), + letterSpacing: terminal.options.letterSpacing!, + lineHeight: terminal.options.lineHeight!, scaledCellWidth, scaledCellHeight, scaledCharWidth, scaledCharHeight, - fontFamily: terminal.getOption('fontFamily'), - fontSize: terminal.getOption('fontSize'), - fontWeight: terminal.getOption('fontWeight') as FontWeight, - fontWeightBold: terminal.getOption('fontWeightBold') as FontWeight, - allowTransparency: terminal.getOption('allowTransparency'), - drawBoldTextInBrightColors: terminal.getOption('drawBoldTextInBrightColors'), - minimumContrastRatio: terminal.getOption('minimumContrastRatio'), + fontFamily: terminal.options.fontFamily!, + fontSize: terminal.options.fontSize!, + fontWeight: terminal.options.fontWeight as FontWeight, + fontWeightBold: terminal.options.fontWeightBold as FontWeight, + allowTransparency: terminal.options.allowTransparency!, + drawBoldTextInBrightColors: terminal.options.drawBoldTextInBrightColors!, + minimumContrastRatio: terminal.options.minimumContrastRatio!, colors: clonedColors }; } diff --git a/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts b/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts index 4c17aad4..619ca1a8 100644 --- a/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts +++ b/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts @@ -254,10 +254,10 @@ export abstract class BaseRenderLayer implements IRenderLayer { * @param isBold If we should use the bold fontWeight. */ protected _getFont(terminal: Terminal, isBold: boolean, isItalic: boolean): string { - const fontWeight = isBold ? terminal.getOption('fontWeightBold') : terminal.getOption('fontWeight'); + const fontWeight = isBold ? terminal.options.fontWeightBold : terminal.options.fontWeight; const fontStyle = isItalic ? 'italic' : ''; - return `${fontStyle} ${fontWeight} ${terminal.getOption('fontSize') * window.devicePixelRatio}px ${terminal.getOption('fontFamily')}`; + return `${fontStyle} ${fontWeight} ${terminal.options.fontSize! * window.devicePixelRatio}px ${terminal.options.fontFamily}`; } } diff --git a/addons/xterm-addon-webgl/src/renderLayer/CursorRenderLayer.ts b/addons/xterm-addon-webgl/src/renderLayer/CursorRenderLayer.ts index 4199b7e1..c80b4c56 100644 --- a/addons/xterm-addon-webgl/src/renderLayer/CursorRenderLayer.ts +++ b/addons/xterm-addon-webgl/src/renderLayer/CursorRenderLayer.ts @@ -83,7 +83,7 @@ export class CursorRenderLayer extends BaseRenderLayer { } public onOptionsChanged(terminal: Terminal): void { - if (terminal.getOption('cursorBlink')) { + if (terminal.options.cursorBlink) { if (!this._cursorBlinkStateManager) { this._cursorBlinkStateManager = new CursorBlinkStateManager(terminal, () => { this._render(terminal, true); @@ -140,7 +140,7 @@ export class CursorRenderLayer extends BaseRenderLayer { this._clearCursor(); this._ctx.save(); this._ctx.fillStyle = this._colors.cursor.css; - const cursorStyle = terminal.getOption('cursorStyle'); + const cursorStyle = terminal.options.cursorStyle; if (cursorStyle && cursorStyle !== 'block') { this._cursorRenderers[cursorStyle](terminal, cursorX, viewportRelativeCursorY, this._cell); } else { @@ -150,7 +150,7 @@ export class CursorRenderLayer extends BaseRenderLayer { this._state.x = cursorX; this._state.y = viewportRelativeCursorY; this._state.isFocused = false; - this._state.style = cursorStyle; + this._state.style = cursorStyle!; this._state.width = this._cell.getWidth(); return; } @@ -166,7 +166,7 @@ export class CursorRenderLayer extends BaseRenderLayer { if (this._state.x === cursorX && this._state.y === viewportRelativeCursorY && this._state.isFocused === isTerminalFocused(terminal) && - this._state.style === terminal.getOption('cursorStyle') && + this._state.style === terminal.options.cursorStyle && this._state.width === this._cell.getWidth()) { return; } @@ -174,13 +174,13 @@ export class CursorRenderLayer extends BaseRenderLayer { } this._ctx.save(); - this._cursorRenderers[terminal.getOption('cursorStyle') || 'block'](terminal, cursorX, viewportRelativeCursorY, this._cell); + this._cursorRenderers[terminal.options.cursorStyle || 'block'](terminal, cursorX, viewportRelativeCursorY, this._cell); this._ctx.restore(); this._state.x = cursorX; this._state.y = viewportRelativeCursorY; this._state.isFocused = false; - this._state.style = terminal.getOption('cursorStyle'); + this._state.style = terminal.options.cursorStyle!; this._state.width = this._cell.getWidth(); } @@ -205,7 +205,7 @@ export class CursorRenderLayer extends BaseRenderLayer { private _renderBarCursor(terminal: Terminal, x: number, y: number, cell: ICellData): void { this._ctx.save(); this._ctx.fillStyle = this._colors.cursor.css; - this._fillLeftLineAtCell(x, y, terminal.getOption('cursorWidth')); + this._fillLeftLineAtCell(x, y, terminal.options.cursorWidth!); this._ctx.restore(); } diff --git a/addons/xterm-addon-webgl/test/WebglRenderer.api.ts b/addons/xterm-addon-webgl/test/WebglRenderer.api.ts index e6942d1c..080d7ce8 100644 --- a/addons/xterm-addon-webgl/test/WebglRenderer.api.ts +++ b/addons/xterm-addon-webgl/test/WebglRenderer.api.ts @@ -49,7 +49,7 @@ describe('WebGL Renderer Integration Tests', async () => { cyan: '#131415', white: '#161718' }; - await page.evaluate(`window.term.setOption('theme', ${JSON.stringify(theme)});`); + await page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`); await writeSync(page, `\\x1b[30m█\\x1b[31m█\\x1b[32m█\\x1b[33m█\\x1b[34m█\\x1b[35m█\\x1b[36m█\\x1b[37m█`); await pollFor(page, () => getCellColor(1, 1), [1, 2, 3, 255]); await pollFor(page, () => getCellColor(2, 1), [4, 5, 6, 255]); @@ -73,8 +73,8 @@ describe('WebGL Renderer Integration Tests', async () => { brightWhite: '#161718' }; await page.evaluate(` - window.term.setOption('theme', ${JSON.stringify(theme)}); - window.term.setOption('drawBoldTextInBrightColors', true); + window.term.options.theme = ${JSON.stringify(theme)}; + window.term.options.drawBoldTextInBrightColors = true; `); await writeSync(page, `\\x1b[1;30m█\\x1b[1;31m█\\x1b[1;32m█\\x1b[1;33m█\\x1b[1;34m█\\x1b[1;35m█\\x1b[1;36m█\\x1b[1;37m█`); await pollFor(page, () => getCellColor(1, 1), [1, 2, 3, 255]); @@ -98,7 +98,7 @@ describe('WebGL Renderer Integration Tests', async () => { cyan: '#131415', white: '#161718' }; - await page.evaluate(`window.term.setOption('theme', ${JSON.stringify(theme)});`); + await page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`); await writeSync(page, `\\x1b[40m \\x1b[41m \\x1b[42m \\x1b[43m \\x1b[44m \\x1b[45m \\x1b[46m \\x1b[47m `); await pollFor(page, () => getCellColor(1, 1), [1, 2, 3, 255]); await pollFor(page, () => getCellColor(2, 1), [4, 5, 6, 255]); @@ -121,7 +121,7 @@ describe('WebGL Renderer Integration Tests', async () => { cyan: '#131415', white: '#161718' }; - await page.evaluate(`window.term.setOption('theme', ${JSON.stringify(theme)});`); + await page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`); await writeSync(page, `\\x1b[7;30m \\x1b[7;31m \\x1b[7;32m \\x1b[7;33m \\x1b[7;34m \\x1b[7;35m \\x1b[7;36m \\x1b[7;37m `); await pollFor(page, () => getCellColor(1, 1), [1, 2, 3, 255]); await pollFor(page, () => getCellColor(2, 1), [4, 5, 6, 255]); @@ -144,7 +144,7 @@ describe('WebGL Renderer Integration Tests', async () => { cyan: '#131415', white: '#161718' }; - await page.evaluate(`window.term.setOption('theme', ${JSON.stringify(theme)});`); + await page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`); await writeSync(page, `\\x1b[7;40m█\\x1b[7;41m█\\x1b[7;42m█\\x1b[7;43m█\\x1b[7;44m█\\x1b[7;45m█\\x1b[7;46m█\\x1b[7;47m█`); await pollFor(page, () => getCellColor(1, 1), [1, 2, 3, 255]); await pollFor(page, () => getCellColor(2, 1), [4, 5, 6, 255]); @@ -167,7 +167,7 @@ describe('WebGL Renderer Integration Tests', async () => { cyan: '#131415', white: '#161718' }; - await page.evaluate(`window.term.setOption('theme', ${JSON.stringify(theme)});`); + await page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`); await writeSync(page, `\\x1b[8;30m \\x1b[8;31m \\x1b[8;32m \\x1b[8;33m \\x1b[8;34m \\x1b[8;35m \\x1b[8;36m \\x1b[8;37m `); await pollFor(page, () => getCellColor(1, 1), [0, 0, 0, 255]); await pollFor(page, () => getCellColor(2, 1), [0, 0, 0, 255]); @@ -190,7 +190,7 @@ describe('WebGL Renderer Integration Tests', async () => { cyan: '#131415', white: '#161718' }; - await page.evaluate(`window.term.setOption('theme', ${JSON.stringify(theme)});`); + await page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`); await writeSync(page, `\\x1b[8;40m█\\x1b[8;41m█\\x1b[8;42m█\\x1b[8;43m█\\x1b[8;44m█\\x1b[8;45m█\\x1b[8;46m█\\x1b[8;47m█`); await pollFor(page, () => getCellColor(1, 1), [1, 2, 3, 255]); await pollFor(page, () => getCellColor(2, 1), [4, 5, 6, 255]); @@ -213,7 +213,7 @@ describe('WebGL Renderer Integration Tests', async () => { brightCyan: '#131415', brightWhite: '#161718' }; - await page.evaluate(`window.term.setOption('theme', ${JSON.stringify(theme)});`); + await page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`); await writeSync(page, `\\x1b[90m█\\x1b[91m█\\x1b[92m█\\x1b[93m█\\x1b[94m█\\x1b[95m█\\x1b[96m█\\x1b[97m█`); await pollFor(page, () => getCellColor(1, 1), [1, 2, 3, 255]); await pollFor(page, () => getCellColor(2, 1), [4, 5, 6, 255]); @@ -236,7 +236,7 @@ describe('WebGL Renderer Integration Tests', async () => { brightCyan: '#131415', brightWhite: '#161718' }; - await page.evaluate(`window.term.setOption('theme', ${JSON.stringify(theme)});`); + await page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`); await writeSync(page, `\\x1b[100m \\x1b[101m \\x1b[102m \\x1b[103m \\x1b[104m \\x1b[105m \\x1b[106m \\x1b[107m `); await pollFor(page, () => getCellColor(1, 1), [1, 2, 3, 255]); await pollFor(page, () => getCellColor(2, 1), [4, 5, 6, 255]); @@ -715,8 +715,8 @@ describe('WebGL Renderer Integration Tests', async () => { brightWhite: '#eeeeec' }; await page.evaluate(` - window.term.setOption('theme', ${JSON.stringify(theme)}); - window.term.setOption('minimumContrastRatio', 1); + window.term.options.theme = ${JSON.stringify(theme)}; + window.term.options.minimumContrastRatio = 1; `); await writeSync(page, `\\x1b[30m█\\x1b[31m█\\x1b[32m█\\x1b[33m█\\x1b[34m█\\x1b[35m█\\x1b[36m█\\x1b[37m█\\r\\n` + @@ -742,7 +742,7 @@ describe('WebGL Renderer Integration Tests', async () => { // Setting and check for minimum contrast values, note that these are note // exact to the contrast ratio, if the increase luminance algorithm // changes then these will probably fail - await page.evaluate(`window.term.setOption('minimumContrastRatio', 10);`); + await page.evaluate(`window.term.options.minimumContrastRatio = 10;`); await pollFor(page, () => getCellColor(1, 1), [176, 180, 180, 255]); await pollFor(page, () => getCellColor(2, 1), [238, 158, 158, 255]); await pollFor(page, () => getCellColor(3, 1), [197, 223, 171, 255]); @@ -783,8 +783,8 @@ describe('WebGL Renderer Integration Tests', async () => { brightWhite: '#eeeeec' }; await page.evaluate(` - window.term.setOption('theme', ${JSON.stringify(theme)}); - window.term.setOption('minimumContrastRatio', 1); + window.term.options.theme = ${JSON.stringify(theme)}; + window.term.options.minimumContrastRatio = 1; `); await writeSync(page, `\\x1b[30m█\\x1b[31m█\\x1b[32m█\\x1b[33m█\\x1b[34m█\\x1b[35m█\\x1b[36m█\\x1b[37m█\\r\\n` + @@ -810,7 +810,7 @@ describe('WebGL Renderer Integration Tests', async () => { // Setting and check for minimum contrast values, note that these are note // exact to the contrast ratio, if the increase luminance algorithm // changes then these will probably fail - await page.evaluate(`window.term.setOption('minimumContrastRatio', 10);`); + await page.evaluate(`window.term.options.minimumContrastRatio = 10;`); await pollFor(page, () => getCellColor(1, 1), [46, 52, 54, 255]); await pollFor(page, () => getCellColor(2, 1), [132, 0, 0, 255]); await pollFor(page, () => getCellColor(3, 1), [78, 154, 6, 255]); @@ -843,7 +843,7 @@ describe('WebGL Renderer Integration Tests', async () => { background: '#00FF00', selection: '#0000FF' }; - await page.evaluate(`window.term.setOption('theme', ${JSON.stringify(theme)});`); + await page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`); await writeSync(page, ` █\\x1b[7m█\\x1b[0m`); await pollFor(page, () => getCellColor(1, 1), [0, 255, 0, 255]); await pollFor(page, () => getCellColor(2, 1), [255, 0, 0, 255]); @@ -867,7 +867,7 @@ describe('WebGL Renderer Integration Tests', async () => { const theme: ITheme = { background: '#ff000080' }; - await page.evaluate(`window.term.setOption('theme', ${JSON.stringify(theme)});`); + await page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`); const data = `\\x1b[7m█\x1b[0m`; await writeSync(page, data); // Inverse background should be opaque diff --git a/demo/index.html b/demo/index.html index e024222c..31111631 100644 --- a/demo/index.html +++ b/demo/index.html @@ -28,7 +28,7 @@

Options

-

These options can be set in the Terminal constructor or by using the Terminal.setOption function.

+

These options can be set in the Terminal constructor or by using the Terminal.options property.

diff --git a/test/api/InputHandler.api.ts b/test/api/InputHandler.api.ts index 4d3a15dd..6e0bf88b 100644 --- a/test/api/InputHandler.api.ts +++ b/test/api/InputHandler.api.ts @@ -362,7 +362,7 @@ describe('InputHandler Integration Tests', function(): void { await pollFor(page, async () => await page.evaluate(`(() => _stack)()`), []); }); it('14 - GetWinSizePixels', async function(): Promise { - await page.evaluate(`window.term.setOption('windowOptions', { getWinSizePixels: true }); `); + await page.evaluate(`window.term.options.windowOptions = { getWinSizePixels: true }; `); await page.evaluate(`(() => { window._stack = []; const _h = window.term.onData(data => window._stack.push(data)); @@ -373,7 +373,7 @@ describe('InputHandler Integration Tests', function(): void { await pollFor(page, async () => await page.evaluate(`(() => _stack)()`), [`\x1b[4;${d.height};${d.width}t`]); }); it('16 - GetCellSizePixels', async function(): Promise { - await page.evaluate(`window.term.setOption('windowOptions', { getCellSizePixels: true }); `); + await page.evaluate(`window.term.options.windowOptions = { getCellSizePixels: true }; `); await page.evaluate(`(() => { window._stack = []; const _h = window.term.onData(data => window._stack.push(data)); diff --git a/test/api/MouseTracking.api.ts b/test/api/MouseTracking.api.ts index ebeb680f..16a9b32c 100644 --- a/test/api/MouseTracking.api.ts +++ b/test/api/MouseTracking.api.ts @@ -229,7 +229,7 @@ describe('Mouse Tracking Tests', async () => { window.calls = []; window.term.onData(e => calls.push( Array.from(e).map(el => el.charCodeAt(0)) )); window.term.onBinary(e => calls.push( Array.from(e).map(el => el.charCodeAt(0)) )); - window.term.setOption('fontSize', ${fontSize}); + window.term.options.fontSize = ${fontSize}; window.term.resize(${cols}, ${rows}); `); }); diff --git a/test/api/Terminal.api.ts b/test/api/Terminal.api.ts index ee6a0cfa..cc3b78be 100644 --- a/test/api/Terminal.api.ts +++ b/test/api/Terminal.api.ts @@ -154,13 +154,6 @@ describe('API Integration Tests', function(): void { } }); - it('getOption, setOption', async () => { - await openTerminal(page); - assert.equal(await page.evaluate(`window.term.getOption('rendererType')`), 'canvas'); - await page.evaluate(`window.term.setOption('rendererType', 'dom')`); - assert.equal(await page.evaluate(`window.term.getOption('rendererType')`), 'dom'); - }); - describe('options', () => { it('getter', async () => { await openTerminal(page); From 78c2f06107030f6abfe165b385318c46c5784df5 Mon Sep 17 00:00:00 2001 From: Simon Lamon Date: Wed, 2 Mar 2022 13:19:53 +0000 Subject: [PATCH 002/245] Fix triple select edge case --- src/browser/services/SelectionService.test.ts | 6 ++++++ src/browser/services/SelectionService.ts | 10 +++++++--- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/browser/services/SelectionService.test.ts b/src/browser/services/SelectionService.test.ts index 514d5803..d829e394 100644 --- a/src/browser/services/SelectionService.test.ts +++ b/src/browser/services/SelectionService.test.ts @@ -340,6 +340,9 @@ describe('SelectionService', () => { buffer.lines.set(0, stringToRow('foo bar')); selectionService.selectLineAt(0); assert.equal(selectionService.selectionText, 'foo bar', 'The selected text is correct'); + assert.deepEqual(selectionService.model.selectionStart, [0, 0]); + assert.deepEqual(selectionService.model.selectionEnd, undefined); + assert.deepEqual(selectionService.model.selectionStartLength, 20); assert.deepEqual(selectionService.model.finalSelectionStart, [0, 0]); assert.deepEqual(selectionService.model.finalSelectionEnd, [bufferService.cols, 0], 'The actual selection spans the entire column'); }); @@ -350,6 +353,9 @@ describe('SelectionService', () => { buffer.lines.set(1, line2); selectionService.selectLineAt(0); assert.equal(selectionService.selectionText, 'foobar', 'The selected text is correct'); + assert.deepEqual(selectionService.model.selectionStart, [0, 0]); + assert.deepEqual(selectionService.model.selectionEnd, undefined); + assert.deepEqual(selectionService.model.selectionStartLength, 40); assert.deepEqual(selectionService.model.finalSelectionStart, [0, 0]); assert.deepEqual(selectionService.model.finalSelectionEnd, [bufferService.cols, 1], 'The actual selection spans the entire column'); }); diff --git a/src/browser/services/SelectionService.ts b/src/browser/services/SelectionService.ts index 1ea2395d..53020b53 100644 --- a/src/browser/services/SelectionService.ts +++ b/src/browser/services/SelectionService.ts @@ -11,7 +11,7 @@ import { SelectionModel } from 'browser/selection/SelectionModel'; import { CellData } from 'common/buffer/CellData'; import { EventEmitter, IEvent } from 'common/EventEmitter'; import { IMouseService, ISelectionService, IRenderService } from 'browser/services/Services'; -import { ILinkifier2 } from 'browser/Types'; +import { IBufferRange, ILinkifier2 } from 'browser/Types'; import { IBufferService, IOptionsService, ICoreService } from 'common/services/Services'; import { getCoordsRelativeToElement } from 'browser/input/Mouse'; import { moveToCellSequence } from 'browser/input/MoveToCell'; @@ -1002,8 +1002,12 @@ export class SelectionService extends Disposable implements ISelectionService { */ protected _selectLineAt(line: number): void { const wrappedRange = this._bufferService.buffer.getWrappedRangeForLine(line); + const range: IBufferRange = { + start: { x: 0, y: wrappedRange.first }, + end: { x: this._bufferService.cols - 1, y: wrappedRange.last } + }; this._model.selectionStart = [0, wrappedRange.first]; - this._model.selectionEnd = [this._bufferService.cols, wrappedRange.last]; - this._model.selectionStartLength = 0; + this._model.selectionEnd = undefined; + this._model.selectionStartLength = getRangeLength(range, this._bufferService.cols); } } From c10a451381ca0ae2fe8695a12f2c7de293d9d76f Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 2 Mar 2022 06:59:57 -0800 Subject: [PATCH 003/245] Bring back getOption/setOption api test --- test/api/Terminal.api.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test/api/Terminal.api.ts b/test/api/Terminal.api.ts index cc3b78be..ee6a0cfa 100644 --- a/test/api/Terminal.api.ts +++ b/test/api/Terminal.api.ts @@ -154,6 +154,13 @@ describe('API Integration Tests', function(): void { } }); + it('getOption, setOption', async () => { + await openTerminal(page); + assert.equal(await page.evaluate(`window.term.getOption('rendererType')`), 'canvas'); + await page.evaluate(`window.term.setOption('rendererType', 'dom')`); + assert.equal(await page.evaluate(`window.term.getOption('rendererType')`), 'dom'); + }); + describe('options', () => { it('getter', async () => { await openTerminal(page); From 4799832a33521e2378ba4a56268679626e5e95fc Mon Sep 17 00:00:00 2001 From: Simon Lamon Date: Wed, 2 Mar 2022 16:17:31 +0000 Subject: [PATCH 004/245] Keep the initial wrapped line upon dragging --- src/browser/selection/SelectionModel.test.ts | 6 ++++++ src/browser/selection/SelectionModel.ts | 7 ++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/browser/selection/SelectionModel.test.ts b/src/browser/selection/SelectionModel.test.ts index 410902d7..5ce3e316 100644 --- a/src/browser/selection/SelectionModel.test.ts +++ b/src/browser/selection/SelectionModel.test.ts @@ -116,6 +116,12 @@ describe('SelectionModel', () => { model.selectionStartLength = 4; assert.deepEqual(model.finalSelectionEnd, [2, 3]); }); + it('should return the end on a different row when start + length overflows onto a following row with selectionEnd inbetween', () => { + model.selectionStart = [78, 2]; + model.selectionEnd = [79, 2]; + model.selectionStartLength = 4; + assert.deepEqual(model.finalSelectionEnd, [2, 3]); + }); it('should return selection end if selection end is after selection start + length', () => { model.selectionStart = [2, 2]; model.selectionStartLength = 2; diff --git a/src/browser/selection/SelectionModel.ts b/src/browser/selection/SelectionModel.ts index 1d84446a..6c8abbfd 100644 --- a/src/browser/selection/SelectionModel.ts +++ b/src/browser/selection/SelectionModel.ts @@ -92,7 +92,12 @@ export class SelectionModel { if (this.selectionStartLength) { // Select the larger of the two when start and end are on the same line if (this.selectionEnd[1] === this.selectionStart[1]) { - return [Math.max(this.selectionStart[0] + this.selectionStartLength, this.selectionEnd[0]), this.selectionEnd[1]]; + // Keep the whole wrapped word/line selected if the content wraps multiple lines + const startPlusLength = this.selectionStart[0] + this.selectionStartLength; + if (startPlusLength > this._bufferService.cols) { + return [startPlusLength % this._bufferService.cols, this.selectionStart[1] + Math.floor(startPlusLength / this._bufferService.cols)]; + } + return [Math.max(startPlusLength, this.selectionEnd[0]), this.selectionEnd[1]]; } } return this.selectionEnd; From ff6fa739c43165ad187683f6bdca0481ca4b7c93 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Wed, 2 Mar 2022 17:58:27 +0000 Subject: [PATCH 005/245] update addons --- addons/xterm-addon-ligatures/package.json | 2 +- addons/xterm-addon-serialize/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/addons/xterm-addon-ligatures/package.json b/addons/xterm-addon-ligatures/package.json index f3c3aeff..7e5e5e90 100644 --- a/addons/xterm-addon-ligatures/package.json +++ b/addons/xterm-addon-ligatures/package.json @@ -1,6 +1,6 @@ { "name": "xterm-addon-ligatures", - "version": "0.5.2", + "version": "0.5.3", "description": "Add support for programming ligatures to xterm.js", "author": { "name": "The xterm.js authors", diff --git a/addons/xterm-addon-serialize/package.json b/addons/xterm-addon-serialize/package.json index 1565a7e4..01a8b68f 100644 --- a/addons/xterm-addon-serialize/package.json +++ b/addons/xterm-addon-serialize/package.json @@ -1,6 +1,6 @@ { "name": "xterm-addon-serialize", - "version": "0.6.1", + "version": "0.6.2", "author": { "name": "The xterm.js authors", "url": "https://xtermjs.org/" From f5b66775db75ed7416b20b4fd37766d41fe25fea Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 2 Mar 2022 12:36:35 -0600 Subject: [PATCH 006/245] during buffer clear, don't dispose of markers on first line --- src/common/buffer/Buffer.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/common/buffer/Buffer.ts b/src/common/buffer/Buffer.ts index 7596fef3..f7dc97d3 100644 --- a/src/common/buffer/Buffer.ts +++ b/src/common/buffer/Buffer.ts @@ -595,10 +595,14 @@ export class Buffer implements IBuffer { } } } else { - for (const marker of this.markers) { - marker.dispose(); + // buffer has been cleared + // don't dispose of markers on the current line (0) + for (let i = 0; i < this.markers.length; i++) { + if (this.markers[i].line !== 0) { + this.markers[i].dispose(); + this.markers.splice(i--, 1); + } } - this.markers = []; } this._isClearing = false; } From 7bf12f9123a0f8c133b1fdf41b74b72b0eab341c Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Wed, 2 Mar 2022 20:29:31 +0000 Subject: [PATCH 007/245] break into two methods --- src/common/buffer/Buffer.ts | 21 ++++++++++++++------- src/common/buffer/Types.d.ts | 3 ++- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/src/common/buffer/Buffer.ts b/src/common/buffer/Buffer.ts index f7dc97d3..a4cc4603 100644 --- a/src/common/buffer/Buffer.ts +++ b/src/common/buffer/Buffer.ts @@ -585,24 +585,31 @@ export class Buffer implements IBuffer { return x >= this._cols ? this._cols - 1 : x < 0 ? 0 : x; } - public clearMarkers(y?: number): void { + /** + * Clears markers on line @param y + */ + public clearMarkers(y: number): void { this._isClearing = true; - if (y !== undefined) { for (let i = 0; i < this.markers.length; i++) { if (this.markers[i].line === y) { this.markers[i].dispose(); this.markers.splice(i--, 1); } } - } else { - // buffer has been cleared - // don't dispose of markers on the current line (0) + this._isClearing = false; + } + + /** + * Clears markers on all lines except for + * those on @param excludeY + */ + public clearAllMarkers(excludeY: number): void { + this._isClearing = true; for (let i = 0; i < this.markers.length; i++) { - if (this.markers[i].line !== 0) { + if (this.markers[i].line !== excludeY) { this.markers[i].dispose(); this.markers.splice(i--, 1); } - } } this._isClearing = false; } diff --git a/src/common/buffer/Types.d.ts b/src/common/buffer/Types.d.ts index 36b70b7f..f26b4b26 100644 --- a/src/common/buffer/Types.d.ts +++ b/src/common/buffer/Types.d.ts @@ -45,7 +45,8 @@ export interface IBuffer { getNullCell(attr?: IAttributeData): ICellData; getWhitespaceCell(attr?: IAttributeData): ICellData; addMarker(y: number): IMarker; - clearMarkers(y?: number): void; + clearMarkers(y: number): void; + clearAllMarkers(excludeY: number): void; } export interface IBufferSet extends IDisposable { From 85dd6a9e87bf61ace28be0ae7a13a6fba2cb7838 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Wed, 2 Mar 2022 15:18:24 -0600 Subject: [PATCH 008/245] Update src/common/buffer/Buffer.ts Co-authored-by: Daniel Imms <2193314+Tyriar@users.noreply.github.com> --- src/common/buffer/Buffer.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/common/buffer/Buffer.ts b/src/common/buffer/Buffer.ts index a4cc4603..8693736c 100644 --- a/src/common/buffer/Buffer.ts +++ b/src/common/buffer/Buffer.ts @@ -586,7 +586,8 @@ export class Buffer implements IBuffer { } /** - * Clears markers on line @param y + * Clears markers on single line. + * @param y The line to clear. */ public clearMarkers(y: number): void { this._isClearing = true; From 1af22b24c021d6dba1cd94dd2346ab48f03345f0 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Wed, 2 Mar 2022 15:18:29 -0600 Subject: [PATCH 009/245] Update src/common/buffer/Buffer.ts Co-authored-by: Daniel Imms <2193314+Tyriar@users.noreply.github.com> --- src/common/buffer/Buffer.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/common/buffer/Buffer.ts b/src/common/buffer/Buffer.ts index 8693736c..bf9d24ce 100644 --- a/src/common/buffer/Buffer.ts +++ b/src/common/buffer/Buffer.ts @@ -601,8 +601,8 @@ export class Buffer implements IBuffer { } /** - * Clears markers on all lines except for - * those on @param excludeY + * Clears markers on all lines except for those on a particular line. + * @param excludeY The line to exclude. */ public clearAllMarkers(excludeY: number): void { this._isClearing = true; From e75b47ca015addc198ee194aa52306903a42d0b8 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 2 Mar 2022 15:21:17 -0600 Subject: [PATCH 010/245] use clearAllMarkers --- src/browser/Terminal.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index 703c995b..08963933 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -1301,7 +1301,7 @@ export class Terminal extends CoreTerminal implements ITerminal { // Don't clear if it's already clear return; } - this.buffer.clearMarkers(); + this.buffer.clearAllMarkers(0); this.buffer.lines.set(0, this.buffer.lines.get(this.buffer.ybase + this.buffer.y)!); this.buffer.lines.length = 1; this.buffer.ydisp = 0; From 82706e77dbba4b69843c785a3ae6bc13723f9d41 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 2 Mar 2022 20:19:49 -0600 Subject: [PATCH 011/245] add to mockbuffer --- src/browser/TestUtils.test.ts | 5 ++++- src/common/buffer/Buffer.ts | 22 +++++++++++----------- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/src/browser/TestUtils.test.ts b/src/browser/TestUtils.test.ts index 10a1435b..ac048be6 100644 --- a/src/browser/TestUtils.test.ts +++ b/src/browser/TestUtils.test.ts @@ -257,7 +257,10 @@ export class MockBuffer implements IBuffer { public getWhitespaceCell(attr?: IAttributeData): ICellData { throw new Error('Method not implemented.'); } - public clearMarkers(): void { + public clearMarkers(y: number): void { + throw new Error('Method not implemented.'); + } + public clearAllMarkers(excludeY: number): void { throw new Error('Method not implemented.'); } } diff --git a/src/common/buffer/Buffer.ts b/src/common/buffer/Buffer.ts index bf9d24ce..ab295784 100644 --- a/src/common/buffer/Buffer.ts +++ b/src/common/buffer/Buffer.ts @@ -591,12 +591,12 @@ export class Buffer implements IBuffer { */ public clearMarkers(y: number): void { this._isClearing = true; - for (let i = 0; i < this.markers.length; i++) { - if (this.markers[i].line === y) { - this.markers[i].dispose(); - this.markers.splice(i--, 1); - } + for (let i = 0; i < this.markers.length; i++) { + if (this.markers[i].line === y) { + this.markers[i].dispose(); + this.markers.splice(i--, 1); } + } this._isClearing = false; } @@ -604,13 +604,13 @@ export class Buffer implements IBuffer { * Clears markers on all lines except for those on a particular line. * @param excludeY The line to exclude. */ - public clearAllMarkers(excludeY: number): void { + public clearAllMarkers(excludeY: number): void { this._isClearing = true; - for (let i = 0; i < this.markers.length; i++) { - if (this.markers[i].line !== excludeY) { - this.markers[i].dispose(); - this.markers.splice(i--, 1); - } + for (let i = 0; i < this.markers.length; i++) { + if (this.markers[i].line !== excludeY) { + this.markers[i].dispose(); + this.markers.splice(i--, 1); + } } this._isClearing = false; } From 2263a1579edc89fd2d209c28bd6a71b5e61793d6 Mon Sep 17 00:00:00 2001 From: coderaiser Date: Tue, 8 Mar 2022 20:13:40 +0200 Subject: [PATCH 012/245] =?UTF-8?q?chore:=20lint=20using=20=F0=9F=90=8APut?= =?UTF-8?q?out?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../test/SearchAddon.api.ts | 2 +- .../src/SerializeAddon.test.ts | 34 +++++++++---------- .../src/SerializeAddon.ts | 2 +- .../test/SerializeAddon.api.ts | 2 +- src/browser/ColorManager.ts | 2 +- src/browser/renderer/atlas/CharAtlasUtils.ts | 2 +- src/common/input/XParseColor.ts | 2 +- 7 files changed, 23 insertions(+), 23 deletions(-) diff --git a/addons/xterm-addon-search/test/SearchAddon.api.ts b/addons/xterm-addon-search/test/SearchAddon.api.ts index 6d75f18f..7cb15c5d 100644 --- a/addons/xterm-addon-search/test/SearchAddon.api.ts +++ b/addons/xterm-addon-search/test/SearchAddon.api.ts @@ -134,7 +134,7 @@ describe('Search Tests', function(): void { .replace(/\n/g, '\\n\\r'); } fixture = fixture - .replace(/'/g, '\\\''); + .replace(/'/g, `\\'`); }); it('should find all occurrences using findNext', async () => { await writeSync(page, fixture); diff --git a/addons/xterm-addon-serialize/src/SerializeAddon.test.ts b/addons/xterm-addon-serialize/src/SerializeAddon.test.ts index caa29053..e35751d1 100644 --- a/addons/xterm-addon-serialize/src/SerializeAddon.test.ts +++ b/addons/xterm-addon-serialize/src/SerializeAddon.test.ts @@ -86,7 +86,7 @@ describe('xterm-addon-serialize html', () => { it('empty terminal with selection turned off', () => { const output = serializeAddon.serializeAsHTML(); assert.notEqual(output, ''); - assert.equal((output.match(new RegExp('
{10}<\/div>', 'g')) || []).length, 2); + assert.equal((output.match(/
{10}<\/span><\/div>/g) || []).length, 2); }); it('empty terminal with no selection', () => { @@ -103,84 +103,84 @@ describe('xterm-addon-serialize html', () => { const output = serializeAddon.serializeAsHTML({ onlySelection: true }); - assert.equal((output.match(new RegExp('
terminal<\/span><\/div>', 'g')) || []).length, 1, output); + assert.equal((output.match(/
terminal<\/span><\/div>/g) || []).length, 1, output); }); it('cells with bold styling', async () => { await writeP(terminal, ' ' + sgr('1') + 'terminal' + sgr('22') + ' '); const output = serializeAddon.serializeAsHTML(); - assert.equal((output.match(new RegExp('terminal<\/span>', 'g')) || []).length, 1, output); + assert.equal((output.match(/terminal<\/span>/g) || []).length, 1, output); }); it('cells with italic styling', async () => { await writeP(terminal, ' ' + sgr('3') + 'terminal' + sgr('23') + ' '); const output = serializeAddon.serializeAsHTML(); - assert.equal((output.match(new RegExp('terminal<\/span>', 'g')) || []).length, 1, output); + assert.equal((output.match(/terminal<\/span>/g) || []).length, 1, output); }); it('cells with inverse styling', async () => { await writeP(terminal, ' ' + sgr('7') + 'terminal' + sgr('27') + ' '); const output = serializeAddon.serializeAsHTML(); - assert.equal((output.match(new RegExp('terminal<\/span>', 'g')) || []).length, 1, output); + assert.equal((output.match(/terminal<\/span>/g) || []).length, 1, output); }); it('cells with underline styling', async () => { await writeP(terminal, ' ' + sgr('4') + 'terminal' + sgr('24') + ' '); const output = serializeAddon.serializeAsHTML(); - assert.equal((output.match(new RegExp('terminal<\/span>', 'g')) || []).length, 1, output); + assert.equal((output.match(/terminal<\/span>/g) || []).length, 1, output); }); it('cells with invisible styling', async () => { await writeP(terminal, ' ' + sgr('8') + 'terminal' + sgr('28') + ' '); const output = serializeAddon.serializeAsHTML(); - assert.equal((output.match(new RegExp('terminal<\/span>', 'g')) || []).length, 1, output); + assert.equal((output.match(/terminal<\/span>/g) || []).length, 1, output); }); it('cells with dim styling', async () => { await writeP(terminal, ' ' + sgr('2') + 'terminal' + sgr('22') + ' '); const output = serializeAddon.serializeAsHTML(); - assert.equal((output.match(new RegExp('terminal<\/span>', 'g')) || []).length, 1, output); + assert.equal((output.match(/terminal<\/span>/g) || []).length, 1, output); }); it('cells with strikethrough styling', async () => { await writeP(terminal, ' ' + sgr('9') + 'terminal' + sgr('29') + ' '); const output = serializeAddon.serializeAsHTML(); - assert.equal((output.match(new RegExp('terminal<\/span>', 'g')) || []).length, 1, output); + assert.equal((output.match(/terminal<\/span>/g) || []).length, 1, output); }); it('cells with combined styling', async () => { await writeP(terminal, sgr('1') + ' ' + sgr('9') + 'termi' + sgr('22') + 'nal' + sgr('29') + ' '); const output = serializeAddon.serializeAsHTML(); - assert.equal((output.match(new RegExp(' <\/span>', 'g')) || []).length, 1, output); - assert.equal((output.match(new RegExp('termi<\/span>', 'g')) || []).length, 1, output); - assert.equal((output.match(new RegExp('nal<\/span>', 'g')) || []).length, 1, output); + assert.equal((output.match(/ <\/span>/g) || []).length, 1, output); + assert.equal((output.match(/termi<\/span>/g) || []).length, 1, output); + assert.equal((output.match(/nal<\/span>/g) || []).length, 1, output); }); it('cells with color styling', async () => { await writeP(terminal, ' ' + sgr('38;5;46') + 'terminal' + sgr('39') + ' '); const output = serializeAddon.serializeAsHTML(); - assert.equal((output.match(new RegExp('terminal<\/span>', 'g')) || []).length, 1, output); + assert.equal((output.match(/terminal<\/span>/g) || []).length, 1, output); }); it('cells with background styling', async () => { await writeP(terminal, ' ' + sgr('48;5;46') + 'terminal' + sgr('49') + ' '); const output = serializeAddon.serializeAsHTML(); - assert.equal((output.match(new RegExp('terminal<\/span>', 'g')) || []).length, 1, output); + assert.equal((output.match(/terminal<\/span>/g) || []).length, 1, output); }); it('empty terminal with default options', async () => { const output = serializeAddon.serializeAsHTML(); - assert.equal((output.match(new RegExp('color: #000000; background-color: #ffffff; font-family: courier-new, courier, monospace; font-size: 15px;', 'g')) || []).length, 1, output); + assert.equal((output.match(/color: #000000; background-color: #ffffff; font-family: courier-new, courier, monospace; font-size: 15px;/g) || []).length, 1, output); }); it('empty terminal with custom options', async () => { @@ -193,13 +193,13 @@ describe('xterm-addon-serialize html', () => { const output = serializeAddon.serializeAsHTML({ includeGlobalBackground: true }); - assert.equal((output.match(new RegExp('color: #ff00ff; background-color: #00ff00; font-family: verdana; font-size: 20px;', 'g')) || []).length, 1, output); + assert.equal((output.match(/color: #ff00ff; background-color: #00ff00; font-family: verdana; font-size: 20px;/g) || []).length, 1, output); }); it('empty terminal with background included', async () => { const output = serializeAddon.serializeAsHTML({ includeGlobalBackground: true }); - assert.equal((output.match(new RegExp('color: #ffffff; background-color: #000000; font-family: courier-new, courier, monospace; font-size: 15px;', 'g')) || []).length, 1, output); + assert.equal((output.match(/color: #ffffff; background-color: #000000; font-family: courier-new, courier, monospace; font-size: 15px;/g) || []).length, 1, output); }); }); diff --git a/addons/xterm-addon-serialize/src/SerializeAddon.ts b/addons/xterm-addon-serialize/src/SerializeAddon.ts index 25a42e65..8d6f8b36 100644 --- a/addons/xterm-addon-serialize/src/SerializeAddon.ts +++ b/addons/xterm-addon-serialize/src/SerializeAddon.ts @@ -544,7 +544,7 @@ export class HTMLSerializeHandler extends BaseSerializeHandler { return target; } - targetLength = targetLength - target.length; + targetLength -= target.length; if (targetLength > padString.length) { padString += padString.repeat(targetLength / padString.length); } diff --git a/addons/xterm-addon-serialize/test/SerializeAddon.api.ts b/addons/xterm-addon-serialize/test/SerializeAddon.api.ts index 334016e6..5c4ae437 100644 --- a/addons/xterm-addon-serialize/test/SerializeAddon.api.ts +++ b/addons/xterm-addon-serialize/test/SerializeAddon.api.ts @@ -14,7 +14,7 @@ let page: Page; const width = 800; const height = 600; -const writeRawSync = (page: any, str: string): Promise => writeSync(page, '\' +' + JSON.stringify(str) + '+ \''); +const writeRawSync = (page: any, str: string): Promise => writeSync(page, `' +` + JSON.stringify(str) + `+ '`); const testNormalScreenEqual = async (page: any, str: string): Promise => { await writeRawSync(page, str); diff --git a/src/browser/ColorManager.ts b/src/browser/ColorManager.ts index b4b57c67..e7ac10ba 100644 --- a/src/browser/ColorManager.ts +++ b/src/browser/ColorManager.ts @@ -185,7 +185,7 @@ export class ColorManager implements IColorManager { foreground: this.colors.foreground, background: this.colors.background, cursor: this.colors.cursor, - ansi: [...this.colors.ansi] + ansi: this.colors.ansi.slice() }; } diff --git a/src/browser/renderer/atlas/CharAtlasUtils.ts b/src/browser/renderer/atlas/CharAtlasUtils.ts index be92727a..696c6c12 100644 --- a/src/browser/renderer/atlas/CharAtlasUtils.ts +++ b/src/browser/renderer/atlas/CharAtlasUtils.ts @@ -16,7 +16,7 @@ export function generateConfig(scaledCharWidth: number, scaledCharHeight: number cursor: undefined, cursorAccent: undefined, selection: undefined, - ansi: [...colors.ansi] + ansi: colors.ansi.slice() }; return { devicePixelRatio: window.devicePixelRatio, diff --git a/src/common/input/XParseColor.ts b/src/common/input/XParseColor.ts index 8c023a38..fd23ec4b 100644 --- a/src/common/input/XParseColor.ts +++ b/src/common/input/XParseColor.ts @@ -5,7 +5,7 @@ // 'rgb:' rule - matching: r/g/b | rr/gg/bb | rrr/ggg/bbb | rrrr/gggg/bbbb (hex digits) -const RGB_REX = /^([\da-f]{1})\/([\da-f]{1})\/([\da-f]{1})$|^([\da-f]{2})\/([\da-f]{2})\/([\da-f]{2})$|^([\da-f]{3})\/([\da-f]{3})\/([\da-f]{3})$|^([\da-f]{4})\/([\da-f]{4})\/([\da-f]{4})$/; +const RGB_REX = /^([\da-f])\/([\da-f])\/([\da-f])$|^([\da-f]{2})\/([\da-f]{2})\/([\da-f]{2})$|^([\da-f]{3})\/([\da-f]{3})\/([\da-f]{3})$|^([\da-f]{4})\/([\da-f]{4})\/([\da-f]{4})$/; // '#...' rule - matching any hex digits const HASH_REX = /^[\da-f]+$/; From a5a9c3b3684797725da03b402c946262b35bced1 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 8 Mar 2022 14:45:14 -0600 Subject: [PATCH 013/245] add scroll decorations --- css/xterm.css | 14 ++++++ demo/client.ts | 9 ++++ demo/index.html | 1 + src/browser/services/DecorationService.ts | 53 ++++++++++++++++++++++- typings/xterm.d.ts | 6 +++ 5 files changed, 82 insertions(+), 1 deletion(-) diff --git a/css/xterm.css b/css/xterm.css index ab3965b4..3ae486c4 100644 --- a/css/xterm.css +++ b/css/xterm.css @@ -178,3 +178,17 @@ z-index: 6; position: absolute; } + +.xterm-decoration-scrollbar { + z-index: 7; + position: fixed; + top: 0px; + right: 0px; + width: 50px; +} + +.xterm-decoration-scrollbar.demo-scrollbar { + height: 436px; + left: 872px; + top: 83px; +} diff --git a/demo/client.ts b/demo/client.ts index aee23401..ec7e95ee 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -151,6 +151,7 @@ if (document.location.pathname === '/test') { document.getElementById('custom-glyph').addEventListener('click', writeCustomGlyphHandler); document.getElementById('load-test').addEventListener('click', loadTest); document.getElementById('add-decoration').addEventListener('click', addDecoration); + document.getElementById('add-scrollbar-decoration').addEventListener('click', addScrollbarDecoration); } function createTerminal(): void { @@ -550,3 +551,11 @@ function addDecoration() { decoration.element.style.backgroundColor = 'red'; }); } + +function addScrollbarDecoration() { + term.registerDecoration({marker: term.addMarker(1), scrollbarDecorationColor: 'red'}); + term.registerDecoration({marker: term.addMarker(3), scrollbarDecorationColor: 'green'}); + term.registerDecoration({marker: term.addMarker(5), scrollbarDecorationColor: 'blue'}); + document.querySelector('.xterm-decoration-scrollbar').classList.add('demo-scrollbar'); +} + diff --git a/demo/index.html b/demo/index.html index e024222c..dab52eec 100644 --- a/demo/index.html +++ b/demo/index.html @@ -69,6 +69,7 @@ +
diff --git a/src/browser/services/DecorationService.ts b/src/browser/services/DecorationService.ts index ed3c7224..dfbcd918 100644 --- a/src/browser/services/DecorationService.ts +++ b/src/browser/services/DecorationService.ts @@ -3,6 +3,7 @@ * @license MIT */ +import { addDisposableDomListener } from 'browser/Lifecycle'; import { IDecorationService, IRenderService } from 'browser/services/Services'; import { EventEmitter, IEvent } from 'common/EventEmitter'; import { Disposable } from 'common/Lifecycle'; @@ -17,7 +18,11 @@ export class DecorationService extends Disposable implements IDecorationService private _renderService: IRenderService | undefined; private _animationFrame: number | undefined; - constructor(@IInstantiationService private readonly _instantiationService: IInstantiationService) { super(); } + private _scrollbarDecorationCanvas: CanvasRenderingContext2D | null = null; + private _scrollbarDecorationNode: HTMLCanvasElement | undefined; + private _scrollbarDecorations: { marker: IMarker, color?: string}[] = []; + + constructor(@IInstantiationService private readonly _instantiationService: IInstantiationService, @IBufferService private readonly _bufferService: IBufferService) { super(); } public attachToDom(screenElement: HTMLElement, renderService: IRenderService): void { this._renderService = renderService; @@ -27,12 +32,22 @@ export class DecorationService extends Disposable implements IDecorationService screenElement.appendChild(this._container); this.register(this._renderService.onRenderedBufferChange(() => this.refresh())); this.register(this._renderService.onDimensionsChange(() => this.refresh(true))); + this.register(addDisposableDomListener(window, 'resize', () => this._refreshScollbarDecorations())); } public registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined { if (decorationOptions.marker.isDisposed || !this._container) { return undefined; } + if (decorationOptions.scrollbarDecorationColor) { + if (!this._scrollbarDecorationCanvas) { + this._scrollbarDecorationNode = document.createElement('canvas'); + this._scrollbarDecorationNode.classList.add('xterm-decoration-scrollbar'); + this._screenElement?.parentElement?.appendChild(this._scrollbarDecorationNode); + this._scrollbarDecorationCanvas = this._scrollbarDecorationNode.getContext('2d'); + } + this._registerScrollbarDecoration(decorationOptions.marker, decorationOptions.scrollbarDecorationColor); + } const decoration = this._instantiationService.createInstance(Decoration, decorationOptions, this._container); this._decorations.push(decoration); decoration.onDispose(() => this._decorations.splice(this._decorations.indexOf(decoration), 1)); @@ -57,6 +72,7 @@ export class DecorationService extends Disposable implements IDecorationService for (const decoration of this._decorations) { decoration.render(this._renderService, shouldRecreate); } + this._refreshScollbarDecorations(); } public dispose(): void { @@ -67,6 +83,41 @@ export class DecorationService extends Disposable implements IDecorationService this._screenElement.removeChild(this._container); } } + + private _registerScrollbarDecoration(marker: IMarker, color?: string): HTMLCanvasElement | undefined { + this._scrollbarDecorations.push({ marker, color }); + if (!this._scrollbarDecorationCanvas) { + return; + } + this._addScrollbarDecoration(marker, color); + } + + private _refreshScollbarDecorations(): void { + if (!this._scrollbarDecorationCanvas) { + return; + } + if (this._scrollbarDecorationNode) { + this._scrollbarDecorationNode.style.width = '7px'; + this._scrollbarDecorationNode.style.height = `${this._screenElement?.parentElement!.clientHeight}px`; + this._scrollbarDecorationNode.width = Math.floor(7*window.devicePixelRatio); + this._scrollbarDecorationNode.height = Math.floor(436*window.devicePixelRatio); + } + this._scrollbarDecorationCanvas.clearRect(0, 0, this._scrollbarDecorationCanvas.canvas.width, this._scrollbarDecorationCanvas.canvas.height); + for (const scrollbarDecoration of this._scrollbarDecorations) { + this._addScrollbarDecoration(scrollbarDecoration.marker, scrollbarDecoration.color); + } + } + + private _addScrollbarDecoration(marker: IMarker, color?: string): void { + if (!this._scrollbarDecorationCanvas || !this._screenElement?.parentElement?.clientHeight) { + return; + } + this._scrollbarDecorationCanvas.lineWidth = 1; + if (color) { + this._scrollbarDecorationCanvas.strokeStyle = color; + } + this._scrollbarDecorationCanvas.strokeRect(0, (marker.line / (this._bufferService.buffers.active.lines.length) * Math.floor(436*window.devicePixelRatio)), Math.floor(7*window.devicePixelRatio), window.devicePixelRatio); + } } export class Decoration extends Disposable implements IDecoration { private readonly _marker: IMarker; diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 2cd4daa6..eb31c4a0 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -470,6 +470,12 @@ declare module 'xterm' { * cell height */ height?: number; + + /** + * When provided, renders the decoration in the scrollbar + * with the given color + */ + scrollbarDecorationColor?: string; } /** From 4350c7ffcd5e52cddae85ee46cd5e9a3677e14e0 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 8 Mar 2022 16:23:51 -0600 Subject: [PATCH 014/245] get it to work --- src/browser/Terminal.ts | 9 +++- src/browser/services/DecorationService.ts | 60 +++++++++++++++++------ src/browser/services/Services.ts | 2 +- 3 files changed, 54 insertions(+), 17 deletions(-) diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index 08963933..a8263cb5 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -70,6 +70,7 @@ export class Terminal extends CoreTerminal implements ITerminal { private _viewportElement: HTMLElement | undefined; private _helperContainer: HTMLElement | undefined; private _compositionView: HTMLElement | undefined; + private _scrollbarDecorationNode: HTMLCanvasElement | undefined; // private _visualBellTimer: number; @@ -471,6 +472,12 @@ export class Terminal extends CoreTerminal implements ITerminal { this._viewportElement = document.createElement('div'); this._viewportElement.classList.add('xterm-viewport'); fragment.appendChild(this._viewportElement); + + //TODO: make this opt in, must be done before the scroll area in order to show up + this._scrollbarDecorationNode = document.createElement('canvas'); + this._scrollbarDecorationNode.classList.add('xterm-decoration-scrollbar'); + this._viewportElement?.appendChild(this._scrollbarDecorationNode); + this._viewportScrollArea = document.createElement('div'); this._viewportScrollArea.classList.add('xterm-scroll-area'); this._viewportElement.appendChild(this._viewportScrollArea); @@ -577,7 +584,7 @@ export class Terminal extends CoreTerminal implements ITerminal { this.linkifier.attachToDom(this.element, this._mouseZoneManager); this.linkifier2.attachToDom(this.screenElement, this._mouseService, this._renderService); - this.decorationService.attachToDom(this.screenElement, this._renderService, this._bufferService); + this.decorationService.attachToDom(this._scrollbarDecorationNode, this.screenElement, this._viewportElement, this._renderService); // This event listener must be registered aftre MouseZoneManager is created this.register(addDisposableDomListener(this.element, 'mousedown', (e: MouseEvent) => this._selectionService!.onMouseDown(e))); diff --git a/src/browser/services/DecorationService.ts b/src/browser/services/DecorationService.ts index dfbcd918..d5b9bbc8 100644 --- a/src/browser/services/DecorationService.ts +++ b/src/browser/services/DecorationService.ts @@ -15,6 +15,7 @@ export class DecorationService extends Disposable implements IDecorationService private readonly _decorations: Decoration[] = []; private _container: HTMLElement | undefined; private _screenElement: HTMLElement | undefined; + private _viewportElement: HTMLElement | undefined; private _renderService: IRenderService | undefined; private _animationFrame: number | undefined; @@ -24,9 +25,11 @@ export class DecorationService extends Disposable implements IDecorationService constructor(@IInstantiationService private readonly _instantiationService: IInstantiationService, @IBufferService private readonly _bufferService: IBufferService) { super(); } - public attachToDom(screenElement: HTMLElement, renderService: IRenderService): void { + public attachToDom(scrollbarDecorationNode: HTMLCanvasElement, screenElement: HTMLElement, viewportElement: HTMLElement, renderService: IRenderService): void { this._renderService = renderService; this._screenElement = screenElement; + this._viewportElement = viewportElement; + this._scrollbarDecorationNode = scrollbarDecorationNode; this._container = document.createElement('div'); this._container.classList.add('xterm-decoration-container'); screenElement.appendChild(this._container); @@ -36,17 +39,14 @@ export class DecorationService extends Disposable implements IDecorationService } public registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined { - if (decorationOptions.marker.isDisposed || !this._container) { + if (decorationOptions.marker.isDisposed || !this._container || !this._scrollbarDecorationNode) { return undefined; } if (decorationOptions.scrollbarDecorationColor) { if (!this._scrollbarDecorationCanvas) { - this._scrollbarDecorationNode = document.createElement('canvas'); - this._scrollbarDecorationNode.classList.add('xterm-decoration-scrollbar'); - this._screenElement?.parentElement?.appendChild(this._scrollbarDecorationNode); this._scrollbarDecorationCanvas = this._scrollbarDecorationNode.getContext('2d'); } - this._registerScrollbarDecoration(decorationOptions.marker, decorationOptions.scrollbarDecorationColor); + return this._registerScrollbarDecoration(decorationOptions.marker, decorationOptions.scrollbarDecorationColor); } const decoration = this._instantiationService.createInstance(Decoration, decorationOptions, this._container); this._decorations.push(decoration); @@ -82,23 +82,23 @@ export class DecorationService extends Disposable implements IDecorationService if (this._screenElement && this._container && this._screenElement.contains(this._container)) { this._screenElement.removeChild(this._container); } + this._scrollbarDecorations = []; + this._scrollbarDecorationNode?.remove(); } - private _registerScrollbarDecoration(marker: IMarker, color?: string): HTMLCanvasElement | undefined { + private _registerScrollbarDecoration(marker: IMarker, color?: string): IDecoration | undefined { this._scrollbarDecorations.push({ marker, color }); - if (!this._scrollbarDecorationCanvas) { - return; - } - this._addScrollbarDecoration(marker, color); + //TODO: marker on dispose + return this._addScrollbarDecoration(marker, color); } private _refreshScollbarDecorations(): void { - if (!this._scrollbarDecorationCanvas) { + if (!this._scrollbarDecorationCanvas || !this._viewportElement) { return; } if (this._scrollbarDecorationNode) { this._scrollbarDecorationNode.style.width = '7px'; - this._scrollbarDecorationNode.style.height = `${this._screenElement?.parentElement!.clientHeight}px`; + this._scrollbarDecorationNode.style.height = `${this._viewportElement.clientHeight}px`; this._scrollbarDecorationNode.width = Math.floor(7*window.devicePixelRatio); this._scrollbarDecorationNode.height = Math.floor(436*window.devicePixelRatio); } @@ -108,8 +108,8 @@ export class DecorationService extends Disposable implements IDecorationService } } - private _addScrollbarDecoration(marker: IMarker, color?: string): void { - if (!this._scrollbarDecorationCanvas || !this._screenElement?.parentElement?.clientHeight) { + private _addScrollbarDecoration(marker: IMarker, color?: string): IDecoration | undefined { + if (!this._scrollbarDecorationCanvas || !this._viewportElement?.clientHeight) { return; } this._scrollbarDecorationCanvas.lineWidth = 1; @@ -117,8 +117,38 @@ export class DecorationService extends Disposable implements IDecorationService this._scrollbarDecorationCanvas.strokeStyle = color; } this._scrollbarDecorationCanvas.strokeRect(0, (marker.line / (this._bufferService.buffers.active.lines.length) * Math.floor(436*window.devicePixelRatio)), Math.floor(7*window.devicePixelRatio), window.devicePixelRatio); + if (this._scrollbarDecorationNode) { + return new ScrollbarDecoration({marker, scrollbarDecorationColor: color }, this._scrollbarDecorationNode); + } + return undefined; } } +export class ScrollbarDecoration extends Disposable implements IDecoration { + private readonly _marker: IMarker; + private _element: HTMLElement | undefined; + + public isDisposed: boolean = false; + + public get element(): HTMLElement | undefined { return this._element; } + public get marker(): IMarker { return this._marker; } + + private _onDispose = new EventEmitter(); + public get onDispose(): IEvent { return this._onDispose.event; } + + private _onRender = new EventEmitter(); + public get onRender(): IEvent { return this._onRender.event; } + + constructor( + options: IDecorationOptions, + element: HTMLCanvasElement + ) { + super(); + this._marker = options.marker; + this._element = element; + this._marker.onDispose(() => this.dispose()); + } +} + export class Decoration extends Disposable implements IDecoration { private readonly _marker: IMarker; private _element: HTMLElement | undefined; diff --git a/src/browser/services/Services.ts b/src/browser/services/Services.ts index 7faf3f0f..caf6ec5a 100644 --- a/src/browser/services/Services.ts +++ b/src/browser/services/Services.ts @@ -121,5 +121,5 @@ export const IDecorationService = createDecorator('Decoratio export interface IDecorationService extends IDisposable { registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined; refresh(): void; - attachToDom(screenElement: HTMLElement, renderService: IRenderService, bufferService: IBufferService): void; + attachToDom(scrollbarDecorationNode: HTMLCanvasElement, screenElement: HTMLElement, viewportElement: HTMLElement, renderService: IRenderService): void; } From 3a499fc35dae6163d16230e4b806e8be202adb7d Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 8 Mar 2022 20:31:40 -0600 Subject: [PATCH 015/245] clear on dispose --- demo/client.ts | 2 +- src/browser/services/DecorationService.ts | 57 +++++++++++++---------- 2 files changed, 34 insertions(+), 25 deletions(-) diff --git a/demo/client.ts b/demo/client.ts index ec7e95ee..7eb9247a 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -553,9 +553,9 @@ function addDecoration() { } function addScrollbarDecoration() { + document.querySelector('.xterm-decoration-scrollbar').classList.add('demo-scrollbar'); term.registerDecoration({marker: term.addMarker(1), scrollbarDecorationColor: 'red'}); term.registerDecoration({marker: term.addMarker(3), scrollbarDecorationColor: 'green'}); term.registerDecoration({marker: term.addMarker(5), scrollbarDecorationColor: 'blue'}); - document.querySelector('.xterm-decoration-scrollbar').classList.add('demo-scrollbar'); } diff --git a/src/browser/services/DecorationService.ts b/src/browser/services/DecorationService.ts index d5b9bbc8..f21be5df 100644 --- a/src/browser/services/DecorationService.ts +++ b/src/browser/services/DecorationService.ts @@ -12,13 +12,14 @@ import { IDecorationOptions, IDecoration, IMarker } from 'xterm'; export class DecorationService extends Disposable implements IDecorationService { - private readonly _decorations: Decoration[] = []; private _container: HTMLElement | undefined; private _screenElement: HTMLElement | undefined; private _viewportElement: HTMLElement | undefined; private _renderService: IRenderService | undefined; private _animationFrame: number | undefined; + private readonly _bufferDecorations: BufferDecoration[] = []; + private _scrollbarDecorationCanvas: CanvasRenderingContext2D | null = null; private _scrollbarDecorationNode: HTMLCanvasElement | undefined; private _scrollbarDecorations: { marker: IMarker, color?: string}[] = []; @@ -46,13 +47,14 @@ export class DecorationService extends Disposable implements IDecorationService if (!this._scrollbarDecorationCanvas) { this._scrollbarDecorationCanvas = this._scrollbarDecorationNode.getContext('2d'); } + this._refreshScollbarDecorations(); return this._registerScrollbarDecoration(decorationOptions.marker, decorationOptions.scrollbarDecorationColor); } - const decoration = this._instantiationService.createInstance(Decoration, decorationOptions, this._container); - this._decorations.push(decoration); - decoration.onDispose(() => this._decorations.splice(this._decorations.indexOf(decoration), 1)); + const bufferDecoration = this._instantiationService.createInstance(BufferDecoration, decorationOptions, this._container); + this._bufferDecorations.push(bufferDecoration); + bufferDecoration.onDispose(() => this._bufferDecorations.splice(this._bufferDecorations.indexOf(bufferDecoration), 1)); this._queueRefresh(); - return decoration; + return bufferDecoration; } private _queueRefresh(): void { @@ -66,18 +68,13 @@ export class DecorationService extends Disposable implements IDecorationService } public refresh(shouldRecreate?: boolean): void { - if (!this._renderService) { - return; - } - for (const decoration of this._decorations) { - decoration.render(this._renderService, shouldRecreate); - } + this._refreshBufferDecorations(shouldRecreate); this._refreshScollbarDecorations(); } public dispose(): void { - for (const decoration of this._decorations) { - decoration.dispose(); + for (const bufferDecoration of this._bufferDecorations) { + bufferDecoration.dispose(); } if (this._screenElement && this._container && this._screenElement.contains(this._container)) { this._screenElement.removeChild(this._container); @@ -88,20 +85,26 @@ export class DecorationService extends Disposable implements IDecorationService private _registerScrollbarDecoration(marker: IMarker, color?: string): IDecoration | undefined { this._scrollbarDecorations.push({ marker, color }); - //TODO: marker on dispose return this._addScrollbarDecoration(marker, color); } - private _refreshScollbarDecorations(): void { - if (!this._scrollbarDecorationCanvas || !this._viewportElement) { + private _refreshBufferDecorations(shouldRecreate?: boolean): void { + if (!this._renderService) { return; } - if (this._scrollbarDecorationNode) { - this._scrollbarDecorationNode.style.width = '7px'; - this._scrollbarDecorationNode.style.height = `${this._viewportElement.clientHeight}px`; - this._scrollbarDecorationNode.width = Math.floor(7*window.devicePixelRatio); - this._scrollbarDecorationNode.height = Math.floor(436*window.devicePixelRatio); + for (const bufferDecoration of this._bufferDecorations) { + bufferDecoration.render(this._renderService, shouldRecreate); } + } + + private _refreshScollbarDecorations(): void { + if (!this._scrollbarDecorationCanvas || !this._viewportElement || !this._scrollbarDecorationNode) { + return; + } + this._scrollbarDecorationNode.style.width = '7px'; + this._scrollbarDecorationNode.style.height = `${this._viewportElement.clientHeight}px`; + this._scrollbarDecorationNode.width = Math.floor(7*window.devicePixelRatio); + this._scrollbarDecorationNode.height = Math.floor(this._viewportElement.clientHeight*window.devicePixelRatio); this._scrollbarDecorationCanvas.clearRect(0, 0, this._scrollbarDecorationCanvas.canvas.width, this._scrollbarDecorationCanvas.canvas.height); for (const scrollbarDecoration of this._scrollbarDecorations) { this._addScrollbarDecoration(scrollbarDecoration.marker, scrollbarDecoration.color); @@ -116,9 +119,15 @@ export class DecorationService extends Disposable implements IDecorationService if (color) { this._scrollbarDecorationCanvas.strokeStyle = color; } - this._scrollbarDecorationCanvas.strokeRect(0, (marker.line / (this._bufferService.buffers.active.lines.length) * Math.floor(436*window.devicePixelRatio)), Math.floor(7*window.devicePixelRatio), window.devicePixelRatio); + this._scrollbarDecorationCanvas.strokeRect( + 0, + (marker.line / this._bufferService.buffers.active.lines.length) * Math.floor(this._viewportElement.clientHeight * window.devicePixelRatio), + Math.floor(7 * window.devicePixelRatio), + window.devicePixelRatio + ); if (this._scrollbarDecorationNode) { - return new ScrollbarDecoration({marker, scrollbarDecorationColor: color }, this._scrollbarDecorationNode); + const scrollbarDecoration = new ScrollbarDecoration({ marker, scrollbarDecorationColor: color }, this._scrollbarDecorationNode); + scrollbarDecoration.onDispose(() => this._scrollbarDecorationCanvas?.clearRect(0, 0, this._scrollbarDecorationCanvas.canvas.width, this._scrollbarDecorationCanvas.canvas.height)); } return undefined; } @@ -149,7 +158,7 @@ export class ScrollbarDecoration extends Disposable implements IDecoration { } } -export class Decoration extends Disposable implements IDecoration { +export class BufferDecoration extends Disposable implements IDecoration { private readonly _marker: IMarker; private _element: HTMLElement | undefined; From 785a8190a665a408563487e1027131098be6a671 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 8 Mar 2022 20:41:49 -0600 Subject: [PATCH 016/245] clean up --- src/browser/services/DecorationService.ts | 62 ++++++++++++++--------- 1 file changed, 37 insertions(+), 25 deletions(-) diff --git a/src/browser/services/DecorationService.ts b/src/browser/services/DecorationService.ts index f21be5df..bb5bd125 100644 --- a/src/browser/services/DecorationService.ts +++ b/src/browser/services/DecorationService.ts @@ -10,6 +10,10 @@ import { Disposable } from 'common/Lifecycle'; import { IBufferService, IInstantiationService } from 'common/services/Services'; import { IDecorationOptions, IDecoration, IMarker } from 'xterm'; +const enum ScrollbarConstants { + WIDTH = 7 +} + export class DecorationService extends Disposable implements IDecorationService { private _container: HTMLElement | undefined; @@ -22,7 +26,7 @@ export class DecorationService extends Disposable implements IDecorationService private _scrollbarDecorationCanvas: CanvasRenderingContext2D | null = null; private _scrollbarDecorationNode: HTMLCanvasElement | undefined; - private _scrollbarDecorations: { marker: IMarker, color?: string}[] = []; + private _scrollbarDecorations: { marker: IMarker, color: string }[] = []; constructor(@IInstantiationService private readonly _instantiationService: IInstantiationService, @IBufferService private readonly _bufferService: IBufferService) { super(); } @@ -57,16 +61,6 @@ export class DecorationService extends Disposable implements IDecorationService return bufferDecoration; } - private _queueRefresh(): void { - if (this._animationFrame !== undefined) { - return; - } - this._animationFrame = window.requestAnimationFrame(() => { - this.refresh(); - this._animationFrame = undefined; - }); - } - public refresh(shouldRecreate?: boolean): void { this._refreshBufferDecorations(shouldRecreate); this._refreshScollbarDecorations(); @@ -83,9 +77,15 @@ export class DecorationService extends Disposable implements IDecorationService this._scrollbarDecorationNode?.remove(); } - private _registerScrollbarDecoration(marker: IMarker, color?: string): IDecoration | undefined { - this._scrollbarDecorations.push({ marker, color }); - return this._addScrollbarDecoration(marker, color); + + private _queueRefresh(): void { + if (this._animationFrame !== undefined) { + return; + } + this._animationFrame = window.requestAnimationFrame(() => { + this.refresh(); + this._animationFrame = undefined; + }); } private _refreshBufferDecorations(shouldRecreate?: boolean): void { @@ -97,37 +97,49 @@ export class DecorationService extends Disposable implements IDecorationService } } + private _registerScrollbarDecoration(marker: IMarker, color: string): IDecoration | undefined { + this._scrollbarDecorations.push({ marker, color }); + return this._addScrollbarDecoration(marker, color); + } + private _refreshScollbarDecorations(): void { if (!this._scrollbarDecorationCanvas || !this._viewportElement || !this._scrollbarDecorationNode) { return; } - this._scrollbarDecorationNode.style.width = '7px'; + this._scrollbarDecorationNode.style.width = `${ScrollbarConstants.WIDTH}px`; this._scrollbarDecorationNode.style.height = `${this._viewportElement.clientHeight}px`; - this._scrollbarDecorationNode.width = Math.floor(7*window.devicePixelRatio); - this._scrollbarDecorationNode.height = Math.floor(this._viewportElement.clientHeight*window.devicePixelRatio); + this._scrollbarDecorationNode.width = Math.floor(ScrollbarConstants.WIDTH * window.devicePixelRatio); + this._scrollbarDecorationNode.height = Math.floor(this._viewportElement.clientHeight * window.devicePixelRatio); this._scrollbarDecorationCanvas.clearRect(0, 0, this._scrollbarDecorationCanvas.canvas.width, this._scrollbarDecorationCanvas.canvas.height); + for (const scrollbarDecoration of this._scrollbarDecorations) { this._addScrollbarDecoration(scrollbarDecoration.marker, scrollbarDecoration.color); } } - private _addScrollbarDecoration(marker: IMarker, color?: string): IDecoration | undefined { - if (!this._scrollbarDecorationCanvas || !this._viewportElement?.clientHeight) { + private _addScrollbarDecoration(marker: IMarker, color: string): IDecoration | undefined { + if (!this._scrollbarDecorationCanvas || !this._scrollbarDecorationNode) { return; } this._scrollbarDecorationCanvas.lineWidth = 1; - if (color) { - this._scrollbarDecorationCanvas.strokeStyle = color; - } + this._scrollbarDecorationCanvas.strokeStyle = color; this._scrollbarDecorationCanvas.strokeRect( 0, - (marker.line / this._bufferService.buffers.active.lines.length) * Math.floor(this._viewportElement.clientHeight * window.devicePixelRatio), - Math.floor(7 * window.devicePixelRatio), + this._scrollbarDecorationNode.height * (marker.line / this._bufferService.buffers.active.lines.length), + this._scrollbarDecorationNode.width, window.devicePixelRatio ); if (this._scrollbarDecorationNode) { const scrollbarDecoration = new ScrollbarDecoration({ marker, scrollbarDecorationColor: color }, this._scrollbarDecorationNode); - scrollbarDecoration.onDispose(() => this._scrollbarDecorationCanvas?.clearRect(0, 0, this._scrollbarDecorationCanvas.canvas.width, this._scrollbarDecorationCanvas.canvas.height)); + scrollbarDecoration.onDispose(() => { + this._scrollbarDecorationCanvas?.clearRect( + 0, + 0, + this._scrollbarDecorationCanvas.canvas.width, + this._scrollbarDecorationCanvas.canvas.height + ); + }); + return scrollbarDecoration; } return undefined; } From d46a68592575c533f11373dee04fbef6cb1b9d20 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 8 Mar 2022 20:49:32 -0600 Subject: [PATCH 017/245] more cleanup --- src/browser/services/DecorationService.ts | 83 +++++++++++++---------- src/browser/services/Services.ts | 3 +- 2 files changed, 48 insertions(+), 38 deletions(-) diff --git a/src/browser/services/DecorationService.ts b/src/browser/services/DecorationService.ts index bb5bd125..25d413fa 100644 --- a/src/browser/services/DecorationService.ts +++ b/src/browser/services/DecorationService.ts @@ -38,8 +38,8 @@ export class DecorationService extends Disposable implements IDecorationService this._container = document.createElement('div'); this._container.classList.add('xterm-decoration-container'); screenElement.appendChild(this._container); - this.register(this._renderService.onRenderedBufferChange(() => this.refresh())); - this.register(this._renderService.onDimensionsChange(() => this.refresh(true))); + this.register(this._renderService.onRenderedBufferChange(() => this._refresh())); + this.register(this._renderService.onDimensionsChange(() => this._refresh(true))); this.register(addDisposableDomListener(window, 'resize', () => this._refreshScollbarDecorations())); } @@ -48,22 +48,9 @@ export class DecorationService extends Disposable implements IDecorationService return undefined; } if (decorationOptions.scrollbarDecorationColor) { - if (!this._scrollbarDecorationCanvas) { - this._scrollbarDecorationCanvas = this._scrollbarDecorationNode.getContext('2d'); - } - this._refreshScollbarDecorations(); return this._registerScrollbarDecoration(decorationOptions.marker, decorationOptions.scrollbarDecorationColor); } - const bufferDecoration = this._instantiationService.createInstance(BufferDecoration, decorationOptions, this._container); - this._bufferDecorations.push(bufferDecoration); - bufferDecoration.onDispose(() => this._bufferDecorations.splice(this._bufferDecorations.indexOf(bufferDecoration), 1)); - this._queueRefresh(); - return bufferDecoration; - } - - public refresh(shouldRecreate?: boolean): void { - this._refreshBufferDecorations(shouldRecreate); - this._refreshScollbarDecorations(); + return this._registerBufferDecoration(decorationOptions); } public dispose(): void { @@ -77,45 +64,44 @@ export class DecorationService extends Disposable implements IDecorationService this._scrollbarDecorationNode?.remove(); } + private _refresh(shouldRecreate?: boolean): void { + this._refreshBufferDecorations(shouldRecreate); + this._refreshScollbarDecorations(); + } private _queueRefresh(): void { if (this._animationFrame !== undefined) { return; } this._animationFrame = window.requestAnimationFrame(() => { - this.refresh(); + this._refresh(); this._animationFrame = undefined; }); } - private _refreshBufferDecorations(shouldRecreate?: boolean): void { - if (!this._renderService) { + private _registerBufferDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined { + if (!this._container) { return; } - for (const bufferDecoration of this._bufferDecorations) { - bufferDecoration.render(this._renderService, shouldRecreate); - } + const bufferDecoration = this._instantiationService.createInstance(BufferDecoration, decorationOptions, this._container); + this._bufferDecorations.push(bufferDecoration); + bufferDecoration.onDispose(() => this._bufferDecorations.splice(this._bufferDecorations.indexOf(bufferDecoration), 1)); + this._queueRefresh(); + return bufferDecoration; } private _registerScrollbarDecoration(marker: IMarker, color: string): IDecoration | undefined { + if (!this._scrollbarDecorationNode) { + return; + } + if (!this._scrollbarDecorationCanvas) { + this._scrollbarDecorationCanvas = this._scrollbarDecorationNode.getContext('2d'); + this._refreshScollbarDecorations(); + } this._scrollbarDecorations.push({ marker, color }); return this._addScrollbarDecoration(marker, color); } - private _refreshScollbarDecorations(): void { - if (!this._scrollbarDecorationCanvas || !this._viewportElement || !this._scrollbarDecorationNode) { - return; - } - this._scrollbarDecorationNode.style.width = `${ScrollbarConstants.WIDTH}px`; - this._scrollbarDecorationNode.style.height = `${this._viewportElement.clientHeight}px`; - this._scrollbarDecorationNode.width = Math.floor(ScrollbarConstants.WIDTH * window.devicePixelRatio); - this._scrollbarDecorationNode.height = Math.floor(this._viewportElement.clientHeight * window.devicePixelRatio); - this._scrollbarDecorationCanvas.clearRect(0, 0, this._scrollbarDecorationCanvas.canvas.width, this._scrollbarDecorationCanvas.canvas.height); - - for (const scrollbarDecoration of this._scrollbarDecorations) { - this._addScrollbarDecoration(scrollbarDecoration.marker, scrollbarDecoration.color); - } - } private _addScrollbarDecoration(marker: IMarker, color: string): IDecoration | undefined { if (!this._scrollbarDecorationCanvas || !this._scrollbarDecorationNode) { @@ -143,6 +129,31 @@ export class DecorationService extends Disposable implements IDecorationService } return undefined; } + + private _refreshBufferDecorations(shouldRecreate?: boolean): void { + if (!this._renderService) { + return; + } + for (const bufferDecoration of this._bufferDecorations) { + bufferDecoration.render(this._renderService, shouldRecreate); + } + } + + private _refreshScollbarDecorations(): void { + if (!this._scrollbarDecorationCanvas || !this._viewportElement || !this._scrollbarDecorationNode) { + return; + } + this._scrollbarDecorationNode.style.width = `${ScrollbarConstants.WIDTH}px`; + this._scrollbarDecorationNode.style.height = `${this._viewportElement.clientHeight}px`; + this._scrollbarDecorationNode.width = Math.floor(ScrollbarConstants.WIDTH * window.devicePixelRatio); + this._scrollbarDecorationNode.height = Math.floor(this._viewportElement.clientHeight * window.devicePixelRatio); + this._scrollbarDecorationCanvas.clearRect(0, 0, this._scrollbarDecorationCanvas.canvas.width, this._scrollbarDecorationCanvas.canvas.height); + + for (const scrollbarDecoration of this._scrollbarDecorations) { + this._addScrollbarDecoration(scrollbarDecoration.marker, scrollbarDecoration.color); + } + } + } export class ScrollbarDecoration extends Disposable implements IDecoration { private readonly _marker: IMarker; diff --git a/src/browser/services/Services.ts b/src/browser/services/Services.ts index caf6ec5a..35943897 100644 --- a/src/browser/services/Services.ts +++ b/src/browser/services/Services.ts @@ -119,7 +119,6 @@ export interface ICharacterJoinerService { export const IDecorationService = createDecorator('DecorationService'); export interface IDecorationService extends IDisposable { - registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined; - refresh(): void; attachToDom(scrollbarDecorationNode: HTMLCanvasElement, screenElement: HTMLElement, viewportElement: HTMLElement, renderService: IRenderService): void; + registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined; } From 89112fb1aef20c993bd3a723683f8593f64797e3 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 8 Mar 2022 21:31:42 -0600 Subject: [PATCH 018/245] refactor to return / store IDecorations --- src/browser/services/DecorationService.ts | 72 +++++++++++++---------- 1 file changed, 40 insertions(+), 32 deletions(-) diff --git a/src/browser/services/DecorationService.ts b/src/browser/services/DecorationService.ts index 25d413fa..33aec4c7 100644 --- a/src/browser/services/DecorationService.ts +++ b/src/browser/services/DecorationService.ts @@ -23,10 +23,10 @@ export class DecorationService extends Disposable implements IDecorationService private _animationFrame: number | undefined; private readonly _bufferDecorations: BufferDecoration[] = []; + private _scrollDecorations: ScrollbarDecoration[] = []; private _scrollbarDecorationCanvas: CanvasRenderingContext2D | null = null; private _scrollbarDecorationNode: HTMLCanvasElement | undefined; - private _scrollbarDecorations: { marker: IMarker, color: string }[] = []; constructor(@IInstantiationService private readonly _instantiationService: IInstantiationService, @IBufferService private readonly _bufferService: IBufferService) { super(); } @@ -60,7 +60,10 @@ export class DecorationService extends Disposable implements IDecorationService if (this._screenElement && this._container && this._screenElement.contains(this._container)) { this._screenElement.removeChild(this._container); } - this._scrollbarDecorations = []; + for (const scrollbarDecoration of this._scrollDecorations) { + scrollbarDecoration.dispose(); + } + this._scrollDecorations = []; this._scrollbarDecorationNode?.remove(); } @@ -91,43 +94,26 @@ export class DecorationService extends Disposable implements IDecorationService } private _registerScrollbarDecoration(marker: IMarker, color: string): IDecoration | undefined { - if (!this._scrollbarDecorationNode) { + if (!this._scrollbarDecorationNode || !this._viewportElement) { return; } if (!this._scrollbarDecorationCanvas) { this._scrollbarDecorationCanvas = this._scrollbarDecorationNode.getContext('2d'); this._refreshScollbarDecorations(); } - this._scrollbarDecorations.push({ marker, color }); - return this._addScrollbarDecoration(marker, color); - } - - - private _addScrollbarDecoration(marker: IMarker, color: string): IDecoration | undefined { - if (!this._scrollbarDecorationCanvas || !this._scrollbarDecorationNode) { - return; - } - this._scrollbarDecorationCanvas.lineWidth = 1; - this._scrollbarDecorationCanvas.strokeStyle = color; - this._scrollbarDecorationCanvas.strokeRect( + this._scrollbarDecorationCanvas!.lineWidth = 1; + this._scrollbarDecorationCanvas!.strokeStyle = color; + this._scrollbarDecorationCanvas!.strokeRect( 0, this._scrollbarDecorationNode.height * (marker.line / this._bufferService.buffers.active.lines.length), this._scrollbarDecorationNode.width, window.devicePixelRatio ); if (this._scrollbarDecorationNode) { - const scrollbarDecoration = new ScrollbarDecoration({ marker, scrollbarDecorationColor: color }, this._scrollbarDecorationNode); - scrollbarDecoration.onDispose(() => { - this._scrollbarDecorationCanvas?.clearRect( - 0, - 0, - this._scrollbarDecorationCanvas.canvas.width, - this._scrollbarDecorationCanvas.canvas.height - ); - }); + const scrollbarDecoration = new ScrollbarDecoration({ marker, scrollbarDecorationColor: color }, this._scrollbarDecorationNode, this._scrollbarDecorationCanvas!, this._viewportElement, this._bufferService); + this._scrollDecorations.push(scrollbarDecoration); return scrollbarDecoration; } - return undefined; } private _refreshBufferDecorations(shouldRecreate?: boolean): void { @@ -148,21 +134,22 @@ export class DecorationService extends Disposable implements IDecorationService this._scrollbarDecorationNode.width = Math.floor(ScrollbarConstants.WIDTH * window.devicePixelRatio); this._scrollbarDecorationNode.height = Math.floor(this._viewportElement.clientHeight * window.devicePixelRatio); this._scrollbarDecorationCanvas.clearRect(0, 0, this._scrollbarDecorationCanvas.canvas.width, this._scrollbarDecorationCanvas.canvas.height); - - for (const scrollbarDecoration of this._scrollbarDecorations) { - this._addScrollbarDecoration(scrollbarDecoration.marker, scrollbarDecoration.color); + for (const decoration of this._scrollDecorations) { + decoration.render(); } } } export class ScrollbarDecoration extends Disposable implements IDecoration { private readonly _marker: IMarker; - private _element: HTMLElement | undefined; + private _canvas: HTMLCanvasElement | undefined; + private _color: string | undefined; public isDisposed: boolean = false; - public get element(): HTMLElement | undefined { return this._element; } + public get element(): HTMLCanvasElement { return this._canvas!; } public get marker(): IMarker { return this._marker; } + public get color(): string { return this._color!; } private _onDispose = new EventEmitter(); public get onDispose(): IEvent { return this._onDispose.event; } @@ -172,12 +159,33 @@ export class ScrollbarDecoration extends Disposable implements IDecoration { constructor( options: IDecorationOptions, - element: HTMLCanvasElement + canvas: HTMLCanvasElement, + private readonly _ctx: CanvasRenderingContext2D, + private readonly _viewport: HTMLElement, + private readonly _bufferService: IBufferService ) { super(); this._marker = options.marker; - this._element = element; + this._canvas = canvas; + this._color = options.scrollbarDecorationColor; this._marker.onDispose(() => this.dispose()); + this.render(); + } + public render(): void { + this._ctx.lineWidth = 1; + this._ctx.strokeStyle = this.color; + this._ctx.strokeRect( + 0, + this.element.height * (this.marker.line / this._bufferService.buffers.active.lines.length), + this.element.width, + window.devicePixelRatio + ); + } + + public override dispose(): void { + this._ctx.clearRect(0, 0, this._ctx.canvas.width, this._ctx.canvas.height); + + super.dispose(); } } From 249e73cd255ec71c3e6e9ae37149e02910a8ec6b Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 8 Mar 2022 21:35:31 -0600 Subject: [PATCH 019/245] delete unused code --- src/browser/services/DecorationService.ts | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/src/browser/services/DecorationService.ts b/src/browser/services/DecorationService.ts index 33aec4c7..db8aaee2 100644 --- a/src/browser/services/DecorationService.ts +++ b/src/browser/services/DecorationService.ts @@ -101,19 +101,9 @@ export class DecorationService extends Disposable implements IDecorationService this._scrollbarDecorationCanvas = this._scrollbarDecorationNode.getContext('2d'); this._refreshScollbarDecorations(); } - this._scrollbarDecorationCanvas!.lineWidth = 1; - this._scrollbarDecorationCanvas!.strokeStyle = color; - this._scrollbarDecorationCanvas!.strokeRect( - 0, - this._scrollbarDecorationNode.height * (marker.line / this._bufferService.buffers.active.lines.length), - this._scrollbarDecorationNode.width, - window.devicePixelRatio - ); - if (this._scrollbarDecorationNode) { - const scrollbarDecoration = new ScrollbarDecoration({ marker, scrollbarDecorationColor: color }, this._scrollbarDecorationNode, this._scrollbarDecorationCanvas!, this._viewportElement, this._bufferService); - this._scrollDecorations.push(scrollbarDecoration); - return scrollbarDecoration; - } + const scrollbarDecoration = new ScrollbarDecoration({ marker, scrollbarDecorationColor: color }, this._scrollbarDecorationNode, this._scrollbarDecorationCanvas!, this._bufferService); + this._scrollDecorations.push(scrollbarDecoration); + return scrollbarDecoration; } private _refreshBufferDecorations(shouldRecreate?: boolean): void { @@ -161,7 +151,6 @@ export class ScrollbarDecoration extends Disposable implements IDecoration { options: IDecorationOptions, canvas: HTMLCanvasElement, private readonly _ctx: CanvasRenderingContext2D, - private readonly _viewport: HTMLElement, private readonly _bufferService: IBufferService ) { super(); From 1948ec886f7df322b3ec14fbaa87eaba7acf2872 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 9 Mar 2022 08:27:50 -0600 Subject: [PATCH 020/245] only clear relevant part of canvas on marker dispose --- src/browser/services/DecorationService.ts | 40 ++++++++++++++--------- 1 file changed, 25 insertions(+), 15 deletions(-) diff --git a/src/browser/services/DecorationService.ts b/src/browser/services/DecorationService.ts index db8aaee2..aa3a6e29 100644 --- a/src/browser/services/DecorationService.ts +++ b/src/browser/services/DecorationService.ts @@ -23,7 +23,7 @@ export class DecorationService extends Disposable implements IDecorationService private _animationFrame: number | undefined; private readonly _bufferDecorations: BufferDecoration[] = []; - private _scrollDecorations: ScrollbarDecoration[] = []; + private _scrollbarDecorations: ScrollbarDecoration[] = []; private _scrollbarDecorationCanvas: CanvasRenderingContext2D | null = null; private _scrollbarDecorationNode: HTMLCanvasElement | undefined; @@ -60,10 +60,10 @@ export class DecorationService extends Disposable implements IDecorationService if (this._screenElement && this._container && this._screenElement.contains(this._container)) { this._screenElement.removeChild(this._container); } - for (const scrollbarDecoration of this._scrollDecorations) { + for (const scrollbarDecoration of this._scrollbarDecorations) { scrollbarDecoration.dispose(); } - this._scrollDecorations = []; + this._scrollbarDecorations = []; this._scrollbarDecorationNode?.remove(); } @@ -86,11 +86,11 @@ export class DecorationService extends Disposable implements IDecorationService if (!this._container) { return; } - const bufferDecoration = this._instantiationService.createInstance(BufferDecoration, decorationOptions, this._container); - this._bufferDecorations.push(bufferDecoration); - bufferDecoration.onDispose(() => this._bufferDecorations.splice(this._bufferDecorations.indexOf(bufferDecoration), 1)); + const decoration = this._instantiationService.createInstance(BufferDecoration, decorationOptions, this._container); + this._bufferDecorations.push(decoration); + decoration.onDispose(() => this._bufferDecorations.splice(this._bufferDecorations.indexOf(decoration), 1)); this._queueRefresh(); - return bufferDecoration; + return decoration; } private _registerScrollbarDecoration(marker: IMarker, color: string): IDecoration | undefined { @@ -101,17 +101,18 @@ export class DecorationService extends Disposable implements IDecorationService this._scrollbarDecorationCanvas = this._scrollbarDecorationNode.getContext('2d'); this._refreshScollbarDecorations(); } - const scrollbarDecoration = new ScrollbarDecoration({ marker, scrollbarDecorationColor: color }, this._scrollbarDecorationNode, this._scrollbarDecorationCanvas!, this._bufferService); - this._scrollDecorations.push(scrollbarDecoration); - return scrollbarDecoration; + const decoration = new ScrollbarDecoration({ marker, scrollbarDecorationColor: color }, this._scrollbarDecorationNode, this._scrollbarDecorationCanvas!, this._bufferService); + decoration.onDispose(() => this._scrollbarDecorations.splice(this._scrollbarDecorations.indexOf(decoration), 1)); + this._scrollbarDecorations.push(decoration); + return decoration; } private _refreshBufferDecorations(shouldRecreate?: boolean): void { if (!this._renderService) { return; } - for (const bufferDecoration of this._bufferDecorations) { - bufferDecoration.render(this._renderService, shouldRecreate); + for (const decoration of this._bufferDecorations) { + decoration.render(this._renderService, shouldRecreate); } } @@ -124,7 +125,7 @@ export class DecorationService extends Disposable implements IDecorationService this._scrollbarDecorationNode.width = Math.floor(ScrollbarConstants.WIDTH * window.devicePixelRatio); this._scrollbarDecorationNode.height = Math.floor(this._viewportElement.clientHeight * window.devicePixelRatio); this._scrollbarDecorationCanvas.clearRect(0, 0, this._scrollbarDecorationCanvas.canvas.width, this._scrollbarDecorationCanvas.canvas.height); - for (const decoration of this._scrollDecorations) { + for (const decoration of this._scrollbarDecorations) { decoration.render(); } } @@ -172,8 +173,17 @@ export class ScrollbarDecoration extends Disposable implements IDecoration { } public override dispose(): void { - this._ctx.clearRect(0, 0, this._ctx.canvas.width, this._ctx.canvas.height); - + if (this._isDisposed) { + return; + } + this._ctx.clearRect( + 0, + this.element.height * (this.marker.line / this._bufferService.buffers.active.lines.length), + this.element.width, + window.devicePixelRatio + ); + this.isDisposed = true; + this._onDispose.fire(); super.dispose(); } } From cfd7912afa1c4c35fdebb5cd1217ec147bb28066 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 9 Mar 2022 08:40:40 -0600 Subject: [PATCH 021/245] enable registering a decoration before attach to dom has happened --- src/browser/Terminal.ts | 4 +- src/browser/services/DecorationService.ts | 51 ++++++++++++----------- src/browser/services/Services.ts | 2 +- 3 files changed, 29 insertions(+), 28 deletions(-) diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index a8263cb5..7dec6323 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -473,7 +473,7 @@ export class Terminal extends CoreTerminal implements ITerminal { this._viewportElement.classList.add('xterm-viewport'); fragment.appendChild(this._viewportElement); - //TODO: make this opt in, must be done before the scroll area in order to show up + // TODO: make this opt in, must be done before the scroll area in order to show up this._scrollbarDecorationNode = document.createElement('canvas'); this._scrollbarDecorationNode.classList.add('xterm-decoration-scrollbar'); this._viewportElement?.appendChild(this._scrollbarDecorationNode); @@ -584,7 +584,7 @@ export class Terminal extends CoreTerminal implements ITerminal { this.linkifier.attachToDom(this.element, this._mouseZoneManager); this.linkifier2.attachToDom(this.screenElement, this._mouseService, this._renderService); - this.decorationService.attachToDom(this._scrollbarDecorationNode, this.screenElement, this._viewportElement, this._renderService); + this.decorationService.attachToDom(this._renderService, this.screenElement, this._viewportElement, this._scrollbarDecorationNode); // This event listener must be registered aftre MouseZoneManager is created this.register(addDisposableDomListener(this.element, 'mousedown', (e: MouseEvent) => this._selectionService!.onMouseDown(e))); diff --git a/src/browser/services/DecorationService.ts b/src/browser/services/DecorationService.ts index aa3a6e29..6580a2c9 100644 --- a/src/browser/services/DecorationService.ts +++ b/src/browser/services/DecorationService.ts @@ -7,6 +7,7 @@ import { addDisposableDomListener } from 'browser/Lifecycle'; import { IDecorationService, IRenderService } from 'browser/services/Services'; import { EventEmitter, IEvent } from 'common/EventEmitter'; import { Disposable } from 'common/Lifecycle'; +import { BufferService } from 'common/services/BufferService'; import { IBufferService, IInstantiationService } from 'common/services/Services'; import { IDecorationOptions, IDecoration, IMarker } from 'xterm'; @@ -16,35 +17,33 @@ const enum ScrollbarConstants { export class DecorationService extends Disposable implements IDecorationService { - private _container: HTMLElement | undefined; - private _screenElement: HTMLElement | undefined; - private _viewportElement: HTMLElement | undefined; private _renderService: IRenderService | undefined; private _animationFrame: number | undefined; + private _screenElement: HTMLElement | undefined; + private _viewportElement: HTMLElement | undefined; + private _bufferDecorationContainer: HTMLElement | undefined; + private _scrollbarDecorationCanvas: CanvasRenderingContext2D | null = null; + private _scrollbarDecorationNode: HTMLCanvasElement | undefined; + private readonly _bufferDecorations: BufferDecoration[] = []; private _scrollbarDecorations: ScrollbarDecoration[] = []; - private _scrollbarDecorationCanvas: CanvasRenderingContext2D | null = null; - private _scrollbarDecorationNode: HTMLCanvasElement | undefined; + constructor(@IInstantiationService private readonly _instantiationService: IInstantiationService) { super(); } - constructor(@IInstantiationService private readonly _instantiationService: IInstantiationService, @IBufferService private readonly _bufferService: IBufferService) { super(); } - - public attachToDom(scrollbarDecorationNode: HTMLCanvasElement, screenElement: HTMLElement, viewportElement: HTMLElement, renderService: IRenderService): void { + public attachToDom(renderService: IRenderService, screenElement: HTMLElement, viewportElement: HTMLElement, scrollbarDecorationNode: HTMLCanvasElement): void { this._renderService = renderService; this._screenElement = screenElement; this._viewportElement = viewportElement; this._scrollbarDecorationNode = scrollbarDecorationNode; - this._container = document.createElement('div'); - this._container.classList.add('xterm-decoration-container'); - screenElement.appendChild(this._container); + this.register(this._renderService.onRenderedBufferChange(() => this._refresh())); this.register(this._renderService.onDimensionsChange(() => this._refresh(true))); this.register(addDisposableDomListener(window, 'resize', () => this._refreshScollbarDecorations())); } public registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined { - if (decorationOptions.marker.isDisposed || !this._container || !this._scrollbarDecorationNode) { + if (decorationOptions.marker.isDisposed) { return undefined; } if (decorationOptions.scrollbarDecorationColor) { @@ -57,12 +56,12 @@ export class DecorationService extends Disposable implements IDecorationService for (const bufferDecoration of this._bufferDecorations) { bufferDecoration.dispose(); } - if (this._screenElement && this._container && this._screenElement.contains(this._container)) { - this._screenElement.removeChild(this._container); - } for (const scrollbarDecoration of this._scrollbarDecorations) { scrollbarDecoration.dispose(); } + if (this._screenElement && this._bufferDecorationContainer && this._screenElement.contains(this._bufferDecorationContainer)) { + this._screenElement.removeChild(this._bufferDecorationContainer); + } this._scrollbarDecorations = []; this._scrollbarDecorationNode?.remove(); } @@ -83,10 +82,12 @@ export class DecorationService extends Disposable implements IDecorationService } private _registerBufferDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined { - if (!this._container) { - return; + if (this._screenElement && !this._bufferDecorationContainer) { + this._bufferDecorationContainer = document.createElement('div'); + this._bufferDecorationContainer.classList.add('xterm-decoration-container'); + this._screenElement.appendChild(this._bufferDecorationContainer); } - const decoration = this._instantiationService.createInstance(BufferDecoration, decorationOptions, this._container); + const decoration = new BufferDecoration(this._instantiationService.createInstance(BufferService), decorationOptions, this._bufferDecorationContainer); this._bufferDecorations.push(decoration); decoration.onDispose(() => this._bufferDecorations.splice(this._bufferDecorations.indexOf(decoration), 1)); this._queueRefresh(); @@ -101,7 +102,7 @@ export class DecorationService extends Disposable implements IDecorationService this._scrollbarDecorationCanvas = this._scrollbarDecorationNode.getContext('2d'); this._refreshScollbarDecorations(); } - const decoration = new ScrollbarDecoration({ marker, scrollbarDecorationColor: color }, this._scrollbarDecorationNode, this._scrollbarDecorationCanvas!, this._bufferService); + const decoration = this._instantiationService.createInstance(ScrollbarDecoration, { marker, scrollbarDecorationColor: color }, this._scrollbarDecorationNode, this._scrollbarDecorationCanvas!); decoration.onDispose(() => this._scrollbarDecorations.splice(this._scrollbarDecorations.indexOf(decoration), 1)); this._scrollbarDecorations.push(decoration); return decoration; @@ -129,8 +130,8 @@ export class DecorationService extends Disposable implements IDecorationService decoration.render(); } } - } + export class ScrollbarDecoration extends Disposable implements IDecoration { private readonly _marker: IMarker; private _canvas: HTMLCanvasElement | undefined; @@ -152,7 +153,7 @@ export class ScrollbarDecoration extends Disposable implements IDecoration { options: IDecorationOptions, canvas: HTMLCanvasElement, private readonly _ctx: CanvasRenderingContext2D, - private readonly _bufferService: IBufferService + @IBufferService private readonly _bufferService: IBufferService ) { super(); this._marker = options.marker; @@ -209,9 +210,9 @@ export class BufferDecoration extends Disposable implements IDecoration { public height: number; constructor( + private readonly _bufferService: IBufferService, options: IDecorationOptions, - private readonly _container: HTMLElement, - @IBufferService private readonly _bufferService: IBufferService + private readonly _container?: HTMLElement ) { super(); this.x = options.x ?? 0; @@ -236,7 +237,7 @@ export class BufferDecoration extends Disposable implements IDecoration { } private _createElement(renderService: IRenderService, shouldRecreate?: boolean): void { - if (shouldRecreate && this._element && this._container.contains(this._element)) { + if (shouldRecreate && this._element && this._container && this._container.contains(this._element)) { this._container.removeChild(this._element); } this._element = document.createElement('div'); @@ -272,7 +273,7 @@ export class BufferDecoration extends Disposable implements IDecoration { } public override dispose(): void { - if (this.isDisposed) { + if (this.isDisposed || !this._container) { return; } if (this._element && this._container.contains(this._element)) { diff --git a/src/browser/services/Services.ts b/src/browser/services/Services.ts index 35943897..6c9d29b5 100644 --- a/src/browser/services/Services.ts +++ b/src/browser/services/Services.ts @@ -119,6 +119,6 @@ export interface ICharacterJoinerService { export const IDecorationService = createDecorator('DecorationService'); export interface IDecorationService extends IDisposable { - attachToDom(scrollbarDecorationNode: HTMLCanvasElement, screenElement: HTMLElement, viewportElement: HTMLElement, renderService: IRenderService): void; + attachToDom(renderService: IRenderService, screenElement: HTMLElement, viewportElement: HTMLElement, scrollbarDecorationNode: HTMLCanvasElement): void; registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined; } From e797070121c6f1e3cf06ec950cbdc89248d48dfc Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 9 Mar 2022 08:42:12 -0600 Subject: [PATCH 022/245] call on render --- src/browser/services/DecorationService.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/browser/services/DecorationService.ts b/src/browser/services/DecorationService.ts index 6580a2c9..39d89294 100644 --- a/src/browser/services/DecorationService.ts +++ b/src/browser/services/DecorationService.ts @@ -171,6 +171,7 @@ export class ScrollbarDecoration extends Disposable implements IDecoration { this.element.width, window.devicePixelRatio ); + this._onRender.fire(this.element); } public override dispose(): void { From 724bbebc14d92ec4019bf0eeb7b8e85be08652b0 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 9 Mar 2022 08:46:09 -0600 Subject: [PATCH 023/245] assign element in render call --- src/browser/services/DecorationService.ts | 10 ++++++---- typings/xterm.d.ts | 4 ++-- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/browser/services/DecorationService.ts b/src/browser/services/DecorationService.ts index 39d89294..ee1bc2e7 100644 --- a/src/browser/services/DecorationService.ts +++ b/src/browser/services/DecorationService.ts @@ -134,12 +134,12 @@ export class DecorationService extends Disposable implements IDecorationService export class ScrollbarDecoration extends Disposable implements IDecoration { private readonly _marker: IMarker; - private _canvas: HTMLCanvasElement | undefined; + private _element: HTMLCanvasElement | undefined; private _color: string | undefined; public isDisposed: boolean = false; - public get element(): HTMLCanvasElement { return this._canvas!; } + public get element(): HTMLCanvasElement { return this._element!; } public get marker(): IMarker { return this._marker; } public get color(): string { return this._color!; } @@ -151,18 +151,20 @@ export class ScrollbarDecoration extends Disposable implements IDecoration { constructor( options: IDecorationOptions, - canvas: HTMLCanvasElement, + private readonly _canvas: HTMLCanvasElement, private readonly _ctx: CanvasRenderingContext2D, @IBufferService private readonly _bufferService: IBufferService ) { super(); this._marker = options.marker; - this._canvas = canvas; this._color = options.scrollbarDecorationColor; this._marker.onDispose(() => this.dispose()); this.render(); } public render(): void { + if (!this._element) { + this._element = this._canvas; + } this._ctx.lineWidth = 1; this._ctx.strokeStyle = this.color; this._ctx.strokeRect( diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index eb31c4a0..510c099f 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -427,8 +427,8 @@ declare module 'xterm' { readonly onRender: IEvent; /** - * The HTMLElement that gets created after the - * first _onRender call, or undefined if accessed before + * The HTMLElement that gets created or drawn to (for scrollbar decorations) + * after the first _onRender call, or undefined if accessed before * that. */ readonly element: HTMLElement | undefined; From 89a0cee5a49006bc29096e69e49efeb530a8423f Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 9 Mar 2022 11:17:26 -0600 Subject: [PATCH 024/245] when alt buffer is active, hide decorations --- src/browser/services/DecorationService.ts | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/browser/services/DecorationService.ts b/src/browser/services/DecorationService.ts index ee1bc2e7..0a54adc6 100644 --- a/src/browser/services/DecorationService.ts +++ b/src/browser/services/DecorationService.ts @@ -29,7 +29,7 @@ export class DecorationService extends Disposable implements IDecorationService private readonly _bufferDecorations: BufferDecoration[] = []; private _scrollbarDecorations: ScrollbarDecoration[] = []; - constructor(@IInstantiationService private readonly _instantiationService: IInstantiationService) { super(); } + constructor(@IInstantiationService private readonly _instantiationService: IInstantiationService, @IBufferService private readonly _bufferService: IBufferService) { super(); } public attachToDom(renderService: IRenderService, screenElement: HTMLElement, viewportElement: HTMLElement, scrollbarDecorationNode: HTMLCanvasElement): void { this._renderService = renderService; @@ -40,6 +40,9 @@ export class DecorationService extends Disposable implements IDecorationService this.register(this._renderService.onRenderedBufferChange(() => this._refresh())); this.register(this._renderService.onDimensionsChange(() => this._refresh(true))); this.register(addDisposableDomListener(window, 'resize', () => this._refreshScollbarDecorations())); + this.register(this._bufferService.buffers.onBufferActivate(() => { + this._scrollbarDecorationNode!.style.display = this._bufferService.buffer === this._bufferService.buffers.alt ? 'none' : 'block'; + })); } public registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined { @@ -87,7 +90,7 @@ export class DecorationService extends Disposable implements IDecorationService this._bufferDecorationContainer.classList.add('xterm-decoration-container'); this._screenElement.appendChild(this._bufferDecorationContainer); } - const decoration = new BufferDecoration(this._instantiationService.createInstance(BufferService), decorationOptions, this._bufferDecorationContainer); + const decoration = new BufferDecoration(this._bufferService, decorationOptions, this._bufferDecorationContainer); this._bufferDecorations.push(decoration); decoration.onDispose(() => this._bufferDecorations.splice(this._bufferDecorations.indexOf(decoration), 1)); this._queueRefresh(); @@ -207,6 +210,8 @@ export class BufferDecoration extends Disposable implements IDecoration { private _onRender = new EventEmitter(); public get onRender(): IEvent { return this._onRender.event; } + private _altBufferIsActive: boolean = false; + public x: number; public anchor: 'left' | 'right'; public width: number; @@ -224,6 +229,9 @@ export class BufferDecoration extends Disposable implements IDecoration { this.anchor = options.anchor || 'left'; this.width = options.width || 1; this.height = options.height || 1; + this.register(this._bufferService.buffers.onBufferActivate(() => { + this._altBufferIsActive = this._bufferService.buffer === this._bufferService.buffers.alt; + })); } public render(renderService: IRenderService, shouldRecreate?: boolean): void { @@ -271,7 +279,7 @@ export class BufferDecoration extends Disposable implements IDecoration { this._element.style.display = 'none'; } else { this._element.style.top = `${line * renderService.dimensions.actualCellHeight}px`; - this._element.style.display = this._bufferService.buffer === this._bufferService.buffers.alt ? 'none' : 'block'; + this._element.style.display = this._altBufferIsActive ? 'none' : 'block'; } } From 167a6fdbf6a602b930bd421f7792291c1e8600ca Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 9 Mar 2022 12:24:37 -0600 Subject: [PATCH 025/245] use position sticky --- css/xterm.css | 5 ++--- demo/client.ts | 5 +++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/css/xterm.css b/css/xterm.css index 3ae486c4..913cdb99 100644 --- a/css/xterm.css +++ b/css/xterm.css @@ -181,7 +181,7 @@ .xterm-decoration-scrollbar { z-index: 7; - position: fixed; + position: sticky; top: 0px; right: 0px; width: 50px; @@ -189,6 +189,5 @@ .xterm-decoration-scrollbar.demo-scrollbar { height: 436px; - left: 872px; - top: 83px; + z-index:10; } diff --git a/demo/client.ts b/demo/client.ts index 7eb9247a..f7de9692 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -553,8 +553,9 @@ function addDecoration() { } function addScrollbarDecoration() { - document.querySelector('.xterm-decoration-scrollbar').classList.add('demo-scrollbar'); - term.registerDecoration({marker: term.addMarker(1), scrollbarDecorationColor: 'red'}); + document.querySelector('.xterm-decoration-scrollbar')?.classList.add('demo-scrollbar'); + const scrollbarDecorationCanvas = term.registerDecoration({marker: term.addMarker(1), scrollbarDecorationColor: 'red'}); + scrollbarDecorationCanvas.element!.style.left = `${scrollbarDecorationCanvas.element!.nextElementSibling!.clientWidth + 89}px`; term.registerDecoration({marker: term.addMarker(3), scrollbarDecorationColor: 'green'}); term.registerDecoration({marker: term.addMarker(5), scrollbarDecorationColor: 'blue'}); } From bdfc82d1b78c5b656c4351bb56849ef5c5039e12 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 10 Mar 2022 10:25:34 -0500 Subject: [PATCH 026/245] delete unused import --- src/browser/services/DecorationService.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/browser/services/DecorationService.ts b/src/browser/services/DecorationService.ts index 0a54adc6..d264b43c 100644 --- a/src/browser/services/DecorationService.ts +++ b/src/browser/services/DecorationService.ts @@ -7,7 +7,6 @@ import { addDisposableDomListener } from 'browser/Lifecycle'; import { IDecorationService, IRenderService } from 'browser/services/Services'; import { EventEmitter, IEvent } from 'common/EventEmitter'; import { Disposable } from 'common/Lifecycle'; -import { BufferService } from 'common/services/BufferService'; import { IBufferService, IInstantiationService } from 'common/services/Services'; import { IDecorationOptions, IDecoration, IMarker } from 'xterm'; From 60c11e87f67919d56a6d0314c01dfd60f4562c85 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 10 Mar 2022 23:40:00 -0500 Subject: [PATCH 027/245] start work --- addons/xterm-addon-search/src/SearchAddon.ts | 130 +++++-------------- typings/xterm.d.ts | 2 +- 2 files changed, 33 insertions(+), 99 deletions(-) diff --git a/addons/xterm-addon-search/src/SearchAddon.ts b/addons/xterm-addon-search/src/SearchAddon.ts index 4651f6c7..4a1a6ef0 100644 --- a/addons/xterm-addon-search/src/SearchAddon.ts +++ b/addons/xterm-addon-search/src/SearchAddon.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { Terminal, IBufferLine, IDisposable, ITerminalAddon, ISelectionPosition } from 'xterm'; +import { Terminal, IBufferLine, IDisposable, ITerminalAddon, ISelectionPosition, IDecoration } from 'xterm'; export interface ISearchOptions { regex?: boolean; @@ -40,7 +40,7 @@ const LINES_CACHE_TIME_TO_LIVE = 15 * 1000; // 15 secs export class SearchAddon implements ITerminalAddon { private _terminal: Terminal | undefined; - + private _resultDecorations: IDecoration[] = []; /** * translateBufferLineToStringWithWrap is a fairly expensive call. * We memoize the calls into an array that has a time based ttl. @@ -130,7 +130,7 @@ export class SearchAddon implements ITerminalAddon { } // Set selection and scroll if a result was found - return this._selectResult(result); + return true; } /** @@ -149,69 +149,23 @@ export class SearchAddon implements ITerminalAddon { this._terminal.clearSelection(); return false; } - - const isReverseSearch = true; - let startRow = this._terminal.buffer.active.baseY + this._terminal.rows; - let startCol = this._terminal.cols; - let result: ISearchResult | undefined; - const incremental = searchOptions ? searchOptions.incremental : false; - let currentSelection: ISelectionPosition | undefined; - if (this._terminal.hasSelection()) { - currentSelection = this._terminal.getSelectionPosition()!; - // Start from selection start if there is a selection - startRow = currentSelection.startRow; - startCol = currentSelection.startColumn; - } - - this._initLinesCache(); - const searchPosition: ISearchPosition = { - startRow, - startCol - }; - - if (incremental) { - // Try to expand selection to right first. - result = this._findInLine(term, searchPosition, searchOptions, false); - const isOldResultHighlighted = result && result.row === startRow && result.col === startCol; - if (!isOldResultHighlighted) { - // If selection was not able to be expanded to the right, then try reverse search - if (currentSelection) { - searchPosition.startRow = currentSelection.endRow; - searchPosition.startCol = currentSelection.endColumn; - } - result = this._findInLine(term, searchPosition, searchOptions, true); - } - } else { - result = this._findInLine(term, searchPosition, searchOptions, isReverseSearch); - } - - // Search from startRow - 1 to top - if (!result) { - searchPosition.startCol = Math.max(searchPosition.startCol, this._terminal.cols); - for (let y = startRow - 1; y >= 0; y--) { - searchPosition.startRow = y; - result = this._findInLine(term, searchPosition, searchOptions, isReverseSearch); - if (result) { - break; - } + const results = []; + for (let i = this._terminal.buffer.active.viewportY; i < this._terminal.buffer.active.viewportY + this._terminal.rows; i++) { + const result = this._findInLine(term, { startCol: 0, startRow: i }, searchOptions); + if (result) { + results.push(result); } } - // If we hit the top and didn't search from the very bottom wrap back down - if (!result && startRow !== (this._terminal.buffer.active.baseY + this._terminal.rows)) { - for (let y = (this._terminal.buffer.active.baseY + this._terminal.rows); y >= startRow; y--) { - searchPosition.startRow = y; - result = this._findInLine(term, searchPosition, searchOptions, isReverseSearch); - if (result) { - break; - } + for (const result of results.filter(r => !!r && r.term.length)) { + const resultDecoration = this._showResultDecoration(result); + if (resultDecoration) { + // Add decoration + this._resultDecorations.push(resultDecoration); } } - - // If there is only one result, return true. - if (!result && currentSelection) return true; - - // Set selection and scroll if a result was found - return this._selectResult(result); + console.log(results); + console.log(this._resultDecorations); + return true; } /** @@ -267,7 +221,7 @@ export class SearchAddon implements ITerminalAddon { * @param isReverseSearch Whether the search should start from the right side of the terminal and search to the left. * @return The search result if it was found. */ - protected _findInLine(term: string, searchPosition: ISearchPosition, searchOptions: ISearchOptions = {}, isReverseSearch: boolean = false): ISearchResult | undefined { + protected _findInLine(term: string, searchPosition: ISearchPosition, searchOptions: ISearchOptions = {}): ISearchResult | undefined { const terminal = this._terminal!; const row = searchPosition.startRow; const col = searchPosition.startCol; @@ -275,10 +229,6 @@ export class SearchAddon implements ITerminalAddon { // Ignore wrapped lines, only consider on unwrapped line (first row of command string). const firstLine = terminal.buffer.active.getLine(row); if (firstLine?.isWrapped) { - if (isReverseSearch) { - searchPosition.startCol += terminal.cols; - return; - } // This will iterate until we find the line start. // When we find it, we will search using the calculated start column. @@ -302,29 +252,13 @@ export class SearchAddon implements ITerminalAddon { let resultIndex = -1; if (searchOptions.regex) { const searchRegex = RegExp(searchTerm, 'g'); - let foundTerm: RegExpExecArray | null; - if (isReverseSearch) { - // This loop will get the resultIndex of the _last_ regex match in the range 0..offset - while (foundTerm = searchRegex.exec(searchStringLine.slice(0, offset))) { - resultIndex = searchRegex.lastIndex - foundTerm[0].length; - term = foundTerm[0]; - searchRegex.lastIndex -= (term.length - 1); - } - } else { - foundTerm = searchRegex.exec(searchStringLine.slice(offset)); - if (foundTerm && foundTerm[0].length > 0) { - resultIndex = offset + (searchRegex.lastIndex - foundTerm[0].length); - term = foundTerm[0]; - } + const foundTerm = searchRegex.exec(searchStringLine.slice(offset)); + if (foundTerm && foundTerm[0].length > 0) { + resultIndex = offset + (searchRegex.lastIndex - foundTerm[0].length); + term = foundTerm[0]; } } else { - if (isReverseSearch) { - if (offset - searchTerm.length >= 0) { - resultIndex = searchStringLine.lastIndexOf(searchTerm, offset - searchTerm.length); - } - } else { - resultIndex = searchStringLine.indexOf(searchTerm, offset); - } + resultIndex = searchStringLine.indexOf(searchTerm, offset); } if (resultIndex >= 0) { @@ -448,19 +382,19 @@ export class SearchAddon implements ITerminalAddon { * @param result The result to select. * @return Whethera result was selected. */ - private _selectResult(result: ISearchResult | undefined): boolean { + private _showResultDecoration(result: ISearchResult | undefined): IDecoration | undefined { const terminal = this._terminal!; - if (!result) { + if (!result || result.row >= (terminal.buffer.active.viewportY + terminal.rows) || result.row < terminal.buffer.active.viewportY) { terminal.clearSelection(); - return false; + return; } - terminal.select(result.col, result.row, result.size); - // If it is not in the viewport then we scroll else it just gets selected - if (result.row >= (terminal.buffer.active.viewportY + terminal.rows) || result.row < terminal.buffer.active.viewportY) { - let scroll = result.row - terminal.buffer.active.viewportY; - scroll -= Math.floor(terminal.rows / 2); - terminal.scrollLines(scroll); + // TODO: + const marker = terminal.registerMarker(undefined, result.col, result.row); + if (!marker) { + return undefined; } - return true; + const findResultDecoration = terminal.registerDecoration({ marker, width: result.size }); + findResultDecoration?.onRender((e) => console.log('rendered', e, result?.term, result?.col)); + return findResultDecoration; } } diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 2cd4daa6..44106354 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -930,7 +930,7 @@ declare module 'xterm' { * @param cursorYOffset The y position offset of the marker from the cursor. * @returns The new marker or undefined. */ - registerMarker(cursorYOffset?: number): IMarker | undefined; + registerMarker(cursorYOffset?: number, col?: number, row?: number): IMarker | undefined; /** * @deprecated use `registerMarker` instead. From 96574425c7a6363b4ec031ff821b36d90d05962f Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 11 Mar 2022 00:39:18 -0500 Subject: [PATCH 028/245] get it to sort of work --- addons/xterm-addon-search/src/SearchAddon.ts | 14 +++++++++++--- src/browser/Terminal.ts | 7 +++++-- src/browser/Types.d.ts | 2 +- src/browser/public/Terminal.ts | 7 +++++-- typings/xterm.d.ts | 2 +- 5 files changed, 23 insertions(+), 9 deletions(-) diff --git a/addons/xterm-addon-search/src/SearchAddon.ts b/addons/xterm-addon-search/src/SearchAddon.ts index 4a1a6ef0..1f1ff961 100644 --- a/addons/xterm-addon-search/src/SearchAddon.ts +++ b/addons/xterm-addon-search/src/SearchAddon.ts @@ -144,6 +144,8 @@ export class SearchAddon implements ITerminalAddon { if (!this._terminal) { throw new Error('Cannot use addon until it has been loaded'); } + this._resultDecorations.forEach(d => d.dispose()); + this._resultDecorations = []; if (!term || term.length === 0) { this._terminal.clearSelection(); @@ -388,13 +390,19 @@ export class SearchAddon implements ITerminalAddon { terminal.clearSelection(); return; } - // TODO: - const marker = terminal.registerMarker(undefined, result.col, result.row); + const marker = terminal.registerMarker(undefined, result.row - 1); if (!marker) { return undefined; } const findResultDecoration = terminal.registerDecoration({ marker, width: result.size }); - findResultDecoration?.onRender((e) => console.log('rendered', e, result?.term, result?.col)); + findResultDecoration?.onRender((e) => { + console.log('rendered', e, result?.term, result?.row); + e.style.backgroundColor = 'blue'; + e.style.color = 'white'; + e.style.opacity = '60%'; + // TODO: use cell width here instead of 10 + e.style.left = `${(result.col === 0 ? 0 : result.col - 1) * 10}px`; + }); return findResultDecoration; } } diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index 08963933..69d5bfad 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -993,12 +993,15 @@ export class Terminal extends CoreTerminal implements ITerminal { return this.buffer.markers; } - public addMarker(cursorYOffset: number): IMarker | undefined { + public addMarker(cursorYOffset: number, row?: number): IMarker | undefined { // Disallow markers on the alt buffer if (this.buffer !== this.buffers.normal) { return; } - + if (row) { + console.log(row); + return this.buffer.addMarker(row + 1); + } return this.buffer.addMarker(this.buffer.ybase + this.buffer.y + cursorYOffset); } diff --git a/src/browser/Types.d.ts b/src/browser/Types.d.ts index 35b52d62..c66b0928 100644 --- a/src/browser/Types.d.ts +++ b/src/browser/Types.d.ts @@ -60,7 +60,7 @@ export interface IPublicTerminal extends IDisposable { registerLinkProvider(linkProvider: ILinkProvider): IDisposable; registerCharacterJoiner(handler: (text: string) => [number, number][]): number; deregisterCharacterJoiner(joinerId: number): void; - addMarker(cursorYOffset: number): IMarker | undefined; + addMarker(cursorYOffset: number, col?: number, row?: number): IMarker | undefined; registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined; hasSelection(): boolean; getSelection(): string; diff --git a/src/browser/public/Terminal.ts b/src/browser/public/Terminal.ts index 1acde934..8c290fda 100644 --- a/src/browser/public/Terminal.ts +++ b/src/browser/public/Terminal.ts @@ -166,10 +166,13 @@ export class Terminal implements ITerminalApi { this._checkProposedApi(); this._core.deregisterCharacterJoiner(joinerId); } - public registerMarker(cursorYOffset: number = 0): IMarker | undefined { + public registerMarker(cursorYOffset: number = 0, row?: number): IMarker | undefined { this._checkProposedApi(); this._verifyIntegers(cursorYOffset); - return this._core.addMarker(cursorYOffset); + if (row) { + this._verifyPositiveIntegers(row); + } + return this._core.addMarker(cursorYOffset, row); } public registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined { this._checkProposedApi(); diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 44106354..cc6dc8ad 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -930,7 +930,7 @@ declare module 'xterm' { * @param cursorYOffset The y position offset of the marker from the cursor. * @returns The new marker or undefined. */ - registerMarker(cursorYOffset?: number, col?: number, row?: number): IMarker | undefined; + registerMarker(cursorYOffset?: number, row?: number): IMarker | undefined; /** * @deprecated use `registerMarker` instead. From fa4479c79724b2dffcce2ce15ce8d52a6fc8bdf0 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 11 Mar 2022 12:01:19 -0500 Subject: [PATCH 029/245] use decoration width, find all and select next --- addons/xterm-addon-search/src/SearchAddon.ts | 208 ++++++++++++++---- .../typings/xterm-addon-search.d.ts | 24 +- demo/client.ts | 5 + demo/index.html | 1 + src/browser/Terminal.ts | 3 +- 5 files changed, 192 insertions(+), 49 deletions(-) diff --git a/addons/xterm-addon-search/src/SearchAddon.ts b/addons/xterm-addon-search/src/SearchAddon.ts index 1f1ff961..d26dd6be 100644 --- a/addons/xterm-addon-search/src/SearchAddon.ts +++ b/addons/xterm-addon-search/src/SearchAddon.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { Terminal, IBufferLine, IDisposable, ITerminalAddon, ISelectionPosition, IDecoration } from 'xterm'; +import { Terminal, IDisposable, ITerminalAddon, ISelectionPosition, IDecoration } from 'xterm'; export interface ISearchOptions { regex?: boolean; @@ -41,6 +41,7 @@ const LINES_CACHE_TIME_TO_LIVE = 15 * 1000; // 15 secs export class SearchAddon implements ITerminalAddon { private _terminal: Terminal | undefined; private _resultDecorations: IDecoration[] = []; + private _result: ISearchResult | undefined; /** * translateBufferLineToStringWithWrap is a fairly expensive call. * We memoize the calls into an array that has a time based ttl. @@ -57,6 +58,52 @@ export class SearchAddon implements ITerminalAddon { public dispose(): void { } + /** + * Find all instances of the term, selecting the next one with each + * enter. If it doesn't exist, do nothing. + * @param term The search term. + * @param searchOptions Search options. + * @return Whether a result was found. + */ + public find(term: string, searchOptions?: ISearchOptions): boolean { + if (!this._terminal) { + throw new Error('Cannot use addon until it has been loaded'); + } + + if (!term || term.length === 0) { + this._terminal.clearSelection(); + this._resultDecorations.forEach(d => d.dispose()); + this._resultDecorations = []; + return false; + } + + // new search, clear out the old decorations + this._resultDecorations.forEach(d => d.dispose()); + this._resultDecorations = []; + + const results: ISearchResult[] = []; + let found = this.findNext(term, searchOptions); + while (found && !results.find(r => r?.col === this._result?.col && r?.row === this._result?.row)) { + if (this._result) { + results.push(this._result); + } + found = this.findNext(term, searchOptions); + } + + for (const result of results) { + const resultDecoration = this._showResultDecoration(result); + if (resultDecoration) { + // Add decoration + this._resultDecorations.push(resultDecoration); + } + } + if (results.length > 0) { + // this.findNext(term, searchOptions); + } + return true; + } + + /** * Find the next instance of the term, then scroll to and select it. If it * doesn't exist, do nothing. @@ -70,6 +117,7 @@ export class SearchAddon implements ITerminalAddon { } if (!term || term.length === 0) { + this._result = undefined; this._terminal.clearSelection(); return false; } @@ -94,43 +142,42 @@ export class SearchAddon implements ITerminalAddon { }; // Search startRow - let result = this._findInLine(term, searchPosition, searchOptions); - + this._result = this._findInLine(term, searchPosition, searchOptions); // Search from startRow + 1 to end - if (!result) { + if (!this._result) { for (let y = startRow + 1; y < this._terminal.buffer.active.baseY + this._terminal.rows; y++) { searchPosition.startRow = y; searchPosition.startCol = 0; // If the current line is wrapped line, increase index of column to ignore the previous scan // Otherwise, reset beginning column index to zero with set new unwrapped line index - result = this._findInLine(term, searchPosition, searchOptions); - if (result) { + this._result = this._findInLine(term, searchPosition, searchOptions); + if (this._result) { break; } } } // If we hit the bottom and didn't search from the very top wrap back up - if (!result && startRow !== 0) { + if (!this._result && startRow !== 0) { for (let y = 0; y < startRow; y++) { searchPosition.startRow = y; searchPosition.startCol = 0; - result = this._findInLine(term, searchPosition, searchOptions); - if (result) { + this._result = this._findInLine(term, searchPosition, searchOptions); + if (this._result) { break; } } } // If there is only one result, wrap back and return selection if it exists. - if (!result && currentSelection) { + if (!this._result && currentSelection) { searchPosition.startRow = currentSelection.startRow; searchPosition.startCol = 0; - result = this._findInLine(term, searchPosition, searchOptions); + this._result = this._findInLine(term, searchPosition, searchOptions); } // Set selection and scroll if a result was found - return true; + return this._selectResult(this._result); } /** @@ -144,32 +191,77 @@ export class SearchAddon implements ITerminalAddon { if (!this._terminal) { throw new Error('Cannot use addon until it has been loaded'); } - this._resultDecorations.forEach(d => d.dispose()); - this._resultDecorations = []; if (!term || term.length === 0) { this._terminal.clearSelection(); return false; } - const results = []; - for (let i = this._terminal.buffer.active.viewportY; i < this._terminal.buffer.active.viewportY + this._terminal.rows; i++) { - const result = this._findInLine(term, { startCol: 0, startRow: i }, searchOptions); - if (result) { - results.push(result); + + const isReverseSearch = true; + let startRow = this._terminal.buffer.active.baseY + this._terminal.rows; + let startCol = this._terminal.cols; + let result: ISearchResult | undefined; + const incremental = searchOptions ? searchOptions.incremental : false; + let currentSelection: ISelectionPosition | undefined; + if (this._terminal.hasSelection()) { + currentSelection = this._terminal.getSelectionPosition()!; + // Start from selection start if there is a selection + startRow = currentSelection.startRow; + startCol = currentSelection.startColumn; + } + + this._initLinesCache(); + const searchPosition: ISearchPosition = { + startRow, + startCol + }; + + if (incremental) { + // Try to expand selection to right first. + result = this._findInLine(term, searchPosition, searchOptions, false); + const isOldResultHighlighted = result && result.row === startRow && result.col === startCol; + if (!isOldResultHighlighted) { + // If selection was not able to be expanded to the right, then try reverse search + if (currentSelection) { + searchPosition.startRow = currentSelection.endRow; + searchPosition.startCol = currentSelection.endColumn; + } + result = this._findInLine(term, searchPosition, searchOptions, true); + } + } else { + result = this._findInLine(term, searchPosition, searchOptions, isReverseSearch); + } + + // Search from startRow - 1 to top + if (!result) { + searchPosition.startCol = Math.max(searchPosition.startCol, this._terminal.cols); + for (let y = startRow - 1; y >= 0; y--) { + searchPosition.startRow = y; + result = this._findInLine(term, searchPosition, searchOptions, isReverseSearch); + if (result) { + break; + } } } - for (const result of results.filter(r => !!r && r.term.length)) { - const resultDecoration = this._showResultDecoration(result); - if (resultDecoration) { - // Add decoration - this._resultDecorations.push(resultDecoration); + // If we hit the top and didn't search from the very bottom wrap back down + if (!result && startRow !== (this._terminal.buffer.active.baseY + this._terminal.rows)) { + for (let y = (this._terminal.buffer.active.baseY + this._terminal.rows); y >= startRow; y--) { + searchPosition.startRow = y; + result = this._findInLine(term, searchPosition, searchOptions, isReverseSearch); + if (result) { + break; + } } } - console.log(results); - console.log(this._resultDecorations); - return true; + + // If there is only one result, return true. + if (!result && currentSelection) return true; + + // Set selection and scroll if a result was found + return this._selectResult(result); } + /** * Sets up a line cache with a ttl */ @@ -223,7 +315,7 @@ export class SearchAddon implements ITerminalAddon { * @param isReverseSearch Whether the search should start from the right side of the terminal and search to the left. * @return The search result if it was found. */ - protected _findInLine(term: string, searchPosition: ISearchPosition, searchOptions: ISearchOptions = {}): ISearchResult | undefined { + protected _findInLine(term: string, searchPosition: ISearchPosition, searchOptions: ISearchOptions = {}, isReverseSearch: boolean = false): ISearchResult | undefined { const terminal = this._terminal!; const row = searchPosition.startRow; const col = searchPosition.startCol; @@ -231,6 +323,10 @@ export class SearchAddon implements ITerminalAddon { // Ignore wrapped lines, only consider on unwrapped line (first row of command string). const firstLine = terminal.buffer.active.getLine(row); if (firstLine?.isWrapped) { + if (isReverseSearch) { + searchPosition.startCol += terminal.cols; + return; + } // This will iterate until we find the line start. // When we find it, we will search using the calculated start column. @@ -254,13 +350,29 @@ export class SearchAddon implements ITerminalAddon { let resultIndex = -1; if (searchOptions.regex) { const searchRegex = RegExp(searchTerm, 'g'); - const foundTerm = searchRegex.exec(searchStringLine.slice(offset)); - if (foundTerm && foundTerm[0].length > 0) { - resultIndex = offset + (searchRegex.lastIndex - foundTerm[0].length); - term = foundTerm[0]; + let foundTerm: RegExpExecArray | null; + if (isReverseSearch) { + // This loop will get the resultIndex of the _last_ regex match in the range 0..offset + while (foundTerm = searchRegex.exec(searchStringLine.slice(0, offset))) { + resultIndex = searchRegex.lastIndex - foundTerm[0].length; + term = foundTerm[0]; + searchRegex.lastIndex -= (term.length - 1); + } + } else { + foundTerm = searchRegex.exec(searchStringLine.slice(offset)); + if (foundTerm && foundTerm[0].length > 0) { + resultIndex = offset + (searchRegex.lastIndex - foundTerm[0].length); + term = foundTerm[0]; + } } } else { - resultIndex = searchStringLine.indexOf(searchTerm, offset); + if (isReverseSearch) { + if (offset - searchTerm.length >= 0) { + resultIndex = searchStringLine.lastIndexOf(searchTerm, offset - searchTerm.length); + } + } else { + resultIndex = searchStringLine.indexOf(searchTerm, offset); + } } if (resultIndex >= 0) { @@ -384,24 +496,42 @@ export class SearchAddon implements ITerminalAddon { * @param result The result to select. * @return Whethera result was selected. */ + private _selectResult(result: ISearchResult | undefined): boolean { + const terminal = this._terminal!; + if (!result) { + terminal.clearSelection(); + return false; + } + terminal.select(result.col, result.row, result.size); + // If it is not in the viewport then we scroll else it just gets selected + if (result.row >= (terminal.buffer.active.viewportY + terminal.rows) || result.row < terminal.buffer.active.viewportY) { + let scroll = result.row - terminal.buffer.active.viewportY; + scroll -= Math.floor(terminal.rows / 2); + terminal.scrollLines(scroll); + } + return true; + } + + /** + * Registers a decoration for the @param result + * and @returns the decoration or undefined if + * the marker has already been disposed of + */ private _showResultDecoration(result: ISearchResult | undefined): IDecoration | undefined { const terminal = this._terminal!; - if (!result || result.row >= (terminal.buffer.active.viewportY + terminal.rows) || result.row < terminal.buffer.active.viewportY) { + if (!result) { terminal.clearSelection(); return; } - const marker = terminal.registerMarker(undefined, result.row - 1); + const marker = terminal.registerMarker(undefined, result.row); if (!marker) { return undefined; } const findResultDecoration = terminal.registerDecoration({ marker, width: result.size }); findResultDecoration?.onRender((e) => { - console.log('rendered', e, result?.term, result?.row); e.style.backgroundColor = 'blue'; - e.style.color = 'white'; e.style.opacity = '60%'; - // TODO: use cell width here instead of 10 - e.style.left = `${(result.col === 0 ? 0 : result.col - 1) * 10}px`; + e.style.left = `${result.col * e.clientWidth}px`; }); return findResultDecoration; } diff --git a/addons/xterm-addon-search/typings/xterm-addon-search.d.ts b/addons/xterm-addon-search/typings/xterm-addon-search.d.ts index f27aba78..228a5ffe 100644 --- a/addons/xterm-addon-search/typings/xterm-addon-search.d.ts +++ b/addons/xterm-addon-search/typings/xterm-addon-search.d.ts @@ -49,20 +49,28 @@ declare module 'xterm-addon-search' { */ public dispose(): void; + /** + * Find all instances of the term, selecting the next one with each + * enter. If it doesn't exist, do nothing. + * @param term The search term. + * @param searchOptions The options for the search. + */ + public find(term: string, searchOptions?: ISearchOptions): boolean; + /** * Search forwards for the next result that matches the search term and * options. * @param term The search term. * @param searchOptions The options for the search. */ - public findNext(term: string, searchOptions?: ISearchOptions): boolean; + public findNext(term: string, searchOptions?: ISearchOptions): boolean; - /** - * Search backwards for the previous result that matches the search term and - * options. - * @param term The search term. - * @param searchOptions The options for the search. - */ - public findPrevious(term: string, searchOptions?: ISearchOptions): boolean; + /** + * Search backwards for the previous result that matches the search term and + * options. + * @param term The search term. + * @param searchOptions The options for the search. + */ + public findPrevious(term: string, searchOptions?: ISearchOptions): boolean; } } diff --git a/demo/client.ts b/demo/client.ts index aee23401..65bdc6ce 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -92,6 +92,7 @@ const addons: { [T in AddonType]: IDemoAddon} = { const terminalContainer = document.getElementById('terminal-container'); const actionElements = { + find: document.querySelector('#find'), findNext: document.querySelector('#find-next'), findPrevious: document.querySelector('#find-previous') }; @@ -199,6 +200,10 @@ function createTerminal(): void { addDomListener(paddingElement, 'change', setPadding); + addDomListener(actionElements.find, 'keyup', (e) => { + addons.search.instance.find(actionElements.find.value, getSearchOptions(e)); + }); + addDomListener(actionElements.findNext, 'keyup', (e) => { addons.search.instance.findNext(actionElements.findNext.value, getSearchOptions(e)); }); diff --git a/demo/index.html b/demo/index.html index e024222c..590bf1e6 100644 --- a/demo/index.html +++ b/demo/index.html @@ -38,6 +38,7 @@

Addons Control

SearchAddon

+ diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index 69d5bfad..4d546dcb 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -999,8 +999,7 @@ export class Terminal extends CoreTerminal implements ITerminal { return; } if (row) { - console.log(row); - return this.buffer.addMarker(row + 1); + return this.buffer.addMarker(row); } return this.buffer.addMarker(this.buffer.ybase + this.buffer.y + cursorYOffset); } From 372b6b633b6d0b291775482d8dc7c691a1f34329 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 11 Mar 2022 12:13:43 -0500 Subject: [PATCH 030/245] set incremental search to false --- addons/xterm-addon-search/src/SearchAddon.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/addons/xterm-addon-search/src/SearchAddon.ts b/addons/xterm-addon-search/src/SearchAddon.ts index d26dd6be..a06f32e0 100644 --- a/addons/xterm-addon-search/src/SearchAddon.ts +++ b/addons/xterm-addon-search/src/SearchAddon.ts @@ -80,8 +80,9 @@ export class SearchAddon implements ITerminalAddon { // new search, clear out the old decorations this._resultDecorations.forEach(d => d.dispose()); this._resultDecorations = []; - const results: ISearchResult[] = []; + searchOptions = searchOptions || {}; + searchOptions.incremental = false; let found = this.findNext(term, searchOptions); while (found && !results.find(r => r?.col === this._result?.col && r?.row === this._result?.row)) { if (this._result) { @@ -527,11 +528,12 @@ export class SearchAddon implements ITerminalAddon { if (!marker) { return undefined; } - const findResultDecoration = terminal.registerDecoration({ marker, width: result.size }); + const findResultDecoration = terminal.registerDecoration({ marker }); findResultDecoration?.onRender((e) => { e.style.backgroundColor = 'blue'; e.style.opacity = '60%'; e.style.left = `${result.col * e.clientWidth}px`; + e.style.width = `${e.clientWidth * result.term.length}px`; }); return findResultDecoration; } From da2c59d1d0450d5a1f1677c2d323c642e5c8d938 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 11 Mar 2022 12:24:36 -0500 Subject: [PATCH 031/245] remove unused variable --- addons/xterm-addon-search/src/SearchAddon.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/addons/xterm-addon-search/src/SearchAddon.ts b/addons/xterm-addon-search/src/SearchAddon.ts index a06f32e0..e28b409a 100644 --- a/addons/xterm-addon-search/src/SearchAddon.ts +++ b/addons/xterm-addon-search/src/SearchAddon.ts @@ -42,6 +42,8 @@ export class SearchAddon implements ITerminalAddon { private _terminal: Terminal | undefined; private _resultDecorations: IDecoration[] = []; private _result: ISearchResult | undefined; + private _reset: boolean = false; + private _cachedSearchTerm: string | undefined; /** * translateBufferLineToStringWithWrap is a fairly expensive call. * We memoize the calls into an array that has a time based ttl. @@ -54,6 +56,7 @@ export class SearchAddon implements ITerminalAddon { public activate(terminal: Terminal): void { this._terminal = terminal; + this._terminal.onData(() => this._reset = true); } public dispose(): void { } @@ -77,6 +80,12 @@ export class SearchAddon implements ITerminalAddon { return false; } + if (!this._reset && term === this._cachedSearchTerm) { + return this.findNext(term, searchOptions); + } + this._reset = false; + + // new search, clear out the old decorations this._resultDecorations.forEach(d => d.dispose()); this._resultDecorations = []; @@ -99,7 +108,7 @@ export class SearchAddon implements ITerminalAddon { } } if (results.length > 0) { - // this.findNext(term, searchOptions); + this._cachedSearchTerm = term; } return true; } From 453cb00807cb501dac96194006ac3a86a8de41cf Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 11 Mar 2022 12:43:09 -0500 Subject: [PATCH 032/245] use a class --- addons/xterm-addon-search/src/SearchAddon.ts | 10 ++++++---- css/xterm.css | 5 +++++ 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/addons/xterm-addon-search/src/SearchAddon.ts b/addons/xterm-addon-search/src/SearchAddon.ts index e28b409a..e50fdd17 100644 --- a/addons/xterm-addon-search/src/SearchAddon.ts +++ b/addons/xterm-addon-search/src/SearchAddon.ts @@ -539,10 +539,12 @@ export class SearchAddon implements ITerminalAddon { } const findResultDecoration = terminal.registerDecoration({ marker }); findResultDecoration?.onRender((e) => { - e.style.backgroundColor = 'blue'; - e.style.opacity = '60%'; - e.style.left = `${result.col * e.clientWidth}px`; - e.style.width = `${e.clientWidth * result.term.length}px`; + if (!e.classList.contains('xterm-find-result-decoration')) { + e.classList.add('xterm-find-result-decoration'); + // decoration's clientWidth = actualCellWidth + e.style.left = `${e.clientWidth * result.col}px`; + e.style.width = `${e.clientWidth * result.term.length}px`; + } }); return findResultDecoration; } diff --git a/css/xterm.css b/css/xterm.css index ab3965b4..ded93074 100644 --- a/css/xterm.css +++ b/css/xterm.css @@ -178,3 +178,8 @@ z-index: 6; position: absolute; } + +.xterm-find-result-decoration { + background-color: blue; + opacity: 60%; +} \ No newline at end of file From e7bbc4151afb795770e529a46de265b8764d38c9 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 11 Mar 2022 13:10:26 -0500 Subject: [PATCH 033/245] start at cursor position and mandate a width gt 0 --- addons/xterm-addon-search/src/SearchAddon.ts | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/addons/xterm-addon-search/src/SearchAddon.ts b/addons/xterm-addon-search/src/SearchAddon.ts index e50fdd17..5e089594 100644 --- a/addons/xterm-addon-search/src/SearchAddon.ts +++ b/addons/xterm-addon-search/src/SearchAddon.ts @@ -101,10 +101,11 @@ export class SearchAddon implements ITerminalAddon { } for (const result of results) { - const resultDecoration = this._showResultDecoration(result); - if (resultDecoration) { - // Add decoration - this._resultDecorations.push(resultDecoration); + if (result) { + const resultDecoration = this._showResultDecoration(result); + if (resultDecoration) { + this._resultDecorations.push(resultDecoration); + } } } if (results.length > 0) { @@ -142,6 +143,9 @@ export class SearchAddon implements ITerminalAddon { currentSelection = this._terminal.getSelectionPosition()!; startRow = incremental ? currentSelection.startRow : currentSelection.endRow; startCol = incremental ? currentSelection.startColumn : currentSelection.endColumn; + } else { + startRow = this._terminal.buffer.active.cursorY; + startCol = this._terminal.buffer.active.cursorX; } this._initLinesCache(); @@ -527,19 +531,15 @@ export class SearchAddon implements ITerminalAddon { * and @returns the decoration or undefined if * the marker has already been disposed of */ - private _showResultDecoration(result: ISearchResult | undefined): IDecoration | undefined { + private _showResultDecoration(result: ISearchResult): IDecoration | undefined { const terminal = this._terminal!; - if (!result) { - terminal.clearSelection(); - return; - } const marker = terminal.registerMarker(undefined, result.row); if (!marker) { return undefined; } const findResultDecoration = terminal.registerDecoration({ marker }); findResultDecoration?.onRender((e) => { - if (!e.classList.contains('xterm-find-result-decoration')) { + if (!e.classList.contains('xterm-find-result-decoration') && result.term.length && e.clientWidth > 0) { e.classList.add('xterm-find-result-decoration'); // decoration's clientWidth = actualCellWidth e.style.left = `${e.clientWidth * result.col}px`; From b4de2787c7d0734774efcbe3d365bdcc5e1e258f Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 11 Mar 2022 13:13:05 -0500 Subject: [PATCH 034/245] hide decorations >= bufferService rows --- src/browser/services/DecorationService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/browser/services/DecorationService.ts b/src/browser/services/DecorationService.ts index ed3c7224..a77409db 100644 --- a/src/browser/services/DecorationService.ts +++ b/src/browser/services/DecorationService.ts @@ -142,7 +142,7 @@ export class Decoration extends Disposable implements IDecoration { return; } const line = this.marker.line - this._bufferService.buffers.active.ydisp; - if (line < 0 || line > this._bufferService.rows) { + if (line < 0 || line >= this._bufferService.rows) { // outside of viewport this._element.style.display = 'none'; } else { From 2f85448e512cc24b0914992c7688fd9cce7c5edd Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Fri, 11 Mar 2022 16:53:59 -0500 Subject: [PATCH 035/245] Update css/xterm.css Co-authored-by: Daniel Imms <2193314+Tyriar@users.noreply.github.com> --- css/xterm.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/css/xterm.css b/css/xterm.css index 913cdb99..215c1446 100644 --- a/css/xterm.css +++ b/css/xterm.css @@ -182,8 +182,8 @@ .xterm-decoration-scrollbar { z-index: 7; position: sticky; - top: 0px; - right: 0px; + top: 0; + right: 0; width: 50px; } From 98a03dd477c182707b6e7dd5dfc0dd9f7dd6188f Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 11 Mar 2022 17:15:11 -0500 Subject: [PATCH 036/245] re-arrange dom structure --- css/xterm.css | 11 +++-------- demo/client.ts | 3 +-- src/browser/Terminal.ts | 8 +------- src/browser/services/DecorationService.ts | 11 ++++++++--- src/browser/services/Services.ts | 2 +- 5 files changed, 14 insertions(+), 21 deletions(-) diff --git a/css/xterm.css b/css/xterm.css index 913cdb99..ef12f004 100644 --- a/css/xterm.css +++ b/css/xterm.css @@ -180,14 +180,9 @@ } .xterm-decoration-scrollbar { - z-index: 7; - position: sticky; + z-index: 6; + position: absolute; top: 0px; right: 0px; width: 50px; -} - -.xterm-decoration-scrollbar.demo-scrollbar { - height: 436px; - z-index:10; -} +} \ No newline at end of file diff --git a/demo/client.ts b/demo/client.ts index f7de9692..0b3ce37e 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -553,9 +553,8 @@ function addDecoration() { } function addScrollbarDecoration() { - document.querySelector('.xterm-decoration-scrollbar')?.classList.add('demo-scrollbar'); const scrollbarDecorationCanvas = term.registerDecoration({marker: term.addMarker(1), scrollbarDecorationColor: 'red'}); - scrollbarDecorationCanvas.element!.style.left = `${scrollbarDecorationCanvas.element!.nextElementSibling!.clientWidth + 89}px`; + scrollbarDecorationCanvas.element!.style.left = `${document.querySelector('.xterm-viewport').clientWidth + 5}px`; term.registerDecoration({marker: term.addMarker(3), scrollbarDecorationColor: 'green'}); term.registerDecoration({marker: term.addMarker(5), scrollbarDecorationColor: 'blue'}); } diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index 7dec6323..ba0fc93d 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -70,7 +70,6 @@ export class Terminal extends CoreTerminal implements ITerminal { private _viewportElement: HTMLElement | undefined; private _helperContainer: HTMLElement | undefined; private _compositionView: HTMLElement | undefined; - private _scrollbarDecorationNode: HTMLCanvasElement | undefined; // private _visualBellTimer: number; @@ -473,11 +472,6 @@ export class Terminal extends CoreTerminal implements ITerminal { this._viewportElement.classList.add('xterm-viewport'); fragment.appendChild(this._viewportElement); - // TODO: make this opt in, must be done before the scroll area in order to show up - this._scrollbarDecorationNode = document.createElement('canvas'); - this._scrollbarDecorationNode.classList.add('xterm-decoration-scrollbar'); - this._viewportElement?.appendChild(this._scrollbarDecorationNode); - this._viewportScrollArea = document.createElement('div'); this._viewportScrollArea.classList.add('xterm-scroll-area'); this._viewportElement.appendChild(this._viewportScrollArea); @@ -584,7 +578,7 @@ export class Terminal extends CoreTerminal implements ITerminal { this.linkifier.attachToDom(this.element, this._mouseZoneManager); this.linkifier2.attachToDom(this.screenElement, this._mouseService, this._renderService); - this.decorationService.attachToDom(this._renderService, this.screenElement, this._viewportElement, this._scrollbarDecorationNode); + this.decorationService.attachToDom(this._renderService, this.screenElement, this._viewportElement); // This event listener must be registered aftre MouseZoneManager is created this.register(addDisposableDomListener(this.element, 'mousedown', (e: MouseEvent) => this._selectionService!.onMouseDown(e))); diff --git a/src/browser/services/DecorationService.ts b/src/browser/services/DecorationService.ts index d264b43c..74489345 100644 --- a/src/browser/services/DecorationService.ts +++ b/src/browser/services/DecorationService.ts @@ -30,11 +30,10 @@ export class DecorationService extends Disposable implements IDecorationService constructor(@IInstantiationService private readonly _instantiationService: IInstantiationService, @IBufferService private readonly _bufferService: IBufferService) { super(); } - public attachToDom(renderService: IRenderService, screenElement: HTMLElement, viewportElement: HTMLElement, scrollbarDecorationNode: HTMLCanvasElement): void { + public attachToDom(renderService: IRenderService, screenElement: HTMLElement, viewportElement: HTMLElement): void { this._renderService = renderService; this._screenElement = screenElement; this._viewportElement = viewportElement; - this._scrollbarDecorationNode = scrollbarDecorationNode; this.register(this._renderService.onRenderedBufferChange(() => this._refresh())); this.register(this._renderService.onDimensionsChange(() => this._refresh(true))); @@ -97,9 +96,15 @@ export class DecorationService extends Disposable implements IDecorationService } private _registerScrollbarDecoration(marker: IMarker, color: string): IDecoration | undefined { - if (!this._scrollbarDecorationNode || !this._viewportElement) { + if (!this._viewportElement?.parentElement) { return; } + if (!this._scrollbarDecorationNode) { + // TODO: make this opt in, must be done before the scroll area in order to show up + this._scrollbarDecorationNode = document.createElement('canvas'); + this._scrollbarDecorationNode.classList.add('xterm-decoration-scrollbar'); + this._viewportElement.parentElement.appendChild(this._scrollbarDecorationNode); + } if (!this._scrollbarDecorationCanvas) { this._scrollbarDecorationCanvas = this._scrollbarDecorationNode.getContext('2d'); this._refreshScollbarDecorations(); diff --git a/src/browser/services/Services.ts b/src/browser/services/Services.ts index 6c9d29b5..2d1f3d03 100644 --- a/src/browser/services/Services.ts +++ b/src/browser/services/Services.ts @@ -119,6 +119,6 @@ export interface ICharacterJoinerService { export const IDecorationService = createDecorator('DecorationService'); export interface IDecorationService extends IDisposable { - attachToDom(renderService: IRenderService, screenElement: HTMLElement, viewportElement: HTMLElement, scrollbarDecorationNode: HTMLCanvasElement): void; + attachToDom(renderService: IRenderService, screenElement: HTMLElement, viewportElement: HTMLElement): void; registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined; } From 1250ab637a67ef681ec03e5d96354c730c52e36f Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 11 Mar 2022 17:52:04 -0500 Subject: [PATCH 037/245] insert before --- src/browser/services/DecorationService.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/browser/services/DecorationService.ts b/src/browser/services/DecorationService.ts index 74489345..2dddf825 100644 --- a/src/browser/services/DecorationService.ts +++ b/src/browser/services/DecorationService.ts @@ -103,7 +103,7 @@ export class DecorationService extends Disposable implements IDecorationService // TODO: make this opt in, must be done before the scroll area in order to show up this._scrollbarDecorationNode = document.createElement('canvas'); this._scrollbarDecorationNode.classList.add('xterm-decoration-scrollbar'); - this._viewportElement.parentElement.appendChild(this._scrollbarDecorationNode); + this._viewportElement.parentElement.insertBefore(this._scrollbarDecorationNode, this._viewportElement); } if (!this._scrollbarDecorationCanvas) { this._scrollbarDecorationCanvas = this._scrollbarDecorationNode.getContext('2d'); @@ -129,9 +129,9 @@ export class DecorationService extends Disposable implements IDecorationService return; } this._scrollbarDecorationNode.style.width = `${ScrollbarConstants.WIDTH}px`; - this._scrollbarDecorationNode.style.height = `${this._viewportElement.clientHeight}px`; + this._scrollbarDecorationNode.style.height = `${this._screenElement!.clientHeight}px`; this._scrollbarDecorationNode.width = Math.floor(ScrollbarConstants.WIDTH * window.devicePixelRatio); - this._scrollbarDecorationNode.height = Math.floor(this._viewportElement.clientHeight * window.devicePixelRatio); + this._scrollbarDecorationNode.height = Math.floor(this._screenElement!.clientHeight * window.devicePixelRatio); this._scrollbarDecorationCanvas.clearRect(0, 0, this._scrollbarDecorationCanvas.canvas.width, this._scrollbarDecorationCanvas.canvas.height); for (const decoration of this._scrollbarDecorations) { decoration.render(); From 4022277cec2fd75265a7faaf36c23139df9d65e1 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 11 Mar 2022 21:52:16 -0500 Subject: [PATCH 038/245] scroll -> overviewRuler --- demo/client.ts | 12 +++--- demo/index.html | 2 +- src/browser/services/DecorationService.ts | 52 +++++++++++------------ typings/xterm.d.ts | 2 +- 4 files changed, 34 insertions(+), 34 deletions(-) diff --git a/demo/client.ts b/demo/client.ts index 0b3ce37e..586114d1 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -151,7 +151,7 @@ if (document.location.pathname === '/test') { document.getElementById('custom-glyph').addEventListener('click', writeCustomGlyphHandler); document.getElementById('load-test').addEventListener('click', loadTest); document.getElementById('add-decoration').addEventListener('click', addDecoration); - document.getElementById('add-scrollbar-decoration').addEventListener('click', addScrollbarDecoration); + document.getElementById('add-overview-ruler').addEventListener('click', addOverviewRuler); } function createTerminal(): void { @@ -552,10 +552,10 @@ function addDecoration() { }); } -function addScrollbarDecoration() { - const scrollbarDecorationCanvas = term.registerDecoration({marker: term.addMarker(1), scrollbarDecorationColor: 'red'}); - scrollbarDecorationCanvas.element!.style.left = `${document.querySelector('.xterm-viewport').clientWidth + 5}px`; - term.registerDecoration({marker: term.addMarker(3), scrollbarDecorationColor: 'green'}); - term.registerDecoration({marker: term.addMarker(5), scrollbarDecorationColor: 'blue'}); +function addOverviewRuler() { + const canvas = term.registerDecoration({marker: term.addMarker(1), overviewRulerItemColor: 'red'}); + canvas.element!.style.left = `${document.querySelector('.xterm-viewport').clientWidth + 5}px`; + term.registerDecoration({marker: term.addMarker(3), overviewRulerItemColor: 'green'}); + term.registerDecoration({marker: term.addMarker(5), overviewRulerItemColor: 'blue'}); } diff --git a/demo/index.html b/demo/index.html index dab52eec..d2b8d481 100644 --- a/demo/index.html +++ b/demo/index.html @@ -69,7 +69,7 @@ - +
diff --git a/src/browser/services/DecorationService.ts b/src/browser/services/DecorationService.ts index 2dddf825..2e44b1ec 100644 --- a/src/browser/services/DecorationService.ts +++ b/src/browser/services/DecorationService.ts @@ -22,11 +22,11 @@ export class DecorationService extends Disposable implements IDecorationService private _screenElement: HTMLElement | undefined; private _viewportElement: HTMLElement | undefined; private _bufferDecorationContainer: HTMLElement | undefined; - private _scrollbarDecorationCanvas: CanvasRenderingContext2D | null = null; - private _scrollbarDecorationNode: HTMLCanvasElement | undefined; + private _overviewRulerCtx: CanvasRenderingContext2D | null = null; + private _overviewRulerCanvas: HTMLCanvasElement | undefined; private readonly _bufferDecorations: BufferDecoration[] = []; - private _scrollbarDecorations: ScrollbarDecoration[] = []; + private _overviewRulerDecorations: ScrollbarDecoration[] = []; constructor(@IInstantiationService private readonly _instantiationService: IInstantiationService, @IBufferService private readonly _bufferService: IBufferService) { super(); } @@ -39,7 +39,7 @@ export class DecorationService extends Disposable implements IDecorationService this.register(this._renderService.onDimensionsChange(() => this._refresh(true))); this.register(addDisposableDomListener(window, 'resize', () => this._refreshScollbarDecorations())); this.register(this._bufferService.buffers.onBufferActivate(() => { - this._scrollbarDecorationNode!.style.display = this._bufferService.buffer === this._bufferService.buffers.alt ? 'none' : 'block'; + this._overviewRulerCanvas!.style.display = this._bufferService.buffer === this._bufferService.buffers.alt ? 'none' : 'block'; })); } @@ -47,8 +47,8 @@ export class DecorationService extends Disposable implements IDecorationService if (decorationOptions.marker.isDisposed) { return undefined; } - if (decorationOptions.scrollbarDecorationColor) { - return this._registerScrollbarDecoration(decorationOptions.marker, decorationOptions.scrollbarDecorationColor); + if (decorationOptions.overviewRulerItemColor) { + return this._registerScrollbarDecoration(decorationOptions.marker, decorationOptions.overviewRulerItemColor); } return this._registerBufferDecoration(decorationOptions); } @@ -57,14 +57,14 @@ export class DecorationService extends Disposable implements IDecorationService for (const bufferDecoration of this._bufferDecorations) { bufferDecoration.dispose(); } - for (const scrollbarDecoration of this._scrollbarDecorations) { + for (const scrollbarDecoration of this._overviewRulerDecorations) { scrollbarDecoration.dispose(); } if (this._screenElement && this._bufferDecorationContainer && this._screenElement.contains(this._bufferDecorationContainer)) { this._screenElement.removeChild(this._bufferDecorationContainer); } - this._scrollbarDecorations = []; - this._scrollbarDecorationNode?.remove(); + this._overviewRulerDecorations = []; + this._overviewRulerCanvas?.remove(); } private _refresh(shouldRecreate?: boolean): void { @@ -99,19 +99,19 @@ export class DecorationService extends Disposable implements IDecorationService if (!this._viewportElement?.parentElement) { return; } - if (!this._scrollbarDecorationNode) { + if (!this._overviewRulerCanvas) { // TODO: make this opt in, must be done before the scroll area in order to show up - this._scrollbarDecorationNode = document.createElement('canvas'); - this._scrollbarDecorationNode.classList.add('xterm-decoration-scrollbar'); - this._viewportElement.parentElement.insertBefore(this._scrollbarDecorationNode, this._viewportElement); + this._overviewRulerCanvas = document.createElement('canvas'); + this._overviewRulerCanvas.classList.add('xterm-decoration-scrollbar'); + this._viewportElement.parentElement.insertBefore(this._overviewRulerCanvas, this._viewportElement); } - if (!this._scrollbarDecorationCanvas) { - this._scrollbarDecorationCanvas = this._scrollbarDecorationNode.getContext('2d'); + if (!this._overviewRulerCtx) { + this._overviewRulerCtx = this._overviewRulerCanvas.getContext('2d'); this._refreshScollbarDecorations(); } - const decoration = this._instantiationService.createInstance(ScrollbarDecoration, { marker, scrollbarDecorationColor: color }, this._scrollbarDecorationNode, this._scrollbarDecorationCanvas!); - decoration.onDispose(() => this._scrollbarDecorations.splice(this._scrollbarDecorations.indexOf(decoration), 1)); - this._scrollbarDecorations.push(decoration); + const decoration = this._instantiationService.createInstance(ScrollbarDecoration, { marker, overviewRulerItemColor: color }, this._overviewRulerCanvas, this._overviewRulerCtx!); + decoration.onDispose(() => this._overviewRulerDecorations.splice(this._overviewRulerDecorations.indexOf(decoration), 1)); + this._overviewRulerDecorations.push(decoration); return decoration; } @@ -125,15 +125,15 @@ export class DecorationService extends Disposable implements IDecorationService } private _refreshScollbarDecorations(): void { - if (!this._scrollbarDecorationCanvas || !this._viewportElement || !this._scrollbarDecorationNode) { + if (!this._overviewRulerCtx || !this._viewportElement || !this._overviewRulerCanvas) { return; } - this._scrollbarDecorationNode.style.width = `${ScrollbarConstants.WIDTH}px`; - this._scrollbarDecorationNode.style.height = `${this._screenElement!.clientHeight}px`; - this._scrollbarDecorationNode.width = Math.floor(ScrollbarConstants.WIDTH * window.devicePixelRatio); - this._scrollbarDecorationNode.height = Math.floor(this._screenElement!.clientHeight * window.devicePixelRatio); - this._scrollbarDecorationCanvas.clearRect(0, 0, this._scrollbarDecorationCanvas.canvas.width, this._scrollbarDecorationCanvas.canvas.height); - for (const decoration of this._scrollbarDecorations) { + this._overviewRulerCanvas.style.width = `${ScrollbarConstants.WIDTH}px`; + this._overviewRulerCanvas.style.height = `${this._screenElement!.clientHeight}px`; + this._overviewRulerCanvas.width = Math.floor(ScrollbarConstants.WIDTH * window.devicePixelRatio); + this._overviewRulerCanvas.height = Math.floor(this._screenElement!.clientHeight * window.devicePixelRatio); + this._overviewRulerCtx.clearRect(0, 0, this._overviewRulerCtx.canvas.width, this._overviewRulerCtx.canvas.height); + for (const decoration of this._overviewRulerDecorations) { decoration.render(); } } @@ -164,7 +164,7 @@ export class ScrollbarDecoration extends Disposable implements IDecoration { ) { super(); this._marker = options.marker; - this._color = options.scrollbarDecorationColor; + this._color = options.overviewRulerItemColor; this._marker.onDispose(() => this.dispose()); this.render(); } diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 510c099f..cb952e70 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -475,7 +475,7 @@ declare module 'xterm' { * When provided, renders the decoration in the scrollbar * with the given color */ - scrollbarDecorationColor?: string; + overviewRulerItemColor?: string; } /** From 692df151e5813270e6ee86f21eb822bddcda96bc Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 11 Mar 2022 22:34:17 -0500 Subject: [PATCH 039/245] part 1 of massive refactor --- src/browser/services/DecorationService.ts | 211 +++++++++++++--------- 1 file changed, 127 insertions(+), 84 deletions(-) diff --git a/src/browser/services/DecorationService.ts b/src/browser/services/DecorationService.ts index 2e44b1ec..2daa9e92 100644 --- a/src/browser/services/DecorationService.ts +++ b/src/browser/services/DecorationService.ts @@ -14,6 +14,110 @@ const enum ScrollbarConstants { WIDTH = 7 } +interface IDecorationRenderer { + refreshDecorations(shouldRecreate?: boolean): void; + registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined; +} + +class BufferDecorationRenderer extends Disposable implements IDecorationRenderer { + private _decorationContainer: HTMLElement | undefined; + private readonly _decorations: BufferDecoration[] = []; + private _renderService: IRenderService | undefined; + + constructor( + @IBufferService private readonly _bufferService: IBufferService, + private readonly _screenElement: HTMLElement) { + super(); + this.register(this._bufferService.buffers.onBufferActivate(() => { + // this._canvas!.style.display = this._bufferService.buffer === this._bufferService.buffers.alt ? 'none' : 'block'; + })); + } + public refreshDecorations(shouldRecreate?: boolean): void { + if (!this._renderService) { + return; + } + for (const decoration of this._decorations) { + decoration.render(this._renderService, shouldRecreate); + } + } + public registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined { + if (this._screenElement && !this._decorationContainer) { + this._decorationContainer = document.createElement('div'); + this._decorationContainer.classList.add('xterm-decoration-container'); + this._screenElement.appendChild(this._decorationContainer); + } + const decoration = new BufferDecoration(this._bufferService, decorationOptions, this._decorationContainer); + this._decorations.push(decoration); + decoration.onDispose(() => this._decorations.splice(this._decorations.indexOf(decoration), 1)); + this.refreshDecorations(); + return decoration; + } + public override dispose(): void { + if (this._screenElement && this._decorationContainer && this._screenElement.contains(this._decorationContainer)) { + this._screenElement.removeChild(this._decorationContainer); + } + for (const bufferDecoration of this._decorations) { + bufferDecoration.dispose(); + } + super.dispose(); + } + public attachToDom(renderService: IRenderService): void { + this._renderService = renderService; + } +} + +class OverviewRulerRenderer extends Disposable implements IDecorationRenderer { + private _canvas: HTMLCanvasElement | undefined; + private _ctx: CanvasRenderingContext2D | null = null; + private _decorations: ScrollbarDecoration[] = []; + + constructor(@IInstantiationService private readonly _instantiationService: IInstantiationService, @IBufferService private readonly _bufferService: IBufferService, private readonly _viewportElement: HTMLElement, private readonly _screenElement: HTMLElement) { + super(); + this.register(this._bufferService.buffers.onBufferActivate(() => { + this._canvas!.style.display = this._bufferService.buffer === this._bufferService.buffers.alt ? 'none' : 'block'; + })); + } + public registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined { + if (!this._viewportElement.parentElement) { + return; + } + if (!this._canvas) { + this._canvas = document.createElement('canvas'); + this._canvas.classList.add('xterm-decoration-scrollbar'); + this._viewportElement.parentElement.insertBefore(this._canvas, this._viewportElement); + } + if (!this._ctx) { + this._ctx = this._canvas.getContext('2d'); + this.refreshDecorations(); + } + const decoration = this._instantiationService.createInstance(ScrollbarDecoration, { marker: decorationOptions.marker, overviewRulerItemColor: decorationOptions.overviewRulerItemColor }, this._canvas, this._ctx!); + decoration.onDispose(() => this._decorations.splice(this._decorations.indexOf(decoration), 1)); + this._decorations.push(decoration); + return decoration; + } + public refreshDecorations(): void { + if (!this._canvas || !this._ctx || !this._screenElement) { + return; + } + this._canvas.style.width = `${ScrollbarConstants.WIDTH}px`; + this._canvas.style.height = `${this._screenElement.clientHeight}px`; + this._canvas.width = Math.floor(ScrollbarConstants.WIDTH * window.devicePixelRatio); + this._canvas.height = Math.floor(this._screenElement.clientHeight * window.devicePixelRatio); + this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height); + for (const decoration of this._decorations) { + decoration.render(); + } + } + public override dispose(): void { + for (const decoration of this._decorations) { + decoration.dispose(); + } + this._decorations = []; + this._canvas?.remove(); + super.dispose(); + } +} + export class DecorationService extends Disposable implements IDecorationService { private _renderService: IRenderService | undefined; @@ -21,55 +125,50 @@ export class DecorationService extends Disposable implements IDecorationService private _screenElement: HTMLElement | undefined; private _viewportElement: HTMLElement | undefined; - private _bufferDecorationContainer: HTMLElement | undefined; - private _overviewRulerCtx: CanvasRenderingContext2D | null = null; - private _overviewRulerCanvas: HTMLCanvasElement | undefined; - private readonly _bufferDecorations: BufferDecoration[] = []; - private _overviewRulerDecorations: ScrollbarDecoration[] = []; + private _overviewRulerRenderer: OverviewRulerRenderer | undefined; + private _bufferDecorationRenderer: BufferDecorationRenderer | undefined; - constructor(@IInstantiationService private readonly _instantiationService: IInstantiationService, @IBufferService private readonly _bufferService: IBufferService) { super(); } + constructor(@IInstantiationService private readonly _instantiationService: IInstantiationService, @IBufferService private readonly _bufferService: IBufferService) { + super(); + } public attachToDom(renderService: IRenderService, screenElement: HTMLElement, viewportElement: HTMLElement): void { this._renderService = renderService; this._screenElement = screenElement; this._viewportElement = viewportElement; - - this.register(this._renderService.onRenderedBufferChange(() => this._refresh())); + this.register(this._renderService.onRenderedBufferChange(() => this._queueRefresh())); this.register(this._renderService.onDimensionsChange(() => this._refresh(true))); - this.register(addDisposableDomListener(window, 'resize', () => this._refreshScollbarDecorations())); - this.register(this._bufferService.buffers.onBufferActivate(() => { - this._overviewRulerCanvas!.style.display = this._bufferService.buffer === this._bufferService.buffers.alt ? 'none' : 'block'; - })); + this.register(addDisposableDomListener(window, 'resize', () => this._queueRefresh())); + if (!this._bufferDecorationRenderer && this._viewportElement && this._screenElement) { + // TODO: allow registering before the viewport element exists + this._bufferDecorationRenderer = new BufferDecorationRenderer(this._bufferService, this._screenElement); + this._bufferDecorationRenderer.attachToDom(renderService); + } } public registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined { if (decorationOptions.marker.isDisposed) { return undefined; } - if (decorationOptions.overviewRulerItemColor) { - return this._registerScrollbarDecoration(decorationOptions.marker, decorationOptions.overviewRulerItemColor); + + if (decorationOptions.overviewRulerItemColor && this._viewportElement && this._screenElement) { + if (!this._overviewRulerRenderer) { + this._overviewRulerRenderer = new OverviewRulerRenderer(this._instantiationService, this._bufferService, this._viewportElement, this._screenElement); + } + return this._overviewRulerRenderer.registerDecoration(decorationOptions); } - return this._registerBufferDecoration(decorationOptions); + return this._bufferDecorationRenderer?.registerDecoration(decorationOptions); } public dispose(): void { - for (const bufferDecoration of this._bufferDecorations) { - bufferDecoration.dispose(); - } - for (const scrollbarDecoration of this._overviewRulerDecorations) { - scrollbarDecoration.dispose(); - } - if (this._screenElement && this._bufferDecorationContainer && this._screenElement.contains(this._bufferDecorationContainer)) { - this._screenElement.removeChild(this._bufferDecorationContainer); - } - this._overviewRulerDecorations = []; - this._overviewRulerCanvas?.remove(); + this._overviewRulerRenderer?.dispose(); + this._bufferDecorationRenderer?.dispose(); } private _refresh(shouldRecreate?: boolean): void { - this._refreshBufferDecorations(shouldRecreate); - this._refreshScollbarDecorations(); + this._bufferDecorationRenderer?.refreshDecorations(shouldRecreate); + this._overviewRulerRenderer?.refreshDecorations(); } private _queueRefresh(): void { @@ -81,62 +180,6 @@ export class DecorationService extends Disposable implements IDecorationService this._animationFrame = undefined; }); } - - private _registerBufferDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined { - if (this._screenElement && !this._bufferDecorationContainer) { - this._bufferDecorationContainer = document.createElement('div'); - this._bufferDecorationContainer.classList.add('xterm-decoration-container'); - this._screenElement.appendChild(this._bufferDecorationContainer); - } - const decoration = new BufferDecoration(this._bufferService, decorationOptions, this._bufferDecorationContainer); - this._bufferDecorations.push(decoration); - decoration.onDispose(() => this._bufferDecorations.splice(this._bufferDecorations.indexOf(decoration), 1)); - this._queueRefresh(); - return decoration; - } - - private _registerScrollbarDecoration(marker: IMarker, color: string): IDecoration | undefined { - if (!this._viewportElement?.parentElement) { - return; - } - if (!this._overviewRulerCanvas) { - // TODO: make this opt in, must be done before the scroll area in order to show up - this._overviewRulerCanvas = document.createElement('canvas'); - this._overviewRulerCanvas.classList.add('xterm-decoration-scrollbar'); - this._viewportElement.parentElement.insertBefore(this._overviewRulerCanvas, this._viewportElement); - } - if (!this._overviewRulerCtx) { - this._overviewRulerCtx = this._overviewRulerCanvas.getContext('2d'); - this._refreshScollbarDecorations(); - } - const decoration = this._instantiationService.createInstance(ScrollbarDecoration, { marker, overviewRulerItemColor: color }, this._overviewRulerCanvas, this._overviewRulerCtx!); - decoration.onDispose(() => this._overviewRulerDecorations.splice(this._overviewRulerDecorations.indexOf(decoration), 1)); - this._overviewRulerDecorations.push(decoration); - return decoration; - } - - private _refreshBufferDecorations(shouldRecreate?: boolean): void { - if (!this._renderService) { - return; - } - for (const decoration of this._bufferDecorations) { - decoration.render(this._renderService, shouldRecreate); - } - } - - private _refreshScollbarDecorations(): void { - if (!this._overviewRulerCtx || !this._viewportElement || !this._overviewRulerCanvas) { - return; - } - this._overviewRulerCanvas.style.width = `${ScrollbarConstants.WIDTH}px`; - this._overviewRulerCanvas.style.height = `${this._screenElement!.clientHeight}px`; - this._overviewRulerCanvas.width = Math.floor(ScrollbarConstants.WIDTH * window.devicePixelRatio); - this._overviewRulerCanvas.height = Math.floor(this._screenElement!.clientHeight * window.devicePixelRatio); - this._overviewRulerCtx.clearRect(0, 0, this._overviewRulerCtx.canvas.width, this._overviewRulerCtx.canvas.height); - for (const decoration of this._overviewRulerDecorations) { - decoration.render(); - } - } } export class ScrollbarDecoration extends Disposable implements IDecoration { From f025c0cf4e4df27234a137d1118e7b204d715283 Mon Sep 17 00:00:00 2001 From: Eugene Pankov Date: Sat, 12 Mar 2022 20:56:02 +0100 Subject: [PATCH 040/245] ime: handle missing compositionend events for Sogou IME --- src/browser/Terminal.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index 08963933..f5684f84 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -92,6 +92,12 @@ export class Terminal extends CoreTerminal implements ITerminal { */ private _keyDownHandled: boolean = false; + /** + * Records whether a keydown event has occured since the last keyup event, i.e. whether a key + * is currently "pressed". + */ + private _keyDownSeen: boolean = false; + /** * Records whether the keypress event has already been handled and triggered a data event, if so * the input event should not trigger a data event but should still print to the textarea so @@ -1070,6 +1076,7 @@ export class Terminal extends CoreTerminal implements ITerminal { */ protected _keyDown(event: KeyboardEvent): boolean | undefined { this._keyDownHandled = false; + this._keyDownSeen = true; if (this._customKeyEventHandler && this._customKeyEventHandler(event) === false) { return false; @@ -1155,6 +1162,8 @@ export class Terminal extends CoreTerminal implements ITerminal { } protected _keyUp(ev: KeyboardEvent): void { + this._keyDownSeen = false; + if (this._customKeyEventHandler && this._customKeyEventHandler(ev) === false) { return; } @@ -1228,7 +1237,8 @@ export class Terminal extends CoreTerminal implements ITerminal { protected _inputEvent(ev: InputEvent): boolean { // Only support emoji IMEs when screen reader mode is disabled as the event must bubble up to // support reading out character input which can doubling up input characters - if (ev.data && ev.inputType === 'insertText' && !ev.composed && !this.optionsService.rawOptions.screenReaderMode) { + // Based on these event traces: https://github.com/xtermjs/xterm.js/issues/3679 + if (ev.data && ev.inputType === 'insertText' && (!ev.composed || !this._keyDownSeen) && !this.optionsService.rawOptions.screenReaderMode) { if (this._keyPressHandled) { return false; } From 9e598d9fba43562f42ff839183ee54e2e873b895 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 14 Mar 2022 09:30:30 -0400 Subject: [PATCH 041/245] allow setting width --- src/browser/services/DecorationService.ts | 30 ++++++++++++++++++----- typings/xterm.d.ts | 3 ++- 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/src/browser/services/DecorationService.ts b/src/browser/services/DecorationService.ts index 2daa9e92..f3f5f1f2 100644 --- a/src/browser/services/DecorationService.ts +++ b/src/browser/services/DecorationService.ts @@ -70,8 +70,17 @@ class OverviewRulerRenderer extends Disposable implements IDecorationRenderer { private _canvas: HTMLCanvasElement | undefined; private _ctx: CanvasRenderingContext2D | null = null; private _decorations: ScrollbarDecoration[] = []; + private _width: number | undefined; + private _anchor: 'right' | 'left' | undefined; + private _x: number | undefined; - constructor(@IInstantiationService private readonly _instantiationService: IInstantiationService, @IBufferService private readonly _bufferService: IBufferService, private readonly _viewportElement: HTMLElement, private readonly _screenElement: HTMLElement) { + constructor( + @IInstantiationService private readonly _instantiationService: IInstantiationService, + @IBufferService private readonly _bufferService: IBufferService, + @IRenderService private readonly _renderService: IRenderService, + private readonly _viewportElement: HTMLElement, + private readonly _screenElement: HTMLElement + ) { super(); this.register(this._bufferService.buffers.onBufferActivate(() => { this._canvas!.style.display = this._bufferService.buffer === this._bufferService.buffers.alt ? 'none' : 'block'; @@ -86,6 +95,10 @@ class OverviewRulerRenderer extends Disposable implements IDecorationRenderer { this._canvas.classList.add('xterm-decoration-scrollbar'); this._viewportElement.parentElement.insertBefore(this._canvas, this._viewportElement); } + this._width = decorationOptions.width; + this._anchor = decorationOptions.anchor; + this._x = decorationOptions.x; + if (!this._ctx) { this._ctx = this._canvas.getContext('2d'); this.refreshDecorations(); @@ -96,13 +109,18 @@ class OverviewRulerRenderer extends Disposable implements IDecorationRenderer { return decoration; } public refreshDecorations(): void { - if (!this._canvas || !this._ctx || !this._screenElement) { + if (!this._canvas || !this._ctx || !this._screenElement || !this._renderService) { return; } - this._canvas.style.width = `${ScrollbarConstants.WIDTH}px`; + this._canvas.style.width = `${this._width || ScrollbarConstants.WIDTH}px`; this._canvas.style.height = `${this._screenElement.clientHeight}px`; - this._canvas.width = Math.floor(ScrollbarConstants.WIDTH * window.devicePixelRatio); + this._canvas.width = Math.floor((this._width || ScrollbarConstants.WIDTH)* window.devicePixelRatio); this._canvas.height = Math.floor(this._screenElement.clientHeight * window.devicePixelRatio); + if (this._anchor === 'right') { + this._canvas.style.right = this._x ? `${this._x * this._renderService.dimensions.actualCellWidth}px` : ''; + } else { + this._canvas.style.left = this._x ? `${this._x * this._renderService.dimensions.actualCellWidth}px` : ''; + } this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height); for (const decoration of this._decorations) { decoration.render(); @@ -148,13 +166,13 @@ export class DecorationService extends Disposable implements IDecorationService } public registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined { - if (decorationOptions.marker.isDisposed) { + if (decorationOptions.marker.isDisposed || !this._renderService) { return undefined; } if (decorationOptions.overviewRulerItemColor && this._viewportElement && this._screenElement) { if (!this._overviewRulerRenderer) { - this._overviewRulerRenderer = new OverviewRulerRenderer(this._instantiationService, this._bufferService, this._viewportElement, this._screenElement); + this._overviewRulerRenderer = new OverviewRulerRenderer(this._instantiationService, this._bufferService, this._renderService, this._viewportElement, this._screenElement); } return this._overviewRulerRenderer.registerDecoration(decorationOptions); } diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index cb952e70..00f4072f 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -461,7 +461,8 @@ declare module 'xterm' { /** * The width of the decoration in cells, which defaults to - * cell width + * cell width or the width in pixels, when an overlayRulerItemColor + * is provided. */ width?: number; From 3b279e4a4dd9cf49bab49d24f97a354dd30217e6 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 14 Mar 2022 09:52:12 -0400 Subject: [PATCH 042/245] round to the nearest pixel --- src/browser/services/DecorationService.ts | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/browser/services/DecorationService.ts b/src/browser/services/DecorationService.ts index f3f5f1f2..181588cd 100644 --- a/src/browser/services/DecorationService.ts +++ b/src/browser/services/DecorationService.ts @@ -28,9 +28,6 @@ class BufferDecorationRenderer extends Disposable implements IDecorationRenderer @IBufferService private readonly _bufferService: IBufferService, private readonly _screenElement: HTMLElement) { super(); - this.register(this._bufferService.buffers.onBufferActivate(() => { - // this._canvas!.style.display = this._bufferService.buffer === this._bufferService.buffers.alt ? 'none' : 'block'; - })); } public refreshDecorations(shouldRecreate?: boolean): void { if (!this._renderService) { @@ -207,9 +204,9 @@ export class ScrollbarDecoration extends Disposable implements IDecoration { public isDisposed: boolean = false; - public get element(): HTMLCanvasElement { return this._element!; } + public get element(): HTMLCanvasElement | undefined { return this._element; } public get marker(): IMarker { return this._marker; } - public get color(): string { return this._color!; } + public get color(): string | undefined { return this._color; } private _onDispose = new EventEmitter(); public get onDispose(): IEvent { return this._onDispose.event; } @@ -230,6 +227,9 @@ export class ScrollbarDecoration extends Disposable implements IDecoration { this.render(); } public render(): void { + if (!this.color) { + throw new Error('No color was provided for the overview ruler decoraiton'); + } if (!this._element) { this._element = this._canvas; } @@ -237,21 +237,21 @@ export class ScrollbarDecoration extends Disposable implements IDecoration { this._ctx.strokeStyle = this.color; this._ctx.strokeRect( 0, - this.element.height * (this.marker.line / this._bufferService.buffers.active.lines.length), - this.element.width, + Math.round(this._element.height * (this.marker.line / this._bufferService.buffers.active.lines.length)), + this._element.width, window.devicePixelRatio ); - this._onRender.fire(this.element); + this._onRender.fire(this._element); } public override dispose(): void { - if (this._isDisposed) { + if (this._isDisposed || !this._element) { return; } this._ctx.clearRect( 0, - this.element.height * (this.marker.line / this._bufferService.buffers.active.lines.length), - this.element.width, + Math.round(this._element.height * (this.marker.line / this._bufferService.buffers.active.lines.length)), + this._element.width, window.devicePixelRatio ); this.isDisposed = true; From ced06629d45129115fbb7e59de8dbd5a6eb16680 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 14 Mar 2022 15:11:20 -0400 Subject: [PATCH 043/245] large refactor, broken --- .../Decorations/BufferDecorationRenderer.ts | 162 ++++++++ .../Decorations/OverviewRulerRenderer.ts | 150 ++++++++ src/browser/Terminal.ts | 28 +- src/browser/Types.d.ts | 6 + src/browser/services/DecorationService.ts | 361 ------------------ src/browser/services/Services.ts | 5 +- src/browser/tsconfig.json | 2 +- src/common/services/DecorationService.ts | 81 ++++ src/common/services/Services.ts | 9 +- 9 files changed, 431 insertions(+), 373 deletions(-) create mode 100644 src/browser/Decorations/BufferDecorationRenderer.ts create mode 100644 src/browser/Decorations/OverviewRulerRenderer.ts delete mode 100644 src/browser/services/DecorationService.ts create mode 100644 src/common/services/DecorationService.ts diff --git a/src/browser/Decorations/BufferDecorationRenderer.ts b/src/browser/Decorations/BufferDecorationRenderer.ts new file mode 100644 index 00000000..56559ad9 --- /dev/null +++ b/src/browser/Decorations/BufferDecorationRenderer.ts @@ -0,0 +1,162 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { addDisposableDomListener } from 'browser/Lifecycle'; +import { IDecorationService, IRenderService } from 'browser/services/Services'; +import { IEvent, EventEmitter } from 'common/EventEmitter'; +import { Disposable } from 'common/Lifecycle'; +import { IBufferService } from 'common/services/Services'; +import { IMarker } from 'common/Types'; +import { IDecoration, IDecorationOptions } from 'xterm'; + +export interface IDecorationRenderer { + refreshDecorations(shouldRecreate?: boolean): void; + renderDecoration(decoration: IDecoration, decorationOptions: IDecorationOptions): void; +} + +export class BufferDecorationRenderer extends Disposable implements IDecorationRenderer { + private _decorationContainer: HTMLElement; + private readonly _decorations: BufferDecoration[] = []; + private _altBufferIsActive: boolean = false; + + constructor( + @IBufferService private readonly _bufferService: IBufferService, + @IRenderService private readonly _renderService: IRenderService, + private readonly _decorationService: IDecorationService, + private readonly _screenElement: HTMLElement) { + super(); + this._decorationContainer = document.createElement('div'); + this._decorationContainer.classList.add('xterm-decoration-container'); + this._screenElement.appendChild(this._decorationContainer); + this.register(this._renderService.onRenderedBufferChange(() => this.refreshDecorations())); + this.register(this._renderService.onDimensionsChange(() => this.refreshDecorations())); + this.register(addDisposableDomListener(window, 'resize', () => this.refreshDecorations())); + this.register(this._bufferService.buffers.onBufferActivate(() => { + this._altBufferIsActive = this._bufferService.buffer === this._bufferService.buffers.alt; + })); + this.register(this._decorationService.onDecorationRegistered(options => this.renderDecoration(options))); + } + public refreshDecorations(shouldRecreate?: boolean): void { + if (!this._renderService) { + return; + } + for (const decoration of this._decorations) { + decoration.render(this._decorationContainer, this._renderService, shouldRecreate); + } + } + + public renderDecoration(decorationOptions: IDecorationOptions): void { + const decoration = new BufferDecoration(this._bufferService, decorationOptions); + if (this._decorationContainer && decoration.element && !this._decorationContainer.contains(decoration.element)) { + this._decorationContainer.append(decoration.element); + } + (decoration as BufferDecoration).render(this._decorationContainer, this._renderService, true); + } + + public override dispose(): void { + if (this._screenElement && this._decorationContainer && this._screenElement.contains(this._decorationContainer)) { + this._screenElement.removeChild(this._decorationContainer); + } + for (const bufferDecoration of this._decorations) { + bufferDecoration.dispose(); + } + super.dispose(); + } +} +export class BufferDecoration extends Disposable implements IDecoration { + private readonly _marker: IMarker; + private _element: HTMLElement | undefined; + private _container: HTMLElement | undefined; + private _altBufferIsActive: boolean = false; + + public isDisposed: boolean = false; + + public get element(): HTMLElement | undefined { return this._element; } + public get marker(): IMarker { return this._marker; } + + private _onDispose = new EventEmitter(); + public get onDispose(): IEvent { return this._onDispose.event; } + + private _onRender = new EventEmitter(); + public get onRender(): IEvent { return this._onRender.event; } + + + public x: number; + public anchor: 'left' | 'right'; + public width: number; + public height: number; + + constructor( + private readonly _bufferService: IBufferService, + options: IDecorationOptions + ) { + super(); + this.x = options.x ?? 0; + this._marker = options.marker; + this._marker.onDispose(() => this.dispose()); + this.anchor = options.anchor || 'left'; + this.width = options.width || 1; + this.height = options.height || 1; + } + + public render(container: HTMLElement, renderService: IRenderService, shouldRecreate?: boolean): void { + this._container = container; + if (!this._element || shouldRecreate) { + this._createElement(renderService, shouldRecreate); + } + this._refreshStyle(renderService); + if (this._element) { + this._onRender.fire(this._element); + } + } + + private _createElement(renderService: IRenderService, shouldRecreate?: boolean): void { + if (shouldRecreate && this._element && this._container && this._container.contains(this._element)) { + this._container.removeChild(this._element); + } + this._element = document.createElement('div'); + this._element.classList.add('xterm-decoration'); + this._element.style.width = `${this.width * renderService.dimensions.actualCellWidth}px`; + this._element.style.height = `${this.height * renderService.dimensions.actualCellHeight}px`; + this._element.style.top = `${(this.marker.line - this._bufferService.buffers.active.ydisp) * renderService.dimensions.actualCellHeight}px`; + this._element.style.lineHeight = `${renderService.dimensions.actualCellHeight}px`; + + if (this.x && this.x > this._bufferService.cols) { + // exceeded the container width, so hide + this._element.style.display = 'none'; + } + if (this.anchor === 'right') { + this._element.style.right = this.x ? `${this.x * renderService.dimensions.actualCellWidth}px` : ''; + } else { + this._element.style.left = this.x ? `${this.x * renderService.dimensions.actualCellWidth}px` : ''; + } + } + + private _refreshStyle(renderService: IRenderService): void { + if (!this._element) { + return; + } + const line = this.marker.line - this._bufferService.buffers.active.ydisp; + if (line < 0 || line > this._bufferService.rows) { + // outside of viewport + this._element.style.display = 'none'; + } else { + this._element.style.top = `${line * renderService.dimensions.actualCellHeight}px`; + this._element.style.display = this._altBufferIsActive ? 'none' : 'block'; + } + } + + public override dispose(): void { + if (this.isDisposed || !this._container) { + return; + } + if (this._element && this._container.contains(this._element)) { + this._container.removeChild(this._element); + } + this.isDisposed = true; + this._onDispose.fire(); + } +} + diff --git a/src/browser/Decorations/OverviewRulerRenderer.ts b/src/browser/Decorations/OverviewRulerRenderer.ts new file mode 100644 index 00000000..580a831a --- /dev/null +++ b/src/browser/Decorations/OverviewRulerRenderer.ts @@ -0,0 +1,150 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { addDisposableDomListener } from 'browser/Lifecycle'; +import { IDecorationRenderer } from 'browser/Decorations/BufferDecorationRenderer'; +import { IDecorationService, IRenderService } from 'browser/services/Services'; +import { EventEmitter, IEvent } from 'common/EventEmitter'; +import { Disposable } from 'common/Lifecycle'; +import { IBufferService, IInstantiationService } from 'common/services/Services'; +import { IDecorationOptions, IDecoration, IMarker } from 'xterm'; +const enum ScrollbarConstants { + WIDTH = 7 +} + +export class OverviewRulerRenderer extends Disposable implements IDecorationRenderer { + private _canvas: HTMLCanvasElement; + private _ctx: CanvasRenderingContext2D | null; + private _decorations: ScrollbarDecoration[] = []; + private _width: number | undefined; + private _anchor: 'right' | 'left' | undefined; + private _x: number | undefined; + + constructor( + @IBufferService private readonly _bufferService: IBufferService, + @IRenderService private readonly _renderService: IRenderService, + @IDecorationService private readonly _decorationService: IDecorationService, + @IInstantiationService private readonly _instantiationService: IInstantiationService, + private readonly _viewportElement: HTMLElement, + private readonly _screenElement: HTMLElement + ) { + super(); + this._canvas = document.createElement('canvas'); + this._canvas.classList.add('xterm-decoration-scrollbar'); + this._viewportElement.parentElement?.insertBefore(this._canvas, this._viewportElement); + this._ctx = this._canvas.getContext('2d'); + this.refreshDecorations(); + this.register(this._bufferService.buffers.onBufferActivate(() => { + this._canvas!.style.display = this._bufferService.buffer === this._bufferService.buffers.alt ? 'none' : 'block'; + })); + this.register(this._renderService.onRenderedBufferChange(() => this.refreshDecorations())); + this.register(this._renderService.onDimensionsChange(() => this.refreshDecorations())); + this.register(addDisposableDomListener(window, 'resize', () => this.refreshDecorations())); + this.register(this._decorationService.onDecorationRegistered(e => this.renderDecoration(e))); + this.register(this._decorationService.onDecorationRemoved(d => d.dispose())); + } + public renderDecoration(decorationOptions: IDecorationOptions): void { + if (!this._ctx || !decorationOptions.overviewRulerItemColor) { + return; + } + const decoration = this._instantiationService.createInstance(ScrollbarDecoration, { marker: decorationOptions.marker, overviewRulerItemColor: decorationOptions.overviewRulerItemColor }); + + this._ctx.lineWidth = 1; + this._ctx.strokeStyle = decorationOptions.overviewRulerItemColor; + this._ctx.strokeRect( + 0, + Math.round(this._canvas.height * (decoration.marker.line / this._bufferService.buffers.active.lines.length)), + this._canvas.width, + window.devicePixelRatio + ); + } + + public refreshDecorations(): void { + if (!this._ctx) { + return; + } + this._canvas.style.width = `${this._width || ScrollbarConstants.WIDTH}px`; + this._canvas.style.height = `${this._screenElement.clientHeight}px`; + this._canvas.width = Math.floor((this._width || ScrollbarConstants.WIDTH)* window.devicePixelRatio); + this._canvas.height = Math.floor(this._screenElement.clientHeight * window.devicePixelRatio); + if (this._anchor === 'right') { + this._canvas.style.right = this._x ? `${this._x * this._renderService.dimensions.actualCellWidth}px` : ''; + } else { + this._canvas.style.left = this._x ? `${this._x * this._renderService.dimensions.actualCellWidth}px` : ''; + } + this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height); + for (const decoration of this._decorations) { + decoration.render(this._ctx, this._canvas); + } + } + public override dispose(): void { + for (const decoration of this._decorations) { + decoration.dispose(); + } + this._decorations = []; + this._canvas?.remove(); + super.dispose(); + } +} +class ScrollbarDecoration extends Disposable implements IDecoration { + private readonly _marker: IMarker; + private _ctx: CanvasRenderingContext2D | undefined; + private _canvas: HTMLCanvasElement | undefined; + private _color: string | undefined; + + public isDisposed: boolean = false; + + public get element(): HTMLCanvasElement | undefined { return this._canvas; } + public get marker(): IMarker { return this._marker; } + public get color(): string | undefined { return this._color; } + + private _onDispose = new EventEmitter(); + public get onDispose(): IEvent { return this._onDispose.event; } + + private _onRender = new EventEmitter(); + public get onRender(): IEvent { return this._onRender.event; } + + constructor( + options: IDecorationOptions, + @IBufferService private readonly _bufferService: IBufferService + ) { + super(); + this._marker = options.marker; + this._color = options.overviewRulerItemColor; + this._marker.onDispose(() => this.dispose()); + } + + public render(ctx: CanvasRenderingContext2D, canvas: HTMLCanvasElement): void { + if (!this.color) { + throw new Error('No color was provided for the overview ruler decoraiton'); + } + this._ctx = ctx; + this._canvas = canvas; + ctx.lineWidth = 1; + ctx.strokeStyle = this.color; + ctx.strokeRect( + 0, + Math.round(canvas.height * (this.marker.line / this._bufferService.buffers.active.lines.length)), + canvas.width, + window.devicePixelRatio + ); + this._onRender.fire(canvas); + } + + public override dispose(): void { + if (this._isDisposed || !this._canvas || !this._ctx) { + return; + } + this._ctx.clearRect( + 0, + Math.round(this._canvas.height * (this.marker.line / this._bufferService.buffers.active.lines.length)), + this._canvas.width, + window.devicePixelRatio + ); + this.isDisposed = true; + this._onDispose.fire(); + super.dispose(); + } +} diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index ba0fc93d..39f0a68a 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -45,7 +45,7 @@ import { EventEmitter, IEvent, forwardEvent } from 'common/EventEmitter'; import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine'; import { ColorManager } from 'browser/ColorManager'; import { RenderService } from 'browser/services/RenderService'; -import { ICharSizeService, IRenderService, IMouseService, ISelectionService, ISoundService, ICoreBrowserService, ICharacterJoinerService, IDecorationService } from 'browser/services/Services'; +import { ICharSizeService, IRenderService, IMouseService, ISelectionService, ISoundService, ICoreBrowserService, ICharacterJoinerService } from 'browser/services/Services'; import { CharSizeService } from 'browser/services/CharSizeService'; import { IBuffer } from 'common/buffer/Types'; import { MouseService } from 'browser/services/MouseService'; @@ -55,7 +55,10 @@ import { CoreTerminal } from 'common/CoreTerminal'; import { color, rgba } from 'browser/Color'; import { CharacterJoinerService } from 'browser/services/CharacterJoinerService'; import { toRgbString } from 'common/input/XParseColor'; -import { DecorationService } from 'browser/services/DecorationService'; +import { BufferDecorationRenderer } from 'browser/Decorations/BufferDecorationRenderer'; +import { OverviewRulerRenderer } from 'browser/Decorations/OverviewRulerRenderer'; +// import { IDecorationService } from 'common/services/Services'; +import { DecorationService } from 'common/services/DecorationService'; // Let it work inside Node.js for automated testing purposes. const document: Document = (typeof window !== 'undefined') ? window.document : null as any; @@ -71,6 +74,9 @@ export class Terminal extends CoreTerminal implements ITerminal { private _helperContainer: HTMLElement | undefined; private _compositionView: HTMLElement | undefined; + private _overviewRulerRenderer: OverviewRulerRenderer | undefined; + private _bufferDecorationRenderer: BufferDecorationRenderer | undefined; + // private _visualBellTimer: number; public browser: IBrowser = Browser as any; @@ -81,6 +87,7 @@ export class Terminal extends CoreTerminal implements ITerminal { private _charSizeService: ICharSizeService | undefined; private _mouseService: IMouseService | undefined; private _renderService: IRenderService | undefined; + private _decorationService: DecorationService | undefined; private _characterJoinerService: ICharacterJoinerService | undefined; private _selectionService: ISelectionService | undefined; private _soundService: ISoundService | undefined; @@ -109,7 +116,6 @@ export class Terminal extends CoreTerminal implements ITerminal { public linkifier: ILinkifier; public linkifier2: ILinkifier2; public viewport: IViewport | undefined; - public decorationService: IDecorationService; private _compositionHelper: ICompositionHelper | undefined; private _mouseZoneManager: IMouseZoneManager | undefined; private _accessibilityManager: AccessibilityManager | undefined; @@ -159,7 +165,7 @@ export class Terminal extends CoreTerminal implements ITerminal { this.linkifier = this._instantiationService.createInstance(Linkifier); this.linkifier2 = this.register(this._instantiationService.createInstance(Linkifier2)); - this.decorationService = this.register(this._instantiationService.createInstance(DecorationService)); + this._decorationService = this._instantiationService.createInstance(DecorationService); // Setup InputHandler listeners this.register(this._inputHandler.onRequestBell(() => this.bell())); @@ -577,8 +583,16 @@ export class Terminal extends CoreTerminal implements ITerminal { this.register(this.onScroll(() => this._mouseZoneManager!.clearAll())); this.linkifier.attachToDom(this.element, this._mouseZoneManager); this.linkifier2.attachToDom(this.screenElement, this._mouseService, this._renderService); - - this.decorationService.attachToDom(this._renderService, this.screenElement, this._viewportElement); + if (this._decorationService) { + this._bufferDecorationRenderer = new BufferDecorationRenderer(this._bufferService, this._renderService, this._decorationService, this.screenElement); + } + // if (this.options.overviewRulerWidth && this._decorationService) { + // this._overviewRulerRenderer = new OverviewRulerRenderer(this._bufferService, this._renderService, this._decorationService, this._viewportElement, this.screenElement); + // } + // this.optionsService.onOptionChange(() => { + // if (!this._overviewRulerRenderer && this.options.overviewRulerWidth && this._renderService && this._viewportElement && this.screenElement && this._decorationService) { + // this._overviewRulerRenderer = new OverviewRulerRenderer(this._bufferService, this._renderService, this._decorationService, this._viewportElement, this.screenElement); + // }}); // This event listener must be registered aftre MouseZoneManager is created this.register(addDisposableDomListener(this.element, 'mousedown', (e: MouseEvent) => this._selectionService!.onMouseDown(e))); @@ -1004,7 +1018,7 @@ export class Terminal extends CoreTerminal implements ITerminal { } public registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined { - return this.decorationService!.registerDecoration(decorationOptions); + return this._decorationService!.registerDecoration(decorationOptions); } /** diff --git a/src/browser/Types.d.ts b/src/browser/Types.d.ts index 35b52d62..b8e1626e 100644 --- a/src/browser/Types.d.ts +++ b/src/browser/Types.d.ts @@ -9,6 +9,7 @@ import { ICoreTerminal, CharData, ITerminalOptions } from 'common/Types'; import { IMouseService, IRenderService } from './services/Services'; import { IBuffer } from 'common/buffer/Types'; import { IFunctionIdentifier, IParams } from 'common/parser/Types'; +import { createDecorator } from 'common/services/ServiceRegistry'; export interface ITerminal extends IPublicTerminal, ICoreTerminal { element: HTMLElement | undefined; @@ -205,6 +206,11 @@ export interface ILinkifier { registerLinkMatcher(regex: RegExp, handler: LinkMatcherHandler, options?: ILinkMatcherOptions): number; deregisterLinkMatcher(matcherId: number): boolean; } +export interface IDecorationService extends IDisposable { + readonly onDecorationRegistered: IEvent; + readonly onDecorationRemoved: IEvent; + registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined; +} interface ILinkState { decorations: ILinkDecorations; diff --git a/src/browser/services/DecorationService.ts b/src/browser/services/DecorationService.ts deleted file mode 100644 index 181588cd..00000000 --- a/src/browser/services/DecorationService.ts +++ /dev/null @@ -1,361 +0,0 @@ -/** - * Copyright (c) 2022 The xterm.js authors. All rights reserved. - * @license MIT - */ - -import { addDisposableDomListener } from 'browser/Lifecycle'; -import { IDecorationService, IRenderService } from 'browser/services/Services'; -import { EventEmitter, IEvent } from 'common/EventEmitter'; -import { Disposable } from 'common/Lifecycle'; -import { IBufferService, IInstantiationService } from 'common/services/Services'; -import { IDecorationOptions, IDecoration, IMarker } from 'xterm'; - -const enum ScrollbarConstants { - WIDTH = 7 -} - -interface IDecorationRenderer { - refreshDecorations(shouldRecreate?: boolean): void; - registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined; -} - -class BufferDecorationRenderer extends Disposable implements IDecorationRenderer { - private _decorationContainer: HTMLElement | undefined; - private readonly _decorations: BufferDecoration[] = []; - private _renderService: IRenderService | undefined; - - constructor( - @IBufferService private readonly _bufferService: IBufferService, - private readonly _screenElement: HTMLElement) { - super(); - } - public refreshDecorations(shouldRecreate?: boolean): void { - if (!this._renderService) { - return; - } - for (const decoration of this._decorations) { - decoration.render(this._renderService, shouldRecreate); - } - } - public registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined { - if (this._screenElement && !this._decorationContainer) { - this._decorationContainer = document.createElement('div'); - this._decorationContainer.classList.add('xterm-decoration-container'); - this._screenElement.appendChild(this._decorationContainer); - } - const decoration = new BufferDecoration(this._bufferService, decorationOptions, this._decorationContainer); - this._decorations.push(decoration); - decoration.onDispose(() => this._decorations.splice(this._decorations.indexOf(decoration), 1)); - this.refreshDecorations(); - return decoration; - } - public override dispose(): void { - if (this._screenElement && this._decorationContainer && this._screenElement.contains(this._decorationContainer)) { - this._screenElement.removeChild(this._decorationContainer); - } - for (const bufferDecoration of this._decorations) { - bufferDecoration.dispose(); - } - super.dispose(); - } - public attachToDom(renderService: IRenderService): void { - this._renderService = renderService; - } -} - -class OverviewRulerRenderer extends Disposable implements IDecorationRenderer { - private _canvas: HTMLCanvasElement | undefined; - private _ctx: CanvasRenderingContext2D | null = null; - private _decorations: ScrollbarDecoration[] = []; - private _width: number | undefined; - private _anchor: 'right' | 'left' | undefined; - private _x: number | undefined; - - constructor( - @IInstantiationService private readonly _instantiationService: IInstantiationService, - @IBufferService private readonly _bufferService: IBufferService, - @IRenderService private readonly _renderService: IRenderService, - private readonly _viewportElement: HTMLElement, - private readonly _screenElement: HTMLElement - ) { - super(); - this.register(this._bufferService.buffers.onBufferActivate(() => { - this._canvas!.style.display = this._bufferService.buffer === this._bufferService.buffers.alt ? 'none' : 'block'; - })); - } - public registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined { - if (!this._viewportElement.parentElement) { - return; - } - if (!this._canvas) { - this._canvas = document.createElement('canvas'); - this._canvas.classList.add('xterm-decoration-scrollbar'); - this._viewportElement.parentElement.insertBefore(this._canvas, this._viewportElement); - } - this._width = decorationOptions.width; - this._anchor = decorationOptions.anchor; - this._x = decorationOptions.x; - - if (!this._ctx) { - this._ctx = this._canvas.getContext('2d'); - this.refreshDecorations(); - } - const decoration = this._instantiationService.createInstance(ScrollbarDecoration, { marker: decorationOptions.marker, overviewRulerItemColor: decorationOptions.overviewRulerItemColor }, this._canvas, this._ctx!); - decoration.onDispose(() => this._decorations.splice(this._decorations.indexOf(decoration), 1)); - this._decorations.push(decoration); - return decoration; - } - public refreshDecorations(): void { - if (!this._canvas || !this._ctx || !this._screenElement || !this._renderService) { - return; - } - this._canvas.style.width = `${this._width || ScrollbarConstants.WIDTH}px`; - this._canvas.style.height = `${this._screenElement.clientHeight}px`; - this._canvas.width = Math.floor((this._width || ScrollbarConstants.WIDTH)* window.devicePixelRatio); - this._canvas.height = Math.floor(this._screenElement.clientHeight * window.devicePixelRatio); - if (this._anchor === 'right') { - this._canvas.style.right = this._x ? `${this._x * this._renderService.dimensions.actualCellWidth}px` : ''; - } else { - this._canvas.style.left = this._x ? `${this._x * this._renderService.dimensions.actualCellWidth}px` : ''; - } - this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height); - for (const decoration of this._decorations) { - decoration.render(); - } - } - public override dispose(): void { - for (const decoration of this._decorations) { - decoration.dispose(); - } - this._decorations = []; - this._canvas?.remove(); - super.dispose(); - } -} - -export class DecorationService extends Disposable implements IDecorationService { - - private _renderService: IRenderService | undefined; - private _animationFrame: number | undefined; - - private _screenElement: HTMLElement | undefined; - private _viewportElement: HTMLElement | undefined; - - private _overviewRulerRenderer: OverviewRulerRenderer | undefined; - private _bufferDecorationRenderer: BufferDecorationRenderer | undefined; - - constructor(@IInstantiationService private readonly _instantiationService: IInstantiationService, @IBufferService private readonly _bufferService: IBufferService) { - super(); - } - - public attachToDom(renderService: IRenderService, screenElement: HTMLElement, viewportElement: HTMLElement): void { - this._renderService = renderService; - this._screenElement = screenElement; - this._viewportElement = viewportElement; - this.register(this._renderService.onRenderedBufferChange(() => this._queueRefresh())); - this.register(this._renderService.onDimensionsChange(() => this._refresh(true))); - this.register(addDisposableDomListener(window, 'resize', () => this._queueRefresh())); - if (!this._bufferDecorationRenderer && this._viewportElement && this._screenElement) { - // TODO: allow registering before the viewport element exists - this._bufferDecorationRenderer = new BufferDecorationRenderer(this._bufferService, this._screenElement); - this._bufferDecorationRenderer.attachToDom(renderService); - } - } - - public registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined { - if (decorationOptions.marker.isDisposed || !this._renderService) { - return undefined; - } - - if (decorationOptions.overviewRulerItemColor && this._viewportElement && this._screenElement) { - if (!this._overviewRulerRenderer) { - this._overviewRulerRenderer = new OverviewRulerRenderer(this._instantiationService, this._bufferService, this._renderService, this._viewportElement, this._screenElement); - } - return this._overviewRulerRenderer.registerDecoration(decorationOptions); - } - return this._bufferDecorationRenderer?.registerDecoration(decorationOptions); - } - - public dispose(): void { - this._overviewRulerRenderer?.dispose(); - this._bufferDecorationRenderer?.dispose(); - } - - private _refresh(shouldRecreate?: boolean): void { - this._bufferDecorationRenderer?.refreshDecorations(shouldRecreate); - this._overviewRulerRenderer?.refreshDecorations(); - } - - private _queueRefresh(): void { - if (this._animationFrame !== undefined) { - return; - } - this._animationFrame = window.requestAnimationFrame(() => { - this._refresh(); - this._animationFrame = undefined; - }); - } -} - -export class ScrollbarDecoration extends Disposable implements IDecoration { - private readonly _marker: IMarker; - private _element: HTMLCanvasElement | undefined; - private _color: string | undefined; - - public isDisposed: boolean = false; - - public get element(): HTMLCanvasElement | undefined { return this._element; } - public get marker(): IMarker { return this._marker; } - public get color(): string | undefined { return this._color; } - - private _onDispose = new EventEmitter(); - public get onDispose(): IEvent { return this._onDispose.event; } - - private _onRender = new EventEmitter(); - public get onRender(): IEvent { return this._onRender.event; } - - constructor( - options: IDecorationOptions, - private readonly _canvas: HTMLCanvasElement, - private readonly _ctx: CanvasRenderingContext2D, - @IBufferService private readonly _bufferService: IBufferService - ) { - super(); - this._marker = options.marker; - this._color = options.overviewRulerItemColor; - this._marker.onDispose(() => this.dispose()); - this.render(); - } - public render(): void { - if (!this.color) { - throw new Error('No color was provided for the overview ruler decoraiton'); - } - if (!this._element) { - this._element = this._canvas; - } - this._ctx.lineWidth = 1; - this._ctx.strokeStyle = this.color; - this._ctx.strokeRect( - 0, - Math.round(this._element.height * (this.marker.line / this._bufferService.buffers.active.lines.length)), - this._element.width, - window.devicePixelRatio - ); - this._onRender.fire(this._element); - } - - public override dispose(): void { - if (this._isDisposed || !this._element) { - return; - } - this._ctx.clearRect( - 0, - Math.round(this._element.height * (this.marker.line / this._bufferService.buffers.active.lines.length)), - this._element.width, - window.devicePixelRatio - ); - this.isDisposed = true; - this._onDispose.fire(); - super.dispose(); - } -} - -export class BufferDecoration extends Disposable implements IDecoration { - private readonly _marker: IMarker; - private _element: HTMLElement | undefined; - - public isDisposed: boolean = false; - - public get element(): HTMLElement | undefined { return this._element; } - public get marker(): IMarker { return this._marker; } - - private _onDispose = new EventEmitter(); - public get onDispose(): IEvent { return this._onDispose.event; } - - private _onRender = new EventEmitter(); - public get onRender(): IEvent { return this._onRender.event; } - - private _altBufferIsActive: boolean = false; - - public x: number; - public anchor: 'left' | 'right'; - public width: number; - public height: number; - - constructor( - private readonly _bufferService: IBufferService, - options: IDecorationOptions, - private readonly _container?: HTMLElement - ) { - super(); - this.x = options.x ?? 0; - this._marker = options.marker; - this._marker.onDispose(() => this.dispose()); - this.anchor = options.anchor || 'left'; - this.width = options.width || 1; - this.height = options.height || 1; - this.register(this._bufferService.buffers.onBufferActivate(() => { - this._altBufferIsActive = this._bufferService.buffer === this._bufferService.buffers.alt; - })); - } - - public render(renderService: IRenderService, shouldRecreate?: boolean): void { - if (!this._element || shouldRecreate) { - this._createElement(renderService, shouldRecreate); - } - if (this._container && this._element && !this._container.contains(this._element)) { - this._container.append(this._element); - } - this._refreshStyle(renderService); - if (this._element) { - this._onRender.fire(this._element); - } - } - - private _createElement(renderService: IRenderService, shouldRecreate?: boolean): void { - if (shouldRecreate && this._element && this._container && this._container.contains(this._element)) { - this._container.removeChild(this._element); - } - this._element = document.createElement('div'); - this._element.classList.add('xterm-decoration'); - this._element.style.width = `${this.width * renderService.dimensions.actualCellWidth}px`; - this._element.style.height = `${this.height * renderService.dimensions.actualCellHeight}px`; - this._element.style.top = `${(this.marker.line - this._bufferService.buffers.active.ydisp) * renderService.dimensions.actualCellHeight}px`; - this._element.style.lineHeight = `${renderService.dimensions.actualCellHeight}px`; - - if (this.x && this.x > this._bufferService.cols) { - // exceeded the container width, so hide - this._element.style.display = 'none'; - } - if (this.anchor === 'right') { - this._element.style.right = this.x ? `${this.x * renderService.dimensions.actualCellWidth}px` : ''; - } else { - this._element.style.left = this.x ? `${this.x * renderService.dimensions.actualCellWidth}px` : ''; - } - } - - private _refreshStyle(renderService: IRenderService): void { - if (!this._element) { - return; - } - const line = this.marker.line - this._bufferService.buffers.active.ydisp; - if (line < 0 || line > this._bufferService.rows) { - // outside of viewport - this._element.style.display = 'none'; - } else { - this._element.style.top = `${line * renderService.dimensions.actualCellHeight}px`; - this._element.style.display = this._altBufferIsActive ? 'none' : 'block'; - } - } - - public override dispose(): void { - if (this.isDisposed || !this._container) { - return; - } - if (this._element && this._container.contains(this._element)) { - this._container.removeChild(this._element); - } - this.isDisposed = true; - this._onDispose.fire(); - } -} diff --git a/src/browser/services/Services.ts b/src/browser/services/Services.ts index 2d1f3d03..8587a5c9 100644 --- a/src/browser/services/Services.ts +++ b/src/browser/services/Services.ts @@ -115,10 +115,9 @@ export interface ICharacterJoinerService { deregister(joinerId: number): boolean; getJoinedCharacters(row: number): [number, number][]; } - - export const IDecorationService = createDecorator('DecorationService'); export interface IDecorationService extends IDisposable { - attachToDom(renderService: IRenderService, screenElement: HTMLElement, viewportElement: HTMLElement): void; + readonly onDecorationRegistered: IEvent; + readonly onDecorationRemoved: IEvent; registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined; } diff --git a/src/browser/tsconfig.json b/src/browser/tsconfig.json index 212aeab8..fc997477 100644 --- a/src/browser/tsconfig.json +++ b/src/browser/tsconfig.json @@ -18,7 +18,7 @@ "include": [ "./**/*", "../../typings/xterm.d.ts" - ], +], "references": [ { "path": "../common" } ] diff --git a/src/common/services/DecorationService.ts b/src/common/services/DecorationService.ts new file mode 100644 index 00000000..206d50e8 --- /dev/null +++ b/src/common/services/DecorationService.ts @@ -0,0 +1,81 @@ +/** + * Copyright (c) 2022 The xterm.js authors. All rights reserved. + * @license MIT + */ + + +import { EventEmitter } from 'common/EventEmitter'; +import { Disposable } from 'common/Lifecycle'; +import { IBufferService, IInstantiationService, IDecorationService } from 'common/services/Services'; +import { IDecorationOptions, IDecoration, IMarker, IDisposable, IEvent } from 'xterm'; + +export class DecorationService extends Disposable implements IDecorationService { + private _animationFrame: number | undefined; + private _onDecorationRegistered = this.register(new EventEmitter()); + public get onDecorationRegistered(): IEvent { return this._onDecorationRegistered.event; } + private _onDecorationRemoved = this.register(new EventEmitter()); + public get onDecorationRemoved(): IEvent { return this._onDecorationRemoved.event; } + private _decorations: IDecoration[] = []; + + constructor() { + super(); + } + + public registerDecoration(options: IDecorationOptions): IDecoration | undefined { + if (options.marker.isDisposed) { + return undefined; + } + const decoration = new Decoration(options); + if (decoration) { + decoration.onDispose(() => { + if (decoration) { + this._decorations.splice(this._decorations.indexOf(decoration), 1); + } + }); + this._decorations.push(decoration); + this._onDecorationRegistered.fire(options); + decoration.onRender(d => { + decoration.setElement(d); + }); + } + return decoration; + } + + public dispose(): void { + for (const decoration of this._decorations) { + this._onDecorationRemoved.fire(decoration); + decoration.dispose(); + } + this._decorations = []; + } + + private _queueRefresh(): void { + if (this._animationFrame !== undefined) { + return; + } + this._animationFrame = window.requestAnimationFrame(() => { + // this._refresh(); + this._animationFrame = undefined; + }); + } +} + +class Decoration implements IDecoration { + public marker: IMarker; + private _onRender = new EventEmitter(); + public get onRender(): IEvent { return this._onRender.event; } + private _onDispose = new EventEmitter(); + public get onDispose(): IEvent { return this._onDispose.event; } + public element: HTMLElement | undefined; + public isDisposed: boolean = false; + public dispose(): void { + throw new Error('Method not implemented.'); + } + constructor(decorationOptions?: IDecorationOptions) { + this.marker = decorationOptions?.marker!; + this.element = undefined; + } + public setElement(element: HTMLElement): void { + this.element = element; + } +} diff --git a/src/common/services/Services.ts b/src/common/services/Services.ts index 90dca988..8bd272f0 100644 --- a/src/common/services/Services.ts +++ b/src/common/services/Services.ts @@ -5,8 +5,9 @@ import { IEvent } from 'common/EventEmitter'; import { IBuffer, IBufferSet } from 'common/buffer/Types'; -import { IDecPrivateModes, ICoreMouseEvent, CoreMouseEncoding, ICoreMouseProtocol, CoreMouseEventType, ICharset, IWindowOptions, IModes, IAttributeData, ScrollSource } from 'common/Types'; +import { IDecPrivateModes, ICoreMouseEvent, CoreMouseEncoding, ICoreMouseProtocol, CoreMouseEventType, ICharset, IWindowOptions, IModes, IAttributeData, ScrollSource, IDisposable } from 'common/Types'; import { createDecorator } from 'common/services/ServiceRegistry'; +import { IDecorationOptions, IDecoration } from 'xterm'; export const IBufferService = createDecorator('BufferService'); export interface IBufferService { @@ -245,6 +246,7 @@ export interface ITerminalOptions { windowsMode: boolean; windowOptions: IWindowOptions; wordSeparator: string; + overviewRulerWidth?: number; [key: string]: any; cancelEvents: boolean; @@ -298,3 +300,8 @@ export interface IUnicodeVersionProvider { readonly version: string; wcwidth(ucs: number): 0 | 1 | 2; } +export interface IDecorationService extends IDisposable { + readonly onDecorationRegistered: IEvent; + readonly onDecorationRemoved: IEvent; + registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined; +} From 7dc3332b05d91d5a00857bfb361aab2f309578be Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 14 Mar 2022 15:50:40 -0400 Subject: [PATCH 044/245] commit before reverting --- demo/client.ts | 7 ++++--- .../Decorations/BufferDecorationRenderer.ts | 10 +++++++--- src/browser/Terminal.ts | 14 +++++++------- src/common/services/DecorationService.ts | 14 ++++---------- 4 files changed, 22 insertions(+), 23 deletions(-) diff --git a/demo/client.ts b/demo/client.ts index 586114d1..0f2148c9 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -547,8 +547,9 @@ function loadTest() { function addDecoration() { const marker = term.addMarker(1); const decoration = term.registerDecoration({ marker }); - decoration.onRender(() => { - decoration.element.style.backgroundColor = 'red'; + decoration.onRender((e) => { + console.log(e); + e.style.backgroundColor = 'red'; }); } @@ -557,5 +558,5 @@ function addOverviewRuler() { canvas.element!.style.left = `${document.querySelector('.xterm-viewport').clientWidth + 5}px`; term.registerDecoration({marker: term.addMarker(3), overviewRulerItemColor: 'green'}); term.registerDecoration({marker: term.addMarker(5), overviewRulerItemColor: 'blue'}); -} +} diff --git a/src/browser/Decorations/BufferDecorationRenderer.ts b/src/browser/Decorations/BufferDecorationRenderer.ts index 56559ad9..efb97c16 100644 --- a/src/browser/Decorations/BufferDecorationRenderer.ts +++ b/src/browser/Decorations/BufferDecorationRenderer.ts @@ -48,11 +48,14 @@ export class BufferDecorationRenderer extends Disposable implements IDecorationR } public renderDecoration(decorationOptions: IDecorationOptions): void { - const decoration = new BufferDecoration(this._bufferService, decorationOptions); - if (this._decorationContainer && decoration.element && !this._decorationContainer.contains(decoration.element)) { - this._decorationContainer.append(decoration.element); + if (decorationOptions.overviewRulerItemColor) { + return; } + const decoration = new BufferDecoration(this._bufferService, decorationOptions); (decoration as BufferDecoration).render(this._decorationContainer, this._renderService, true); + if (this._decorationContainer && decoration.element && !this._decorationContainer.contains(decoration.element)) { + this._decorationContainer.append(decoration.element!); + } } public override dispose(): void { @@ -108,6 +111,7 @@ export class BufferDecoration extends Disposable implements IDecoration { } this._refreshStyle(renderService); if (this._element) { + console.log('firing on render'); this._onRender.fire(this._element); } } diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index 39f0a68a..2c00d79b 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -586,13 +586,13 @@ export class Terminal extends CoreTerminal implements ITerminal { if (this._decorationService) { this._bufferDecorationRenderer = new BufferDecorationRenderer(this._bufferService, this._renderService, this._decorationService, this.screenElement); } - // if (this.options.overviewRulerWidth && this._decorationService) { - // this._overviewRulerRenderer = new OverviewRulerRenderer(this._bufferService, this._renderService, this._decorationService, this._viewportElement, this.screenElement); - // } - // this.optionsService.onOptionChange(() => { - // if (!this._overviewRulerRenderer && this.options.overviewRulerWidth && this._renderService && this._viewportElement && this.screenElement && this._decorationService) { - // this._overviewRulerRenderer = new OverviewRulerRenderer(this._bufferService, this._renderService, this._decorationService, this._viewportElement, this.screenElement); - // }}); + if (this.options.overviewRulerWidth && this._decorationService) { + this._overviewRulerRenderer = new OverviewRulerRenderer(this._bufferService, this._renderService, this._decorationService, this._instantiationService, this._viewportElement, this.screenElement); + } + this.optionsService.onOptionChange(() => { + if (!this._overviewRulerRenderer && this.options.overviewRulerWidth && this._renderService && this._viewportElement && this.screenElement && this._decorationService) { + this._overviewRulerRenderer = new OverviewRulerRenderer(this._bufferService, this._renderService, this._decorationService, this._instantiationService, this._viewportElement, this.screenElement); + }}); // This event listener must be registered aftre MouseZoneManager is created this.register(addDisposableDomListener(this.element, 'mousedown', (e: MouseEvent) => this._selectionService!.onMouseDown(e))); diff --git a/src/common/services/DecorationService.ts b/src/common/services/DecorationService.ts index 206d50e8..1bd67e0c 100644 --- a/src/common/services/DecorationService.ts +++ b/src/common/services/DecorationService.ts @@ -6,8 +6,8 @@ import { EventEmitter } from 'common/EventEmitter'; import { Disposable } from 'common/Lifecycle'; -import { IBufferService, IInstantiationService, IDecorationService } from 'common/services/Services'; -import { IDecorationOptions, IDecoration, IMarker, IDisposable, IEvent } from 'xterm'; +import { IDecorationService } from 'common/services/Services'; +import { IDecorationOptions, IDecoration, IMarker, IEvent } from 'xterm'; export class DecorationService extends Disposable implements IDecorationService { private _animationFrame: number | undefined; @@ -34,9 +34,6 @@ export class DecorationService extends Disposable implements IDecorationService }); this._decorations.push(decoration); this._onDecorationRegistered.fire(options); - decoration.onRender(d => { - decoration.setElement(d); - }); } return decoration; } @@ -71,11 +68,8 @@ class Decoration implements IDecoration { public dispose(): void { throw new Error('Method not implemented.'); } - constructor(decorationOptions?: IDecorationOptions) { - this.marker = decorationOptions?.marker!; + constructor(decorationOptions: IDecorationOptions) { + this.marker = decorationOptions?.marker; this.element = undefined; } - public setElement(element: HTMLElement): void { - this.element = element; - } } From c8d1266f0ae82495f34be7ed6d742a472fb0df77 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 14 Mar 2022 15:24:53 -0700 Subject: [PATCH 045/245] Fix buffer decoration onRender, start using internal decoration --- demo/client.ts | 2 +- .../Decorations/BufferDecorationRenderer.ts | 52 ++++++++++++------- .../Decorations/OverviewRulerRenderer.ts | 14 ++--- src/browser/Terminal.ts | 5 +- src/browser/Types.d.ts | 5 -- src/browser/services/Services.ts | 6 --- src/common/services/DecorationService.ts | 28 +++++----- src/common/services/Services.ts | 12 +++-- 8 files changed, 70 insertions(+), 54 deletions(-) diff --git a/demo/client.ts b/demo/client.ts index 0f2148c9..55e97ed4 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -548,7 +548,7 @@ function addDecoration() { const marker = term.addMarker(1); const decoration = term.registerDecoration({ marker }); decoration.onRender((e) => { - console.log(e); + console.log('onRender', e); e.style.backgroundColor = 'red'; }); } diff --git a/src/browser/Decorations/BufferDecorationRenderer.ts b/src/browser/Decorations/BufferDecorationRenderer.ts index efb97c16..5b97130e 100644 --- a/src/browser/Decorations/BufferDecorationRenderer.ts +++ b/src/browser/Decorations/BufferDecorationRenderer.ts @@ -4,19 +4,20 @@ *--------------------------------------------------------------------------------------------*/ import { addDisposableDomListener } from 'browser/Lifecycle'; -import { IDecorationService, IRenderService } from 'browser/services/Services'; +import { IRenderService } from 'browser/services/Services'; import { IEvent, EventEmitter } from 'common/EventEmitter'; import { Disposable } from 'common/Lifecycle'; -import { IBufferService } from 'common/services/Services'; +import { IBufferService, IDecorationService, IInternalDecoration } from 'common/services/Services'; import { IMarker } from 'common/Types'; import { IDecoration, IDecorationOptions } from 'xterm'; export interface IDecorationRenderer { refreshDecorations(shouldRecreate?: boolean): void; - renderDecoration(decoration: IDecoration, decorationOptions: IDecorationOptions): void; + renderDecoration(decoration: IInternalDecoration, decorationOptions: IDecorationOptions): void; } export class BufferDecorationRenderer extends Disposable implements IDecorationRenderer { + private _animationFrame: number | undefined; private _decorationContainer: HTMLElement; private readonly _decorations: BufferDecoration[] = []; private _altBufferIsActive: boolean = false; @@ -30,32 +31,40 @@ export class BufferDecorationRenderer extends Disposable implements IDecorationR this._decorationContainer = document.createElement('div'); this._decorationContainer.classList.add('xterm-decoration-container'); this._screenElement.appendChild(this._decorationContainer); - this.register(this._renderService.onRenderedBufferChange(() => this.refreshDecorations())); - this.register(this._renderService.onDimensionsChange(() => this.refreshDecorations())); - this.register(addDisposableDomListener(window, 'resize', () => this.refreshDecorations())); + this.register(this._renderService.onRenderedBufferChange(() => this._queueRefresh())); + this.register(this._renderService.onDimensionsChange(() => this._queueRefresh())); + this.register(addDisposableDomListener(window, 'resize', () => this._queueRefresh())); this.register(this._bufferService.buffers.onBufferActivate(() => { this._altBufferIsActive = this._bufferService.buffer === this._bufferService.buffers.alt; })); this.register(this._decorationService.onDecorationRegistered(options => this.renderDecoration(options))); } - public refreshDecorations(shouldRecreate?: boolean): void { - if (!this._renderService) { + + private _queueRefresh(): void { + if (this._animationFrame !== undefined) { return; } + this._animationFrame = window.requestAnimationFrame(() => { + this.refreshDecorations(); + this._animationFrame = undefined; + }); + } + + public refreshDecorations(shouldRecreate?: boolean): void { + console.log('refresh decorations', this._decorations.length); for (const decoration of this._decorations) { decoration.render(this._decorationContainer, this._renderService, shouldRecreate); } } - public renderDecoration(decorationOptions: IDecorationOptions): void { - if (decorationOptions.overviewRulerItemColor) { - return; - } - const decoration = new BufferDecoration(this._bufferService, decorationOptions); - (decoration as BufferDecoration).render(this._decorationContainer, this._renderService, true); - if (this._decorationContainer && decoration.element && !this._decorationContainer.contains(decoration.element)) { - this._decorationContainer.append(decoration.element!); + public renderDecoration(decoration: IInternalDecoration): void { + const bufferDecoration = new BufferDecoration(this._bufferService, decoration, decoration.options); + this._decorations.push(bufferDecoration); + // bufferDecoration.render(this._decorationContainer, this._renderService, true); + if (this._decorationContainer && bufferDecoration.element && !this._decorationContainer.contains(bufferDecoration.element)) { + this._decorationContainer.append(bufferDecoration.element!); } + this._queueRefresh(); } public override dispose(): void { @@ -68,6 +77,7 @@ export class BufferDecorationRenderer extends Disposable implements IDecorationR super.dispose(); } } + export class BufferDecoration extends Disposable implements IDecoration { private readonly _marker: IMarker; private _element: HTMLElement | undefined; @@ -85,7 +95,6 @@ export class BufferDecoration extends Disposable implements IDecoration { private _onRender = new EventEmitter(); public get onRender(): IEvent { return this._onRender.event; } - public x: number; public anchor: 'left' | 'right'; public width: number; @@ -93,6 +102,7 @@ export class BufferDecoration extends Disposable implements IDecoration { constructor( private readonly _bufferService: IBufferService, + private readonly _internalDecoration: IInternalDecoration, options: IDecorationOptions ) { super(); @@ -107,16 +117,18 @@ export class BufferDecoration extends Disposable implements IDecoration { public render(container: HTMLElement, renderService: IRenderService, shouldRecreate?: boolean): void { this._container = container; if (!this._element || shouldRecreate) { - this._createElement(renderService, shouldRecreate); + const element = this._createElement(renderService, shouldRecreate); + this._container.appendChild(element); } this._refreshStyle(renderService); if (this._element) { console.log('firing on render'); this._onRender.fire(this._element); + this._internalDecoration.onRenderEmitter.fire(this._element!); } } - private _createElement(renderService: IRenderService, shouldRecreate?: boolean): void { + private _createElement(renderService: IRenderService, shouldRecreate?: boolean): HTMLElement { if (shouldRecreate && this._element && this._container && this._container.contains(this._element)) { this._container.removeChild(this._element); } @@ -136,6 +148,8 @@ export class BufferDecoration extends Disposable implements IDecoration { } else { this._element.style.left = this.x ? `${this.x * renderService.dimensions.actualCellWidth}px` : ''; } + + return this._element; } private _refreshStyle(renderService: IRenderService): void { diff --git a/src/browser/Decorations/OverviewRulerRenderer.ts b/src/browser/Decorations/OverviewRulerRenderer.ts index 580a831a..5f5fc397 100644 --- a/src/browser/Decorations/OverviewRulerRenderer.ts +++ b/src/browser/Decorations/OverviewRulerRenderer.ts @@ -5,10 +5,10 @@ import { addDisposableDomListener } from 'browser/Lifecycle'; import { IDecorationRenderer } from 'browser/Decorations/BufferDecorationRenderer'; -import { IDecorationService, IRenderService } from 'browser/services/Services'; +import { IRenderService } from 'browser/services/Services'; import { EventEmitter, IEvent } from 'common/EventEmitter'; import { Disposable } from 'common/Lifecycle'; -import { IBufferService, IInstantiationService } from 'common/services/Services'; +import { IBufferService, IDecorationService, IInstantiationService, IInternalDecoration } from 'common/services/Services'; import { IDecorationOptions, IDecoration, IMarker } from 'xterm'; const enum ScrollbarConstants { WIDTH = 7 @@ -45,14 +45,16 @@ export class OverviewRulerRenderer extends Disposable implements IDecorationRend this.register(this._decorationService.onDecorationRegistered(e => this.renderDecoration(e))); this.register(this._decorationService.onDecorationRemoved(d => d.dispose())); } - public renderDecoration(decorationOptions: IDecorationOptions): void { - if (!this._ctx || !decorationOptions.overviewRulerItemColor) { + public renderDecoration(decoration: IInternalDecoration): void { + if (!this._ctx || !decoration.options.overviewRulerItemColor) { return; } - const decoration = this._instantiationService.createInstance(ScrollbarDecoration, { marker: decorationOptions.marker, overviewRulerItemColor: decorationOptions.overviewRulerItemColor }); + + // TODO: Does this do anything anymore? + this._instantiationService.createInstance(ScrollbarDecoration, { marker: decoration.options.marker, overviewRulerItemColor: decoration.options.overviewRulerItemColor }); this._ctx.lineWidth = 1; - this._ctx.strokeStyle = decorationOptions.overviewRulerItemColor; + this._ctx.strokeStyle = decoration.options.overviewRulerItemColor; this._ctx.strokeRect( 0, Math.round(this._canvas.height * (decoration.marker.line / this._bufferService.buffers.active.lines.length)), diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index 2c00d79b..d07b9c30 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -83,11 +83,14 @@ export class Terminal extends CoreTerminal implements ITerminal { private _customKeyEventHandler: CustomKeyEventHandler | undefined; + // TODO: Move into CoreTerminal.ts + // common services + private _decorationService: DecorationService; + // browser services private _charSizeService: ICharSizeService | undefined; private _mouseService: IMouseService | undefined; private _renderService: IRenderService | undefined; - private _decorationService: DecorationService | undefined; private _characterJoinerService: ICharacterJoinerService | undefined; private _selectionService: ISelectionService | undefined; private _soundService: ISoundService | undefined; diff --git a/src/browser/Types.d.ts b/src/browser/Types.d.ts index b8e1626e..8860bb41 100644 --- a/src/browser/Types.d.ts +++ b/src/browser/Types.d.ts @@ -206,11 +206,6 @@ export interface ILinkifier { registerLinkMatcher(regex: RegExp, handler: LinkMatcherHandler, options?: ILinkMatcherOptions): number; deregisterLinkMatcher(matcherId: number): boolean; } -export interface IDecorationService extends IDisposable { - readonly onDecorationRegistered: IEvent; - readonly onDecorationRemoved: IEvent; - registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined; -} interface ILinkState { decorations: ILinkDecorations; diff --git a/src/browser/services/Services.ts b/src/browser/services/Services.ts index 8587a5c9..1598ef02 100644 --- a/src/browser/services/Services.ts +++ b/src/browser/services/Services.ts @@ -115,9 +115,3 @@ export interface ICharacterJoinerService { deregister(joinerId: number): boolean; getJoinedCharacters(row: number): [number, number][]; } -export const IDecorationService = createDecorator('DecorationService'); -export interface IDecorationService extends IDisposable { - readonly onDecorationRegistered: IEvent; - readonly onDecorationRemoved: IEvent; - registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined; -} diff --git a/src/common/services/DecorationService.ts b/src/common/services/DecorationService.ts index 1bd67e0c..7c49c7b4 100644 --- a/src/common/services/DecorationService.ts +++ b/src/common/services/DecorationService.ts @@ -6,16 +6,16 @@ import { EventEmitter } from 'common/EventEmitter'; import { Disposable } from 'common/Lifecycle'; -import { IDecorationService } from 'common/services/Services'; +import { IDecorationService, IInternalDecoration } from 'common/services/Services'; import { IDecorationOptions, IDecoration, IMarker, IEvent } from 'xterm'; export class DecorationService extends Disposable implements IDecorationService { private _animationFrame: number | undefined; - private _onDecorationRegistered = this.register(new EventEmitter()); - public get onDecorationRegistered(): IEvent { return this._onDecorationRegistered.event; } - private _onDecorationRemoved = this.register(new EventEmitter()); - public get onDecorationRemoved(): IEvent { return this._onDecorationRemoved.event; } - private _decorations: IDecoration[] = []; + private _onDecorationRegistered = this.register(new EventEmitter()); + public get onDecorationRegistered(): IEvent { return this._onDecorationRegistered.event; } + private _onDecorationRemoved = this.register(new EventEmitter()); + public get onDecorationRemoved(): IEvent { return this._onDecorationRemoved.event; } + private _decorations: IInternalDecoration[] = []; constructor() { super(); @@ -33,7 +33,7 @@ export class DecorationService extends Disposable implements IDecorationService } }); this._decorations.push(decoration); - this._onDecorationRegistered.fire(options); + this._onDecorationRegistered.fire(decoration); } return decoration; } @@ -57,19 +57,21 @@ export class DecorationService extends Disposable implements IDecorationService } } -class Decoration implements IDecoration { +class Decoration implements IInternalDecoration { public marker: IMarker; - private _onRender = new EventEmitter(); - public get onRender(): IEvent { return this._onRender.event; } + public readonly onRenderEmitter = new EventEmitter(); + public readonly onRender = this.onRenderEmitter.event; private _onDispose = new EventEmitter(); - public get onDispose(): IEvent { return this._onDispose.event; } + public readonly onDispose = this._onDispose.event; public element: HTMLElement | undefined; public isDisposed: boolean = false; public dispose(): void { throw new Error('Method not implemented.'); } - constructor(decorationOptions: IDecorationOptions) { - this.marker = decorationOptions?.marker; + constructor( + public readonly options: IDecorationOptions + ) { + this.marker = options.marker; this.element = undefined; } } diff --git a/src/common/services/Services.ts b/src/common/services/Services.ts index 8bd272f0..62108ffb 100644 --- a/src/common/services/Services.ts +++ b/src/common/services/Services.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { IEvent } from 'common/EventEmitter'; +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 } from 'common/Types'; import { createDecorator } from 'common/services/ServiceRegistry'; @@ -300,8 +300,14 @@ export interface IUnicodeVersionProvider { readonly version: string; wcwidth(ucs: number): 0 | 1 | 2; } + +export const IDecorationService = createDecorator('DecorationService'); export interface IDecorationService extends IDisposable { - readonly onDecorationRegistered: IEvent; - readonly onDecorationRemoved: IEvent; + readonly onDecorationRegistered: IEvent; + readonly onDecorationRemoved: IEvent; registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined; } +export interface IInternalDecoration extends IDecoration { + readonly options: IDecorationOptions; + readonly onRenderEmitter: IEventEmitter; +} From 4b615f48ff07627a031b0175b5039f8a03b9280e Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 14 Mar 2022 15:48:09 -0700 Subject: [PATCH 046/245] Reduce state in buffer decorations --- .../Decorations/BufferDecorationRenderer.ts | 168 +++++------------- .../Decorations/OverviewRulerRenderer.ts | 8 +- src/common/services/DecorationService.ts | 2 + src/common/services/Services.ts | 1 + src/tsconfig-library-base.json | 3 +- 5 files changed, 55 insertions(+), 127 deletions(-) diff --git a/src/browser/Decorations/BufferDecorationRenderer.ts b/src/browser/Decorations/BufferDecorationRenderer.ts index 5b97130e..9e3b2d72 100644 --- a/src/browser/Decorations/BufferDecorationRenderer.ts +++ b/src/browser/Decorations/BufferDecorationRenderer.ts @@ -5,39 +5,41 @@ import { addDisposableDomListener } from 'browser/Lifecycle'; import { IRenderService } from 'browser/services/Services'; -import { IEvent, EventEmitter } from 'common/EventEmitter'; import { Disposable } from 'common/Lifecycle'; import { IBufferService, IDecorationService, IInternalDecoration } from 'common/services/Services'; -import { IMarker } from 'common/Types'; -import { IDecoration, IDecorationOptions } from 'xterm'; -export interface IDecorationRenderer { - refreshDecorations(shouldRecreate?: boolean): void; - renderDecoration(decoration: IInternalDecoration, decorationOptions: IDecorationOptions): void; -} +export class BufferDecorationRenderer extends Disposable { + private readonly _container: HTMLElement; + private readonly _decorationElements: Map = new Map(); -export class BufferDecorationRenderer extends Disposable implements IDecorationRenderer { private _animationFrame: number | undefined; - private _decorationContainer: HTMLElement; - private readonly _decorations: BufferDecoration[] = []; private _altBufferIsActive: boolean = false; constructor( @IBufferService private readonly _bufferService: IBufferService, @IRenderService private readonly _renderService: IRenderService, private readonly _decorationService: IDecorationService, - private readonly _screenElement: HTMLElement) { + private readonly _screenElement: HTMLElement + ) { super(); - this._decorationContainer = document.createElement('div'); - this._decorationContainer.classList.add('xterm-decoration-container'); - this._screenElement.appendChild(this._decorationContainer); + + this._container = document.createElement('div'); + this._container.classList.add('xterm-decoration-container'); + this._screenElement.appendChild(this._container); + this.register(this._renderService.onRenderedBufferChange(() => this._queueRefresh())); this.register(this._renderService.onDimensionsChange(() => this._queueRefresh())); this.register(addDisposableDomListener(window, 'resize', () => this._queueRefresh())); this.register(this._bufferService.buffers.onBufferActivate(() => { this._altBufferIsActive = this._bufferService.buffer === this._bufferService.buffers.alt; })); - this.register(this._decorationService.onDecorationRegistered(options => this.renderDecoration(options))); + this.register(this._decorationService.onDecorationRegistered(() => this._queueRefresh())); + } + + public override dispose(): void { + this._container.remove(); + this._decorationElements.clear(); + super.dispose(); } private _queueRefresh(): void { @@ -50,131 +52,53 @@ export class BufferDecorationRenderer extends Disposable implements IDecorationR }); } - public refreshDecorations(shouldRecreate?: boolean): void { - console.log('refresh decorations', this._decorations.length); - for (const decoration of this._decorations) { - decoration.render(this._decorationContainer, this._renderService, shouldRecreate); + public refreshDecorations(): void { + for (const decoration of this._decorationService.decorations) { + this._renderDecoration(decoration); } } - public renderDecoration(decoration: IInternalDecoration): void { - const bufferDecoration = new BufferDecoration(this._bufferService, decoration, decoration.options); - this._decorations.push(bufferDecoration); - // bufferDecoration.render(this._decorationContainer, this._renderService, true); - if (this._decorationContainer && bufferDecoration.element && !this._decorationContainer.contains(bufferDecoration.element)) { - this._decorationContainer.append(bufferDecoration.element!); - } - this._queueRefresh(); - } - - public override dispose(): void { - if (this._screenElement && this._decorationContainer && this._screenElement.contains(this._decorationContainer)) { - this._screenElement.removeChild(this._decorationContainer); - } - for (const bufferDecoration of this._decorations) { - bufferDecoration.dispose(); - } - super.dispose(); - } -} - -export class BufferDecoration extends Disposable implements IDecoration { - private readonly _marker: IMarker; - private _element: HTMLElement | undefined; - private _container: HTMLElement | undefined; - private _altBufferIsActive: boolean = false; - - public isDisposed: boolean = false; - - public get element(): HTMLElement | undefined { return this._element; } - public get marker(): IMarker { return this._marker; } - - private _onDispose = new EventEmitter(); - public get onDispose(): IEvent { return this._onDispose.event; } - - private _onRender = new EventEmitter(); - public get onRender(): IEvent { return this._onRender.event; } - - public x: number; - public anchor: 'left' | 'right'; - public width: number; - public height: number; - - constructor( - private readonly _bufferService: IBufferService, - private readonly _internalDecoration: IInternalDecoration, - options: IDecorationOptions - ) { - super(); - this.x = options.x ?? 0; - this._marker = options.marker; - this._marker.onDispose(() => this.dispose()); - this.anchor = options.anchor || 'left'; - this.width = options.width || 1; - this.height = options.height || 1; - } - - public render(container: HTMLElement, renderService: IRenderService, shouldRecreate?: boolean): void { - this._container = container; - if (!this._element || shouldRecreate) { - const element = this._createElement(renderService, shouldRecreate); + private _renderDecoration(decoration: IInternalDecoration): void { + let element = this._decorationElements.get(decoration); + if (!element) { + element = this._createElement(decoration); + this._decorationElements.set(decoration, element); this._container.appendChild(element); } - this._refreshStyle(renderService); - if (this._element) { - console.log('firing on render'); - this._onRender.fire(this._element); - this._internalDecoration.onRenderEmitter.fire(this._element!); - } + this._refreshStyle(decoration, element); + decoration.onRenderEmitter.fire(element); } - private _createElement(renderService: IRenderService, shouldRecreate?: boolean): HTMLElement { - if (shouldRecreate && this._element && this._container && this._container.contains(this._element)) { - this._container.removeChild(this._element); - } - this._element = document.createElement('div'); - this._element.classList.add('xterm-decoration'); - this._element.style.width = `${this.width * renderService.dimensions.actualCellWidth}px`; - this._element.style.height = `${this.height * renderService.dimensions.actualCellHeight}px`; - this._element.style.top = `${(this.marker.line - this._bufferService.buffers.active.ydisp) * renderService.dimensions.actualCellHeight}px`; - this._element.style.lineHeight = `${renderService.dimensions.actualCellHeight}px`; + private _createElement(decoration: IInternalDecoration): HTMLElement { + const element = document.createElement('div'); + element.classList.add('xterm-decoration'); + element.style.width = `${(decoration.options.width || 1) * this._renderService.dimensions.actualCellWidth}px`; + element.style.height = `${(decoration.options.height || 1) * this._renderService.dimensions.actualCellHeight}px`; + element.style.top = `${(decoration.marker.line - this._bufferService.buffers.active.ydisp) * this._renderService.dimensions.actualCellHeight}px`; + element.style.lineHeight = `${this._renderService.dimensions.actualCellHeight}px`; - if (this.x && this.x > this._bufferService.cols) { + const x = decoration.options.x ?? 0; + if (x && x > this._bufferService.cols) { // exceeded the container width, so hide - this._element.style.display = 'none'; + element.style.display = 'none'; } - if (this.anchor === 'right') { - this._element.style.right = this.x ? `${this.x * renderService.dimensions.actualCellWidth}px` : ''; + if ((decoration.options.anchor || 'left') === 'right') { + element.style.right = x ? `${x * this._renderService.dimensions.actualCellWidth}px` : ''; } else { - this._element.style.left = this.x ? `${this.x * renderService.dimensions.actualCellWidth}px` : ''; + element.style.left = x ? `${x * this._renderService.dimensions.actualCellWidth}px` : ''; } - return this._element; + return element; } - private _refreshStyle(renderService: IRenderService): void { - if (!this._element) { - return; - } - const line = this.marker.line - this._bufferService.buffers.active.ydisp; + private _refreshStyle(decoration: IInternalDecoration, element: HTMLElement): void { + const line = decoration.marker.line - this._bufferService.buffers.active.ydisp; if (line < 0 || line > this._bufferService.rows) { // outside of viewport - this._element.style.display = 'none'; + element.style.display = 'none'; } else { - this._element.style.top = `${line * renderService.dimensions.actualCellHeight}px`; - this._element.style.display = this._altBufferIsActive ? 'none' : 'block'; + element.style.top = `${line * this._renderService.dimensions.actualCellHeight}px`; + element.style.display = this._altBufferIsActive ? 'none' : 'block'; } } - - public override dispose(): void { - if (this.isDisposed || !this._container) { - return; - } - if (this._element && this._container.contains(this._element)) { - this._container.removeChild(this._element); - } - this.isDisposed = true; - this._onDispose.fire(); - } } - diff --git a/src/browser/Decorations/OverviewRulerRenderer.ts b/src/browser/Decorations/OverviewRulerRenderer.ts index 5f5fc397..2ee0970c 100644 --- a/src/browser/Decorations/OverviewRulerRenderer.ts +++ b/src/browser/Decorations/OverviewRulerRenderer.ts @@ -4,17 +4,17 @@ *--------------------------------------------------------------------------------------------*/ import { addDisposableDomListener } from 'browser/Lifecycle'; -import { IDecorationRenderer } from 'browser/Decorations/BufferDecorationRenderer'; import { IRenderService } from 'browser/services/Services'; import { EventEmitter, IEvent } from 'common/EventEmitter'; import { Disposable } from 'common/Lifecycle'; import { IBufferService, IDecorationService, IInstantiationService, IInternalDecoration } from 'common/services/Services'; import { IDecorationOptions, IDecoration, IMarker } from 'xterm'; + const enum ScrollbarConstants { WIDTH = 7 } -export class OverviewRulerRenderer extends Disposable implements IDecorationRenderer { +export class OverviewRulerRenderer extends Disposable { private _canvas: HTMLCanvasElement; private _ctx: CanvasRenderingContext2D | null; private _decorations: ScrollbarDecoration[] = []; @@ -42,10 +42,10 @@ export class OverviewRulerRenderer extends Disposable implements IDecorationRend this.register(this._renderService.onRenderedBufferChange(() => this.refreshDecorations())); this.register(this._renderService.onDimensionsChange(() => this.refreshDecorations())); this.register(addDisposableDomListener(window, 'resize', () => this.refreshDecorations())); - this.register(this._decorationService.onDecorationRegistered(e => this.renderDecoration(e))); + this.register(this._decorationService.onDecorationRegistered(e => this.registerDecoration(e))); this.register(this._decorationService.onDecorationRemoved(d => d.dispose())); } - public renderDecoration(decoration: IInternalDecoration): void { + public registerDecoration(decoration: IInternalDecoration): void { if (!this._ctx || !decoration.options.overviewRulerItemColor) { return; } diff --git a/src/common/services/DecorationService.ts b/src/common/services/DecorationService.ts index 7c49c7b4..af1d30b0 100644 --- a/src/common/services/DecorationService.ts +++ b/src/common/services/DecorationService.ts @@ -17,6 +17,8 @@ export class DecorationService extends Disposable implements IDecorationService public get onDecorationRemoved(): IEvent { return this._onDecorationRemoved.event; } private _decorations: IInternalDecoration[] = []; + public get decorations(): IterableIterator { return this._decorations.values(); } + constructor() { super(); } diff --git a/src/common/services/Services.ts b/src/common/services/Services.ts index 62108ffb..67bbb5ee 100644 --- a/src/common/services/Services.ts +++ b/src/common/services/Services.ts @@ -303,6 +303,7 @@ export interface IUnicodeVersionProvider { export const IDecorationService = createDecorator('DecorationService'); export interface IDecorationService extends IDisposable { + readonly decorations: IterableIterator; readonly onDecorationRegistered: IEvent; readonly onDecorationRemoved: IEvent; registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined; diff --git a/src/tsconfig-library-base.json b/src/tsconfig-library-base.json index e08695d7..00cecc06 100644 --- a/src/tsconfig-library-base.json +++ b/src/tsconfig-library-base.json @@ -4,6 +4,7 @@ "composite": true, "strict": true, "declarationMap": true, - "experimentalDecorators": true + "experimentalDecorators": true, + "downlevelIteration": true } } From 65b9a6cfc41889a53d11f92a3cd99005fc46ae18 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 14 Mar 2022 15:52:32 -0700 Subject: [PATCH 047/245] Fix dependency injection for decoration service --- src/browser/Decorations/BufferDecorationRenderer.ts | 6 +++--- src/browser/Decorations/OverviewRulerRenderer.ts | 6 +++--- src/browser/Terminal.ts | 6 +++--- src/common/services/DecorationService.ts | 8 +++++--- src/common/services/Services.ts | 1 + 5 files changed, 15 insertions(+), 12 deletions(-) diff --git a/src/browser/Decorations/BufferDecorationRenderer.ts b/src/browser/Decorations/BufferDecorationRenderer.ts index 9e3b2d72..2f91d968 100644 --- a/src/browser/Decorations/BufferDecorationRenderer.ts +++ b/src/browser/Decorations/BufferDecorationRenderer.ts @@ -16,10 +16,10 @@ export class BufferDecorationRenderer extends Disposable { private _altBufferIsActive: boolean = false; constructor( + private readonly _screenElement: HTMLElement, @IBufferService private readonly _bufferService: IBufferService, - @IRenderService private readonly _renderService: IRenderService, - private readonly _decorationService: IDecorationService, - private readonly _screenElement: HTMLElement + @IDecorationService private readonly _decorationService: IDecorationService, + @IRenderService private readonly _renderService: IRenderService ) { super(); diff --git a/src/browser/Decorations/OverviewRulerRenderer.ts b/src/browser/Decorations/OverviewRulerRenderer.ts index 2ee0970c..b880fa33 100644 --- a/src/browser/Decorations/OverviewRulerRenderer.ts +++ b/src/browser/Decorations/OverviewRulerRenderer.ts @@ -23,12 +23,12 @@ export class OverviewRulerRenderer extends Disposable { private _x: number | undefined; constructor( + private readonly _viewportElement: HTMLElement, + private readonly _screenElement: HTMLElement, @IBufferService private readonly _bufferService: IBufferService, - @IRenderService private readonly _renderService: IRenderService, @IDecorationService private readonly _decorationService: IDecorationService, @IInstantiationService private readonly _instantiationService: IInstantiationService, - private readonly _viewportElement: HTMLElement, - private readonly _screenElement: HTMLElement + @IRenderService private readonly _renderService: IRenderService ) { super(); this._canvas = document.createElement('canvas'); diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index d07b9c30..3d490d08 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -587,14 +587,14 @@ export class Terminal extends CoreTerminal implements ITerminal { this.linkifier.attachToDom(this.element, this._mouseZoneManager); this.linkifier2.attachToDom(this.screenElement, this._mouseService, this._renderService); if (this._decorationService) { - this._bufferDecorationRenderer = new BufferDecorationRenderer(this._bufferService, this._renderService, this._decorationService, this.screenElement); + this._bufferDecorationRenderer = this._instantiationService.createInstance(BufferDecorationRenderer, this.screenElement); } if (this.options.overviewRulerWidth && this._decorationService) { - this._overviewRulerRenderer = new OverviewRulerRenderer(this._bufferService, this._renderService, this._decorationService, this._instantiationService, this._viewportElement, this.screenElement); + this._overviewRulerRenderer = this._instantiationService.createInstance(OverviewRulerRenderer, this._viewportElement, this.screenElement); } this.optionsService.onOptionChange(() => { if (!this._overviewRulerRenderer && this.options.overviewRulerWidth && this._renderService && this._viewportElement && this.screenElement && this._decorationService) { - this._overviewRulerRenderer = new OverviewRulerRenderer(this._bufferService, this._renderService, this._decorationService, this._instantiationService, this._viewportElement, this.screenElement); + this._overviewRulerRenderer = this._instantiationService.createInstance(OverviewRulerRenderer, this._viewportElement, this.screenElement); }}); // This event listener must be registered aftre MouseZoneManager is created this.register(addDisposableDomListener(this.element, 'mousedown', (e: MouseEvent) => this._selectionService!.onMouseDown(e))); diff --git a/src/common/services/DecorationService.ts b/src/common/services/DecorationService.ts index af1d30b0..693f8944 100644 --- a/src/common/services/DecorationService.ts +++ b/src/common/services/DecorationService.ts @@ -3,19 +3,21 @@ * @license MIT */ - import { EventEmitter } from 'common/EventEmitter'; import { Disposable } from 'common/Lifecycle'; import { IDecorationService, IInternalDecoration } from 'common/services/Services'; import { IDecorationOptions, IDecoration, IMarker, IEvent } from 'xterm'; export class DecorationService extends Disposable implements IDecorationService { + public serviceBrand: any; + + private readonly _decorations: IInternalDecoration[] = []; private _animationFrame: number | undefined; + private _onDecorationRegistered = this.register(new EventEmitter()); public get onDecorationRegistered(): IEvent { return this._onDecorationRegistered.event; } private _onDecorationRemoved = this.register(new EventEmitter()); public get onDecorationRemoved(): IEvent { return this._onDecorationRemoved.event; } - private _decorations: IInternalDecoration[] = []; public get decorations(): IterableIterator { return this._decorations.values(); } @@ -45,7 +47,7 @@ export class DecorationService extends Disposable implements IDecorationService this._onDecorationRemoved.fire(decoration); decoration.dispose(); } - this._decorations = []; + this._decorations.length = 0; } private _queueRefresh(): void { diff --git a/src/common/services/Services.ts b/src/common/services/Services.ts index 67bbb5ee..876d90bc 100644 --- a/src/common/services/Services.ts +++ b/src/common/services/Services.ts @@ -303,6 +303,7 @@ export interface IUnicodeVersionProvider { export const IDecorationService = createDecorator('DecorationService'); export interface IDecorationService extends IDisposable { + serviceBrand: undefined; readonly decorations: IterableIterator; readonly onDecorationRegistered: IEvent; readonly onDecorationRemoved: IEvent; From 3dc4f1a7f2edbb06496494679e56d2a4aac18954 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 14 Mar 2022 15:56:27 -0700 Subject: [PATCH 048/245] Remove old decoration elements --- src/browser/Decorations/BufferDecorationRenderer.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/browser/Decorations/BufferDecorationRenderer.ts b/src/browser/Decorations/BufferDecorationRenderer.ts index 2f91d968..74fc59a7 100644 --- a/src/browser/Decorations/BufferDecorationRenderer.ts +++ b/src/browser/Decorations/BufferDecorationRenderer.ts @@ -34,6 +34,7 @@ export class BufferDecorationRenderer extends Disposable { this._altBufferIsActive = this._bufferService.buffer === this._bufferService.buffers.alt; })); this.register(this._decorationService.onDecorationRegistered(() => this._queueRefresh())); + this.register(this._decorationService.onDecorationRemoved(decoration => this._removeDecoration(decoration))); } public override dispose(): void { @@ -101,4 +102,10 @@ export class BufferDecorationRenderer extends Disposable { element.style.display = this._altBufferIsActive ? 'none' : 'block'; } } + + private _removeDecoration(decoration: IInternalDecoration): void { + const element = this._decorationElements.get(decoration); + element?.remove(); + this._decorationElements.delete(decoration); + } } From fc09730ffbbda2727f83534bdea2a793d3dd57d7 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 14 Mar 2022 16:07:30 -0700 Subject: [PATCH 049/245] Tidy up DecorationService --- src/common/services/DecorationService.ts | 31 ++++++++---------------- 1 file changed, 10 insertions(+), 21 deletions(-) diff --git a/src/common/services/DecorationService.ts b/src/common/services/DecorationService.ts index 693f8944..f18ad9b5 100644 --- a/src/common/services/DecorationService.ts +++ b/src/common/services/DecorationService.ts @@ -12,7 +12,6 @@ export class DecorationService extends Disposable implements IDecorationService public serviceBrand: any; private readonly _decorations: IInternalDecoration[] = []; - private _animationFrame: number | undefined; private _onDecorationRegistered = this.register(new EventEmitter()); public get onDecorationRegistered(): IEvent { return this._onDecorationRegistered.event; } @@ -49,33 +48,23 @@ export class DecorationService extends Disposable implements IDecorationService } this._decorations.length = 0; } - - private _queueRefresh(): void { - if (this._animationFrame !== undefined) { - return; - } - this._animationFrame = window.requestAnimationFrame(() => { - // this._refresh(); - this._animationFrame = undefined; - }); - } } -class Decoration implements IInternalDecoration { - public marker: IMarker; - public readonly onRenderEmitter = new EventEmitter(); - public readonly onRender = this.onRenderEmitter.event; - private _onDispose = new EventEmitter(); - public readonly onDispose = this._onDispose.event; +class Decoration extends Disposable implements IInternalDecoration { + public readonly marker: IMarker; public element: HTMLElement | undefined; public isDisposed: boolean = false; - public dispose(): void { - throw new Error('Method not implemented.'); - } + + public readonly onRenderEmitter = this.register(new EventEmitter()); + public readonly onRender = this.onRenderEmitter.event; + private _onDispose = this.register(new EventEmitter()); + public readonly onDispose = this._onDispose.event; + constructor( public readonly options: IDecorationOptions ) { + super(); this.marker = options.marker; - this.element = undefined; + // TODO: Make sure dispose doesn't need to do anything else? } } From bfb048110feb3aa09c3055d58fc5a03cff2868e3 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 15 Mar 2022 08:57:15 -0400 Subject: [PATCH 050/245] register service --- demo/client.ts | 1 - src/browser/Terminal.ts | 3 ++- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/demo/client.ts b/demo/client.ts index 55e97ed4..7eba89a5 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -548,7 +548,6 @@ function addDecoration() { const marker = term.addMarker(1); const decoration = term.registerDecoration({ marker }); decoration.onRender((e) => { - console.log('onRender', e); e.style.backgroundColor = 'red'; }); } diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index 3d490d08..b1f96ee5 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -57,8 +57,8 @@ import { CharacterJoinerService } from 'browser/services/CharacterJoinerService' import { toRgbString } from 'common/input/XParseColor'; import { BufferDecorationRenderer } from 'browser/Decorations/BufferDecorationRenderer'; import { OverviewRulerRenderer } from 'browser/Decorations/OverviewRulerRenderer'; -// import { IDecorationService } from 'common/services/Services'; import { DecorationService } from 'common/services/DecorationService'; +import { IDecorationService } from 'common/services/Services'; // Let it work inside Node.js for automated testing purposes. const document: Document = (typeof window !== 'undefined') ? window.document : null as any; @@ -169,6 +169,7 @@ export class Terminal extends CoreTerminal implements ITerminal { this.linkifier = this._instantiationService.createInstance(Linkifier); this.linkifier2 = this.register(this._instantiationService.createInstance(Linkifier2)); this._decorationService = this._instantiationService.createInstance(DecorationService); + this._instantiationService.setService(IDecorationService, this._decorationService); // Setup InputHandler listeners this.register(this._inputHandler.onRequestBell(() => this.bell())); From 079bbc8242307a828911b4ef4b7380c5e330441a Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 15 Mar 2022 10:27:01 -0400 Subject: [PATCH 051/245] get it working in demo with overviewRulerwidth not considered" --- demo/client.ts | 4 +- .../Decorations/OverviewRulerRenderer.ts | 158 +++++++----------- src/browser/Terminal.ts | 19 +-- src/common/services/OptionsService.ts | 3 +- typings/xterm.d.ts | 17 +- 5 files changed, 88 insertions(+), 113 deletions(-) diff --git a/demo/client.ts b/demo/client.ts index 7eba89a5..f6223f2c 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -554,8 +554,10 @@ function addDecoration() { function addOverviewRuler() { const canvas = term.registerDecoration({marker: term.addMarker(1), overviewRulerItemColor: 'red'}); - canvas.element!.style.left = `${document.querySelector('.xterm-viewport').clientWidth + 5}px`; term.registerDecoration({marker: term.addMarker(3), overviewRulerItemColor: 'green'}); term.registerDecoration({marker: term.addMarker(5), overviewRulerItemColor: 'blue'}); + canvas.onRender((e) => { + e.style.left = `${document.querySelector('.xterm-viewport').clientWidth + 5}px`; + }); } diff --git a/src/browser/Decorations/OverviewRulerRenderer.ts b/src/browser/Decorations/OverviewRulerRenderer.ts index b880fa33..8a78bb78 100644 --- a/src/browser/Decorations/OverviewRulerRenderer.ts +++ b/src/browser/Decorations/OverviewRulerRenderer.ts @@ -5,36 +5,37 @@ import { addDisposableDomListener } from 'browser/Lifecycle'; import { IRenderService } from 'browser/services/Services'; -import { EventEmitter, IEvent } from 'common/EventEmitter'; import { Disposable } from 'common/Lifecycle'; -import { IBufferService, IDecorationService, IInstantiationService, IInternalDecoration } from 'common/services/Services'; -import { IDecorationOptions, IDecoration, IMarker } from 'xterm'; +import { IBufferService, IDecorationService, IInternalDecoration, IOptionsService } from 'common/services/Services'; const enum ScrollbarConstants { - WIDTH = 7 + WIDTH = 15 } export class OverviewRulerRenderer extends Disposable { private _canvas: HTMLCanvasElement; private _ctx: CanvasRenderingContext2D | null; - private _decorations: ScrollbarDecoration[] = []; - private _width: number | undefined; - private _anchor: 'right' | 'left' | undefined; - private _x: number | undefined; + private readonly _decorationElements: Map = new Map(); + + private _animationFrame: number | undefined; constructor( private readonly _viewportElement: HTMLElement, private readonly _screenElement: HTMLElement, @IBufferService private readonly _bufferService: IBufferService, @IDecorationService private readonly _decorationService: IDecorationService, - @IInstantiationService private readonly _instantiationService: IInstantiationService, - @IRenderService private readonly _renderService: IRenderService + @IRenderService private readonly _renderService: IRenderService, + @IOptionsService private readonly _optionsService: IOptionsService ) { super(); this._canvas = document.createElement('canvas'); this._canvas.classList.add('xterm-decoration-scrollbar'); this._viewportElement.parentElement?.insertBefore(this._canvas, this._viewportElement); this._ctx = this._canvas.getContext('2d'); + this._canvas.style.width = `${this._optionsService.options.overviewRulerWidth || ScrollbarConstants.WIDTH}px`; + this._canvas.style.height = `${this._screenElement.clientHeight}px`; + this._canvas.width = Math.floor((this._optionsService.options.overviewRulerWidth|| ScrollbarConstants.WIDTH)* window.devicePixelRatio); + this._canvas.height = Math.floor(this._screenElement.clientHeight * window.devicePixelRatio); this.refreshDecorations(); this.register(this._bufferService.buffers.onBufferActivate(() => { this._canvas!.style.display = this._bufferService.buffer === this._bufferService.buffers.alt ? 'none' : 'block'; @@ -42,111 +43,80 @@ export class OverviewRulerRenderer extends Disposable { this.register(this._renderService.onRenderedBufferChange(() => this.refreshDecorations())); this.register(this._renderService.onDimensionsChange(() => this.refreshDecorations())); this.register(addDisposableDomListener(window, 'resize', () => this.refreshDecorations())); - this.register(this._decorationService.onDecorationRegistered(e => this.registerDecoration(e))); - this.register(this._decorationService.onDecorationRemoved(d => d.dispose())); + this.register(this._decorationService.onDecorationRegistered(() => this._queueRefresh())); + this.register(this._decorationService.onDecorationRemoved(decoration => this._removeDecoration(decoration))); } - public registerDecoration(decoration: IInternalDecoration): void { - if (!this._ctx || !decoration.options.overviewRulerItemColor) { + + public override dispose(): void { + for (const decoration of this._decorationElements) { + this._ctx?.clearRect( + 0, + Math.round(this._canvas.height * (decoration[0].marker.line / this._bufferService.buffers.active.lines.length)), + this._canvas.width, + window.devicePixelRatio + ); + } + this._decorationElements.clear(); + this._canvas?.remove(); + super.dispose(); + } + + private _refreshStyle(decoration: IInternalDecoration): void { + if (!this._ctx) { + return; + } + if (decoration.options.anchor === 'right') { + this._canvas.style.right = decoration.options.x ? `${decoration.options.x * this._renderService.dimensions.actualCellWidth}px` : ''; + } else { + this._canvas.style.left = decoration.options.x ? `${decoration.options.x * this._renderService.dimensions.actualCellWidth}px` : ''; + } + if (!decoration.options.overviewRulerItemColor) { + this._decorationElements.delete(decoration); return; } - - // TODO: Does this do anything anymore? - this._instantiationService.createInstance(ScrollbarDecoration, { marker: decoration.options.marker, overviewRulerItemColor: decoration.options.overviewRulerItemColor }); - this._ctx.lineWidth = 1; this._ctx.strokeStyle = decoration.options.overviewRulerItemColor; this._ctx.strokeRect( 0, - Math.round(this._canvas.height * (decoration.marker.line / this._bufferService.buffers.active.lines.length)), + Math.round(this._canvas.height * (decoration.options.marker.line / this._bufferService.buffers.active.lines.length)), this._canvas.width, window.devicePixelRatio ); } public refreshDecorations(): void { - if (!this._ctx) { - return; - } - this._canvas.style.width = `${this._width || ScrollbarConstants.WIDTH}px`; + this._canvas.style.width = `${this._canvas.width || ScrollbarConstants.WIDTH}px`; this._canvas.style.height = `${this._screenElement.clientHeight}px`; - this._canvas.width = Math.floor((this._width || ScrollbarConstants.WIDTH)* window.devicePixelRatio); + this._canvas.width = Math.floor((this._canvas.width || ScrollbarConstants.WIDTH)* window.devicePixelRatio); this._canvas.height = Math.floor(this._screenElement.clientHeight * window.devicePixelRatio); - if (this._anchor === 'right') { - this._canvas.style.right = this._x ? `${this._x * this._renderService.dimensions.actualCellWidth}px` : ''; - } else { - this._canvas.style.left = this._x ? `${this._x * this._renderService.dimensions.actualCellWidth}px` : ''; - } - this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height); - for (const decoration of this._decorations) { - decoration.render(this._ctx, this._canvas); + + for (const decoration of this._decorationService.decorations) { + this._renderDecoration(decoration); } } - public override dispose(): void { - for (const decoration of this._decorations) { - decoration.dispose(); + + private _renderDecoration(decoration: IInternalDecoration): void { + const element = this._decorationElements.get(decoration); + if (!element) { + this._decorationElements.set(decoration, this._canvas); } - this._decorations = []; - this._canvas?.remove(); - super.dispose(); - } -} -class ScrollbarDecoration extends Disposable implements IDecoration { - private readonly _marker: IMarker; - private _ctx: CanvasRenderingContext2D | undefined; - private _canvas: HTMLCanvasElement | undefined; - private _color: string | undefined; - - public isDisposed: boolean = false; - - public get element(): HTMLCanvasElement | undefined { return this._canvas; } - public get marker(): IMarker { return this._marker; } - public get color(): string | undefined { return this._color; } - - private _onDispose = new EventEmitter(); - public get onDispose(): IEvent { return this._onDispose.event; } - - private _onRender = new EventEmitter(); - public get onRender(): IEvent { return this._onRender.event; } - - constructor( - options: IDecorationOptions, - @IBufferService private readonly _bufferService: IBufferService - ) { - super(); - this._marker = options.marker; - this._color = options.overviewRulerItemColor; - this._marker.onDispose(() => this.dispose()); + this._refreshStyle(decoration); + decoration.onRenderEmitter.fire(this._canvas); } - public render(ctx: CanvasRenderingContext2D, canvas: HTMLCanvasElement): void { - if (!this.color) { - throw new Error('No color was provided for the overview ruler decoraiton'); - } - this._ctx = ctx; - this._canvas = canvas; - ctx.lineWidth = 1; - ctx.strokeStyle = this.color; - ctx.strokeRect( - 0, - Math.round(canvas.height * (this.marker.line / this._bufferService.buffers.active.lines.length)), - canvas.width, - window.devicePixelRatio - ); - this._onRender.fire(canvas); - } - - public override dispose(): void { - if (this._isDisposed || !this._canvas || !this._ctx) { + private _queueRefresh(): void { + if (this._animationFrame !== undefined) { return; } - this._ctx.clearRect( - 0, - Math.round(this._canvas.height * (this.marker.line / this._bufferService.buffers.active.lines.length)), - this._canvas.width, - window.devicePixelRatio - ); - this.isDisposed = true; - this._onDispose.fire(); - super.dispose(); + this._animationFrame = window.requestAnimationFrame(() => { + this.refreshDecorations(); + this._animationFrame = undefined; + }); + } + + private _removeDecoration(decoration: IInternalDecoration): void { + const element = this._decorationElements.get(decoration); + element?.remove(); + this._decorationElements.delete(decoration); } } diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index b1f96ee5..6d0aabb1 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -75,7 +75,6 @@ export class Terminal extends CoreTerminal implements ITerminal { private _compositionView: HTMLElement | undefined; private _overviewRulerRenderer: OverviewRulerRenderer | undefined; - private _bufferDecorationRenderer: BufferDecorationRenderer | undefined; // private _visualBellTimer: number; @@ -587,16 +586,7 @@ export class Terminal extends CoreTerminal implements ITerminal { this.register(this.onScroll(() => this._mouseZoneManager!.clearAll())); this.linkifier.attachToDom(this.element, this._mouseZoneManager); this.linkifier2.attachToDom(this.screenElement, this._mouseService, this._renderService); - if (this._decorationService) { - this._bufferDecorationRenderer = this._instantiationService.createInstance(BufferDecorationRenderer, this.screenElement); - } - if (this.options.overviewRulerWidth && this._decorationService) { - this._overviewRulerRenderer = this._instantiationService.createInstance(OverviewRulerRenderer, this._viewportElement, this.screenElement); - } - this.optionsService.onOptionChange(() => { - if (!this._overviewRulerRenderer && this.options.overviewRulerWidth && this._renderService && this._viewportElement && this.screenElement && this._decorationService) { - this._overviewRulerRenderer = this._instantiationService.createInstance(OverviewRulerRenderer, this._viewportElement, this.screenElement); - }}); + this._instantiationService.createInstance(BufferDecorationRenderer, this.screenElement); // This event listener must be registered aftre MouseZoneManager is created this.register(addDisposableDomListener(this.element, 'mousedown', (e: MouseEvent) => this._selectionService!.onMouseDown(e))); @@ -614,6 +604,13 @@ export class Terminal extends CoreTerminal implements ITerminal { this._accessibilityManager = new AccessibilityManager(this, this._renderService); } + // if (this.options.overviewRulerWidth) { + this._overviewRulerRenderer = this._instantiationService.createInstance(OverviewRulerRenderer, this._viewportElement, this.screenElement); + // } + this.optionsService.onOptionChange(() => { + if (!this._overviewRulerRenderer && this.options.overviewRulerWidth && this._viewportElement && this.screenElement) { + this._overviewRulerRenderer = this._instantiationService.createInstance(OverviewRulerRenderer, this._viewportElement, this.screenElement); + }}); // Measure the character size this._charSizeService.measure(); diff --git a/src/common/services/OptionsService.ts b/src/common/services/OptionsService.ts index 43fe9981..4008a431 100644 --- a/src/common/services/OptionsService.ts +++ b/src/common/services/OptionsService.ts @@ -52,7 +52,8 @@ export const DEFAULT_OPTIONS: Readonly = { altClickMovesCursor: true, convertEol: false, termName: 'xterm', - cancelEvents: false + cancelEvents: false, + overviewRulerWidth: undefined }; const FONT_WEIGHT_OPTIONS: Extract[] = ['normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900']; diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 00f4072f..986e77c9 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -266,6 +266,11 @@ declare module 'xterm' { * All features are disabled by default for security reasons. */ windowOptions?: IWindowOptions; + + /** + * The width, in pixels, of the canvas for the overview ruler. + */ + overviewRulerWidth?: number; } /** @@ -394,7 +399,7 @@ declare module 'xterm' { } /** - * Represents a disposable with an + * Represents a disposable with an * @param onDispose event listener and * @param isDisposed property. */ @@ -436,7 +441,7 @@ declare module 'xterm' { /** * Options provided when registering a decoration - * containing a @param marker, @param anchor, + * containing a @param marker, @param anchor, * @param x offset from the anchor, @param width in cells * and @param height in cells. */ @@ -455,19 +460,19 @@ declare module 'xterm' { /** * The x position offset relative to the anchor - */ + */ x?: number; /** - * The width of the decoration in cells, which defaults to + * The width of the decoration in cells, which defaults to * cell width or the width in pixels, when an overlayRulerItemColor * is provided. */ width?: number; /** - * The height of the decoration in cells, which defaults to + * The height of the decoration in cells, which defaults to * cell height */ height?: number; @@ -946,7 +951,7 @@ declare module 'xterm' { /** * (EXPERIMENTAL) Adds a decoration to the terminal using - * @param decorationOptions, which takes a marker and an optional anchor, + * @param decorationOptions, which takes a marker and an optional anchor, * width, height, and x offset from the anchor. Returns the decoration or * undefined if the alt buffer is active or the marker has already been disposed of. * @throws when options include a negative x offset. From 3e47c96445e6baa448707ff3456ed09da166b6fa Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 15 Mar 2022 10:31:01 -0400 Subject: [PATCH 052/245] get demo to work the right way --- demo/client.ts | 1 + src/browser/Terminal.ts | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/demo/client.ts b/demo/client.ts index f6223f2c..e1347e3d 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -553,6 +553,7 @@ function addDecoration() { } function addOverviewRuler() { + term.options['overviewRulerWidth'] = 15; const canvas = term.registerDecoration({marker: term.addMarker(1), overviewRulerItemColor: 'red'}); term.registerDecoration({marker: term.addMarker(3), overviewRulerItemColor: 'green'}); term.registerDecoration({marker: term.addMarker(5), overviewRulerItemColor: 'blue'}); diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index 6d0aabb1..c2fb1500 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -604,9 +604,9 @@ export class Terminal extends CoreTerminal implements ITerminal { this._accessibilityManager = new AccessibilityManager(this, this._renderService); } - // if (this.options.overviewRulerWidth) { - this._overviewRulerRenderer = this._instantiationService.createInstance(OverviewRulerRenderer, this._viewportElement, this.screenElement); - // } + if (this.options.overviewRulerWidth) { + this._overviewRulerRenderer = this._instantiationService.createInstance(OverviewRulerRenderer, this._viewportElement, this.screenElement); + } this.optionsService.onOptionChange(() => { if (!this._overviewRulerRenderer && this.options.overviewRulerWidth && this._viewportElement && this.screenElement) { this._overviewRulerRenderer = this._instantiationService.createInstance(OverviewRulerRenderer, this._viewportElement, this.screenElement); From 39f9c3507bcd856441e3bf1db627550b164fc625 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 15 Mar 2022 10:37:38 -0400 Subject: [PATCH 053/245] rename css, fix background color getting applied --- css/xterm.css | 4 ++-- demo/client.ts | 4 +++- src/browser/Decorations/OverviewRulerRenderer.ts | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/css/xterm.css b/css/xterm.css index 44bb062a..0ca59399 100644 --- a/css/xterm.css +++ b/css/xterm.css @@ -179,10 +179,10 @@ position: absolute; } -.xterm-decoration-scrollbar { +.xterm-decoration-overview-ruler { z-index: 7; position: absolute; top: 0; right: 0; width: 50px; -} \ No newline at end of file +} diff --git a/demo/client.ts b/demo/client.ts index e1347e3d..b80e2e76 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -548,7 +548,9 @@ function addDecoration() { const marker = term.addMarker(1); const decoration = term.registerDecoration({ marker }); decoration.onRender((e) => { - e.style.backgroundColor = 'red'; + if (e.classList.value === 'xterm-decoration') { + e.style.backgroundColor = 'red'; + } }); } diff --git a/src/browser/Decorations/OverviewRulerRenderer.ts b/src/browser/Decorations/OverviewRulerRenderer.ts index 8a78bb78..083db5e9 100644 --- a/src/browser/Decorations/OverviewRulerRenderer.ts +++ b/src/browser/Decorations/OverviewRulerRenderer.ts @@ -29,7 +29,7 @@ export class OverviewRulerRenderer extends Disposable { ) { super(); this._canvas = document.createElement('canvas'); - this._canvas.classList.add('xterm-decoration-scrollbar'); + this._canvas.classList.add('xterm-decoration-overview-ruler'); this._viewportElement.parentElement?.insertBefore(this._canvas, this._viewportElement); this._ctx = this._canvas.getContext('2d'); this._canvas.style.width = `${this._optionsService.options.overviewRulerWidth || ScrollbarConstants.WIDTH}px`; From 4ef5f6ae86d8c25d625a108a95608f0cbd2ac149 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 15 Mar 2022 10:53:32 -0400 Subject: [PATCH 054/245] add listener for on option change --- src/browser/Decorations/OverviewRulerRenderer.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/browser/Decorations/OverviewRulerRenderer.ts b/src/browser/Decorations/OverviewRulerRenderer.ts index 083db5e9..7d8475f2 100644 --- a/src/browser/Decorations/OverviewRulerRenderer.ts +++ b/src/browser/Decorations/OverviewRulerRenderer.ts @@ -45,6 +45,11 @@ export class OverviewRulerRenderer extends Disposable { this.register(addDisposableDomListener(window, 'resize', () => this.refreshDecorations())); this.register(this._decorationService.onDecorationRegistered(() => this._queueRefresh())); this.register(this._decorationService.onDecorationRemoved(decoration => this._removeDecoration(decoration))); + this.register(this._optionsService.onOptionChange(o => { + if (o === 'overviewRulerWidth') { + this.refreshDecorations(); + } + })); } public override dispose(): void { @@ -85,9 +90,9 @@ export class OverviewRulerRenderer extends Disposable { } public refreshDecorations(): void { - this._canvas.style.width = `${this._canvas.width || ScrollbarConstants.WIDTH}px`; + this._canvas.style.width = `${this._optionsService.options.overviewRulerWidth || ScrollbarConstants.WIDTH}px`; this._canvas.style.height = `${this._screenElement.clientHeight}px`; - this._canvas.width = Math.floor((this._canvas.width || ScrollbarConstants.WIDTH)* window.devicePixelRatio); + this._canvas.width = Math.floor((this._optionsService.options.overviewRulerWidth || ScrollbarConstants.WIDTH)* window.devicePixelRatio); this._canvas.height = Math.floor(this._screenElement.clientHeight * window.devicePixelRatio); for (const decoration of this._decorationService.decorations) { From 520d5dd9eb848db6f4ae5949ea59d3abe1c53e19 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 15 Mar 2022 11:37:20 -0400 Subject: [PATCH 055/245] delete unused css --- css/xterm.css | 1 - 1 file changed, 1 deletion(-) diff --git a/css/xterm.css b/css/xterm.css index 0ca59399..7432fbb1 100644 --- a/css/xterm.css +++ b/css/xterm.css @@ -184,5 +184,4 @@ position: absolute; top: 0; right: 0; - width: 50px; } From f98b4ce1bafae14b63f32342e429c0dc41ecd242 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 15 Mar 2022 12:16:33 -0400 Subject: [PATCH 056/245] properly dispose of buffer decoration --- src/browser/Decorations/BufferDecorationRenderer.ts | 1 + src/common/services/DecorationService.ts | 7 ++++++- test/api/Terminal.api.ts | 2 +- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/browser/Decorations/BufferDecorationRenderer.ts b/src/browser/Decorations/BufferDecorationRenderer.ts index 74fc59a7..26166c77 100644 --- a/src/browser/Decorations/BufferDecorationRenderer.ts +++ b/src/browser/Decorations/BufferDecorationRenderer.ts @@ -63,6 +63,7 @@ export class BufferDecorationRenderer extends Disposable { let element = this._decorationElements.get(decoration); if (!element) { element = this._createElement(decoration); + decoration.onDispose(() => this._removeDecoration(decoration)); this._decorationElements.set(decoration, element); this._container.appendChild(element); } diff --git a/src/common/services/DecorationService.ts b/src/common/services/DecorationService.ts index f18ad9b5..d561e91b 100644 --- a/src/common/services/DecorationService.ts +++ b/src/common/services/DecorationService.ts @@ -65,6 +65,11 @@ class Decoration extends Disposable implements IInternalDecoration { ) { super(); this.marker = options.marker; - // TODO: Make sure dispose doesn't need to do anything else? + this.marker.onDispose(() => this.dispose()); + } + public override dispose(): void { + this.element?.remove(); + this._onDispose.fire(); + super.dispose(); } } diff --git a/test/api/Terminal.api.ts b/test/api/Terminal.api.ts index ee6a0cfa..a73cdc19 100644 --- a/test/api/Terminal.api.ts +++ b/test/api/Terminal.api.ts @@ -574,7 +574,7 @@ describe('API Integration Tests', function(): void { await page.evaluate(`window.term.scrollLines(10)`); await page.evaluate(`window.term.addMarker(3)`); await page.evaluate(`window.term.addMarker(4)`); - await page.evaluate(` + await page.evaluate(` for (let i = 0; i < window.term.markers.length; ++i) { const marker = window.term.markers[i]; marker.onDispose(() => window.disposeStack.push(marker)); From 80e7963dbe6288a9109adb5190092cc85f45e36c Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 15 Mar 2022 12:43:03 -0400 Subject: [PATCH 057/245] fix tests and add one --- test/api/Terminal.api.ts | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/test/api/Terminal.api.ts b/test/api/Terminal.api.ts index a73cdc19..c1412223 100644 --- a/test/api/Terminal.api.ts +++ b/test/api/Terminal.api.ts @@ -732,7 +732,7 @@ describe('API Integration Tests', function(): void { }); describe('registerDecoration', () => { - it('should register decorations and render them', async () => { + it('should register decorations and not render them', async () => { await openTerminal(page); await writeSync(page, '\\n\\n\\n\\n'); await writeSync(page, '\\n\\n\\n\\n'); @@ -742,6 +742,18 @@ describe('API Integration Tests', function(): void { await page.evaluate(`window.term.registerDecoration({ marker: window.marker1 })`); await page.evaluate(`window.term.registerDecoration({ marker: window.marker2 })`); await page.evaluate(`window.term.resize(10, 5)`); + assert.equal(await page.evaluate(`document.querySelectorAll('.xterm-screen .xterm-decoration').length`), 0); + }); + it('should register decorations and render them when open is called', async () => { + await openTerminal(page); + await writeSync(page, '\\n\\n\\n\\n'); + await writeSync(page, '\\n\\n\\n\\n'); + await writeSync(page, '\\n\\n\\n\\n'); + await page.evaluate(`window.marker1 = window.term.addMarker(1)`); + await page.evaluate(`window.marker2 = window.term.addMarker(2)`); + await page.evaluate(`window.term.registerDecoration({ marker: window.marker1 })`); + await page.evaluate(`window.term.registerDecoration({ marker: window.marker2 })`); + await page.evaluate(`window.term.open(document.querySelector('#terminal-container'))`); assert.equal(await page.evaluate(`document.querySelectorAll('.xterm-screen .xterm-decoration').length`), 2); }); it('on resize should dispose of the old decoration and create a new one', async () => { @@ -750,6 +762,7 @@ describe('API Integration Tests', function(): void { await writeSync(page, '\\n\\n\\n\\n'); await page.evaluate(`window.marker = window.term.addMarker(1)`); await page.evaluate(`window.decoration = window.term.registerDecoration({ marker: window.marker })`); + await page.evaluate(`window.term.open(document.querySelector('#terminal-container'))`); await page.evaluate(`window.term.resize(10, 5)`); assert.equal(await page.evaluate(`document.querySelectorAll('.xterm-screen .xterm-decoration').length`), 1); }); From e7e9d5ca4c5ef799b20f05d099592656944749e4 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 15 Mar 2022 12:56:34 -0400 Subject: [PATCH 058/245] remove test --- demo/client.ts | 6 +++--- src/browser/Decorations/BufferDecorationRenderer.ts | 3 +++ src/browser/Decorations/OverviewRulerRenderer.ts | 4 ++-- src/common/services/DecorationService.ts | 3 ++- test/api/Terminal.api.ts | 12 ------------ typings/xterm.d.ts | 8 +++++--- 6 files changed, 15 insertions(+), 21 deletions(-) diff --git a/demo/client.ts b/demo/client.ts index b80e2e76..b219b733 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -556,9 +556,9 @@ function addDecoration() { function addOverviewRuler() { term.options['overviewRulerWidth'] = 15; - const canvas = term.registerDecoration({marker: term.addMarker(1), overviewRulerItemColor: 'red'}); - term.registerDecoration({marker: term.addMarker(3), overviewRulerItemColor: 'green'}); - term.registerDecoration({marker: term.addMarker(5), overviewRulerItemColor: 'blue'}); + const canvas = term.registerDecoration({marker: term.addMarker(1), { color }: 'red'}); + term.registerDecoration({marker: term.addMarker(3), { color }: 'green'}); + term.registerDecoration({marker: term.addMarker(5), { color }: 'blue'}); canvas.onRender((e) => { e.style.left = `${document.querySelector('.xterm-viewport').clientWidth + 5}px`; }); diff --git a/src/browser/Decorations/BufferDecorationRenderer.ts b/src/browser/Decorations/BufferDecorationRenderer.ts index 26166c77..8821ccc1 100644 --- a/src/browser/Decorations/BufferDecorationRenderer.ts +++ b/src/browser/Decorations/BufferDecorationRenderer.ts @@ -106,6 +106,9 @@ export class BufferDecorationRenderer extends Disposable { private _removeDecoration(decoration: IInternalDecoration): void { const element = this._decorationElements.get(decoration); + if (element && this._container && this._container.contains(element)) { + this._container.removeChild(element); + } element?.remove(); this._decorationElements.delete(decoration); } diff --git a/src/browser/Decorations/OverviewRulerRenderer.ts b/src/browser/Decorations/OverviewRulerRenderer.ts index 7d8475f2..45f30c35 100644 --- a/src/browser/Decorations/OverviewRulerRenderer.ts +++ b/src/browser/Decorations/OverviewRulerRenderer.ts @@ -75,12 +75,12 @@ export class OverviewRulerRenderer extends Disposable { } else { this._canvas.style.left = decoration.options.x ? `${decoration.options.x * this._renderService.dimensions.actualCellWidth}px` : ''; } - if (!decoration.options.overviewRulerItemColor) { + if (!decoration.options.overviewRulerOptions?.color) { this._decorationElements.delete(decoration); return; } this._ctx.lineWidth = 1; - this._ctx.strokeStyle = decoration.options.overviewRulerItemColor; + this._ctx.strokeStyle = decoration.options.overviewRulerOptions.color; this._ctx.strokeRect( 0, Math.round(this._canvas.height * (decoration.options.marker.line / this._bufferService.buffers.active.lines.length)), diff --git a/src/common/services/DecorationService.ts b/src/common/services/DecorationService.ts index d561e91b..9d7e3314 100644 --- a/src/common/services/DecorationService.ts +++ b/src/common/services/DecorationService.ts @@ -68,8 +68,9 @@ class Decoration extends Disposable implements IInternalDecoration { this.marker.onDispose(() => this.dispose()); } public override dispose(): void { - this.element?.remove(); this._onDispose.fire(); + this.element?.remove(); + this.element = undefined; super.dispose(); } } diff --git a/test/api/Terminal.api.ts b/test/api/Terminal.api.ts index c1412223..0a6732d7 100644 --- a/test/api/Terminal.api.ts +++ b/test/api/Terminal.api.ts @@ -744,18 +744,6 @@ describe('API Integration Tests', function(): void { await page.evaluate(`window.term.resize(10, 5)`); assert.equal(await page.evaluate(`document.querySelectorAll('.xterm-screen .xterm-decoration').length`), 0); }); - it('should register decorations and render them when open is called', async () => { - await openTerminal(page); - await writeSync(page, '\\n\\n\\n\\n'); - await writeSync(page, '\\n\\n\\n\\n'); - await writeSync(page, '\\n\\n\\n\\n'); - await page.evaluate(`window.marker1 = window.term.addMarker(1)`); - await page.evaluate(`window.marker2 = window.term.addMarker(2)`); - await page.evaluate(`window.term.registerDecoration({ marker: window.marker1 })`); - await page.evaluate(`window.term.registerDecoration({ marker: window.marker2 })`); - await page.evaluate(`window.term.open(document.querySelector('#terminal-container'))`); - assert.equal(await page.evaluate(`document.querySelectorAll('.xterm-screen .xterm-decoration').length`), 2); - }); it('on resize should dispose of the old decoration and create a new one', async () => { await openTerminal(page); await writeSync(page, '\\n\\n\\n\\n'); diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 986e77c9..c6062967 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -478,10 +478,12 @@ declare module 'xterm' { height?: number; /** - * When provided, renders the decoration in the scrollbar - * with the given color + * Renders the decoration in the scrollbar + * with the given @param color and optional @param position. + * If @param position is not set, it will span the full @param overviewRulerWidth, which + * must be provided via @TerminalOptions for this to work. */ - overviewRulerItemColor?: string; + overviewRulerOptions?: { color: string; position?: 'left' | 'center' | 'right'} } /** From df929f2dd5b65390e82f422c4125cd75eae629ba Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 15 Mar 2022 13:07:49 -0400 Subject: [PATCH 059/245] add position --- demo/client.ts | 9 ++++++--- src/browser/Decorations/OverviewRulerRenderer.ts | 8 +++++--- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/demo/client.ts b/demo/client.ts index b219b733..70588213 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -556,9 +556,12 @@ function addDecoration() { function addOverviewRuler() { term.options['overviewRulerWidth'] = 15; - const canvas = term.registerDecoration({marker: term.addMarker(1), { color }: 'red'}); - term.registerDecoration({marker: term.addMarker(3), { color }: 'green'}); - term.registerDecoration({marker: term.addMarker(5), { color }: 'blue'}); + const canvas = term.registerDecoration({marker: term.addMarker(1), overviewRulerOptions: { color: 'red' }}); + term.registerDecoration({marker: term.addMarker(3), overviewRulerOptions: { color: 'green' }}); + term.registerDecoration({marker: term.addMarker(5), overviewRulerOptions: { color: 'blue' }}); + term.registerDecoration({marker: term.addMarker(7), overviewRulerOptions: { color: 'red', position: 'left' }}); + term.registerDecoration({marker: term.addMarker(7), overviewRulerOptions: { color: 'green', position: 'center' }}); + term.registerDecoration({marker: term.addMarker(7), overviewRulerOptions: { color: 'blue', position: 'right' }}); canvas.onRender((e) => { e.style.left = `${document.querySelector('.xterm-viewport').clientWidth + 5}px`; }); diff --git a/src/browser/Decorations/OverviewRulerRenderer.ts b/src/browser/Decorations/OverviewRulerRenderer.ts index 45f30c35..eb58bc10 100644 --- a/src/browser/Decorations/OverviewRulerRenderer.ts +++ b/src/browser/Decorations/OverviewRulerRenderer.ts @@ -67,7 +67,7 @@ export class OverviewRulerRenderer extends Disposable { } private _refreshStyle(decoration: IInternalDecoration): void { - if (!this._ctx) { + if (!this._ctx || !this._optionsService.options.overviewRulerWidth) { return; } if (decoration.options.anchor === 'right') { @@ -81,10 +81,12 @@ export class OverviewRulerRenderer extends Disposable { } this._ctx.lineWidth = 1; this._ctx.strokeStyle = decoration.options.overviewRulerOptions.color; + const size = Math.floor(this._optionsService.options.overviewRulerWidth / 3); + const position = decoration.options.overviewRulerOptions.position; this._ctx.strokeRect( - 0, + !position || position === 'left' ? 0 : position === 'right' ? size * 2 + 1: size, Math.round(this._canvas.height * (decoration.options.marker.line / this._bufferService.buffers.active.lines.length)), - this._canvas.width, + !position ? this._canvas.width : position === 'center' ? size + 1 : size, window.devicePixelRatio ); } From bd21c6afa59f9c8b8517365df89caaa4ac60ff0d Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 15 Mar 2022 13:15:43 -0400 Subject: [PATCH 060/245] adjust test --- demo/client.ts | 2 +- test/api/Terminal.api.ts | 14 ++------------ 2 files changed, 3 insertions(+), 13 deletions(-) diff --git a/demo/client.ts b/demo/client.ts index 70588213..f3f11806 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -555,7 +555,7 @@ function addDecoration() { } function addOverviewRuler() { - term.options['overviewRulerWidth'] = 15; + term.options['overviewRulerWidth'] = 13; const canvas = term.registerDecoration({marker: term.addMarker(1), overviewRulerOptions: { color: 'red' }}); term.registerDecoration({marker: term.addMarker(3), overviewRulerOptions: { color: 'green' }}); term.registerDecoration({marker: term.addMarker(5), overviewRulerOptions: { color: 'blue' }}); diff --git a/test/api/Terminal.api.ts b/test/api/Terminal.api.ts index 0a6732d7..f13b2ada 100644 --- a/test/api/Terminal.api.ts +++ b/test/api/Terminal.api.ts @@ -732,7 +732,7 @@ describe('API Integration Tests', function(): void { }); describe('registerDecoration', () => { - it('should register decorations and not render them', async () => { + it('should register decorations and render them', async () => { await openTerminal(page); await writeSync(page, '\\n\\n\\n\\n'); await writeSync(page, '\\n\\n\\n\\n'); @@ -742,17 +742,7 @@ describe('API Integration Tests', function(): void { await page.evaluate(`window.term.registerDecoration({ marker: window.marker1 })`); await page.evaluate(`window.term.registerDecoration({ marker: window.marker2 })`); await page.evaluate(`window.term.resize(10, 5)`); - assert.equal(await page.evaluate(`document.querySelectorAll('.xterm-screen .xterm-decoration').length`), 0); - }); - it('on resize should dispose of the old decoration and create a new one', async () => { - await openTerminal(page); - await writeSync(page, '\\n\\n\\n\\n'); - await writeSync(page, '\\n\\n\\n\\n'); - await page.evaluate(`window.marker = window.term.addMarker(1)`); - await page.evaluate(`window.decoration = window.term.registerDecoration({ marker: window.marker })`); - await page.evaluate(`window.term.open(document.querySelector('#terminal-container'))`); - await page.evaluate(`window.term.resize(10, 5)`); - assert.equal(await page.evaluate(`document.querySelectorAll('.xterm-screen .xterm-decoration').length`), 1); + assert.equal(await page.evaluate(`document.querySelectorAll('.xterm-screen .xterm-decoration').length`), 2); }); it('should return undefined when the marker has already been disposed of', async () => { await openTerminal(page); From 71649f5e407f0fc1b6bf55966bd2b32794f1aff0 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 15 Mar 2022 13:21:34 -0400 Subject: [PATCH 061/245] fix tests --- test/api/Terminal.api.ts | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/test/api/Terminal.api.ts b/test/api/Terminal.api.ts index f13b2ada..f2abf2f8 100644 --- a/test/api/Terminal.api.ts +++ b/test/api/Terminal.api.ts @@ -731,31 +731,26 @@ describe('API Integration Tests', function(): void { await pollFor(page, `window.term._core._renderService.dimensions.actualCellWidth > 0`, true); }); - describe('registerDecoration', () => { - it('should register decorations and render them', async () => { - await openTerminal(page); - await writeSync(page, '\\n\\n\\n\\n'); - await writeSync(page, '\\n\\n\\n\\n'); - await writeSync(page, '\\n\\n\\n\\n'); + describe.only('registerDecoration', () => { + it('should register decorations and render them when terminal open is called', async () => { + await page.evaluate(`window.term = new Terminal({})`); + await page.evaluate(`window.term.open(document.querySelector('#terminal-container'))`); + await page.waitForSelector('.xterm-text-layer'); await page.evaluate(`window.marker1 = window.term.addMarker(1)`); await page.evaluate(`window.marker2 = window.term.addMarker(2)`); await page.evaluate(`window.term.registerDecoration({ marker: window.marker1 })`); await page.evaluate(`window.term.registerDecoration({ marker: window.marker2 })`); - await page.evaluate(`window.term.resize(10, 5)`); + await openTerminal(page); assert.equal(await page.evaluate(`document.querySelectorAll('.xterm-screen .xterm-decoration').length`), 2); }); it('should return undefined when the marker has already been disposed of', async () => { await openTerminal(page); - await writeSync(page, '\\n\\n\\n\\n'); - await writeSync(page, '\\n\\n\\n\\n'); await page.evaluate(`window.marker = window.term.addMarker(1)`); await page.evaluate(`window.marker.dispose()`); assert.equal(await page.evaluate(`window.decoration = window.term.registerDecoration({ marker: window.marker });`), undefined); }); it('should throw when a negative x offset is provided', async () => { await openTerminal(page); - await writeSync(page, '\\n\\n\\n\\n'); - await writeSync(page, '\\n\\n\\n\\n'); await page.evaluate(`window.marker = window.term.addMarker(1)`); await page.evaluate(` try { From adaee5e495785769d8752a075e630445e87ab0bb Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 15 Mar 2022 13:23:09 -0400 Subject: [PATCH 062/245] use poll for instead --- test/api/Terminal.api.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/api/Terminal.api.ts b/test/api/Terminal.api.ts index f2abf2f8..0e50e7fc 100644 --- a/test/api/Terminal.api.ts +++ b/test/api/Terminal.api.ts @@ -741,13 +741,13 @@ describe('API Integration Tests', function(): void { await page.evaluate(`window.term.registerDecoration({ marker: window.marker1 })`); await page.evaluate(`window.term.registerDecoration({ marker: window.marker2 })`); await openTerminal(page); - assert.equal(await page.evaluate(`document.querySelectorAll('.xterm-screen .xterm-decoration').length`), 2); + await pollFor(page, `document.querySelectorAll('.xterm-screen .xterm-decoration').length`, 2); }); it('should return undefined when the marker has already been disposed of', async () => { await openTerminal(page); await page.evaluate(`window.marker = window.term.addMarker(1)`); await page.evaluate(`window.marker.dispose()`); - assert.equal(await page.evaluate(`window.decoration = window.term.registerDecoration({ marker: window.marker });`), undefined); + await pollFor(page, `window.decoration = window.term.registerDecoration({ marker: window.marker });`, undefined); }); it('should throw when a negative x offset is provided', async () => { await openTerminal(page); From 1905d7d8cad1f318887bb38065e95344e28a8e3d Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 15 Mar 2022 13:28:50 -0400 Subject: [PATCH 063/245] add overview ruler tests --- test/api/Terminal.api.ts | 82 ++++++++++++++++++++++++++-------------- 1 file changed, 54 insertions(+), 28 deletions(-) diff --git a/test/api/Terminal.api.ts b/test/api/Terminal.api.ts index 0e50e7fc..43f1e97d 100644 --- a/test/api/Terminal.api.ts +++ b/test/api/Terminal.api.ts @@ -731,35 +731,61 @@ describe('API Integration Tests', function(): void { await pollFor(page, `window.term._core._renderService.dimensions.actualCellWidth > 0`, true); }); - describe.only('registerDecoration', () => { - it('should register decorations and render them when terminal open is called', async () => { - await page.evaluate(`window.term = new Terminal({})`); - await page.evaluate(`window.term.open(document.querySelector('#terminal-container'))`); - await page.waitForSelector('.xterm-text-layer'); - await page.evaluate(`window.marker1 = window.term.addMarker(1)`); - await page.evaluate(`window.marker2 = window.term.addMarker(2)`); - await page.evaluate(`window.term.registerDecoration({ marker: window.marker1 })`); - await page.evaluate(`window.term.registerDecoration({ marker: window.marker2 })`); - await openTerminal(page); - await pollFor(page, `document.querySelectorAll('.xterm-screen .xterm-decoration').length`, 2); + describe('registerDecoration', () => { + describe('bufferDecorations', () => { + it('should register decorations and render them when terminal open is called', async () => { + await page.evaluate(`window.term = new Terminal({})`); + await page.evaluate(`window.term.open(document.querySelector('#terminal-container'))`); + await page.waitForSelector('.xterm-text-layer'); + await page.evaluate(`window.marker1 = window.term.addMarker(1)`); + await page.evaluate(`window.marker2 = window.term.addMarker(2)`); + await page.evaluate(`window.term.registerDecoration({ marker: window.marker1 })`); + await page.evaluate(`window.term.registerDecoration({ marker: window.marker2 })`); + await openTerminal(page); + await pollFor(page, `document.querySelectorAll('.xterm-screen .xterm-decoration').length`, 2); + }); + it('should return undefined when the marker has already been disposed of', async () => { + await openTerminal(page); + await page.evaluate(`window.marker = window.term.addMarker(1)`); + await page.evaluate(`window.marker.dispose()`); + await pollFor(page, `window.decoration = window.term.registerDecoration({ marker: window.marker });`, undefined); + }); + it('should throw when a negative x offset is provided', async () => { + await openTerminal(page); + await page.evaluate(`window.marker = window.term.addMarker(1)`); + await page.evaluate(` + try { + window.decoration = window.term.registerDecoration({ marker: window.marker, x: -2 }); + } catch (e) { + window.throwMessage = e.message; + } + `); + await pollFor(page, 'window.throwMessage', 'This API only accepts positive integers'); + }); }); - it('should return undefined when the marker has already been disposed of', async () => { - await openTerminal(page); - await page.evaluate(`window.marker = window.term.addMarker(1)`); - await page.evaluate(`window.marker.dispose()`); - await pollFor(page, `window.decoration = window.term.registerDecoration({ marker: window.marker });`, undefined); - }); - it('should throw when a negative x offset is provided', async () => { - await openTerminal(page); - await page.evaluate(`window.marker = window.term.addMarker(1)`); - await page.evaluate(` - try { - window.decoration = window.term.registerDecoration({ marker: window.marker, x: -2 }); - } catch (e) { - window.throwMessage = e.message; - } - `); - await pollFor(page, 'window.throwMessage', 'This API only accepts positive integers'); + describe('overviewRulerDecorations', () => { + it('should not add an overview ruler when width is not set', async () => { + await page.evaluate(`window.term = new Terminal({})`); + await page.evaluate(`window.term.open(document.querySelector('#terminal-container'))`); + await page.waitForSelector('.xterm-text-layer'); + await page.evaluate(`window.marker1 = window.term.addMarker(1)`); + await page.evaluate(`window.marker2 = window.term.addMarker(2)`); + await page.evaluate(`window.term.registerDecoration({ marker: window.marker1, overviewRulerOptions: { color: 'red' } })`); + await page.evaluate(`window.term.registerDecoration({ marker: window.marker2, overviewRulerOptions: { color: 'blue' } })`); + await openTerminal(page); + await pollFor(page, `document.querySelectorAll('.xterm-decoration-overview-ruler').length`, 0); + }); + it('should add an overview ruler when width is set', async () => { + await page.evaluate(`window.term = new Terminal({ overviewRulerWidth: 15 })`); + await page.evaluate(`window.term.open(document.querySelector('#terminal-container'))`); + await page.waitForSelector('.xterm-text-layer'); + await page.evaluate(`window.marker1 = window.term.addMarker(1)`); + await page.evaluate(`window.marker2 = window.term.addMarker(2)`); + await page.evaluate(`window.term.registerDecoration({ marker: window.marker1, overviewRulerOptions: { color: 'red' } })`); + await page.evaluate(`window.term.registerDecoration({ marker: window.marker2, overviewRulerOptions: { color: 'blue' } })`); + await openTerminal(page); + await pollFor(page, `document.querySelectorAll('.xterm-decoration-overview-ruler').length`, 1); + }); }); }); From 21263a5f60f390e33644a3be0d851719a7a0b8b3 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 15 Mar 2022 14:54:37 -0400 Subject: [PATCH 064/245] cleanup --- demo/client.ts | 12 ++++++------ .../Decorations/BufferDecorationRenderer.ts | 8 +++----- src/browser/Decorations/OverviewRulerRenderer.ts | 16 +++++++--------- src/common/services/DecorationService.ts | 3 --- typings/xterm.d.ts | 2 +- 5 files changed, 17 insertions(+), 24 deletions(-) diff --git a/demo/client.ts b/demo/client.ts index f3f11806..047cd602 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -556,12 +556,12 @@ function addDecoration() { function addOverviewRuler() { term.options['overviewRulerWidth'] = 13; - const canvas = term.registerDecoration({marker: term.addMarker(1), overviewRulerOptions: { color: 'red' }}); - term.registerDecoration({marker: term.addMarker(3), overviewRulerOptions: { color: 'green' }}); - term.registerDecoration({marker: term.addMarker(5), overviewRulerOptions: { color: 'blue' }}); - term.registerDecoration({marker: term.addMarker(7), overviewRulerOptions: { color: 'red', position: 'left' }}); - term.registerDecoration({marker: term.addMarker(7), overviewRulerOptions: { color: 'green', position: 'center' }}); - term.registerDecoration({marker: term.addMarker(7), overviewRulerOptions: { color: 'blue', position: 'right' }}); + const canvas = term.registerDecoration({marker: term.addMarker(1), overviewRulerOptions: { color: '#ef2929' }}); + term.registerDecoration({marker: term.addMarker(3), overviewRulerOptions: { color: '#8ae234' }}); + term.registerDecoration({marker: term.addMarker(5), overviewRulerOptions: { color: '#729fcf' }}); + term.registerDecoration({marker: term.addMarker(7), overviewRulerOptions: { color: '#ef2929', position: 'left' }}); + term.registerDecoration({marker: term.addMarker(7), overviewRulerOptions: { color: '#8ae234', position: 'center' }}); + term.registerDecoration({marker: term.addMarker(7), overviewRulerOptions: { color: '#729fcf', position: 'right' }}); canvas.onRender((e) => { e.style.left = `${document.querySelector('.xterm-viewport').clientWidth + 5}px`; }); diff --git a/src/browser/Decorations/BufferDecorationRenderer.ts b/src/browser/Decorations/BufferDecorationRenderer.ts index 8821ccc1..116c09a5 100644 --- a/src/browser/Decorations/BufferDecorationRenderer.ts +++ b/src/browser/Decorations/BufferDecorationRenderer.ts @@ -64,6 +64,8 @@ export class BufferDecorationRenderer extends Disposable { if (!element) { element = this._createElement(decoration); decoration.onDispose(() => this._removeDecoration(decoration)); + decoration.marker.onDispose(() => decoration.dispose()); + decoration.element = element; this._decorationElements.set(decoration, element); this._container.appendChild(element); } @@ -105,11 +107,7 @@ export class BufferDecorationRenderer extends Disposable { } private _removeDecoration(decoration: IInternalDecoration): void { - const element = this._decorationElements.get(decoration); - if (element && this._container && this._container.contains(element)) { - this._container.removeChild(element); - } - element?.remove(); + this._decorationElements.get(decoration)?.remove(); this._decorationElements.delete(decoration); } } diff --git a/src/browser/Decorations/OverviewRulerRenderer.ts b/src/browser/Decorations/OverviewRulerRenderer.ts index eb58bc10..a9439d9f 100644 --- a/src/browser/Decorations/OverviewRulerRenderer.ts +++ b/src/browser/Decorations/OverviewRulerRenderer.ts @@ -8,15 +8,13 @@ import { IRenderService } from 'browser/services/Services'; import { Disposable } from 'common/Lifecycle'; import { IBufferService, IDecorationService, IInternalDecoration, IOptionsService } from 'common/services/Services'; -const enum ScrollbarConstants { - WIDTH = 15 -} - export class OverviewRulerRenderer extends Disposable { private _canvas: HTMLCanvasElement; private _ctx: CanvasRenderingContext2D | null; private readonly _decorationElements: Map = new Map(); - + private get _width(): number { + return this._optionsService.options.overviewRulerWidth || 0; + } private _animationFrame: number | undefined; constructor( @@ -32,9 +30,9 @@ export class OverviewRulerRenderer extends Disposable { this._canvas.classList.add('xterm-decoration-overview-ruler'); this._viewportElement.parentElement?.insertBefore(this._canvas, this._viewportElement); this._ctx = this._canvas.getContext('2d'); - this._canvas.style.width = `${this._optionsService.options.overviewRulerWidth || ScrollbarConstants.WIDTH}px`; + this._canvas.style.width = `${this._width}px`; this._canvas.style.height = `${this._screenElement.clientHeight}px`; - this._canvas.width = Math.floor((this._optionsService.options.overviewRulerWidth|| ScrollbarConstants.WIDTH)* window.devicePixelRatio); + this._canvas.width = Math.floor((this._width)* window.devicePixelRatio); this._canvas.height = Math.floor(this._screenElement.clientHeight * window.devicePixelRatio); this.refreshDecorations(); this.register(this._bufferService.buffers.onBufferActivate(() => { @@ -92,9 +90,9 @@ export class OverviewRulerRenderer extends Disposable { } public refreshDecorations(): void { - this._canvas.style.width = `${this._optionsService.options.overviewRulerWidth || ScrollbarConstants.WIDTH}px`; + this._canvas.style.width = `${this._width}px`; this._canvas.style.height = `${this._screenElement.clientHeight}px`; - this._canvas.width = Math.floor((this._optionsService.options.overviewRulerWidth || ScrollbarConstants.WIDTH)* window.devicePixelRatio); + this._canvas.width = Math.floor((this._width)* window.devicePixelRatio); this._canvas.height = Math.floor(this._screenElement.clientHeight * window.devicePixelRatio); for (const decoration of this._decorationService.decorations) { diff --git a/src/common/services/DecorationService.ts b/src/common/services/DecorationService.ts index 9d7e3314..911fd369 100644 --- a/src/common/services/DecorationService.ts +++ b/src/common/services/DecorationService.ts @@ -65,12 +65,9 @@ class Decoration extends Disposable implements IInternalDecoration { ) { super(); this.marker = options.marker; - this.marker.onDispose(() => this.dispose()); } public override dispose(): void { this._onDispose.fire(); - this.element?.remove(); - this.element = undefined; super.dispose(); } } diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index c6062967..0dce1ce6 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -436,7 +436,7 @@ declare module 'xterm' { * after the first _onRender call, or undefined if accessed before * that. */ - readonly element: HTMLElement | undefined; + element: HTMLElement | undefined; } /** From a93f81c270798e2d37ec2c615f34eed6cd031790 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Tue, 15 Mar 2022 14:57:34 -0400 Subject: [PATCH 065/245] Update typings/xterm.d.ts Co-authored-by: Daniel Imms <2193314+Tyriar@users.noreply.github.com> --- typings/xterm.d.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 0dce1ce6..0703021a 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -432,8 +432,8 @@ declare module 'xterm' { readonly onRender: IEvent; /** - * The HTMLElement that gets created or drawn to (for scrollbar decorations) - * after the first _onRender call, or undefined if accessed before + * The element that the decoration is rendered to. This will be undefined + * until it is rendered for the first time by @{link IDecoration.onRender}. * that. */ element: HTMLElement | undefined; From 8488f1ad6312c36c8f39307836d4782c522a8653 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Tue, 15 Mar 2022 14:59:09 -0400 Subject: [PATCH 066/245] Update src/browser/Decorations/OverviewRulerRenderer.ts Co-authored-by: Daniel Imms <2193314+Tyriar@users.noreply.github.com> --- src/browser/Decorations/OverviewRulerRenderer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/browser/Decorations/OverviewRulerRenderer.ts b/src/browser/Decorations/OverviewRulerRenderer.ts index a9439d9f..87ec3589 100644 --- a/src/browser/Decorations/OverviewRulerRenderer.ts +++ b/src/browser/Decorations/OverviewRulerRenderer.ts @@ -73,7 +73,7 @@ export class OverviewRulerRenderer extends Disposable { } else { this._canvas.style.left = decoration.options.x ? `${decoration.options.x * this._renderService.dimensions.actualCellWidth}px` : ''; } - if (!decoration.options.overviewRulerOptions?.color) { + if (!decoration.options.overviewRulerOptions) { this._decorationElements.delete(decoration); return; } From 5b8777267a5566e7d52395e4008588241f2ee153 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Tue, 15 Mar 2022 14:59:17 -0400 Subject: [PATCH 067/245] Update typings/xterm.d.ts Co-authored-by: Daniel Imms <2193314+Tyriar@users.noreply.github.com> --- typings/xterm.d.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 0703021a..193f2ab9 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -465,9 +465,7 @@ declare module 'xterm' { /** - * The width of the decoration in cells, which defaults to - * cell width or the width in pixels, when an overlayRulerItemColor - * is provided. + * The width of the decoration in cells, defaults to 1. */ width?: number; From 45ecc837e40740e3c01b0b787783d1380045c334 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Tue, 15 Mar 2022 14:59:23 -0400 Subject: [PATCH 068/245] Update typings/xterm.d.ts Co-authored-by: Daniel Imms <2193314+Tyriar@users.noreply.github.com> --- typings/xterm.d.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 193f2ab9..05a1e786 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -470,8 +470,7 @@ declare module 'xterm' { width?: number; /** - * The height of the decoration in cells, which defaults to - * cell height + * The height of the decoration in cells, defaults to 1. */ height?: number; From b146ffd30f3a72652404dcefc221af456d1a93a2 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Tue, 15 Mar 2022 14:59:43 -0400 Subject: [PATCH 069/245] Update src/browser/Decorations/OverviewRulerRenderer.ts Co-authored-by: Daniel Imms <2193314+Tyriar@users.noreply.github.com> --- src/browser/Decorations/OverviewRulerRenderer.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/browser/Decorations/OverviewRulerRenderer.ts b/src/browser/Decorations/OverviewRulerRenderer.ts index 87ec3589..04d2cb47 100644 --- a/src/browser/Decorations/OverviewRulerRenderer.ts +++ b/src/browser/Decorations/OverviewRulerRenderer.ts @@ -79,7 +79,8 @@ export class OverviewRulerRenderer extends Disposable { } this._ctx.lineWidth = 1; this._ctx.strokeStyle = decoration.options.overviewRulerOptions.color; - const size = Math.floor(this._optionsService.options.overviewRulerWidth / 3); + const outerSize = Math.floor(this._optionsService.options.overviewRulerWidth / 3); + const innerSize = Math.ceil(this._optionsService.options.overviewRulerWidth / 3); const position = decoration.options.overviewRulerOptions.position; this._ctx.strokeRect( !position || position === 'left' ? 0 : position === 'right' ? size * 2 + 1: size, From fdc2bc49604280985bdf094905abab6421d59880 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Tue, 15 Mar 2022 14:59:48 -0400 Subject: [PATCH 070/245] Update typings/xterm.d.ts Co-authored-by: Daniel Imms <2193314+Tyriar@users.noreply.github.com> --- typings/xterm.d.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 05a1e786..b7c89319 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -475,10 +475,11 @@ declare module 'xterm' { height?: number; /** - * Renders the decoration in the scrollbar - * with the given @param color and optional @param position. - * If @param position is not set, it will span the full @param overviewRulerWidth, which - * must be provided via @TerminalOptions for this to work. + * When defined, renders the decoration in the overview ruler to the right + * of the terminal. {@link ITerminalOptions.overviewRulerWidth} must be set + * in order to see the overview ruler. + * @param color The color of the decoration. + * @param position The position of the decoration. */ overviewRulerOptions?: { color: string; position?: 'left' | 'center' | 'right'} } From cee2411fb445885461fdac2e619c31db41b85155 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 15 Mar 2022 15:00:05 -0400 Subject: [PATCH 071/245] more cleanup --- .../Decorations/OverviewRulerRenderer.ts | 29 ++++++++++--------- typings/xterm.d.ts | 12 ++++---- 2 files changed, 21 insertions(+), 20 deletions(-) diff --git a/src/browser/Decorations/OverviewRulerRenderer.ts b/src/browser/Decorations/OverviewRulerRenderer.ts index a9439d9f..54862fb0 100644 --- a/src/browser/Decorations/OverviewRulerRenderer.ts +++ b/src/browser/Decorations/OverviewRulerRenderer.ts @@ -9,8 +9,8 @@ import { Disposable } from 'common/Lifecycle'; import { IBufferService, IDecorationService, IInternalDecoration, IOptionsService } from 'common/services/Services'; export class OverviewRulerRenderer extends Disposable { - private _canvas: HTMLCanvasElement; - private _ctx: CanvasRenderingContext2D | null; + private readonly _canvas: HTMLCanvasElement; + private readonly _ctx: CanvasRenderingContext2D; private readonly _decorationElements: Map = new Map(); private get _width(): number { return this._optionsService.options.overviewRulerWidth || 0; @@ -29,23 +29,27 @@ export class OverviewRulerRenderer extends Disposable { this._canvas = document.createElement('canvas'); this._canvas.classList.add('xterm-decoration-overview-ruler'); this._viewportElement.parentElement?.insertBefore(this._canvas, this._viewportElement); - this._ctx = this._canvas.getContext('2d'); + const ctx = this._canvas.getContext('2d'); + if (!ctx) { + throw new Error('Ctx cannot be null'); + } else { + this._ctx = ctx; + } this._canvas.style.width = `${this._width}px`; this._canvas.style.height = `${this._screenElement.clientHeight}px`; this._canvas.width = Math.floor((this._width)* window.devicePixelRatio); this._canvas.height = Math.floor(this._screenElement.clientHeight * window.devicePixelRatio); - this.refreshDecorations(); this.register(this._bufferService.buffers.onBufferActivate(() => { this._canvas!.style.display = this._bufferService.buffer === this._bufferService.buffers.alt ? 'none' : 'block'; })); - this.register(this._renderService.onRenderedBufferChange(() => this.refreshDecorations())); - this.register(this._renderService.onDimensionsChange(() => this.refreshDecorations())); - this.register(addDisposableDomListener(window, 'resize', () => this.refreshDecorations())); + this.register(this._renderService.onRenderedBufferChange(() => this._queueRefresh())); + this.register(this._renderService.onDimensionsChange(() => this._queueRefresh())); + this.register(addDisposableDomListener(window, 'resize', () => this._queueRefresh())); this.register(this._decorationService.onDecorationRegistered(() => this._queueRefresh())); this.register(this._decorationService.onDecorationRemoved(decoration => this._removeDecoration(decoration))); this.register(this._optionsService.onOptionChange(o => { if (o === 'overviewRulerWidth') { - this.refreshDecorations(); + this._queueRefresh(); } })); } @@ -65,9 +69,6 @@ export class OverviewRulerRenderer extends Disposable { } private _refreshStyle(decoration: IInternalDecoration): void { - if (!this._ctx || !this._optionsService.options.overviewRulerWidth) { - return; - } if (decoration.options.anchor === 'right') { this._canvas.style.right = decoration.options.x ? `${decoration.options.x * this._renderService.dimensions.actualCellWidth}px` : ''; } else { @@ -79,7 +80,7 @@ export class OverviewRulerRenderer extends Disposable { } this._ctx.lineWidth = 1; this._ctx.strokeStyle = decoration.options.overviewRulerOptions.color; - const size = Math.floor(this._optionsService.options.overviewRulerWidth / 3); + const size = Math.floor(this._width / 3); const position = decoration.options.overviewRulerOptions.position; this._ctx.strokeRect( !position || position === 'left' ? 0 : position === 'right' ? size * 2 + 1: size, @@ -89,7 +90,7 @@ export class OverviewRulerRenderer extends Disposable { ); } - public refreshDecorations(): void { + private _refreshDecorations(): void { this._canvas.style.width = `${this._width}px`; this._canvas.style.height = `${this._screenElement.clientHeight}px`; this._canvas.width = Math.floor((this._width)* window.devicePixelRatio); @@ -114,7 +115,7 @@ export class OverviewRulerRenderer extends Disposable { return; } this._animationFrame = window.requestAnimationFrame(() => { - this.refreshDecorations(); + this._refreshDecorations(); this._animationFrame = undefined; }); } diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 0dce1ce6..e1c51934 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -450,18 +450,18 @@ declare module 'xterm' { * The line in the terminal where * the decoration will be displayed */ - marker: IMarker; + readonly marker: IMarker; /* * Where the decoration will be anchored - * defaults to the left edge */ - anchor?: 'right' | 'left'; + readonly anchor?: 'right' | 'left'; /** * The x position offset relative to the anchor */ - x?: number; + readonly x?: number; /** @@ -469,13 +469,13 @@ declare module 'xterm' { * cell width or the width in pixels, when an overlayRulerItemColor * is provided. */ - width?: number; + readonly width?: number; /** * The height of the decoration in cells, which defaults to * cell height */ - height?: number; + readonly height?: number; /** * Renders the decoration in the scrollbar @@ -483,7 +483,7 @@ declare module 'xterm' { * If @param position is not set, it will span the full @param overviewRulerWidth, which * must be provided via @TerminalOptions for this to work. */ - overviewRulerOptions?: { color: string; position?: 'left' | 'center' | 'right'} + readonly overviewRulerOptions?: { color: string; position?: 'left' | 'center' | 'right'} } /** From a39facfe28a6d555b3a60338fb7fcc08118ce506 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 15 Mar 2022 12:42:47 -0700 Subject: [PATCH 072/245] Restore cursor style too during serialize Fixes #3677 --- .../src/SerializeAddon.test.ts | 231 +++++++++--------- .../src/SerializeAddon.ts | 18 +- 2 files changed, 134 insertions(+), 115 deletions(-) diff --git a/addons/xterm-addon-serialize/src/SerializeAddon.test.ts b/addons/xterm-addon-serialize/src/SerializeAddon.test.ts index e35751d1..67b83884 100644 --- a/addons/xterm-addon-serialize/src/SerializeAddon.test.ts +++ b/addons/xterm-addon-serialize/src/SerializeAddon.test.ts @@ -43,7 +43,7 @@ class TestSelectionService { } } -describe('xterm-addon-serialize html', () => { +describe('xterm-addon-serialize', () => { let cm: ColorManager; let dom: jsdom.JSDOM; let document: Document; @@ -83,123 +83,132 @@ describe('xterm-addon-serialize html', () => { (terminal as any)._core._selectionService = selectionService; }); - it('empty terminal with selection turned off', () => { - const output = serializeAddon.serializeAsHTML(); - assert.notEqual(output, ''); - assert.equal((output.match(/
{10}<\/span><\/div>/g) || []).length, 2); - }); - - it('empty terminal with no selection', () => { - const output = serializeAddon.serializeAsHTML({ - onlySelection: true + describe('text', () => { + it('restoring cursor styles', async () => { + await writeP(terminal, sgr('32') + '> ' + sgr('0')); + assert.equal(serializeAddon.serialize(), '\u001b[32m> \u001b[0m'); }); - assert.equal(output, ''); }); - it('basic terminal with selection', async () => { - await writeP(terminal, ' terminal '); - terminal.select(1, 0, 8); - - const output = serializeAddon.serializeAsHTML({ - onlySelection: true + describe('html', () => { + it('empty terminal with selection turned off', () => { + const output = serializeAddon.serializeAsHTML(); + assert.notEqual(output, ''); + assert.equal((output.match(/
{10}<\/span><\/div>/g) || []).length, 2); }); - assert.equal((output.match(/
terminal<\/span><\/div>/g) || []).length, 1, output); - }); - it('cells with bold styling', async () => { - await writeP(terminal, ' ' + sgr('1') + 'terminal' + sgr('22') + ' '); - - const output = serializeAddon.serializeAsHTML(); - assert.equal((output.match(/terminal<\/span>/g) || []).length, 1, output); - }); - - it('cells with italic styling', async () => { - await writeP(terminal, ' ' + sgr('3') + 'terminal' + sgr('23') + ' '); - - const output = serializeAddon.serializeAsHTML(); - assert.equal((output.match(/terminal<\/span>/g) || []).length, 1, output); - }); - - it('cells with inverse styling', async () => { - await writeP(terminal, ' ' + sgr('7') + 'terminal' + sgr('27') + ' '); - - const output = serializeAddon.serializeAsHTML(); - assert.equal((output.match(/terminal<\/span>/g) || []).length, 1, output); - }); - - it('cells with underline styling', async () => { - await writeP(terminal, ' ' + sgr('4') + 'terminal' + sgr('24') + ' '); - - const output = serializeAddon.serializeAsHTML(); - assert.equal((output.match(/terminal<\/span>/g) || []).length, 1, output); - }); - - it('cells with invisible styling', async () => { - await writeP(terminal, ' ' + sgr('8') + 'terminal' + sgr('28') + ' '); - - const output = serializeAddon.serializeAsHTML(); - assert.equal((output.match(/terminal<\/span>/g) || []).length, 1, output); - }); - - it('cells with dim styling', async () => { - await writeP(terminal, ' ' + sgr('2') + 'terminal' + sgr('22') + ' '); - - const output = serializeAddon.serializeAsHTML(); - assert.equal((output.match(/terminal<\/span>/g) || []).length, 1, output); - }); - - it('cells with strikethrough styling', async () => { - await writeP(terminal, ' ' + sgr('9') + 'terminal' + sgr('29') + ' '); - - const output = serializeAddon.serializeAsHTML(); - assert.equal((output.match(/terminal<\/span>/g) || []).length, 1, output); - }); - - it('cells with combined styling', async () => { - await writeP(terminal, sgr('1') + ' ' + sgr('9') + 'termi' + sgr('22') + 'nal' + sgr('29') + ' '); - - const output = serializeAddon.serializeAsHTML(); - assert.equal((output.match(/ <\/span>/g) || []).length, 1, output); - assert.equal((output.match(/termi<\/span>/g) || []).length, 1, output); - assert.equal((output.match(/nal<\/span>/g) || []).length, 1, output); - }); - - it('cells with color styling', async () => { - await writeP(terminal, ' ' + sgr('38;5;46') + 'terminal' + sgr('39') + ' '); - - const output = serializeAddon.serializeAsHTML(); - assert.equal((output.match(/terminal<\/span>/g) || []).length, 1, output); - }); - - it('cells with background styling', async () => { - await writeP(terminal, ' ' + sgr('48;5;46') + 'terminal' + sgr('49') + ' '); - - const output = serializeAddon.serializeAsHTML(); - assert.equal((output.match(/terminal<\/span>/g) || []).length, 1, output); - }); - - it('empty terminal with default options', async () => { - const output = serializeAddon.serializeAsHTML(); - assert.equal((output.match(/color: #000000; background-color: #ffffff; font-family: courier-new, courier, monospace; font-size: 15px;/g) || []).length, 1, output); - }); - - it('empty terminal with custom options', async () => { - terminal.options.fontFamily = 'verdana'; - terminal.options.fontSize = 20; - terminal.options.theme = { - foreground: '#ff00ff', - background: '#00ff00' - }; - const output = serializeAddon.serializeAsHTML({ - includeGlobalBackground: true + it('empty terminal with no selection', () => { + const output = serializeAddon.serializeAsHTML({ + onlySelection: true + }); + assert.equal(output, ''); }); - assert.equal((output.match(/color: #ff00ff; background-color: #00ff00; font-family: verdana; font-size: 20px;/g) || []).length, 1, output); - }); - it('empty terminal with background included', async () => { - const output = serializeAddon.serializeAsHTML({ - includeGlobalBackground: true + it('basic terminal with selection', async () => { + await writeP(terminal, ' terminal '); + terminal.select(1, 0, 8); + + const output = serializeAddon.serializeAsHTML({ + onlySelection: true + }); + assert.equal((output.match(/
terminal<\/span><\/div>/g) || []).length, 1, output); + }); + + it('cells with bold styling', async () => { + await writeP(terminal, ' ' + sgr('1') + 'terminal' + sgr('22') + ' '); + + const output = serializeAddon.serializeAsHTML(); + assert.equal((output.match(/terminal<\/span>/g) || []).length, 1, output); + }); + + it('cells with italic styling', async () => { + await writeP(terminal, ' ' + sgr('3') + 'terminal' + sgr('23') + ' '); + + const output = serializeAddon.serializeAsHTML(); + assert.equal((output.match(/terminal<\/span>/g) || []).length, 1, output); + }); + + it('cells with inverse styling', async () => { + await writeP(terminal, ' ' + sgr('7') + 'terminal' + sgr('27') + ' '); + + const output = serializeAddon.serializeAsHTML(); + assert.equal((output.match(/terminal<\/span>/g) || []).length, 1, output); + }); + + it('cells with underline styling', async () => { + await writeP(terminal, ' ' + sgr('4') + 'terminal' + sgr('24') + ' '); + + const output = serializeAddon.serializeAsHTML(); + assert.equal((output.match(/terminal<\/span>/g) || []).length, 1, output); + }); + + it('cells with invisible styling', async () => { + await writeP(terminal, ' ' + sgr('8') + 'terminal' + sgr('28') + ' '); + + const output = serializeAddon.serializeAsHTML(); + assert.equal((output.match(/terminal<\/span>/g) || []).length, 1, output); + }); + + it('cells with dim styling', async () => { + await writeP(terminal, ' ' + sgr('2') + 'terminal' + sgr('22') + ' '); + + const output = serializeAddon.serializeAsHTML(); + assert.equal((output.match(/terminal<\/span>/g) || []).length, 1, output); + }); + + it('cells with strikethrough styling', async () => { + await writeP(terminal, ' ' + sgr('9') + 'terminal' + sgr('29') + ' '); + + const output = serializeAddon.serializeAsHTML(); + assert.equal((output.match(/terminal<\/span>/g) || []).length, 1, output); + }); + + it('cells with combined styling', async () => { + await writeP(terminal, sgr('1') + ' ' + sgr('9') + 'termi' + sgr('22') + 'nal' + sgr('29') + ' '); + + const output = serializeAddon.serializeAsHTML(); + assert.equal((output.match(/ <\/span>/g) || []).length, 1, output); + assert.equal((output.match(/termi<\/span>/g) || []).length, 1, output); + assert.equal((output.match(/nal<\/span>/g) || []).length, 1, output); + }); + + it('cells with color styling', async () => { + await writeP(terminal, ' ' + sgr('38;5;46') + 'terminal' + sgr('39') + ' '); + + const output = serializeAddon.serializeAsHTML(); + assert.equal((output.match(/terminal<\/span>/g) || []).length, 1, output); + }); + + it('cells with background styling', async () => { + await writeP(terminal, ' ' + sgr('48;5;46') + 'terminal' + sgr('49') + ' '); + + const output = serializeAddon.serializeAsHTML(); + assert.equal((output.match(/terminal<\/span>/g) || []).length, 1, output); + }); + + it('empty terminal with default options', async () => { + const output = serializeAddon.serializeAsHTML(); + assert.equal((output.match(/color: #000000; background-color: #ffffff; font-family: courier-new, courier, monospace; font-size: 15px;/g) || []).length, 1, output); + }); + + it('empty terminal with custom options', async () => { + terminal.options.fontFamily = 'verdana'; + terminal.options.fontSize = 20; + terminal.options.theme = { + foreground: '#ff00ff', + background: '#00ff00' + }; + const output = serializeAddon.serializeAsHTML({ + includeGlobalBackground: true + }); + assert.equal((output.match(/color: #ff00ff; background-color: #00ff00; font-family: verdana; font-size: 20px;/g) || []).length, 1, output); + }); + + it('empty terminal with background included', async () => { + const output = serializeAddon.serializeAsHTML({ + includeGlobalBackground: true + }); + assert.equal((output.match(/color: #ffffff; background-color: #000000; font-family: courier-new, courier, monospace; font-size: 15px;/g) || []).length, 1, output); }); - assert.equal((output.match(/color: #ffffff; background-color: #000000; font-family: courier-new, courier, monospace; font-size: 15px;/g) || []).length, 1, output); }); }); diff --git a/addons/xterm-addon-serialize/src/SerializeAddon.ts b/addons/xterm-addon-serialize/src/SerializeAddon.ts index 8d6f8b36..ed71e4e1 100644 --- a/addons/xterm-addon-serialize/src/SerializeAddon.ts +++ b/addons/xterm-addon-serialize/src/SerializeAddon.ts @@ -7,6 +7,7 @@ import { Terminal, ITerminalAddon, IBuffer, IBufferCell, IBufferRange } from 'xterm'; import { IColorSet } from 'browser/Types'; +import { IAttributeData } from 'common/Types'; function constrain(value: number, low: number, high: number): number { return Math.max(low, Math.min(value, high)); @@ -62,17 +63,17 @@ abstract class BaseSerializeHandler { protected _serializeString(): string { return ''; } } -function equalFg(cell1: IBufferCell, cell2: IBufferCell): boolean { +function equalFg(cell1: IBufferCell | IAttributeData, cell2: IBufferCell): boolean { return cell1.getFgColorMode() === cell2.getFgColorMode() && cell1.getFgColor() === cell2.getFgColor(); } -function equalBg(cell1: IBufferCell, cell2: IBufferCell): boolean { +function equalBg(cell1: IBufferCell | IAttributeData, cell2: IBufferCell): boolean { return cell1.getBgColorMode() === cell2.getBgColorMode() && cell1.getBgColor() === cell2.getBgColor(); } -function equalFlags(cell1: IBufferCell, cell2: IBufferCell): boolean { +function equalFlags(cell1: IBufferCell | IAttributeData, cell2: IBufferCell): boolean { return cell1.isInverse() === cell2.isInverse() && cell1.isBold() === cell2.isBold() && cell1.isUnderline() === cell2.isUnderline() @@ -229,7 +230,7 @@ class StringSerializeHandler extends BaseSerializeHandler { this._nullCellCount = 0; } - private _diffStyle(cell: IBufferCell, oldCell: IBufferCell): number[] { + private _diffStyle(cell: IBufferCell | IAttributeData, oldCell: IBufferCell): number[] { const sgrSeq: number[] = []; const fgChanged = !equalFg(cell, oldCell); const bgChanged = !equalBg(cell, oldCell); @@ -393,6 +394,15 @@ class StringSerializeHandler extends BaseSerializeHandler { moveRight(realCursorCol - this._lastCursorCol); } + // Restore the cursor's current style, see https://github.com/xtermjs/xterm.js/issues/3677 + // HACK: Internal API access since it's awkward to expose this in the API and serialize will + // likely be the only consumer + const curAttrData: IAttributeData = (this._terminal as any)._core._inputHandler._curAttrData; + const sgrSeq = this._diffStyle(curAttrData, this._cursorStyle); + if (sgrSeq.length > 0) { + content += `\u001b[${sgrSeq.join(';')}m`; + } + return content; } } From 4cc0db42734895b7bd82ac5d151c56af6c20076a Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Tue, 15 Mar 2022 17:06:10 -0400 Subject: [PATCH 073/245] Update src/browser/Decorations/OverviewRulerRenderer.ts Co-authored-by: Daniel Imms <2193314+Tyriar@users.noreply.github.com> --- src/browser/Decorations/OverviewRulerRenderer.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/browser/Decorations/OverviewRulerRenderer.ts b/src/browser/Decorations/OverviewRulerRenderer.ts index d6ac4941..a797a746 100644 --- a/src/browser/Decorations/OverviewRulerRenderer.ts +++ b/src/browser/Decorations/OverviewRulerRenderer.ts @@ -122,8 +122,7 @@ export class OverviewRulerRenderer extends Disposable { } private _removeDecoration(decoration: IInternalDecoration): void { - const element = this._decorationElements.get(decoration); - element?.remove(); + this._decorationElements.get(decoration)?.remove(); this._decorationElements.delete(decoration); } } From e7baccdbcd200cd8da9cda970c1c3fb3ab4b1d2a Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Tue, 15 Mar 2022 17:07:01 -0400 Subject: [PATCH 074/245] Update src/browser/Terminal.ts Co-authored-by: Daniel Imms <2193314+Tyriar@users.noreply.github.com> --- src/browser/Terminal.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index c2fb1500..98ed8b9a 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -82,11 +82,8 @@ export class Terminal extends CoreTerminal implements ITerminal { private _customKeyEventHandler: CustomKeyEventHandler | undefined; - // TODO: Move into CoreTerminal.ts - // common services - private _decorationService: DecorationService; - // browser services + private _decorationService: DecorationService; private _charSizeService: ICharSizeService | undefined; private _mouseService: IMouseService | undefined; private _renderService: IRenderService | undefined; From a9a6920b1303abb824abc9ae223ddc80221f19ef Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 15 Mar 2022 17:08:01 -0400 Subject: [PATCH 075/245] more cleanup --- src/browser/Decorations/BufferDecorationRenderer.ts | 1 + typings/xterm.d.ts | 7 ++----- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/browser/Decorations/BufferDecorationRenderer.ts b/src/browser/Decorations/BufferDecorationRenderer.ts index 116c09a5..7cc2ee83 100644 --- a/src/browser/Decorations/BufferDecorationRenderer.ts +++ b/src/browser/Decorations/BufferDecorationRenderer.ts @@ -109,5 +109,6 @@ export class BufferDecorationRenderer extends Disposable { private _removeDecoration(decoration: IInternalDecoration): void { this._decorationElements.get(decoration)?.remove(); this._decorationElements.delete(decoration); + decoration.dispose(); } } diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index a86d80c8..940e3a48 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -439,11 +439,8 @@ declare module 'xterm' { element: HTMLElement | undefined; } - /** - * Options provided when registering a decoration - * containing a @param marker, @param anchor, - * @param x offset from the anchor, @param width in cells - * and @param height in cells. + /* + * Options that define the presentation of the decoration. */ export interface IDecorationOptions { /** From 8ccbef850eeab0f94c829e71596835e4ab1f5da6 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 15 Mar 2022 17:38:15 -0400 Subject: [PATCH 076/245] only update canvas dimensions when needed --- .../Decorations/BufferDecorationRenderer.ts | 1 - .../Decorations/OverviewRulerRenderer.ts | 27 +++++++++---------- 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/src/browser/Decorations/BufferDecorationRenderer.ts b/src/browser/Decorations/BufferDecorationRenderer.ts index 7cc2ee83..116c09a5 100644 --- a/src/browser/Decorations/BufferDecorationRenderer.ts +++ b/src/browser/Decorations/BufferDecorationRenderer.ts @@ -109,6 +109,5 @@ export class BufferDecorationRenderer extends Disposable { private _removeDecoration(decoration: IInternalDecoration): void { this._decorationElements.get(decoration)?.remove(); this._decorationElements.delete(decoration); - decoration.dispose(); } } diff --git a/src/browser/Decorations/OverviewRulerRenderer.ts b/src/browser/Decorations/OverviewRulerRenderer.ts index a797a746..d6d450c3 100644 --- a/src/browser/Decorations/OverviewRulerRenderer.ts +++ b/src/browser/Decorations/OverviewRulerRenderer.ts @@ -35,16 +35,13 @@ export class OverviewRulerRenderer extends Disposable { } else { this._ctx = ctx; } - this._canvas.style.width = `${this._width}px`; - this._canvas.style.height = `${this._screenElement.clientHeight}px`; - this._canvas.width = Math.floor((this._width)* window.devicePixelRatio); - this._canvas.height = Math.floor(this._screenElement.clientHeight * window.devicePixelRatio); + this._queueRefresh(true); this.register(this._bufferService.buffers.onBufferActivate(() => { this._canvas!.style.display = this._bufferService.buffer === this._bufferService.buffers.alt ? 'none' : 'block'; })); this.register(this._renderService.onRenderedBufferChange(() => this._queueRefresh())); - this.register(this._renderService.onDimensionsChange(() => this._queueRefresh())); - this.register(addDisposableDomListener(window, 'resize', () => this._queueRefresh())); + this.register(this._renderService.onDimensionsChange(() => this._queueRefresh(true))); + this.register(addDisposableDomListener(window, 'resize', () => this._queueRefresh(true))); this.register(this._decorationService.onDecorationRegistered(() => this._queueRefresh())); this.register(this._decorationService.onDecorationRemoved(decoration => this._removeDecoration(decoration))); this.register(this._optionsService.onOptionChange(o => { @@ -91,12 +88,14 @@ export class OverviewRulerRenderer extends Disposable { ); } - private _refreshDecorations(): void { - this._canvas.style.width = `${this._width}px`; - this._canvas.style.height = `${this._screenElement.clientHeight}px`; - this._canvas.width = Math.floor((this._width)* window.devicePixelRatio); - this._canvas.height = Math.floor(this._screenElement.clientHeight * window.devicePixelRatio); - + private _refreshDecorations(updateCanvasDimensions?: boolean): void { + if (updateCanvasDimensions) { + this._canvas.style.width = `${this._width}px`; + this._canvas.style.height = `${this._screenElement.clientHeight}px`; + this._canvas.width = Math.floor((this._width)* window.devicePixelRatio); + this._canvas.height = Math.floor(this._screenElement.clientHeight * window.devicePixelRatio); + } + this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height); for (const decoration of this._decorationService.decorations) { this._renderDecoration(decoration); } @@ -111,12 +110,12 @@ export class OverviewRulerRenderer extends Disposable { decoration.onRenderEmitter.fire(this._canvas); } - private _queueRefresh(): void { + private _queueRefresh(updateCanvasDimensions?: boolean): void { if (this._animationFrame !== undefined) { return; } this._animationFrame = window.requestAnimationFrame(() => { - this._refreshDecorations(); + this._refreshDecorations(updateCanvasDimensions); this._animationFrame = undefined; }); } From 53c432df272bbf65de0d2b9a70c47343260e3fce Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 15 Mar 2022 17:58:44 -0400 Subject: [PATCH 077/245] add condition for when to update anchor --- demo/client.ts | 2 +- .../Decorations/OverviewRulerRenderer.ts | 53 ++++++++++++------- 2 files changed, 35 insertions(+), 20 deletions(-) diff --git a/demo/client.ts b/demo/client.ts index 047cd602..d8d792f1 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -563,7 +563,7 @@ function addOverviewRuler() { term.registerDecoration({marker: term.addMarker(7), overviewRulerOptions: { color: '#8ae234', position: 'center' }}); term.registerDecoration({marker: term.addMarker(7), overviewRulerOptions: { color: '#729fcf', position: 'right' }}); canvas.onRender((e) => { - e.style.left = `${document.querySelector('.xterm-viewport').clientWidth + 5}px`; + e.style.left = `${document.querySelector('.xterm-viewport').clientWidth + 1}px`; }); } diff --git a/src/browser/Decorations/OverviewRulerRenderer.ts b/src/browser/Decorations/OverviewRulerRenderer.ts index d6d450c3..865ac990 100644 --- a/src/browser/Decorations/OverviewRulerRenderer.ts +++ b/src/browser/Decorations/OverviewRulerRenderer.ts @@ -8,6 +8,16 @@ import { IRenderService } from 'browser/services/Services'; import { Disposable } from 'common/Lifecycle'; import { IBufferService, IDecorationService, IInternalDecoration, IOptionsService } from 'common/services/Services'; +// This is used to reduce memory usage +// when refreshStyle is called +// by storing and updating +// the sizes of the decorations to be drawn +const workArray = new Uint16Array(3); +const enum WorkIndex { + OUTER_SIZE = 0, + INNER_SIZE = 0 +} + export class OverviewRulerRenderer extends Disposable { private readonly _canvas: HTMLCanvasElement; private readonly _ctx: CanvasRenderingContext2D; @@ -40,15 +50,21 @@ export class OverviewRulerRenderer extends Disposable { this._canvas!.style.display = this._bufferService.buffer === this._bufferService.buffers.alt ? 'none' : 'block'; })); this.register(this._renderService.onRenderedBufferChange(() => this._queueRefresh())); - this.register(this._renderService.onDimensionsChange(() => this._queueRefresh(true))); + this.register(this._renderService.onDimensionsChange(() => this._queueRefresh(true, true))); this.register(addDisposableDomListener(window, 'resize', () => this._queueRefresh(true))); - this.register(this._decorationService.onDecorationRegistered(() => this._queueRefresh())); + this.register(this._decorationService.onDecorationRegistered(() => this._queueRefresh(undefined, true))); this.register(this._decorationService.onDecorationRemoved(decoration => this._removeDecoration(decoration))); this.register(this._optionsService.onOptionChange(o => { - if (o === 'overviewRulerWidth') { + if (o === 'overviewRulerWidth' && this._optionsService.options.overviewRulerWidth) { + workArray[WorkIndex.OUTER_SIZE] = Math.floor(this._optionsService.options.overviewRulerWidth / 3); + workArray[WorkIndex.INNER_SIZE] = Math.ceil(this._optionsService.options.overviewRulerWidth / 3); this._queueRefresh(); } })); + if (this._optionsService.options.overviewRulerWidth) { + workArray[WorkIndex.OUTER_SIZE] = Math.floor(this._optionsService.options.overviewRulerWidth / 3); + workArray[WorkIndex.INNER_SIZE] = Math.ceil(this._optionsService.options.overviewRulerWidth / 3); + } } public override dispose(): void { @@ -65,11 +81,13 @@ export class OverviewRulerRenderer extends Disposable { super.dispose(); } - private _refreshStyle(decoration: IInternalDecoration): void { - if (decoration.options.anchor === 'right') { - this._canvas.style.right = decoration.options.x ? `${decoration.options.x * this._renderService.dimensions.actualCellWidth}px` : ''; - } else { - this._canvas.style.left = decoration.options.x ? `${decoration.options.x * this._renderService.dimensions.actualCellWidth}px` : ''; + private _refreshStyle(decoration: IInternalDecoration, updateAnchor?: boolean): void { + if (updateAnchor) { + if (decoration.options.anchor === 'right') { + this._canvas.style.right = decoration.options.x ? `${decoration.options.x * this._renderService.dimensions.actualCellWidth}px` : ''; + } else { + this._canvas.style.left = decoration.options.x ? `${decoration.options.x * this._renderService.dimensions.actualCellWidth}px` : ''; + } } if (!decoration.options.overviewRulerOptions) { this._decorationElements.delete(decoration); @@ -77,18 +95,15 @@ export class OverviewRulerRenderer extends Disposable { } this._ctx.lineWidth = 1; this._ctx.strokeStyle = decoration.options.overviewRulerOptions.color; - const outerSize = Math.floor(this._width / 3); - const innerSize = Math.ceil(this._width / 3); - const position = decoration.options.overviewRulerOptions.position; this._ctx.strokeRect( - !position || position === 'left' ? 0 : position === 'right' ? outerSize + innerSize: outerSize, + !decoration.options.overviewRulerOptions.position || decoration.options.overviewRulerOptions.position === 'left' ? 0 : decoration.options.overviewRulerOptions.position === 'right' ? workArray[WorkIndex.OUTER_SIZE] + workArray[WorkIndex.INNER_SIZE]: workArray[WorkIndex.OUTER_SIZE], Math.round(this._canvas.height * (decoration.options.marker.line / this._bufferService.buffers.active.lines.length)), - !position ? this._canvas.width : position === 'center' ? innerSize : outerSize, + !decoration.options.overviewRulerOptions.position ? this._canvas.width : decoration.options.overviewRulerOptions.position === 'center' ? workArray[WorkIndex.INNER_SIZE] : workArray[WorkIndex.OUTER_SIZE], window.devicePixelRatio ); } - private _refreshDecorations(updateCanvasDimensions?: boolean): void { + private _refreshDecorations(updateCanvasDimensions?: boolean, updateAnchor?: boolean): void { if (updateCanvasDimensions) { this._canvas.style.width = `${this._width}px`; this._canvas.style.height = `${this._screenElement.clientHeight}px`; @@ -97,25 +112,25 @@ export class OverviewRulerRenderer extends Disposable { } this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height); for (const decoration of this._decorationService.decorations) { - this._renderDecoration(decoration); + this._renderDecoration(decoration, updateAnchor); } } - private _renderDecoration(decoration: IInternalDecoration): void { + private _renderDecoration(decoration: IInternalDecoration, updateAnchor?: boolean): void { const element = this._decorationElements.get(decoration); if (!element) { this._decorationElements.set(decoration, this._canvas); } - this._refreshStyle(decoration); + this._refreshStyle(decoration, updateAnchor); decoration.onRenderEmitter.fire(this._canvas); } - private _queueRefresh(updateCanvasDimensions?: boolean): void { + private _queueRefresh(updateCanvasDimensions?: boolean, updateAnchor?: boolean): void { if (this._animationFrame !== undefined) { return; } this._animationFrame = window.requestAnimationFrame(() => { - this._refreshDecorations(updateCanvasDimensions); + this._refreshDecorations(updateCanvasDimensions, updateAnchor); this._animationFrame = undefined; }); } From b05db37ac13590bd125ad4014dee84e5b0b789c3 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 15 Mar 2022 18:12:19 -0400 Subject: [PATCH 078/245] adjust size based on position --- demo/client.ts | 7 ++----- src/browser/Decorations/OverviewRulerRenderer.ts | 2 +- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/demo/client.ts b/demo/client.ts index d8d792f1..3d3a3c3f 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -555,15 +555,12 @@ function addDecoration() { } function addOverviewRuler() { - term.options['overviewRulerWidth'] = 13; - const canvas = term.registerDecoration({marker: term.addMarker(1), overviewRulerOptions: { color: '#ef2929' }}); + term.options['overviewRulerWidth'] = 12; + term.registerDecoration({marker: term.addMarker(1), overviewRulerOptions: { color: '#ef2929' }}); term.registerDecoration({marker: term.addMarker(3), overviewRulerOptions: { color: '#8ae234' }}); term.registerDecoration({marker: term.addMarker(5), overviewRulerOptions: { color: '#729fcf' }}); term.registerDecoration({marker: term.addMarker(7), overviewRulerOptions: { color: '#ef2929', position: 'left' }}); term.registerDecoration({marker: term.addMarker(7), overviewRulerOptions: { color: '#8ae234', position: 'center' }}); term.registerDecoration({marker: term.addMarker(7), overviewRulerOptions: { color: '#729fcf', position: 'right' }}); - canvas.onRender((e) => { - e.style.left = `${document.querySelector('.xterm-viewport').clientWidth + 1}px`; - }); } diff --git a/src/browser/Decorations/OverviewRulerRenderer.ts b/src/browser/Decorations/OverviewRulerRenderer.ts index 865ac990..e22a3257 100644 --- a/src/browser/Decorations/OverviewRulerRenderer.ts +++ b/src/browser/Decorations/OverviewRulerRenderer.ts @@ -93,7 +93,7 @@ export class OverviewRulerRenderer extends Disposable { this._decorationElements.delete(decoration); return; } - this._ctx.lineWidth = 1; + this._ctx.lineWidth = !decoration.options.overviewRulerOptions.position ? 2 : 6; this._ctx.strokeStyle = decoration.options.overviewRulerOptions.color; this._ctx.strokeRect( !decoration.options.overviewRulerOptions.position || decoration.options.overviewRulerOptions.position === 'left' ? 0 : decoration.options.overviewRulerOptions.position === 'right' ? workArray[WorkIndex.OUTER_SIZE] + workArray[WorkIndex.INNER_SIZE]: workArray[WorkIndex.OUTER_SIZE], From d92e63648269f3df7efb0635e677e3b4c1e51866 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Tue, 15 Mar 2022 18:12:48 -0400 Subject: [PATCH 079/245] Update src/browser/Terminal.ts Co-authored-by: Daniel Imms <2193314+Tyriar@users.noreply.github.com> --- src/browser/Terminal.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index 98ed8b9a..d69e5d6f 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -583,7 +583,7 @@ export class Terminal extends CoreTerminal implements ITerminal { this.register(this.onScroll(() => this._mouseZoneManager!.clearAll())); this.linkifier.attachToDom(this.element, this._mouseZoneManager); this.linkifier2.attachToDom(this.screenElement, this._mouseService, this._renderService); - this._instantiationService.createInstance(BufferDecorationRenderer, this.screenElement); + this.register(this._instantiationService.createInstance(BufferDecorationRenderer, this.screenElement)); // This event listener must be registered aftre MouseZoneManager is created this.register(addDisposableDomListener(this.element, 'mousedown', (e: MouseEvent) => this._selectionService!.onMouseDown(e))); From 035a7f710ed59623572fd4983ab94492950da293 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Tue, 15 Mar 2022 18:12:58 -0400 Subject: [PATCH 080/245] Update src/browser/Terminal.ts Co-authored-by: Daniel Imms <2193314+Tyriar@users.noreply.github.com> --- src/browser/Terminal.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index d69e5d6f..31ef1287 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -1016,7 +1016,7 @@ export class Terminal extends CoreTerminal implements ITerminal { } public registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined { - return this._decorationService!.registerDecoration(decorationOptions); + return this._decorationService.registerDecoration(decorationOptions); } /** From 5777fbc60809fbc271f7f24424c58017a87ee4b5 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Tue, 15 Mar 2022 18:13:10 -0400 Subject: [PATCH 081/245] Update src/browser/tsconfig.json Co-authored-by: Daniel Imms <2193314+Tyriar@users.noreply.github.com> --- src/browser/tsconfig.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/browser/tsconfig.json b/src/browser/tsconfig.json index fc997477..212aeab8 100644 --- a/src/browser/tsconfig.json +++ b/src/browser/tsconfig.json @@ -18,7 +18,7 @@ "include": [ "./**/*", "../../typings/xterm.d.ts" -], + ], "references": [ { "path": "../common" } ] From 6f5008dba691b89943b468cc91321bb5d95d6af0 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Tue, 15 Mar 2022 18:13:23 -0400 Subject: [PATCH 082/245] Update typings/xterm.d.ts Co-authored-by: Daniel Imms <2193314+Tyriar@users.noreply.github.com> --- typings/xterm.d.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 940e3a48..64ef2a08 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -268,7 +268,8 @@ declare module 'xterm' { windowOptions?: IWindowOptions; /** - * The width, in pixels, of the canvas for the overview ruler. + * The width, in pixels, of the canvas for the overview ruler. The overview + * ruler will be hidden when not set. */ overviewRulerWidth?: number; } From a4f830aa5d521cd8acfa8e9a920793806757f6fb Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Tue, 15 Mar 2022 18:13:36 -0400 Subject: [PATCH 083/245] Update typings/xterm.d.ts Co-authored-by: Daniel Imms <2193314+Tyriar@users.noreply.github.com> --- typings/xterm.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 64ef2a08..24ba940e 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -400,7 +400,7 @@ declare module 'xterm' { } /** - * Represents a disposable with an + * Represents a disposable that tracks is disposed state. * @param onDispose event listener and * @param isDisposed property. */ From 22d889ab07ff0b879e6f4408eed5c5cdeb8b088a Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 15 Mar 2022 19:15:59 -0400 Subject: [PATCH 084/245] use this._width instead of options --- .../Decorations/OverviewRulerRenderer.ts | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/browser/Decorations/OverviewRulerRenderer.ts b/src/browser/Decorations/OverviewRulerRenderer.ts index e22a3257..bffdbb66 100644 --- a/src/browser/Decorations/OverviewRulerRenderer.ts +++ b/src/browser/Decorations/OverviewRulerRenderer.ts @@ -12,8 +12,8 @@ import { IBufferService, IDecorationService, IInternalDecoration, IOptionsServic // when refreshStyle is called // by storing and updating // the sizes of the decorations to be drawn -const workArray = new Uint16Array(3); -const enum WorkIndex { +const renderSizes = new Uint16Array(3); +const enum SizeIndex { OUTER_SIZE = 0, INNER_SIZE = 0 } @@ -55,16 +55,14 @@ export class OverviewRulerRenderer extends Disposable { this.register(this._decorationService.onDecorationRegistered(() => this._queueRefresh(undefined, true))); this.register(this._decorationService.onDecorationRemoved(decoration => this._removeDecoration(decoration))); this.register(this._optionsService.onOptionChange(o => { - if (o === 'overviewRulerWidth' && this._optionsService.options.overviewRulerWidth) { - workArray[WorkIndex.OUTER_SIZE] = Math.floor(this._optionsService.options.overviewRulerWidth / 3); - workArray[WorkIndex.INNER_SIZE] = Math.ceil(this._optionsService.options.overviewRulerWidth / 3); + if (o === 'overviewRulerWidth') { + renderSizes[SizeIndex.OUTER_SIZE] = Math.floor(this._width / 3); + renderSizes[SizeIndex.INNER_SIZE] = Math.ceil(this._width / 3); this._queueRefresh(); } })); - if (this._optionsService.options.overviewRulerWidth) { - workArray[WorkIndex.OUTER_SIZE] = Math.floor(this._optionsService.options.overviewRulerWidth / 3); - workArray[WorkIndex.INNER_SIZE] = Math.ceil(this._optionsService.options.overviewRulerWidth / 3); - } + renderSizes[SizeIndex.OUTER_SIZE] = Math.floor(this._width / 3); + renderSizes[SizeIndex.INNER_SIZE] = Math.ceil(this._width / 3); } public override dispose(): void { @@ -95,10 +93,11 @@ export class OverviewRulerRenderer extends Disposable { } this._ctx.lineWidth = !decoration.options.overviewRulerOptions.position ? 2 : 6; this._ctx.strokeStyle = decoration.options.overviewRulerOptions.color; + this._ctx.strokeRect( - !decoration.options.overviewRulerOptions.position || decoration.options.overviewRulerOptions.position === 'left' ? 0 : decoration.options.overviewRulerOptions.position === 'right' ? workArray[WorkIndex.OUTER_SIZE] + workArray[WorkIndex.INNER_SIZE]: workArray[WorkIndex.OUTER_SIZE], + !decoration.options.overviewRulerOptions.position || decoration.options.overviewRulerOptions.position === 'left' ? 0 : decoration.options.overviewRulerOptions.position === 'right' ? renderSizes[SizeIndex.OUTER_SIZE] + renderSizes[SizeIndex.INNER_SIZE]: renderSizes[SizeIndex.OUTER_SIZE], Math.round(this._canvas.height * (decoration.options.marker.line / this._bufferService.buffers.active.lines.length)), - !decoration.options.overviewRulerOptions.position ? this._canvas.width : decoration.options.overviewRulerOptions.position === 'center' ? workArray[WorkIndex.INNER_SIZE] : workArray[WorkIndex.OUTER_SIZE], + !decoration.options.overviewRulerOptions.position ? this._width : decoration.options.overviewRulerOptions.position === 'center' ? renderSizes[SizeIndex.INNER_SIZE] : renderSizes[SizeIndex.OUTER_SIZE], window.devicePixelRatio ); } From 963ed594591d21bdfa0fa95ae4e61c677d64c4da Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 15 Mar 2022 19:29:33 -0400 Subject: [PATCH 085/245] tweak demo --- demo/client.ts | 2 +- src/browser/Decorations/OverviewRulerRenderer.ts | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/demo/client.ts b/demo/client.ts index 3d3a3c3f..bbec94c0 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -555,7 +555,7 @@ function addDecoration() { } function addOverviewRuler() { - term.options['overviewRulerWidth'] = 12; + term.options['overviewRulerWidth'] = 15; term.registerDecoration({marker: term.addMarker(1), overviewRulerOptions: { color: '#ef2929' }}); term.registerDecoration({marker: term.addMarker(3), overviewRulerOptions: { color: '#8ae234' }}); term.registerDecoration({marker: term.addMarker(5), overviewRulerOptions: { color: '#729fcf' }}); diff --git a/src/browser/Decorations/OverviewRulerRenderer.ts b/src/browser/Decorations/OverviewRulerRenderer.ts index bffdbb66..a46f9872 100644 --- a/src/browser/Decorations/OverviewRulerRenderer.ts +++ b/src/browser/Decorations/OverviewRulerRenderer.ts @@ -93,7 +93,6 @@ export class OverviewRulerRenderer extends Disposable { } this._ctx.lineWidth = !decoration.options.overviewRulerOptions.position ? 2 : 6; this._ctx.strokeStyle = decoration.options.overviewRulerOptions.color; - this._ctx.strokeRect( !decoration.options.overviewRulerOptions.position || decoration.options.overviewRulerOptions.position === 'left' ? 0 : decoration.options.overviewRulerOptions.position === 'right' ? renderSizes[SizeIndex.OUTER_SIZE] + renderSizes[SizeIndex.INNER_SIZE]: renderSizes[SizeIndex.OUTER_SIZE], Math.round(this._canvas.height * (decoration.options.marker.line / this._bufferService.buffers.active.lines.length)), From c277fb742d03be07b87d29a519fb5dee41e158c8 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 15 Mar 2022 20:50:13 -0400 Subject: [PATCH 086/245] add overviewRuler to addDecoration --- demo/client.ts | 4 +++- src/browser/Decorations/OverviewRulerRenderer.ts | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/demo/client.ts b/demo/client.ts index bbec94c0..b8023f27 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -545,13 +545,15 @@ function loadTest() { } function addDecoration() { + term.options['overviewRulerWidth'] = 15; const marker = term.addMarker(1); const decoration = term.registerDecoration({ marker }); decoration.onRender((e) => { if (e.classList.value === 'xterm-decoration') { - e.style.backgroundColor = 'red'; + e.style.backgroundColor = '#ef2929'; } }); + term.registerDecoration({marker, overviewRulerOptions: { color: '#ef2929'}}) } function addOverviewRuler() { diff --git a/src/browser/Decorations/OverviewRulerRenderer.ts b/src/browser/Decorations/OverviewRulerRenderer.ts index a46f9872..9f8ada32 100644 --- a/src/browser/Decorations/OverviewRulerRenderer.ts +++ b/src/browser/Decorations/OverviewRulerRenderer.ts @@ -61,6 +61,7 @@ export class OverviewRulerRenderer extends Disposable { this._queueRefresh(); } })); + console.log(this._width/3); renderSizes[SizeIndex.OUTER_SIZE] = Math.floor(this._width / 3); renderSizes[SizeIndex.INNER_SIZE] = Math.ceil(this._width / 3); } @@ -96,7 +97,7 @@ export class OverviewRulerRenderer extends Disposable { this._ctx.strokeRect( !decoration.options.overviewRulerOptions.position || decoration.options.overviewRulerOptions.position === 'left' ? 0 : decoration.options.overviewRulerOptions.position === 'right' ? renderSizes[SizeIndex.OUTER_SIZE] + renderSizes[SizeIndex.INNER_SIZE]: renderSizes[SizeIndex.OUTER_SIZE], Math.round(this._canvas.height * (decoration.options.marker.line / this._bufferService.buffers.active.lines.length)), - !decoration.options.overviewRulerOptions.position ? this._width : decoration.options.overviewRulerOptions.position === 'center' ? renderSizes[SizeIndex.INNER_SIZE] : renderSizes[SizeIndex.OUTER_SIZE], + !decoration.options.overviewRulerOptions.position ? this._width : decoration.options.overviewRulerOptions.position === 'center' ? renderSizes[SizeIndex.INNER_SIZE]: renderSizes[SizeIndex.OUTER_SIZE], window.devicePixelRatio ); } From a0a7abbf497b3af8439e3d801e9b01dd6095d45f Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 15 Mar 2022 21:05:14 -0400 Subject: [PATCH 087/245] add overview options to decoration in demo --- demo/client.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/demo/client.ts b/demo/client.ts index b8023f27..2ae649e4 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -547,13 +547,12 @@ function loadTest() { function addDecoration() { term.options['overviewRulerWidth'] = 15; const marker = term.addMarker(1); - const decoration = term.registerDecoration({ marker }); + const decoration = term.registerDecoration({ marker, overviewRulerOptions: { color: '#ef2929'} }); decoration.onRender((e) => { if (e.classList.value === 'xterm-decoration') { e.style.backgroundColor = '#ef2929'; } }); - term.registerDecoration({marker, overviewRulerOptions: { color: '#ef2929'}}) } function addOverviewRuler() { From 8b55497adff94d99fab18ef43d0b2ccbda131b63 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 16 Mar 2022 07:50:01 -0700 Subject: [PATCH 088/245] Remove unwanted console.log --- src/browser/Decorations/OverviewRulerRenderer.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/browser/Decorations/OverviewRulerRenderer.ts b/src/browser/Decorations/OverviewRulerRenderer.ts index 9f8ada32..53f1a99c 100644 --- a/src/browser/Decorations/OverviewRulerRenderer.ts +++ b/src/browser/Decorations/OverviewRulerRenderer.ts @@ -61,7 +61,6 @@ export class OverviewRulerRenderer extends Disposable { this._queueRefresh(); } })); - console.log(this._width/3); renderSizes[SizeIndex.OUTER_SIZE] = Math.floor(this._width / 3); renderSizes[SizeIndex.INNER_SIZE] = Math.ceil(this._width / 3); } From 1bab173e99885eb70e70e3a0c7396c99fa55c6b6 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 16 Mar 2022 12:45:18 -0400 Subject: [PATCH 089/245] fix problems --- addons/xterm-addon-search/src/SearchAddon.ts | 2 +- src/browser/services/DecorationService.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/addons/xterm-addon-search/src/SearchAddon.ts b/addons/xterm-addon-search/src/SearchAddon.ts index 5e089594..104ca532 100644 --- a/addons/xterm-addon-search/src/SearchAddon.ts +++ b/addons/xterm-addon-search/src/SearchAddon.ts @@ -537,7 +537,7 @@ export class SearchAddon implements ITerminalAddon { if (!marker) { return undefined; } - const findResultDecoration = terminal.registerDecoration({ marker }); + const findResultDecoration = terminal.registerDecoration({ marker, overviewRulerOptions: { color: 'yellow' } }); findResultDecoration?.onRender((e) => { if (!e.classList.contains('xterm-find-result-decoration') && result.term.length && e.clientWidth > 0) { e.classList.add('xterm-find-result-decoration'); diff --git a/src/browser/services/DecorationService.ts b/src/browser/services/DecorationService.ts index a77409db..80f7f0d8 100644 --- a/src/browser/services/DecorationService.ts +++ b/src/browser/services/DecorationService.ts @@ -3,10 +3,10 @@ * @license MIT */ -import { IDecorationService, IRenderService } from 'browser/services/Services'; +import { IRenderService } from 'browser/services/Services'; import { EventEmitter, IEvent } from 'common/EventEmitter'; import { Disposable } from 'common/Lifecycle'; -import { IBufferService, IInstantiationService } from 'common/services/Services'; +import { IBufferService, IInstantiationService, IDecorationService } from 'common/services/Services'; import { IDecorationOptions, IDecoration, IMarker } from 'xterm'; export class DecorationService extends Disposable implements IDecorationService { From 40b8854df4fcb2de238a8213f3ab168e9ae18a69 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 16 Mar 2022 12:47:46 -0400 Subject: [PATCH 090/245] rm bad decoration service --- src/browser/services/DecorationService.ts | 164 ---------------------- 1 file changed, 164 deletions(-) delete mode 100644 src/browser/services/DecorationService.ts diff --git a/src/browser/services/DecorationService.ts b/src/browser/services/DecorationService.ts deleted file mode 100644 index 80f7f0d8..00000000 --- a/src/browser/services/DecorationService.ts +++ /dev/null @@ -1,164 +0,0 @@ -/** - * Copyright (c) 2022 The xterm.js authors. All rights reserved. - * @license MIT - */ - -import { IRenderService } from 'browser/services/Services'; -import { EventEmitter, IEvent } from 'common/EventEmitter'; -import { Disposable } from 'common/Lifecycle'; -import { IBufferService, IInstantiationService, IDecorationService } from 'common/services/Services'; -import { IDecorationOptions, IDecoration, IMarker } from 'xterm'; - -export class DecorationService extends Disposable implements IDecorationService { - - private readonly _decorations: Decoration[] = []; - private _container: HTMLElement | undefined; - private _screenElement: HTMLElement | undefined; - private _renderService: IRenderService | undefined; - private _animationFrame: number | undefined; - - constructor(@IInstantiationService private readonly _instantiationService: IInstantiationService) { super(); } - - public attachToDom(screenElement: HTMLElement, renderService: IRenderService): void { - this._renderService = renderService; - this._screenElement = screenElement; - this._container = document.createElement('div'); - this._container.classList.add('xterm-decoration-container'); - screenElement.appendChild(this._container); - this.register(this._renderService.onRenderedBufferChange(() => this.refresh())); - this.register(this._renderService.onDimensionsChange(() => this.refresh(true))); - } - - public registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined { - if (decorationOptions.marker.isDisposed || !this._container) { - return undefined; - } - const decoration = this._instantiationService.createInstance(Decoration, decorationOptions, this._container); - this._decorations.push(decoration); - decoration.onDispose(() => this._decorations.splice(this._decorations.indexOf(decoration), 1)); - this._queueRefresh(); - return decoration; - } - - private _queueRefresh(): void { - if (this._animationFrame !== undefined) { - return; - } - this._animationFrame = window.requestAnimationFrame(() => { - this.refresh(); - this._animationFrame = undefined; - }); - } - - public refresh(shouldRecreate?: boolean): void { - if (!this._renderService) { - return; - } - for (const decoration of this._decorations) { - decoration.render(this._renderService, shouldRecreate); - } - } - - public dispose(): void { - for (const decoration of this._decorations) { - decoration.dispose(); - } - if (this._screenElement && this._container && this._screenElement.contains(this._container)) { - this._screenElement.removeChild(this._container); - } - } -} -export class Decoration extends Disposable implements IDecoration { - private readonly _marker: IMarker; - private _element: HTMLElement | undefined; - - public isDisposed: boolean = false; - - public get element(): HTMLElement | undefined { return this._element; } - public get marker(): IMarker { return this._marker; } - - private _onDispose = new EventEmitter(); - public get onDispose(): IEvent { return this._onDispose.event; } - - private _onRender = new EventEmitter(); - public get onRender(): IEvent { return this._onRender.event; } - - public x: number; - public anchor: 'left' | 'right'; - public width: number; - public height: number; - - constructor( - options: IDecorationOptions, - private readonly _container: HTMLElement, - @IBufferService private readonly _bufferService: IBufferService - ) { - super(); - this.x = options.x ?? 0; - this._marker = options.marker; - this._marker.onDispose(() => this.dispose()); - this.anchor = options.anchor || 'left'; - this.width = options.width || 1; - this.height = options.height || 1; - } - - public render(renderService: IRenderService, shouldRecreate?: boolean): void { - if (!this._element || shouldRecreate) { - this._createElement(renderService, shouldRecreate); - } - if (this._container && this._element && !this._container.contains(this._element)) { - this._container.append(this._element); - } - this._refreshStyle(renderService); - if (this._element) { - this._onRender.fire(this._element); - } - } - - private _createElement(renderService: IRenderService, shouldRecreate?: boolean): void { - if (shouldRecreate && this._element && this._container.contains(this._element)) { - this._container.removeChild(this._element); - } - this._element = document.createElement('div'); - this._element.classList.add('xterm-decoration'); - this._element.style.width = `${this.width * renderService.dimensions.actualCellWidth}px`; - this._element.style.height = `${this.height * renderService.dimensions.actualCellHeight}px`; - this._element.style.top = `${(this.marker.line - this._bufferService.buffers.active.ydisp) * renderService.dimensions.actualCellHeight}px`; - this._element.style.lineHeight = `${renderService.dimensions.actualCellHeight}px`; - - if (this.x && this.x > this._bufferService.cols) { - // exceeded the container width, so hide - this._element.style.display = 'none'; - } - if (this.anchor === 'right') { - this._element.style.right = this.x ? `${this.x * renderService.dimensions.actualCellWidth}px` : ''; - } else { - this._element.style.left = this.x ? `${this.x * renderService.dimensions.actualCellWidth}px` : ''; - } - } - - private _refreshStyle(renderService: IRenderService): void { - if (!this._element) { - return; - } - const line = this.marker.line - this._bufferService.buffers.active.ydisp; - if (line < 0 || line >= this._bufferService.rows) { - // outside of viewport - this._element.style.display = 'none'; - } else { - this._element.style.top = `${line * renderService.dimensions.actualCellHeight}px`; - this._element.style.display = this._bufferService.buffer === this._bufferService.buffers.alt ? 'none' : 'block'; - } - } - - public override dispose(): void { - if (this.isDisposed) { - return; - } - if (this._element && this._container.contains(this._element)) { - this._container.removeChild(this._element); - } - this.isDisposed = true; - this._onDispose.fire(); - } -} From 05eb12e6dd051c7c845e524a6d2b05e16ade2f4b Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 16 Mar 2022 13:03:21 -0400 Subject: [PATCH 091/245] start work on selection color --- addons/xterm-addon-search/src/SearchAddon.ts | 24 ++++++++++++++------ css/xterm.css | 2 +- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/addons/xterm-addon-search/src/SearchAddon.ts b/addons/xterm-addon-search/src/SearchAddon.ts index 104ca532..1e54ee3b 100644 --- a/addons/xterm-addon-search/src/SearchAddon.ts +++ b/addons/xterm-addon-search/src/SearchAddon.ts @@ -44,6 +44,7 @@ export class SearchAddon implements ITerminalAddon { private _result: ISearchResult | undefined; private _reset: boolean = false; private _cachedSearchTerm: string | undefined; + private _cachedResults: ISearchResult[] = []; /** * translateBufferLineToStringWithWrap is a fairly expensive call. * We memoize the calls into an array that has a time based ttl. @@ -81,6 +82,11 @@ export class SearchAddon implements ITerminalAddon { } if (!this._reset && term === this._cachedSearchTerm) { + // this._resultDecorations.forEach(d => d.dispose()); + // this._resultDecorations = []; + // for (const decoration of this._cachedResults) { + // this._showResultDecoration(decoration); + // } return this.findNext(term, searchOptions); } this._reset = false; @@ -89,18 +95,17 @@ export class SearchAddon implements ITerminalAddon { // new search, clear out the old decorations this._resultDecorations.forEach(d => d.dispose()); this._resultDecorations = []; - const results: ISearchResult[] = []; searchOptions = searchOptions || {}; searchOptions.incremental = false; let found = this.findNext(term, searchOptions); - while (found && !results.find(r => r?.col === this._result?.col && r?.row === this._result?.row)) { + while (found && !this._cachedResults.find(r => r?.col === this._result?.col && r?.row === this._result?.row)) { if (this._result) { - results.push(this._result); + this._cachedResults.push(this._result); } found = this.findNext(term, searchOptions); } - for (const result of results) { + for (const result of this._cachedResults) { if (result) { const resultDecoration = this._showResultDecoration(result); if (resultDecoration) { @@ -108,7 +113,7 @@ export class SearchAddon implements ITerminalAddon { } } } - if (results.length > 0) { + if (this._cachedResults.length > 0) { this._cachedSearchTerm = term; } return true; @@ -517,6 +522,10 @@ export class SearchAddon implements ITerminalAddon { return false; } terminal.select(result.col, result.row, result.size); + const marker = terminal.registerMarker(undefined, result.row); + if (marker) { + terminal.registerDecoration({ marker, overviewRulerOptions: { color: 'yellow' } }); + } // If it is not in the viewport then we scroll else it just gets selected if (result.row >= (terminal.buffer.active.viewportY + terminal.rows) || result.row < terminal.buffer.active.viewportY) { let scroll = result.row - terminal.buffer.active.viewportY; @@ -537,9 +546,10 @@ export class SearchAddon implements ITerminalAddon { if (!marker) { return undefined; } - const findResultDecoration = terminal.registerDecoration({ marker, overviewRulerOptions: { color: 'yellow' } }); + terminal.options.overviewRulerWidth = 10; + const findResultDecoration = terminal.registerDecoration({ marker, overviewRulerOptions: { color: 'blue' } }); findResultDecoration?.onRender((e) => { - if (!e.classList.contains('xterm-find-result-decoration') && result.term.length && e.clientWidth > 0) { + if (!e.classList.contains('xterm-find-result-decoration') && result.term.length && e.clientWidth > 0 && !e.classList.contains('xterm-decoration-overview-ruler')) { e.classList.add('xterm-find-result-decoration'); // decoration's clientWidth = actualCellWidth e.style.left = `${e.clientWidth * result.col}px`; diff --git a/css/xterm.css b/css/xterm.css index 9b9c47f8..3160a26e 100644 --- a/css/xterm.css +++ b/css/xterm.css @@ -180,7 +180,7 @@ } .xterm-find-result-decoration { - background-color: yellow; + background-color: blue; opacity: 60%; } .xterm-decoration-overview-ruler { From 1a9965ef01bba481c6e1b6d62d13e8c0f80bc269 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 16 Mar 2022 13:20:48 -0400 Subject: [PATCH 092/245] fix #3683 --- src/browser/Decorations/OverviewRulerRenderer.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/browser/Decorations/OverviewRulerRenderer.ts b/src/browser/Decorations/OverviewRulerRenderer.ts index 53f1a99c..f62c6647 100644 --- a/src/browser/Decorations/OverviewRulerRenderer.ts +++ b/src/browser/Decorations/OverviewRulerRenderer.ts @@ -15,7 +15,7 @@ import { IBufferService, IDecorationService, IInternalDecoration, IOptionsServic const renderSizes = new Uint16Array(3); const enum SizeIndex { OUTER_SIZE = 0, - INNER_SIZE = 0 + INNER_SIZE = 1 } export class OverviewRulerRenderer extends Disposable { @@ -91,13 +91,14 @@ export class OverviewRulerRenderer extends Disposable { this._decorationElements.delete(decoration); return; } - this._ctx.lineWidth = !decoration.options.overviewRulerOptions.position ? 2 : 6; + this._ctx.lineWidth = 1; this._ctx.strokeStyle = decoration.options.overviewRulerOptions.color; - this._ctx.strokeRect( + this._ctx.fillStyle = decoration.options.overviewRulerOptions.color; + this._ctx.fillRect( !decoration.options.overviewRulerOptions.position || decoration.options.overviewRulerOptions.position === 'left' ? 0 : decoration.options.overviewRulerOptions.position === 'right' ? renderSizes[SizeIndex.OUTER_SIZE] + renderSizes[SizeIndex.INNER_SIZE]: renderSizes[SizeIndex.OUTER_SIZE], Math.round(this._canvas.height * (decoration.options.marker.line / this._bufferService.buffers.active.lines.length)), - !decoration.options.overviewRulerOptions.position ? this._width : decoration.options.overviewRulerOptions.position === 'center' ? renderSizes[SizeIndex.INNER_SIZE]: renderSizes[SizeIndex.OUTER_SIZE], - window.devicePixelRatio + !decoration.options.overviewRulerOptions.position ? this._width : decoration.options.overviewRulerOptions.position === 'center' ? renderSizes[SizeIndex.INNER_SIZE] : renderSizes[SizeIndex.OUTER_SIZE], + window.devicePixelRatio * (!decoration.options.overviewRulerOptions.position ? 2 : 6) ); } From e293d29fd3306fc04b9a0b6fa5308eb16bb4f4d7 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 16 Mar 2022 13:31:03 -0400 Subject: [PATCH 093/245] add a comment --- src/browser/Decorations/OverviewRulerRenderer.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/browser/Decorations/OverviewRulerRenderer.ts b/src/browser/Decorations/OverviewRulerRenderer.ts index f62c6647..aa91cad9 100644 --- a/src/browser/Decorations/OverviewRulerRenderer.ts +++ b/src/browser/Decorations/OverviewRulerRenderer.ts @@ -92,13 +92,13 @@ export class OverviewRulerRenderer extends Disposable { return; } this._ctx.lineWidth = 1; - this._ctx.strokeStyle = decoration.options.overviewRulerOptions.color; this._ctx.fillStyle = decoration.options.overviewRulerOptions.color; this._ctx.fillRect( !decoration.options.overviewRulerOptions.position || decoration.options.overviewRulerOptions.position === 'left' ? 0 : decoration.options.overviewRulerOptions.position === 'right' ? renderSizes[SizeIndex.OUTER_SIZE] + renderSizes[SizeIndex.INNER_SIZE]: renderSizes[SizeIndex.OUTER_SIZE], Math.round(this._canvas.height * (decoration.options.marker.line / this._bufferService.buffers.active.lines.length)), !decoration.options.overviewRulerOptions.position ? this._width : decoration.options.overviewRulerOptions.position === 'center' ? renderSizes[SizeIndex.INNER_SIZE] : renderSizes[SizeIndex.OUTER_SIZE], - window.devicePixelRatio * (!decoration.options.overviewRulerOptions.position ? 2 : 6) + // when a position is provided, the element has less width, so increase its height + window.devicePixelRatio * (decoration.options.overviewRulerOptions.position ? 6 : 2) ); } From a4212e98d29973388eedf32dc2d762578f4c8c1c Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 16 Mar 2022 13:40:06 -0400 Subject: [PATCH 094/245] fix #3686 --- demo/client.ts | 6 +----- src/browser/Decorations/OverviewRulerRenderer.ts | 1 - 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/demo/client.ts b/demo/client.ts index 2ae649e4..67e27759 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -548,11 +548,7 @@ function addDecoration() { term.options['overviewRulerWidth'] = 15; const marker = term.addMarker(1); const decoration = term.registerDecoration({ marker, overviewRulerOptions: { color: '#ef2929'} }); - decoration.onRender((e) => { - if (e.classList.value === 'xterm-decoration') { - e.style.backgroundColor = '#ef2929'; - } - }); + decoration.onRender((e) => e.style.backgroundColor = '#ef2929'); } function addOverviewRuler() { diff --git a/src/browser/Decorations/OverviewRulerRenderer.ts b/src/browser/Decorations/OverviewRulerRenderer.ts index aa91cad9..b34b592d 100644 --- a/src/browser/Decorations/OverviewRulerRenderer.ts +++ b/src/browser/Decorations/OverviewRulerRenderer.ts @@ -121,7 +121,6 @@ export class OverviewRulerRenderer extends Disposable { this._decorationElements.set(decoration, this._canvas); } this._refreshStyle(decoration, updateAnchor); - decoration.onRenderEmitter.fire(this._canvas); } private _queueRefresh(updateCanvasDimensions?: boolean, updateAnchor?: boolean): void { From 6162ae9ffe0acca27a5bcbd2808242c8d034eccc Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 16 Mar 2022 14:33:51 -0400 Subject: [PATCH 095/245] allow changing the color of overview decorations --- src/browser/Decorations/OverviewRulerRenderer.ts | 2 +- src/common/services/DecorationService.ts | 2 ++ typings/xterm.d.ts | 5 +++++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/browser/Decorations/OverviewRulerRenderer.ts b/src/browser/Decorations/OverviewRulerRenderer.ts index b34b592d..b63a5aec 100644 --- a/src/browser/Decorations/OverviewRulerRenderer.ts +++ b/src/browser/Decorations/OverviewRulerRenderer.ts @@ -92,7 +92,7 @@ export class OverviewRulerRenderer extends Disposable { return; } this._ctx.lineWidth = 1; - this._ctx.fillStyle = decoration.options.overviewRulerOptions.color; + this._ctx.fillStyle = decoration.overviewRulerDecorationColor || decoration.options.overviewRulerOptions.color; this._ctx.fillRect( !decoration.options.overviewRulerOptions.position || decoration.options.overviewRulerOptions.position === 'left' ? 0 : decoration.options.overviewRulerOptions.position === 'right' ? renderSizes[SizeIndex.OUTER_SIZE] + renderSizes[SizeIndex.INNER_SIZE]: renderSizes[SizeIndex.OUTER_SIZE], Math.round(this._canvas.height * (decoration.options.marker.line / this._bufferService.buffers.active.lines.length)), diff --git a/src/common/services/DecorationService.ts b/src/common/services/DecorationService.ts index 911fd369..328c51d5 100644 --- a/src/common/services/DecorationService.ts +++ b/src/common/services/DecorationService.ts @@ -60,6 +60,8 @@ class Decoration extends Disposable implements IInternalDecoration { private _onDispose = this.register(new EventEmitter()); public readonly onDispose = this._onDispose.event; + public overviewRulerDecorationColor: string | undefined; + constructor( public readonly options: IDecorationOptions ) { diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 24ba940e..88b2856e 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -438,6 +438,11 @@ declare module 'xterm' { * that. */ element: HTMLElement | undefined; + + /** + * The color for the decoration. + */ + overviewRulerDecorationColor: string | undefined; } /* From fb13f812f7106a561ad4704dc2b517f17591de18 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 16 Mar 2022 14:37:12 -0400 Subject: [PATCH 096/245] tweak description --- typings/xterm.d.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 88b2856e..4853ac48 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -440,7 +440,9 @@ declare module 'xterm' { element: HTMLElement | undefined; /** - * The color for the decoration. + * The color to be used for the overview ruler decoration. + * This will only take effect when @param overviewRulerOptions + * were provided initially. */ overviewRulerDecorationColor: string | undefined; } From f4593b76df7c50c30ce343231b1310316dc22f7f Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 16 Mar 2022 15:20:13 -0400 Subject: [PATCH 097/245] use options --- src/browser/Decorations/OverviewRulerRenderer.ts | 2 +- src/common/services/DecorationService.ts | 2 -- typings/xterm.d.ts | 12 +++++++++--- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/browser/Decorations/OverviewRulerRenderer.ts b/src/browser/Decorations/OverviewRulerRenderer.ts index b63a5aec..620e4dbb 100644 --- a/src/browser/Decorations/OverviewRulerRenderer.ts +++ b/src/browser/Decorations/OverviewRulerRenderer.ts @@ -92,7 +92,7 @@ export class OverviewRulerRenderer extends Disposable { return; } this._ctx.lineWidth = 1; - this._ctx.fillStyle = decoration.overviewRulerDecorationColor || decoration.options.overviewRulerOptions.color; + this._ctx.fillStyle = decoration.overviewRulerOptions?.color || decoration.options.overviewRulerOptions.color; this._ctx.fillRect( !decoration.options.overviewRulerOptions.position || decoration.options.overviewRulerOptions.position === 'left' ? 0 : decoration.options.overviewRulerOptions.position === 'right' ? renderSizes[SizeIndex.OUTER_SIZE] + renderSizes[SizeIndex.INNER_SIZE]: renderSizes[SizeIndex.OUTER_SIZE], Math.round(this._canvas.height * (decoration.options.marker.line / this._bufferService.buffers.active.lines.length)), diff --git a/src/common/services/DecorationService.ts b/src/common/services/DecorationService.ts index 328c51d5..911fd369 100644 --- a/src/common/services/DecorationService.ts +++ b/src/common/services/DecorationService.ts @@ -60,8 +60,6 @@ class Decoration extends Disposable implements IInternalDecoration { private _onDispose = this.register(new EventEmitter()); public readonly onDispose = this._onDispose.event; - public overviewRulerDecorationColor: string | undefined; - constructor( public readonly options: IDecorationOptions ) { diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 4853ac48..2254df1c 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -440,13 +440,19 @@ declare module 'xterm' { element: HTMLElement | undefined; /** - * The color to be used for the overview ruler decoration. + * The options for the overview ruler that can be updated. * This will only take effect when @param overviewRulerOptions * were provided initially. */ - overviewRulerDecorationColor: string | undefined; + overviewRulerOptions?: IDecorationOverviewRulerOptions; } + +interface IDecorationOverviewRulerOptions { + color: string; + position?: 'left' | 'center' | 'right'; +} + /* * Options that define the presentation of the decoration. */ @@ -486,7 +492,7 @@ declare module 'xterm' { * @param color The color of the decoration. * @param position The position of the decoration. */ - readonly overviewRulerOptions?: { color: string; position?: 'left' | 'center' | 'right'} + overviewRulerOptions?: IDecorationOverviewRulerOptions } /** From f92d1f46a6717f00ec0ac188b1b5e6f00b811d65 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 16 Mar 2022 15:36:03 -0400 Subject: [PATCH 098/245] only allow updating color --- typings/xterm.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 2254df1c..f67c30e3 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -444,7 +444,7 @@ declare module 'xterm' { * This will only take effect when @param overviewRulerOptions * were provided initially. */ - overviewRulerOptions?: IDecorationOverviewRulerOptions; + overviewRulerOptions?: Pick; } From 6899f9dc4d74e327d58b8832c73e007d3061dfa4 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 16 Mar 2022 15:55:15 -0400 Subject: [PATCH 099/245] tweak jsdoc --- typings/xterm.d.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index f67c30e3..77f0b4ef 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -434,15 +434,15 @@ declare module 'xterm' { /** * The element that the decoration is rendered to. This will be undefined - * until it is rendered for the first time by @{link IDecoration.onRender}. + * until it is rendered for the first time by {@link IDecoration.onRender}. * that. */ element: HTMLElement | undefined; /** * The options for the overview ruler that can be updated. - * This will only take effect when @param overviewRulerOptions - * were provided initially. + * This will only take effect when + * @param overviewRulerOptions were provided initially. */ overviewRulerOptions?: Pick; } From 450ee0c6a52ecf22dab31703f09a8ff7a28a36d1 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 16 Mar 2022 15:58:06 -0400 Subject: [PATCH 100/245] tweak api --- typings/xterm.d.ts | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 77f0b4ef..deaecb79 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -441,17 +441,20 @@ declare module 'xterm' { /** * The options for the overview ruler that can be updated. - * This will only take effect when - * @param overviewRulerOptions were provided initially. + * This will only take effect when {@link IDecorationOptions.overviewRulerOptions} + * were provided initially. */ overviewRulerOptions?: Pick; } -interface IDecorationOverviewRulerOptions { - color: string; - position?: 'left' | 'center' | 'right'; -} + /** + * Overview ruler decoration options + */ + interface IDecorationOverviewRulerOptions { + color: string; + position?: 'left' | 'center' | 'right'; + } /* * Options that define the presentation of the decoration. From 26f8fce5387c68d446d145f0e6cb789fd7eedd43 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 16 Mar 2022 17:23:33 -0400 Subject: [PATCH 101/245] update color when select next happens --- addons/xterm-addon-search/src/SearchAddon.ts | 25 ++++++++++---------- css/xterm.css | 2 +- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/addons/xterm-addon-search/src/SearchAddon.ts b/addons/xterm-addon-search/src/SearchAddon.ts index 1e54ee3b..67e1f800 100644 --- a/addons/xterm-addon-search/src/SearchAddon.ts +++ b/addons/xterm-addon-search/src/SearchAddon.ts @@ -44,7 +44,7 @@ export class SearchAddon implements ITerminalAddon { private _result: ISearchResult | undefined; private _reset: boolean = false; private _cachedSearchTerm: string | undefined; - private _cachedResults: ISearchResult[] = []; + private _selectedDecoration: IDecoration | undefined; /** * translateBufferLineToStringWithWrap is a fairly expensive call. * We memoize the calls into an array that has a time based ttl. @@ -82,11 +82,9 @@ export class SearchAddon implements ITerminalAddon { } if (!this._reset && term === this._cachedSearchTerm) { - // this._resultDecorations.forEach(d => d.dispose()); - // this._resultDecorations = []; - // for (const decoration of this._cachedResults) { - // this._showResultDecoration(decoration); - // } + for (const decoration of this._resultDecorations) { + decoration.overviewRulerOptions = { color: 'orange' }; + } return this.findNext(term, searchOptions); } this._reset = false; @@ -94,18 +92,18 @@ export class SearchAddon implements ITerminalAddon { // new search, clear out the old decorations this._resultDecorations.forEach(d => d.dispose()); - this._resultDecorations = []; + const results: ISearchResult[] = []; searchOptions = searchOptions || {}; searchOptions.incremental = false; let found = this.findNext(term, searchOptions); - while (found && !this._cachedResults.find(r => r?.col === this._result?.col && r?.row === this._result?.row)) { + while (found && !results.find(r => r?.col === this._result?.col && r?.row === this._result?.row)) { if (this._result) { - this._cachedResults.push(this._result); + results.push(this._result); } found = this.findNext(term, searchOptions); } - for (const result of this._cachedResults) { + for (const result of results) { if (result) { const resultDecoration = this._showResultDecoration(result); if (resultDecoration) { @@ -113,7 +111,7 @@ export class SearchAddon implements ITerminalAddon { } } } - if (this._cachedResults.length > 0) { + if (results.length > 0) { this._cachedSearchTerm = term; } return true; @@ -517,6 +515,7 @@ export class SearchAddon implements ITerminalAddon { */ private _selectResult(result: ISearchResult | undefined): boolean { const terminal = this._terminal!; + this._selectedDecoration?.dispose(); if (!result) { terminal.clearSelection(); return false; @@ -524,7 +523,7 @@ export class SearchAddon implements ITerminalAddon { terminal.select(result.col, result.row, result.size); const marker = terminal.registerMarker(undefined, result.row); if (marker) { - terminal.registerDecoration({ marker, overviewRulerOptions: { color: 'yellow' } }); + this._selectedDecoration = terminal.registerDecoration({ marker, overviewRulerOptions: { color: 'blue' } }); } // If it is not in the viewport then we scroll else it just gets selected if (result.row >= (terminal.buffer.active.viewportY + terminal.rows) || result.row < terminal.buffer.active.viewportY) { @@ -547,7 +546,7 @@ export class SearchAddon implements ITerminalAddon { return undefined; } terminal.options.overviewRulerWidth = 10; - const findResultDecoration = terminal.registerDecoration({ marker, overviewRulerOptions: { color: 'blue' } }); + const findResultDecoration = terminal.registerDecoration({ marker, overviewRulerOptions: { color: 'orange' } }); findResultDecoration?.onRender((e) => { if (!e.classList.contains('xterm-find-result-decoration') && result.term.length && e.clientWidth > 0 && !e.classList.contains('xterm-decoration-overview-ruler')) { e.classList.add('xterm-find-result-decoration'); diff --git a/css/xterm.css b/css/xterm.css index 3160a26e..f8f85f9e 100644 --- a/css/xterm.css +++ b/css/xterm.css @@ -180,7 +180,7 @@ } .xterm-find-result-decoration { - background-color: blue; + background-color: grey; opacity: 60%; } .xterm-decoration-overview-ruler { From 90f64ba6ccd45378cbb91c87af7cd216e3e03029 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 17 Mar 2022 09:52:41 -0400 Subject: [PATCH 102/245] get it to work in the demo --- addons/xterm-addon-search/src/SearchAddon.ts | 6 +++++- src/browser/Decorations/BufferDecorationRenderer.ts | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/addons/xterm-addon-search/src/SearchAddon.ts b/addons/xterm-addon-search/src/SearchAddon.ts index 67e1f800..8027cd73 100644 --- a/addons/xterm-addon-search/src/SearchAddon.ts +++ b/addons/xterm-addon-search/src/SearchAddon.ts @@ -92,6 +92,7 @@ export class SearchAddon implements ITerminalAddon { // new search, clear out the old decorations this._resultDecorations.forEach(d => d.dispose()); + console.log('clearing'); const results: ISearchResult[] = []; searchOptions = searchOptions || {}; searchOptions.incremental = false; @@ -102,7 +103,7 @@ export class SearchAddon implements ITerminalAddon { } found = this.findNext(term, searchOptions); } - + console.log(results); for (const result of results) { if (result) { const resultDecoration = this._showResultDecoration(result); @@ -541,7 +542,10 @@ export class SearchAddon implements ITerminalAddon { */ private _showResultDecoration(result: ISearchResult): IDecoration | undefined { const terminal = this._terminal!; + // for demo to work + // const marker = terminal.registerMarker(undefined, result.row); const marker = terminal.registerMarker(undefined, result.row); + console.log(result.row, marker?.line); if (!marker) { return undefined; } diff --git a/src/browser/Decorations/BufferDecorationRenderer.ts b/src/browser/Decorations/BufferDecorationRenderer.ts index 116c09a5..22dc73e9 100644 --- a/src/browser/Decorations/BufferDecorationRenderer.ts +++ b/src/browser/Decorations/BufferDecorationRenderer.ts @@ -97,7 +97,7 @@ export class BufferDecorationRenderer extends Disposable { private _refreshStyle(decoration: IInternalDecoration, element: HTMLElement): void { const line = decoration.marker.line - this._bufferService.buffers.active.ydisp; - if (line < 0 || line > this._bufferService.rows) { + if (line < 0 || line >= this._bufferService.rows) { // outside of viewport element.style.display = 'none'; } else { From c2145d5125cb794cd60b4cec6cd4b33cd585479e Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 17 Mar 2022 10:38:44 -0400 Subject: [PATCH 103/245] on dispose, clear the canvas at that position --- addons/xterm-addon-search/src/SearchAddon.ts | 17 +++++++++-------- .../Decorations/OverviewRulerRenderer.ts | 16 ++++++++++------ 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/addons/xterm-addon-search/src/SearchAddon.ts b/addons/xterm-addon-search/src/SearchAddon.ts index 8027cd73..05c6892d 100644 --- a/addons/xterm-addon-search/src/SearchAddon.ts +++ b/addons/xterm-addon-search/src/SearchAddon.ts @@ -62,6 +62,14 @@ export class SearchAddon implements ITerminalAddon { public dispose(): void { } + public clear(): void { + this._terminal?.clearSelection(); + this._resultDecorations.forEach(d => d.dispose()); + this._selectedDecoration?.dispose(); + this._resultDecorations = []; + this._reset = true; + } + /** * Find all instances of the term, selecting the next one with each * enter. If it doesn't exist, do nothing. @@ -75,9 +83,7 @@ export class SearchAddon implements ITerminalAddon { } if (!term || term.length === 0) { - this._terminal.clearSelection(); - this._resultDecorations.forEach(d => d.dispose()); - this._resultDecorations = []; + this.clear(); return false; } @@ -92,7 +98,6 @@ export class SearchAddon implements ITerminalAddon { // new search, clear out the old decorations this._resultDecorations.forEach(d => d.dispose()); - console.log('clearing'); const results: ISearchResult[] = []; searchOptions = searchOptions || {}; searchOptions.incremental = false; @@ -103,7 +108,6 @@ export class SearchAddon implements ITerminalAddon { } found = this.findNext(term, searchOptions); } - console.log(results); for (const result of results) { if (result) { const resultDecoration = this._showResultDecoration(result); @@ -542,10 +546,7 @@ export class SearchAddon implements ITerminalAddon { */ private _showResultDecoration(result: ISearchResult): IDecoration | undefined { const terminal = this._terminal!; - // for demo to work - // const marker = terminal.registerMarker(undefined, result.row); const marker = terminal.registerMarker(undefined, result.row); - console.log(result.row, marker?.line); if (!marker) { return undefined; } diff --git a/src/browser/Decorations/OverviewRulerRenderer.ts b/src/browser/Decorations/OverviewRulerRenderer.ts index 620e4dbb..824f27ef 100644 --- a/src/browser/Decorations/OverviewRulerRenderer.ts +++ b/src/browser/Decorations/OverviewRulerRenderer.ts @@ -67,12 +67,7 @@ export class OverviewRulerRenderer extends Disposable { public override dispose(): void { for (const decoration of this._decorationElements) { - this._ctx?.clearRect( - 0, - Math.round(this._canvas.height * (decoration[0].marker.line / this._bufferService.buffers.active.lines.length)), - this._canvas.width, - window.devicePixelRatio - ); + decoration[0].dispose(); } this._decorationElements.clear(); this._canvas?.remove(); @@ -119,6 +114,15 @@ export class OverviewRulerRenderer extends Disposable { const element = this._decorationElements.get(decoration); if (!element) { this._decorationElements.set(decoration, this._canvas); + decoration.onDispose(() => { + this._ctx?.clearRect( + !decoration!.options!.overviewRulerOptions?.position || decoration!.options!.overviewRulerOptions?.position === 'left' ? 0 : decoration!.options!.overviewRulerOptions?.position === 'right' ? renderSizes[SizeIndex.OUTER_SIZE] + renderSizes[SizeIndex.INNER_SIZE]: renderSizes[SizeIndex.OUTER_SIZE], + Math.round(this._canvas.height * (decoration!.options!.marker.line / this._bufferService.buffers.active.lines.length)), + !decoration!.options!.overviewRulerOptions?.position ? this._width : decoration!.options!.overviewRulerOptions?.position === 'center' ? renderSizes[SizeIndex.INNER_SIZE] : renderSizes[SizeIndex.OUTER_SIZE], + // when a position is provided, the element has less width, so increase its height + window.devicePixelRatio * (decoration!.options!.overviewRulerOptions?.position ? 6 : 2) + ); + }); } this._refreshStyle(decoration, updateAnchor); } From de56d04ae51bca703f863b3f426d9972ee752288 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 17 Mar 2022 11:08:40 -0400 Subject: [PATCH 104/245] return decoration from show result --- addons/xterm-addon-search/src/SearchAddon.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/addons/xterm-addon-search/src/SearchAddon.ts b/addons/xterm-addon-search/src/SearchAddon.ts index 05c6892d..16430896 100644 --- a/addons/xterm-addon-search/src/SearchAddon.ts +++ b/addons/xterm-addon-search/src/SearchAddon.ts @@ -528,7 +528,7 @@ export class SearchAddon implements ITerminalAddon { terminal.select(result.col, result.row, result.size); const marker = terminal.registerMarker(undefined, result.row); if (marker) { - this._selectedDecoration = terminal.registerDecoration({ marker, overviewRulerOptions: { color: 'blue' } }); + this._selectedDecoration = terminal.registerDecoration({ marker, overviewRulerOptions: { color: 'blue', position: 'center' } }); } // If it is not in the viewport then we scroll else it just gets selected if (result.row >= (terminal.buffer.active.viewportY + terminal.rows) || result.row < terminal.buffer.active.viewportY) { @@ -550,10 +550,11 @@ export class SearchAddon implements ITerminalAddon { if (!marker) { return undefined; } + // TODO: remove/move? terminal.options.overviewRulerWidth = 10; - const findResultDecoration = terminal.registerDecoration({ marker, overviewRulerOptions: { color: 'orange' } }); + const findResultDecoration = terminal.registerDecoration({ marker, overviewRulerOptions: { color: 'orange', position: 'center' } }); findResultDecoration?.onRender((e) => { - if (!e.classList.contains('xterm-find-result-decoration') && result.term.length && e.clientWidth > 0 && !e.classList.contains('xterm-decoration-overview-ruler')) { + if (!e.classList.contains('xterm-find-result-decoration') && result.term.length && e.clientWidth > 0) { e.classList.add('xterm-find-result-decoration'); // decoration's clientWidth = actualCellWidth e.style.left = `${e.clientWidth * result.col}px`; From 03bc29e0ed3acca117b7badd6b16eb25cae018f0 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 17 Mar 2022 15:02:57 -0400 Subject: [PATCH 105/245] fix #3691 --- src/browser/Decorations/OverviewRulerRenderer.ts | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/browser/Decorations/OverviewRulerRenderer.ts b/src/browser/Decorations/OverviewRulerRenderer.ts index aa91cad9..bbf0995d 100644 --- a/src/browser/Decorations/OverviewRulerRenderer.ts +++ b/src/browser/Decorations/OverviewRulerRenderer.ts @@ -67,12 +67,7 @@ export class OverviewRulerRenderer extends Disposable { public override dispose(): void { for (const decoration of this._decorationElements) { - this._ctx?.clearRect( - 0, - Math.round(this._canvas.height * (decoration[0].marker.line / this._bufferService.buffers.active.lines.length)), - this._canvas.width, - window.devicePixelRatio - ); + decoration[0].dispose(); } this._decorationElements.clear(); this._canvas?.remove(); @@ -119,6 +114,15 @@ export class OverviewRulerRenderer extends Disposable { const element = this._decorationElements.get(decoration); if (!element) { this._decorationElements.set(decoration, this._canvas); + decoration.onDispose(() => { + this._ctx?.clearRect( + !decoration!.options!.overviewRulerOptions?.position || decoration!.options!.overviewRulerOptions?.position === 'left' ? 0 : decoration!.options!.overviewRulerOptions?.position === 'right' ? renderSizes[SizeIndex.OUTER_SIZE] + renderSizes[SizeIndex.INNER_SIZE]: renderSizes[SizeIndex.OUTER_SIZE], + Math.round(this._canvas.height * (decoration!.options!.marker.line / this._bufferService.buffers.active.lines.length)), + !decoration!.options!.overviewRulerOptions?.position ? this._width : decoration!.options!.overviewRulerOptions?.position === 'center' ? renderSizes[SizeIndex.INNER_SIZE] : renderSizes[SizeIndex.OUTER_SIZE], + // when a position is provided, the element has less width, so increase its height + window.devicePixelRatio * (decoration!.options!.overviewRulerOptions?.position ? 6 : 2) + ); + }); } this._refreshStyle(decoration, updateAnchor); decoration.onRenderEmitter.fire(this._canvas); From c70be89bc24f8207e92676374ee7bd8c8db7dea4 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 17 Mar 2022 15:14:35 -0400 Subject: [PATCH 106/245] fix #3690 --- demo/client.ts | 6 +++--- src/browser/Decorations/OverviewRulerRenderer.ts | 7 +++---- test/api/Terminal.api.ts | 8 ++++---- typings/xterm.d.ts | 2 +- 4 files changed, 11 insertions(+), 12 deletions(-) diff --git a/demo/client.ts b/demo/client.ts index 2ae649e4..3e305aef 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -557,9 +557,9 @@ function addDecoration() { function addOverviewRuler() { term.options['overviewRulerWidth'] = 15; - term.registerDecoration({marker: term.addMarker(1), overviewRulerOptions: { color: '#ef2929' }}); - term.registerDecoration({marker: term.addMarker(3), overviewRulerOptions: { color: '#8ae234' }}); - term.registerDecoration({marker: term.addMarker(5), overviewRulerOptions: { color: '#729fcf' }}); + term.registerDecoration({marker: term.addMarker(1), overviewRulerOptions: { color: '#ef2929', position: 'full' }}); + term.registerDecoration({marker: term.addMarker(3), overviewRulerOptions: { color: '#8ae234', position: 'full' }}); + term.registerDecoration({marker: term.addMarker(5), overviewRulerOptions: { color: '#729fcf', position: 'full' }}); term.registerDecoration({marker: term.addMarker(7), overviewRulerOptions: { color: '#ef2929', position: 'left' }}); term.registerDecoration({marker: term.addMarker(7), overviewRulerOptions: { color: '#8ae234', position: 'center' }}); term.registerDecoration({marker: term.addMarker(7), overviewRulerOptions: { color: '#729fcf', position: 'right' }}); diff --git a/src/browser/Decorations/OverviewRulerRenderer.ts b/src/browser/Decorations/OverviewRulerRenderer.ts index aa91cad9..d6fec810 100644 --- a/src/browser/Decorations/OverviewRulerRenderer.ts +++ b/src/browser/Decorations/OverviewRulerRenderer.ts @@ -94,11 +94,10 @@ export class OverviewRulerRenderer extends Disposable { this._ctx.lineWidth = 1; this._ctx.fillStyle = decoration.options.overviewRulerOptions.color; this._ctx.fillRect( - !decoration.options.overviewRulerOptions.position || decoration.options.overviewRulerOptions.position === 'left' ? 0 : decoration.options.overviewRulerOptions.position === 'right' ? renderSizes[SizeIndex.OUTER_SIZE] + renderSizes[SizeIndex.INNER_SIZE]: renderSizes[SizeIndex.OUTER_SIZE], + decoration.options.overviewRulerOptions.position === 'full' || decoration.options.overviewRulerOptions.position === 'left' ? 0 : decoration.options.overviewRulerOptions.position === 'right' ? renderSizes[SizeIndex.OUTER_SIZE] + renderSizes[SizeIndex.INNER_SIZE]: renderSizes[SizeIndex.OUTER_SIZE], Math.round(this._canvas.height * (decoration.options.marker.line / this._bufferService.buffers.active.lines.length)), - !decoration.options.overviewRulerOptions.position ? this._width : decoration.options.overviewRulerOptions.position === 'center' ? renderSizes[SizeIndex.INNER_SIZE] : renderSizes[SizeIndex.OUTER_SIZE], - // when a position is provided, the element has less width, so increase its height - window.devicePixelRatio * (decoration.options.overviewRulerOptions.position ? 6 : 2) + decoration.options.overviewRulerOptions.position === 'full' ? this._width : decoration.options.overviewRulerOptions.position === 'center' ? renderSizes[SizeIndex.INNER_SIZE] : renderSizes[SizeIndex.OUTER_SIZE], + window.devicePixelRatio * (decoration.options.overviewRulerOptions.position === 'full' ? 2 : 6) ); } diff --git a/test/api/Terminal.api.ts b/test/api/Terminal.api.ts index 43f1e97d..b1582be8 100644 --- a/test/api/Terminal.api.ts +++ b/test/api/Terminal.api.ts @@ -770,8 +770,8 @@ describe('API Integration Tests', function(): void { await page.waitForSelector('.xterm-text-layer'); await page.evaluate(`window.marker1 = window.term.addMarker(1)`); await page.evaluate(`window.marker2 = window.term.addMarker(2)`); - await page.evaluate(`window.term.registerDecoration({ marker: window.marker1, overviewRulerOptions: { color: 'red' } })`); - await page.evaluate(`window.term.registerDecoration({ marker: window.marker2, overviewRulerOptions: { color: 'blue' } })`); + await page.evaluate(`window.term.registerDecoration({ marker: window.marker1, overviewRulerOptions: { color: 'red', position: 'full' } })`); + await page.evaluate(`window.term.registerDecoration({ marker: window.marker2, overviewRulerOptions: { color: 'blue', position: 'full' } })`); await openTerminal(page); await pollFor(page, `document.querySelectorAll('.xterm-decoration-overview-ruler').length`, 0); }); @@ -781,8 +781,8 @@ describe('API Integration Tests', function(): void { await page.waitForSelector('.xterm-text-layer'); await page.evaluate(`window.marker1 = window.term.addMarker(1)`); await page.evaluate(`window.marker2 = window.term.addMarker(2)`); - await page.evaluate(`window.term.registerDecoration({ marker: window.marker1, overviewRulerOptions: { color: 'red' } })`); - await page.evaluate(`window.term.registerDecoration({ marker: window.marker2, overviewRulerOptions: { color: 'blue' } })`); + await page.evaluate(`window.term.registerDecoration({ marker: window.marker1, overviewRulerOptions: { color: 'red', position: 'full' } })`); + await page.evaluate(`window.term.registerDecoration({ marker: window.marker2, overviewRulerOptions: { color: 'blue', position: 'full' } })`); await openTerminal(page); await pollFor(page, `document.querySelectorAll('.xterm-decoration-overview-ruler').length`, 1); }); diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 24ba940e..1278309d 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -479,7 +479,7 @@ declare module 'xterm' { * @param color The color of the decoration. * @param position The position of the decoration. */ - readonly overviewRulerOptions?: { color: string; position?: 'left' | 'center' | 'right'} + readonly overviewRulerOptions?: { color: string; position: 'left' | 'center' | 'right' | 'full'} } /** From 731432a85201d28ed1f9c95c5e26ca28e28fdbd8 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 17 Mar 2022 15:16:28 -0400 Subject: [PATCH 107/245] set fallback --- src/common/services/DecorationService.ts | 3 +++ typings/xterm.d.ts | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/common/services/DecorationService.ts b/src/common/services/DecorationService.ts index 911fd369..6c750236 100644 --- a/src/common/services/DecorationService.ts +++ b/src/common/services/DecorationService.ts @@ -65,6 +65,9 @@ class Decoration extends Disposable implements IInternalDecoration { ) { super(); this.marker = options.marker; + if (this.options.overviewRulerOptions && !this.options.overviewRulerOptions.position) { + this.options.overviewRulerOptions.position = 'full'; + } } public override dispose(): void { this._onDispose.fire(); diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 1278309d..3ddef4d3 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -479,7 +479,7 @@ declare module 'xterm' { * @param color The color of the decoration. * @param position The position of the decoration. */ - readonly overviewRulerOptions?: { color: string; position: 'left' | 'center' | 'right' | 'full'} + readonly overviewRulerOptions?: { color: string; position?: 'left' | 'center' | 'right' | 'full'} } /** From 5a6a275a53f127077859c19555e86bff425cc543 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 17 Mar 2022 15:18:21 -0400 Subject: [PATCH 108/245] use interface --- demo/client.ts | 6 +++--- typings/xterm.d.ts | 4 +++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/demo/client.ts b/demo/client.ts index 3e305aef..2ae649e4 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -557,9 +557,9 @@ function addDecoration() { function addOverviewRuler() { term.options['overviewRulerWidth'] = 15; - term.registerDecoration({marker: term.addMarker(1), overviewRulerOptions: { color: '#ef2929', position: 'full' }}); - term.registerDecoration({marker: term.addMarker(3), overviewRulerOptions: { color: '#8ae234', position: 'full' }}); - term.registerDecoration({marker: term.addMarker(5), overviewRulerOptions: { color: '#729fcf', position: 'full' }}); + term.registerDecoration({marker: term.addMarker(1), overviewRulerOptions: { color: '#ef2929' }}); + term.registerDecoration({marker: term.addMarker(3), overviewRulerOptions: { color: '#8ae234' }}); + term.registerDecoration({marker: term.addMarker(5), overviewRulerOptions: { color: '#729fcf' }}); term.registerDecoration({marker: term.addMarker(7), overviewRulerOptions: { color: '#ef2929', position: 'left' }}); term.registerDecoration({marker: term.addMarker(7), overviewRulerOptions: { color: '#8ae234', position: 'center' }}); term.registerDecoration({marker: term.addMarker(7), overviewRulerOptions: { color: '#729fcf', position: 'right' }}); diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 3ddef4d3..87b30288 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -7,6 +7,8 @@ * to be stable and consumed by external programs. */ +import { IModelDecorationOverviewRulerOptions } from 'vs/editor/common/model'; + /// declare module 'xterm' { @@ -479,7 +481,7 @@ declare module 'xterm' { * @param color The color of the decoration. * @param position The position of the decoration. */ - readonly overviewRulerOptions?: { color: string; position?: 'left' | 'center' | 'right' | 'full'} + readonly overviewRulerOptions?: IModelDecorationOverviewRulerOptions } /** From ba4252bc0bb4743b3d5ad7390d38d4080ba90b7d Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 17 Mar 2022 15:21:29 -0400 Subject: [PATCH 109/245] fix merge result --- typings/xterm.d.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index be9ff75d..410cef46 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -446,8 +446,7 @@ declare module 'xterm' { * This will only take effect when {@link IDecorationOptions.overviewRulerOptions} * were provided initially. */ - overviewRulerOptions?: Pick< - Options, 'color'>; + overviewRulerOptions?: Pick; } From bb2193676b483eec739eaef84c8c524841937eb4 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 17 Mar 2022 16:20:47 -0400 Subject: [PATCH 110/245] fix problems --- src/browser/Decorations/OverviewRulerRenderer.ts | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/src/browser/Decorations/OverviewRulerRenderer.ts b/src/browser/Decorations/OverviewRulerRenderer.ts index d497b14b..24a5173b 100644 --- a/src/browser/Decorations/OverviewRulerRenderer.ts +++ b/src/browser/Decorations/OverviewRulerRenderer.ts @@ -87,7 +87,7 @@ export class OverviewRulerRenderer extends Disposable { return; } this._ctx.lineWidth = 1; - this._ctx.fillStyle = decoration.overviewRulerOptions?.color || decoration.options.overviewRulerOptions.color; + this._ctx.fillStyle = decoration.options.overviewRulerOptions.color; this._ctx.fillRect( decoration.options.overviewRulerOptions.position === 'full' || decoration.options.overviewRulerOptions.position === 'left' ? 0 : decoration.options.overviewRulerOptions.position === 'right' ? renderSizes[SizeIndex.OUTER_SIZE] + renderSizes[SizeIndex.INNER_SIZE]: renderSizes[SizeIndex.OUTER_SIZE], Math.round(this._canvas.height * (decoration.options.marker.line / this._bufferService.buffers.active.lines.length)), @@ -113,15 +113,7 @@ export class OverviewRulerRenderer extends Disposable { const element = this._decorationElements.get(decoration); if (!element) { this._decorationElements.set(decoration, this._canvas); - decoration.onDispose(() => { - this._ctx?.clearRect( - !decoration!.options!.overviewRulerOptions?.position || decoration!.options!.overviewRulerOptions?.position === 'left' ? 0 : decoration!.options!.overviewRulerOptions?.position === 'right' ? renderSizes[SizeIndex.OUTER_SIZE] + renderSizes[SizeIndex.INNER_SIZE]: renderSizes[SizeIndex.OUTER_SIZE], - Math.round(this._canvas.height * (decoration!.options!.marker.line / this._bufferService.buffers.active.lines.length)), - !decoration!.options!.overviewRulerOptions?.position ? this._width : decoration!.options!.overviewRulerOptions?.position === 'center' ? renderSizes[SizeIndex.INNER_SIZE] : renderSizes[SizeIndex.OUTER_SIZE], - // when a position is provided, the element has less width, so increase its height - window.devicePixelRatio * (decoration!.options!.overviewRulerOptions?.position ? 6 : 2) - ); - }); + decoration[0].onDispose(() => (this._queueRefresh())); } this._refreshStyle(decoration, updateAnchor); } From 8f1dcecb5088bdaa8e0e5951e6d25d6d1c1ad1cf Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 17 Mar 2022 16:23:37 -0400 Subject: [PATCH 111/245] dispose of decoration --- src/browser/Decorations/OverviewRulerRenderer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/browser/Decorations/OverviewRulerRenderer.ts b/src/browser/Decorations/OverviewRulerRenderer.ts index 24a5173b..1998d18a 100644 --- a/src/browser/Decorations/OverviewRulerRenderer.ts +++ b/src/browser/Decorations/OverviewRulerRenderer.ts @@ -113,7 +113,7 @@ export class OverviewRulerRenderer extends Disposable { const element = this._decorationElements.get(decoration); if (!element) { this._decorationElements.set(decoration, this._canvas); - decoration[0].onDispose(() => (this._queueRefresh())); + decoration.onDispose(() => this._queueRefresh()); } this._refreshStyle(decoration, updateAnchor); } From a05cdbd48e15987ebe105c9805c9bd3af1ddd3f3 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 17 Mar 2022 16:30:55 -0400 Subject: [PATCH 112/245] Remove accidental import --- typings/xterm.d.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 410cef46..4f06d5f5 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -7,8 +7,6 @@ * to be stable and consumed by external programs. */ -import { IModelDecorationOverviewRulerOptions } from 'vs/editor/common/model'; - /// declare module 'xterm' { @@ -432,14 +430,14 @@ declare module 'xterm' { * is rendered, returns the dom element * associated with the decoration. */ - readonly onRender: IEvent; + readonly onRender: IEvent; /** * The element that the decoration is rendered to. This will be undefined * until it is rendered for the first time by {@link IDecoration.onRender}. * that. */ - element: HTMLElement | undefined; + element: HTMLLIElement | undefined; /** * The options for the overview ruler that can be updated. @@ -679,7 +677,7 @@ declare module 'xterm' { /** * The element containing the terminal. */ - readonly element: HTMLElement | undefined; + readonly element: HTMLLIElement | undefined; /** * The textarea that accepts input for the terminal. From 02e2727fb527265a304f87a13f9a442702270774 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 17 Mar 2022 16:36:34 -0400 Subject: [PATCH 113/245] fix typos --- typings/xterm.d.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 4f06d5f5..d1eb3890 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -430,14 +430,14 @@ declare module 'xterm' { * is rendered, returns the dom element * associated with the decoration. */ - readonly onRender: IEvent; + readonly onRender: IEvent; /** * The element that the decoration is rendered to. This will be undefined * until it is rendered for the first time by {@link IDecoration.onRender}. * that. */ - element: HTMLLIElement | undefined; + element: HTMLElement | undefined; /** * The options for the overview ruler that can be updated. @@ -677,7 +677,7 @@ declare module 'xterm' { /** * The element containing the terminal. */ - readonly element: HTMLLIElement | undefined; + readonly element: HTMLElement | undefined; /** * The textarea that accepts input for the terminal. From 9bc5090bac5bb4209b14ebba6a2099ecd275cc2a Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 18 Mar 2022 11:47:26 -0400 Subject: [PATCH 114/245] use a map --- addons/xterm-addon-search/src/SearchAddon.ts | 42 +++++++++++--------- 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/addons/xterm-addon-search/src/SearchAddon.ts b/addons/xterm-addon-search/src/SearchAddon.ts index 16430896..9a3bfb4a 100644 --- a/addons/xterm-addon-search/src/SearchAddon.ts +++ b/addons/xterm-addon-search/src/SearchAddon.ts @@ -40,9 +40,9 @@ const LINES_CACHE_TIME_TO_LIVE = 15 * 1000; // 15 secs export class SearchAddon implements ITerminalAddon { private _terminal: Terminal | undefined; - private _resultDecorations: IDecoration[] = []; + private _resultDecorations: Map = new Map(); private _result: ISearchResult | undefined; - private _reset: boolean = false; + private _dataChanged: boolean = false; private _cachedSearchTerm: string | undefined; private _selectedDecoration: IDecoration | undefined; /** @@ -57,17 +57,18 @@ export class SearchAddon implements ITerminalAddon { public activate(terminal: Terminal): void { this._terminal = terminal; - this._terminal.onData(() => this._reset = true); + // TODO: should this be using on buffer changed instead? + this._terminal.onData(() => this._dataChanged = true); } public dispose(): void { } public clear(): void { this._terminal?.clearSelection(); - this._resultDecorations.forEach(d => d.dispose()); - this._selectedDecoration?.dispose(); - this._resultDecorations = []; - this._reset = true; + this._resultDecorations.forEach(decorations => decorations.forEach(d=> d.dispose())); + this._resultDecorations.clear(); + this._cachedSearchTerm = undefined; + this._dataChanged = true; } /** @@ -86,18 +87,18 @@ export class SearchAddon implements ITerminalAddon { this.clear(); return false; } - - if (!this._reset && term === this._cachedSearchTerm) { - for (const decoration of this._resultDecorations) { - decoration.overviewRulerOptions = { color: 'orange' }; - } + if (!this._dataChanged && term === this._cachedSearchTerm) { return this.findNext(term, searchOptions); } - this._reset = false; - + if (this._dataChanged && term === this._cachedSearchTerm) { + // TODO: + // add to decorations instead of starting from scratch + // by looking at resultDecoration.keys()[resultDecoration.length] + // to ybase + } // new search, clear out the old decorations - this._resultDecorations.forEach(d => d.dispose()); + this._resultDecorations.forEach(decorations => decorations.forEach(d=> d.dispose())); const results: ISearchResult[] = []; searchOptions = searchOptions || {}; searchOptions.incremental = false; @@ -112,10 +113,15 @@ export class SearchAddon implements ITerminalAddon { if (result) { const resultDecoration = this._showResultDecoration(result); if (resultDecoration) { - this._resultDecorations.push(resultDecoration); + const decorationsForLine = this._resultDecorations.get(resultDecoration.marker.line) || []; + decorationsForLine.push(resultDecoration); + this._resultDecorations.set(resultDecoration.marker.line, decorationsForLine); } } } + if (this._dataChanged) { + this._dataChanged = false; + } if (results.length > 0) { this._cachedSearchTerm = term; } @@ -528,7 +534,7 @@ export class SearchAddon implements ITerminalAddon { terminal.select(result.col, result.row, result.size); const marker = terminal.registerMarker(undefined, result.row); if (marker) { - this._selectedDecoration = terminal.registerDecoration({ marker, overviewRulerOptions: { color: 'blue', position: 'center' } }); + this._selectedDecoration = terminal.registerDecoration({ marker, overviewRulerOptions: { color: 'blue' } }); } // If it is not in the viewport then we scroll else it just gets selected if (result.row >= (terminal.buffer.active.viewportY + terminal.rows) || result.row < terminal.buffer.active.viewportY) { @@ -552,7 +558,7 @@ export class SearchAddon implements ITerminalAddon { } // TODO: remove/move? terminal.options.overviewRulerWidth = 10; - const findResultDecoration = terminal.registerDecoration({ marker, overviewRulerOptions: { color: 'orange', position: 'center' } }); + const findResultDecoration = terminal.registerDecoration({ marker, overviewRulerOptions: this._resultDecorations.get(marker.line) && !this._dataChanged ? undefined : { color: 'orange', position: 'center' } }); findResultDecoration?.onRender((e) => { if (!e.classList.contains('xterm-find-result-decoration') && result.term.length && e.clientWidth > 0) { e.classList.add('xterm-find-result-decoration'); From a1468d0f589f5ca3e2b99f8c89de05af43bca25f Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 18 Mar 2022 13:12:32 -0400 Subject: [PATCH 115/245] use start row and col --- addons/xterm-addon-search/src/SearchAddon.ts | 63 +++++++++++--------- 1 file changed, 36 insertions(+), 27 deletions(-) diff --git a/addons/xterm-addon-search/src/SearchAddon.ts b/addons/xterm-addon-search/src/SearchAddon.ts index 9a3bfb4a..b28e2bdd 100644 --- a/addons/xterm-addon-search/src/SearchAddon.ts +++ b/addons/xterm-addon-search/src/SearchAddon.ts @@ -10,6 +10,8 @@ export interface ISearchOptions { wholeWord?: boolean; caseSensitive?: boolean; incremental?: boolean; + startRow?: number; + startCol?: number; } export interface ISearchPosition { @@ -40,11 +42,12 @@ const LINES_CACHE_TIME_TO_LIVE = 15 * 1000; // 15 secs export class SearchAddon implements ITerminalAddon { private _terminal: Terminal | undefined; - private _resultDecorations: Map = new Map(); private _result: ISearchResult | undefined; private _dataChanged: boolean = false; private _cachedSearchTerm: string | undefined; private _selectedDecoration: IDecoration | undefined; + private _resultDecorations: Map = new Map(); + private _searchResults: Map = new Map(); /** * translateBufferLineToStringWithWrap is a fairly expensive call. * We memoize the calls into an array that has a time based ttl. @@ -65,6 +68,7 @@ export class SearchAddon implements ITerminalAddon { public clear(): void { this._terminal?.clearSelection(); + this._searchResults.clear(); this._resultDecorations.forEach(decorations => decorations.forEach(d=> d.dispose())); this._resultDecorations.clear(); this._cachedSearchTerm = undefined; @@ -87,42 +91,47 @@ export class SearchAddon implements ITerminalAddon { this.clear(); return false; } - if (!this._dataChanged && term === this._cachedSearchTerm) { - return this.findNext(term, searchOptions); - } - if (this._dataChanged && term === this._cachedSearchTerm) { - // TODO: - // add to decorations instead of starting from scratch - // by looking at resultDecoration.keys()[resultDecoration.length] - // to ybase + + if (term === this._cachedSearchTerm) { + if (!this._dataChanged) { + return this.findNext(term, searchOptions); + } + // set start row to avoid redoing work + searchOptions = searchOptions || {}; + const key = Array.from(this._searchResults.keys()).pop()?.split('-'); + if (key?.length === 2) { + searchOptions.startRow = Number.parseInt(key[0]) + 1; + searchOptions.startCol = Number.parseInt(key[1]); + console.log(searchOptions.startRow, searchOptions.startCol); + } + } else { + // new search, clear out the old decorations + this._resultDecorations.forEach(decorations => decorations.forEach(d=> d.dispose())); + this._resultDecorations.clear(); + this._searchResults.clear(); + searchOptions = searchOptions || {}; } - // new search, clear out the old decorations - this._resultDecorations.forEach(decorations => decorations.forEach(d=> d.dispose())); - const results: ISearchResult[] = []; - searchOptions = searchOptions || {}; searchOptions.incremental = false; let found = this.findNext(term, searchOptions); - while (found && !results.find(r => r?.col === this._result?.col && r?.row === this._result?.row)) { + while (found && (!this._result || !this._searchResults.get(`${this._result.row}-${this._result.col}`))) { if (this._result) { - results.push(this._result); + this._searchResults.set(`${this._result.row}-${this._result.col}`, this._result); } found = this.findNext(term, searchOptions); } - for (const result of results) { - if (result) { - const resultDecoration = this._showResultDecoration(result); - if (resultDecoration) { - const decorationsForLine = this._resultDecorations.get(resultDecoration.marker.line) || []; - decorationsForLine.push(resultDecoration); - this._resultDecorations.set(resultDecoration.marker.line, decorationsForLine); - } + this._searchResults.forEach(result => { + const resultDecoration = this._showResultDecoration(result); + if (resultDecoration) { + const decorationsForLine = this._resultDecorations.get(resultDecoration.marker.line) || []; + decorationsForLine.push(resultDecoration); + this._resultDecorations.set(resultDecoration.marker.line, decorationsForLine); } - } + }); if (this._dataChanged) { this._dataChanged = false; } - if (results.length > 0) { + if (this._searchResults.size > 0) { this._cachedSearchTerm = term; } return true; @@ -147,8 +156,8 @@ export class SearchAddon implements ITerminalAddon { return false; } - let startCol = 0; - let startRow = 0; + let startCol = searchOptions?.startCol || 0; + let startRow = searchOptions?.startRow || 0; let currentSelection: ISelectionPosition | undefined; if (this._terminal.hasSelection()) { const incremental = searchOptions ? searchOptions.incremental : false; From 6c4f8b42dd83f8e5ab279dc6bfa7bf2eaba013fb Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 18 Mar 2022 13:29:59 -0400 Subject: [PATCH 116/245] append to results --- addons/xterm-addon-search/src/SearchAddon.ts | 9 +++------ typings/xterm.d.ts | 2 -- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/addons/xterm-addon-search/src/SearchAddon.ts b/addons/xterm-addon-search/src/SearchAddon.ts index b28e2bdd..38792414 100644 --- a/addons/xterm-addon-search/src/SearchAddon.ts +++ b/addons/xterm-addon-search/src/SearchAddon.ts @@ -91,25 +91,22 @@ export class SearchAddon implements ITerminalAddon { this.clear(); return false; } - + searchOptions = searchOptions || {}; if (term === this._cachedSearchTerm) { if (!this._dataChanged) { return this.findNext(term, searchOptions); } - // set start row to avoid redoing work - searchOptions = searchOptions || {}; + // set start row and col to avoid redoing work const key = Array.from(this._searchResults.keys()).pop()?.split('-'); if (key?.length === 2) { searchOptions.startRow = Number.parseInt(key[0]) + 1; searchOptions.startCol = Number.parseInt(key[1]); - console.log(searchOptions.startRow, searchOptions.startCol); } } else { // new search, clear out the old decorations this._resultDecorations.forEach(decorations => decorations.forEach(d=> d.dispose())); this._resultDecorations.clear(); this._searchResults.clear(); - searchOptions = searchOptions || {}; } searchOptions.incremental = false; @@ -166,7 +163,7 @@ export class SearchAddon implements ITerminalAddon { currentSelection = this._terminal.getSelectionPosition()!; startRow = incremental ? currentSelection.startRow : currentSelection.endRow; startCol = incremental ? currentSelection.startColumn : currentSelection.endColumn; - } else { + } else if (!startRow) { startRow = this._terminal.buffer.active.cursorY; startCol = this._terminal.buffer.active.cursorX; } diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index d0149c19..363cede3 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -7,8 +7,6 @@ * to be stable and consumed by external programs. */ -import { IModelDecorationOverviewRulerOptions } from 'vs/editor/common/model'; - /// declare module 'xterm' { From f43afde8df4f4e2b40ba6aff9cd3c1eaa1decf79 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 18 Mar 2022 10:42:26 -0700 Subject: [PATCH 117/245] Respect device pixel ratio in overview ruler --- .../Decorations/OverviewRulerRenderer.ts | 45 ++++++++++++++----- 1 file changed, 33 insertions(+), 12 deletions(-) diff --git a/src/browser/Decorations/OverviewRulerRenderer.ts b/src/browser/Decorations/OverviewRulerRenderer.ts index 1998d18a..af894ebf 100644 --- a/src/browser/Decorations/OverviewRulerRenderer.ts +++ b/src/browser/Decorations/OverviewRulerRenderer.ts @@ -18,6 +18,13 @@ const enum SizeIndex { INNER_SIZE = 1 } +const positionHeights = { + full: 2, + left: 6, + center: 6, + right: 6 +}; + export class OverviewRulerRenderer extends Disposable { private readonly _canvas: HTMLCanvasElement; private readonly _ctx: CanvasRenderingContext2D; @@ -38,6 +45,7 @@ export class OverviewRulerRenderer extends Disposable { super(); this._canvas = document.createElement('canvas'); this._canvas.classList.add('xterm-decoration-overview-ruler'); + this._refreshCanvasDimensions(); this._viewportElement.parentElement?.insertBefore(this._canvas, this._viewportElement); const ctx = this._canvas.getContext('2d'); if (!ctx) { @@ -56,13 +64,14 @@ export class OverviewRulerRenderer extends Disposable { this.register(this._decorationService.onDecorationRemoved(decoration => this._removeDecoration(decoration))); this.register(this._optionsService.onOptionChange(o => { if (o === 'overviewRulerWidth') { - renderSizes[SizeIndex.OUTER_SIZE] = Math.floor(this._width / 3); - renderSizes[SizeIndex.INNER_SIZE] = Math.ceil(this._width / 3); + renderSizes[SizeIndex.OUTER_SIZE] = Math.floor(this._canvas.width / 3); + renderSizes[SizeIndex.INNER_SIZE] = Math.ceil(this._canvas.width / 3); this._queueRefresh(); } })); - renderSizes[SizeIndex.OUTER_SIZE] = Math.floor(this._width / 3); - renderSizes[SizeIndex.INNER_SIZE] = Math.ceil(this._width / 3); + console.log('width', this._canvas.width); + renderSizes[SizeIndex.OUTER_SIZE] = Math.floor(this._canvas.width / 3); + renderSizes[SizeIndex.INNER_SIZE] = Math.ceil(this._canvas.width / 3); } public override dispose(): void { @@ -89,19 +98,31 @@ export class OverviewRulerRenderer extends Disposable { this._ctx.lineWidth = 1; this._ctx.fillStyle = decoration.options.overviewRulerOptions.color; this._ctx.fillRect( - decoration.options.overviewRulerOptions.position === 'full' || decoration.options.overviewRulerOptions.position === 'left' ? 0 : decoration.options.overviewRulerOptions.position === 'right' ? renderSizes[SizeIndex.OUTER_SIZE] + renderSizes[SizeIndex.INNER_SIZE]: renderSizes[SizeIndex.OUTER_SIZE], - Math.round(this._canvas.height * (decoration.options.marker.line / this._bufferService.buffers.active.lines.length)), - decoration.options.overviewRulerOptions.position === 'full' ? this._width : decoration.options.overviewRulerOptions.position === 'center' ? renderSizes[SizeIndex.INNER_SIZE] : renderSizes[SizeIndex.OUTER_SIZE], - window.devicePixelRatio * (decoration.options.overviewRulerOptions.position === 'full' ? 2 : 6) + /* x */ decoration.options.overviewRulerOptions.position === 'full' || decoration.options.overviewRulerOptions.position === 'left' + ? 0 + : decoration.options.overviewRulerOptions.position === 'right' + ? renderSizes[SizeIndex.OUTER_SIZE] + renderSizes[SizeIndex.INNER_SIZE] + : renderSizes[SizeIndex.OUTER_SIZE], + /* y */ Math.round(this._canvas.height * (decoration.options.marker.line / this._bufferService.buffers.active.lines.length)), + /* w */ decoration.options.overviewRulerOptions.position === 'full' + ? this._canvas.width + : decoration.options.overviewRulerOptions.position === 'center' + ? renderSizes[SizeIndex.INNER_SIZE] + : renderSizes[SizeIndex.OUTER_SIZE], + /* h */ window.devicePixelRatio * positionHeights[decoration.options.overviewRulerOptions.position!] ); } + private _refreshCanvasDimensions(): void { + this._canvas.style.width = `${this._width}px`; + this._canvas.style.height = `${this._screenElement.clientHeight}px`; + this._canvas.width = Math.floor(this._width * window.devicePixelRatio); + this._canvas.height = Math.floor(this._screenElement.clientHeight * window.devicePixelRatio); + } + private _refreshDecorations(updateCanvasDimensions?: boolean, updateAnchor?: boolean): void { if (updateCanvasDimensions) { - this._canvas.style.width = `${this._width}px`; - this._canvas.style.height = `${this._screenElement.clientHeight}px`; - this._canvas.width = Math.floor((this._width)* window.devicePixelRatio); - this._canvas.height = Math.floor(this._screenElement.clientHeight * window.devicePixelRatio); + this._refreshCanvasDimensions(); } this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height); for (const decoration of this._decorationService.decorations) { From 4a661da57bd61417f49c030490d417bad30c7e11 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 18 Mar 2022 10:50:28 -0700 Subject: [PATCH 118/245] Use object properties for fast access to precalculated draw args --- .../Decorations/OverviewRulerRenderer.ts | 69 +++++++++++++------ 1 file changed, 49 insertions(+), 20 deletions(-) diff --git a/src/browser/Decorations/OverviewRulerRenderer.ts b/src/browser/Decorations/OverviewRulerRenderer.ts index af894ebf..3a1c127a 100644 --- a/src/browser/Decorations/OverviewRulerRenderer.ts +++ b/src/browser/Decorations/OverviewRulerRenderer.ts @@ -18,11 +18,25 @@ const enum SizeIndex { INNER_SIZE = 1 } -const positionHeights = { - full: 2, - left: 6, - center: 6, - right: 6 +const drawHeight = { + full: 0, + left: 0, + center: 0, + right: 0 +}; + +const drawWidth = { + full: 0, + left: 0, + center: 0, + right: 0 +}; + +const drawX = { + full: 0, + left: 0, + center: 0, + right: 0 }; export class OverviewRulerRenderer extends Disposable { @@ -64,14 +78,16 @@ export class OverviewRulerRenderer extends Disposable { this.register(this._decorationService.onDecorationRemoved(decoration => this._removeDecoration(decoration))); this.register(this._optionsService.onOptionChange(o => { if (o === 'overviewRulerWidth') { - renderSizes[SizeIndex.OUTER_SIZE] = Math.floor(this._canvas.width / 3); - renderSizes[SizeIndex.INNER_SIZE] = Math.ceil(this._canvas.width / 3); + // renderSizes[SizeIndex.OUTER_SIZE] = Math.floor(this._canvas.width / 3); + // renderSizes[SizeIndex.INNER_SIZE] = Math.ceil(this._canvas.width / 3); + this._refreshDrawConstants(); this._queueRefresh(); } })); console.log('width', this._canvas.width); - renderSizes[SizeIndex.OUTER_SIZE] = Math.floor(this._canvas.width / 3); - renderSizes[SizeIndex.INNER_SIZE] = Math.ceil(this._canvas.width / 3); + this._refreshDrawConstants(); + // renderSizes[SizeIndex.OUTER_SIZE] = Math.floor(this._canvas.width / 3); + // renderSizes[SizeIndex.INNER_SIZE] = Math.ceil(this._canvas.width / 3); } public override dispose(): void { @@ -83,6 +99,26 @@ export class OverviewRulerRenderer extends Disposable { super.dispose(); } + private _refreshDrawConstants(): void { + // width + const outerWidth = Math.floor(this._canvas.width / 3); + const innerWidth = Math.ceil(this._canvas.width / 3); + drawWidth.full = this._canvas.width; + drawWidth.left = outerWidth; + drawWidth.center = innerWidth; + drawWidth.right = outerWidth; + // height + drawHeight.full = Math.round(2 * window.devicePixelRatio); + drawHeight.left = Math.round(6 * window.devicePixelRatio); + drawHeight.center = Math.round(6 * window.devicePixelRatio); + drawHeight.right = Math.round(6 * window.devicePixelRatio); + // x + drawX.full = 0; + drawX.left = 0; + drawX.center = drawWidth.left; + drawX.right = drawWidth.left + drawWidth.center; + } + private _refreshStyle(decoration: IInternalDecoration, updateAnchor?: boolean): void { if (updateAnchor) { if (decoration.options.anchor === 'right') { @@ -98,18 +134,10 @@ export class OverviewRulerRenderer extends Disposable { this._ctx.lineWidth = 1; this._ctx.fillStyle = decoration.options.overviewRulerOptions.color; this._ctx.fillRect( - /* x */ decoration.options.overviewRulerOptions.position === 'full' || decoration.options.overviewRulerOptions.position === 'left' - ? 0 - : decoration.options.overviewRulerOptions.position === 'right' - ? renderSizes[SizeIndex.OUTER_SIZE] + renderSizes[SizeIndex.INNER_SIZE] - : renderSizes[SizeIndex.OUTER_SIZE], + /* x */ drawX[decoration.options.overviewRulerOptions.position!], /* y */ Math.round(this._canvas.height * (decoration.options.marker.line / this._bufferService.buffers.active.lines.length)), - /* w */ decoration.options.overviewRulerOptions.position === 'full' - ? this._canvas.width - : decoration.options.overviewRulerOptions.position === 'center' - ? renderSizes[SizeIndex.INNER_SIZE] - : renderSizes[SizeIndex.OUTER_SIZE], - /* h */ window.devicePixelRatio * positionHeights[decoration.options.overviewRulerOptions.position!] + /* w */ drawWidth[decoration.options.overviewRulerOptions.position!], + /* h */ drawHeight[decoration.options.overviewRulerOptions.position!] ); } @@ -118,6 +146,7 @@ export class OverviewRulerRenderer extends Disposable { this._canvas.style.height = `${this._screenElement.clientHeight}px`; this._canvas.width = Math.floor(this._width * window.devicePixelRatio); this._canvas.height = Math.floor(this._screenElement.clientHeight * window.devicePixelRatio); + this._refreshDrawConstants(); } private _refreshDecorations(updateCanvasDimensions?: boolean, updateAnchor?: boolean): void { From 816220ebd2195c72240a3683e6f2a1e079b57824 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 18 Mar 2022 10:51:02 -0700 Subject: [PATCH 119/245] Round dpr for more crisp decorations --- src/browser/Decorations/OverviewRulerRenderer.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/browser/Decorations/OverviewRulerRenderer.ts b/src/browser/Decorations/OverviewRulerRenderer.ts index 3a1c127a..c38b5377 100644 --- a/src/browser/Decorations/OverviewRulerRenderer.ts +++ b/src/browser/Decorations/OverviewRulerRenderer.ts @@ -144,8 +144,8 @@ export class OverviewRulerRenderer extends Disposable { private _refreshCanvasDimensions(): void { this._canvas.style.width = `${this._width}px`; this._canvas.style.height = `${this._screenElement.clientHeight}px`; - this._canvas.width = Math.floor(this._width * window.devicePixelRatio); - this._canvas.height = Math.floor(this._screenElement.clientHeight * window.devicePixelRatio); + this._canvas.width = Math.round(this._width * window.devicePixelRatio); + this._canvas.height = Math.round(this._screenElement.clientHeight * window.devicePixelRatio); this._refreshDrawConstants(); } From 77622f8919fe5be92abe767784f08d133ebbe1d2 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 18 Mar 2022 10:54:11 -0700 Subject: [PATCH 120/245] Align full with other decorations Fixes #3692 --- demo/client.ts | 2 ++ src/browser/Decorations/OverviewRulerRenderer.ts | 5 ++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/demo/client.ts b/demo/client.ts index 67e27759..2b622372 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -559,5 +559,7 @@ function addOverviewRuler() { term.registerDecoration({marker: term.addMarker(7), overviewRulerOptions: { color: '#ef2929', position: 'left' }}); term.registerDecoration({marker: term.addMarker(7), overviewRulerOptions: { color: '#8ae234', position: 'center' }}); term.registerDecoration({marker: term.addMarker(7), overviewRulerOptions: { color: '#729fcf', position: 'right' }}); + term.registerDecoration({marker: term.addMarker(10), overviewRulerOptions: { color: '#8ae234', position: 'center' }}); + term.registerDecoration({marker: term.addMarker(10), overviewRulerOptions: { color: '#ffffff80', position: 'full' }}); } diff --git a/src/browser/Decorations/OverviewRulerRenderer.ts b/src/browser/Decorations/OverviewRulerRenderer.ts index c38b5377..0344fe5f 100644 --- a/src/browser/Decorations/OverviewRulerRenderer.ts +++ b/src/browser/Decorations/OverviewRulerRenderer.ts @@ -135,7 +135,10 @@ export class OverviewRulerRenderer extends Disposable { this._ctx.fillStyle = decoration.options.overviewRulerOptions.color; this._ctx.fillRect( /* x */ drawX[decoration.options.overviewRulerOptions.position!], - /* y */ Math.round(this._canvas.height * (decoration.options.marker.line / this._bufferService.buffers.active.lines.length)), + /* y */ Math.round( + (this._canvas.height - 1) * // -1 to ensure at least 2px are allowed for decoration on last line + (decoration.options.marker.line / this._bufferService.buffers.active.lines.length) - drawHeight[decoration.options.overviewRulerOptions.position!] / 2 + ), /* w */ drawWidth[decoration.options.overviewRulerOptions.position!], /* h */ drawHeight[decoration.options.overviewRulerOptions.position!] ); From b5f208294d45f2ffee02181437360432be4e940d Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 18 Mar 2022 10:56:49 -0700 Subject: [PATCH 121/245] Clean up --- .../Decorations/OverviewRulerRenderer.ts | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) diff --git a/src/browser/Decorations/OverviewRulerRenderer.ts b/src/browser/Decorations/OverviewRulerRenderer.ts index 0344fe5f..87f10181 100644 --- a/src/browser/Decorations/OverviewRulerRenderer.ts +++ b/src/browser/Decorations/OverviewRulerRenderer.ts @@ -8,30 +8,20 @@ import { IRenderService } from 'browser/services/Services'; import { Disposable } from 'common/Lifecycle'; import { IBufferService, IDecorationService, IInternalDecoration, IOptionsService } from 'common/services/Services'; -// This is used to reduce memory usage -// when refreshStyle is called -// by storing and updating -// the sizes of the decorations to be drawn -const renderSizes = new Uint16Array(3); -const enum SizeIndex { - OUTER_SIZE = 0, - INNER_SIZE = 1 -} - +// Helper objects to avoid excessive calculation and garbage collection during rendering. These are +// static values for each render and can be accessed using the decoration position as the key. const drawHeight = { full: 0, left: 0, center: 0, right: 0 }; - const drawWidth = { full: 0, left: 0, center: 0, right: 0 }; - const drawX = { full: 0, left: 0, @@ -78,16 +68,11 @@ export class OverviewRulerRenderer extends Disposable { this.register(this._decorationService.onDecorationRemoved(decoration => this._removeDecoration(decoration))); this.register(this._optionsService.onOptionChange(o => { if (o === 'overviewRulerWidth') { - // renderSizes[SizeIndex.OUTER_SIZE] = Math.floor(this._canvas.width / 3); - // renderSizes[SizeIndex.INNER_SIZE] = Math.ceil(this._canvas.width / 3); this._refreshDrawConstants(); this._queueRefresh(); } })); - console.log('width', this._canvas.width); this._refreshDrawConstants(); - // renderSizes[SizeIndex.OUTER_SIZE] = Math.floor(this._canvas.width / 3); - // renderSizes[SizeIndex.INNER_SIZE] = Math.ceil(this._canvas.width / 3); } public override dispose(): void { From 39d1546753e18615a20a41f50b9e06434db33f29 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 18 Mar 2022 10:59:10 -0700 Subject: [PATCH 122/245] Draw full decorations on top Fixes #3696 --- src/browser/Decorations/OverviewRulerRenderer.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/browser/Decorations/OverviewRulerRenderer.ts b/src/browser/Decorations/OverviewRulerRenderer.ts index 87f10181..aa09db56 100644 --- a/src/browser/Decorations/OverviewRulerRenderer.ts +++ b/src/browser/Decorations/OverviewRulerRenderer.ts @@ -143,7 +143,14 @@ export class OverviewRulerRenderer extends Disposable { } this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height); for (const decoration of this._decorationService.decorations) { - this._renderDecoration(decoration, updateAnchor); + if (decoration.options.overviewRulerOptions!.position !== 'full') { + this._renderDecoration(decoration, updateAnchor); + } + } + for (const decoration of this._decorationService.decorations) { + if (decoration.options.overviewRulerOptions!.position === 'full') { + this._renderDecoration(decoration, updateAnchor); + } } } From 47ec31361c547d952781afc0cbb591247a31ed9a Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 18 Mar 2022 14:14:02 -0400 Subject: [PATCH 123/245] support colors --- addons/xterm-addon-search/src/SearchAddon.ts | 43 ++++++++++++-------- demo/client.ts | 5 ++- 2 files changed, 29 insertions(+), 19 deletions(-) diff --git a/addons/xterm-addon-search/src/SearchAddon.ts b/addons/xterm-addon-search/src/SearchAddon.ts index 38792414..00e6830f 100644 --- a/addons/xterm-addon-search/src/SearchAddon.ts +++ b/addons/xterm-addon-search/src/SearchAddon.ts @@ -12,6 +12,8 @@ export interface ISearchOptions { incremental?: boolean; startRow?: number; startCol?: number; + overviewRulerResultDecorationColor?: string; + overviewRulerSelectionDecorationColor?: string; } export interface ISearchPosition { @@ -117,14 +119,17 @@ export class SearchAddon implements ITerminalAddon { } found = this.findNext(term, searchOptions); } - this._searchResults.forEach(result => { - const resultDecoration = this._showResultDecoration(result); - if (resultDecoration) { - const decorationsForLine = this._resultDecorations.get(resultDecoration.marker.line) || []; - decorationsForLine.push(resultDecoration); - this._resultDecorations.set(resultDecoration.marker.line, decorationsForLine); - } - }); + if (searchOptions.overviewRulerResultDecorationColor && searchOptions.overviewRulerSelectionDecorationColor) { + this._searchResults.forEach(result => { + const resultDecoration = this._showResultDecoration(result, searchOptions!.overviewRulerResultDecorationColor!); + if (resultDecoration) { + const decorationsForLine = this._resultDecorations.get(resultDecoration.marker.line) || []; + decorationsForLine.push(resultDecoration); + this._resultDecorations.set(resultDecoration.marker.line, decorationsForLine); + } + }); + } + if (this._dataChanged) { this._dataChanged = false; } @@ -211,7 +216,7 @@ export class SearchAddon implements ITerminalAddon { } // Set selection and scroll if a result was found - return this._selectResult(this._result); + return this._selectResult(this._result, searchOptions?.overviewRulerSelectionDecorationColor); } /** @@ -292,7 +297,7 @@ export class SearchAddon implements ITerminalAddon { if (!result && currentSelection) return true; // Set selection and scroll if a result was found - return this._selectResult(result); + return this._selectResult(result, searchOptions?.overviewRulerSelectionDecorationColor); } @@ -530,7 +535,7 @@ export class SearchAddon implements ITerminalAddon { * @param result The result to select. * @return Whethera result was selected. */ - private _selectResult(result: ISearchResult | undefined): boolean { + private _selectResult(result: ISearchResult | undefined, color?: string): boolean { const terminal = this._terminal!; this._selectedDecoration?.dispose(); if (!result) { @@ -538,10 +543,13 @@ export class SearchAddon implements ITerminalAddon { return false; } terminal.select(result.col, result.row, result.size); - const marker = terminal.registerMarker(undefined, result.row); - if (marker) { - this._selectedDecoration = terminal.registerDecoration({ marker, overviewRulerOptions: { color: 'blue' } }); + if (color) { + const marker = terminal.registerMarker(undefined, result.row); + if (marker) { + this._selectedDecoration = terminal.registerDecoration({ marker, overviewRulerOptions: { color } }); + } } + // If it is not in the viewport then we scroll else it just gets selected if (result.row >= (terminal.buffer.active.viewportY + terminal.rows) || result.row < terminal.buffer.active.viewportY) { let scroll = result.row - terminal.buffer.active.viewportY; @@ -556,15 +564,14 @@ export class SearchAddon implements ITerminalAddon { * and @returns the decoration or undefined if * the marker has already been disposed of */ - private _showResultDecoration(result: ISearchResult): IDecoration | undefined { + private _showResultDecoration(result: ISearchResult, color: string): IDecoration | undefined { const terminal = this._terminal!; const marker = terminal.registerMarker(undefined, result.row); if (!marker) { return undefined; } - // TODO: remove/move? - terminal.options.overviewRulerWidth = 10; - const findResultDecoration = terminal.registerDecoration({ marker, overviewRulerOptions: this._resultDecorations.get(marker.line) && !this._dataChanged ? undefined : { color: 'orange', position: 'center' } }); + + const findResultDecoration = terminal.registerDecoration({ marker, overviewRulerOptions: this._resultDecorations.get(marker.line) && !this._dataChanged ? undefined : { color, position: 'center' } }); findResultDecoration?.onRender((e) => { if (!e.classList.contains('xterm-find-result-decoration') && result.term.length && e.clientWidth > 0) { e.classList.add('xterm-find-result-decoration'); diff --git a/demo/client.ts b/demo/client.ts index 692ad8e5..404435c4 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -108,7 +108,9 @@ function getSearchOptions(e: KeyboardEvent): ISearchOptions { regex: (document.getElementById('regex') as HTMLInputElement).checked, wholeWord: (document.getElementById('whole-word') as HTMLInputElement).checked, caseSensitive: (document.getElementById('case-sensitive') as HTMLInputElement).checked, - incremental: e.key !== `Enter` + incremental: e.key !== `Enter`, + overviewRulerResultDecorationColor: '#555753', + overviewRulerSelectionDecorationColor: '#ef2929' }; } @@ -202,6 +204,7 @@ function createTerminal(): void { addDomListener(paddingElement, 'change', setPadding); addDomListener(actionElements.find, 'keyup', (e) => { + term.options.overviewRulerWidth = 10; addons.search.instance.find(actionElements.find.value, getSearchOptions(e)); }); From 01fd14867327ba241de363a1beff956eb7a1e9df Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 18 Mar 2022 14:46:49 -0400 Subject: [PATCH 124/245] clean up --- addons/xterm-addon-search/src/SearchAddon.ts | 6 +++--- css/xterm.css | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/addons/xterm-addon-search/src/SearchAddon.ts b/addons/xterm-addon-search/src/SearchAddon.ts index 00e6830f..32441f1c 100644 --- a/addons/xterm-addon-search/src/SearchAddon.ts +++ b/addons/xterm-addon-search/src/SearchAddon.ts @@ -62,7 +62,6 @@ export class SearchAddon implements ITerminalAddon { public activate(terminal: Terminal): void { this._terminal = terminal; - // TODO: should this be using on buffer changed instead? this._terminal.onData(() => this._dataChanged = true); } @@ -578,8 +577,9 @@ export class SearchAddon implements ITerminalAddon { // decoration's clientWidth = actualCellWidth e.style.left = `${e.clientWidth * result.col}px`; e.style.width = `${e.clientWidth * result.term.length}px`; - } - }); + e.style.backgroundColor = color; + e.style.color = color; + }}); return findResultDecoration; } } diff --git a/css/xterm.css b/css/xterm.css index f8f85f9e..aa58915c 100644 --- a/css/xterm.css +++ b/css/xterm.css @@ -180,9 +180,9 @@ } .xterm-find-result-decoration { - background-color: grey; opacity: 60%; } + .xterm-decoration-overview-ruler { z-index: 7; position: absolute; From 8e68c67a0c57d2e0a12cbb5078c8095e1b353689 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 18 Mar 2022 15:07:33 -0400 Subject: [PATCH 125/245] applyStyles --- addons/xterm-addon-search/src/SearchAddon.ts | 25 +++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/addons/xterm-addon-search/src/SearchAddon.ts b/addons/xterm-addon-search/src/SearchAddon.ts index 32441f1c..8a8acf06 100644 --- a/addons/xterm-addon-search/src/SearchAddon.ts +++ b/addons/xterm-addon-search/src/SearchAddon.ts @@ -546,6 +546,7 @@ export class SearchAddon implements ITerminalAddon { const marker = terminal.registerMarker(undefined, result.row); if (marker) { this._selectedDecoration = terminal.registerDecoration({ marker, overviewRulerOptions: { color } }); + this._selectedDecoration?.onRender((e) => this._applyStyles(e, color, result)); } } @@ -558,6 +559,20 @@ export class SearchAddon implements ITerminalAddon { return true; } + private _applyStyles(element: HTMLElement, color: string, result: ISearchResult): void { + if (element.clientWidth <= 0) { + return; + } + if (!element.classList.contains('xterm-find-result-decoration')) { + element.classList.add('xterm-find-result-decoration'); + // decoration's clientWidth = actualCellWidth + element.style.left = `${element.clientWidth * result.col}px`; + element.style.width = `${element.clientWidth * result.term.length}px`; + element.style.backgroundColor = color; + element.style.color = color; + } + } + /** * Registers a decoration for the @param result * and @returns the decoration or undefined if @@ -571,15 +586,7 @@ export class SearchAddon implements ITerminalAddon { } const findResultDecoration = terminal.registerDecoration({ marker, overviewRulerOptions: this._resultDecorations.get(marker.line) && !this._dataChanged ? undefined : { color, position: 'center' } }); - findResultDecoration?.onRender((e) => { - if (!e.classList.contains('xterm-find-result-decoration') && result.term.length && e.clientWidth > 0) { - e.classList.add('xterm-find-result-decoration'); - // decoration's clientWidth = actualCellWidth - e.style.left = `${e.clientWidth * result.col}px`; - e.style.width = `${e.clientWidth * result.term.length}px`; - e.style.backgroundColor = color; - e.style.color = color; - }}); + findResultDecoration?.onRender((e) => this._applyStyles(e, color, result)); return findResultDecoration; } } From 66dcd1694c915bcf56e08b354d0037407b0e4194 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 18 Mar 2022 15:13:49 -0400 Subject: [PATCH 126/245] clean up jsdoc --- addons/xterm-addon-search/src/SearchAddon.ts | 21 +++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/addons/xterm-addon-search/src/SearchAddon.ts b/addons/xterm-addon-search/src/SearchAddon.ts index 8a8acf06..d6ec1bb7 100644 --- a/addons/xterm-addon-search/src/SearchAddon.ts +++ b/addons/xterm-addon-search/src/SearchAddon.ts @@ -120,7 +120,7 @@ export class SearchAddon implements ITerminalAddon { } if (searchOptions.overviewRulerResultDecorationColor && searchOptions.overviewRulerSelectionDecorationColor) { this._searchResults.forEach(result => { - const resultDecoration = this._showResultDecoration(result, searchOptions!.overviewRulerResultDecorationColor!); + const resultDecoration = this._createResultDecoration(result, searchOptions!.overviewRulerResultDecorationColor!); if (resultDecoration) { const decorationsForLine = this._resultDecorations.get(resultDecoration.marker.line) || []; decorationsForLine.push(resultDecoration); @@ -532,7 +532,7 @@ export class SearchAddon implements ITerminalAddon { /** * Selects and scrolls to a result. * @param result The result to select. - * @return Whethera result was selected. + * @return Whether a result was selected. */ private _selectResult(result: ISearchResult | undefined, color?: string): boolean { const terminal = this._terminal!; @@ -559,6 +559,13 @@ export class SearchAddon implements ITerminalAddon { return true; } + /** + * Applies styles to the decoration when it is rendered + * @param element the decoration's element + * @param color the color to apply + * @param result the search result associated with the decoration + * @returns + */ private _applyStyles(element: HTMLElement, color: string, result: ISearchResult): void { if (element.clientWidth <= 0) { return; @@ -574,17 +581,17 @@ export class SearchAddon implements ITerminalAddon { } /** - * Registers a decoration for the @param result - * and @returns the decoration or undefined if - * the marker has already been disposed of + * Creates a decoration for the result and applies styles + * @param result the search result for which to create the decoration + * @param color the color to use for the decoration + * @returns the {@link IDecoration} or undefined if the marker has already been disposed of */ - private _showResultDecoration(result: ISearchResult, color: string): IDecoration | undefined { + private _createResultDecoration(result: ISearchResult, color: string): IDecoration | undefined { const terminal = this._terminal!; const marker = terminal.registerMarker(undefined, result.row); if (!marker) { return undefined; } - const findResultDecoration = terminal.registerDecoration({ marker, overviewRulerOptions: this._resultDecorations.get(marker.line) && !this._dataChanged ? undefined : { color, position: 'center' } }); findResultDecoration?.onRender((e) => this._applyStyles(e, color, result)); return findResultDecoration; From 15c2432b6fb20594806c74d900437169012acd44 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 18 Mar 2022 15:23:49 -0400 Subject: [PATCH 127/245] jsdoc --- typings/xterm.d.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 363cede3..451da2e8 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -954,6 +954,7 @@ declare module 'xterm' { * (EXPERIMENTAL) Adds a marker to the normal buffer and returns it. If the * alt buffer is active, undefined is returned. * @param cursorYOffset The y position offset of the marker from the cursor. + * @param row The y position of the marker. * @returns The new marker or undefined. */ registerMarker(cursorYOffset?: number, row?: number): IMarker | undefined; From 85683a457d119bc156ca1b8543d457e1f3324896 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 18 Mar 2022 15:27:50 -0400 Subject: [PATCH 128/245] add to col, not row --- addons/xterm-addon-search/src/SearchAddon.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/addons/xterm-addon-search/src/SearchAddon.ts b/addons/xterm-addon-search/src/SearchAddon.ts index d6ec1bb7..201c7420 100644 --- a/addons/xterm-addon-search/src/SearchAddon.ts +++ b/addons/xterm-addon-search/src/SearchAddon.ts @@ -100,8 +100,8 @@ export class SearchAddon implements ITerminalAddon { // set start row and col to avoid redoing work const key = Array.from(this._searchResults.keys()).pop()?.split('-'); if (key?.length === 2) { - searchOptions.startRow = Number.parseInt(key[0]) + 1; - searchOptions.startCol = Number.parseInt(key[1]); + searchOptions.startRow = Number.parseInt(key[0]); + searchOptions.startCol = Number.parseInt(key[1]) + 1; } } else { // new search, clear out the old decorations From dd503785bef8d68dc9450f68a7f6da36b27f6cc9 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Fri, 18 Mar 2022 15:28:16 -0400 Subject: [PATCH 129/245] Update css/xterm.css Co-authored-by: Daniel Imms <2193314+Tyriar@users.noreply.github.com> --- css/xterm.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/css/xterm.css b/css/xterm.css index aa58915c..b9382ccc 100644 --- a/css/xterm.css +++ b/css/xterm.css @@ -180,7 +180,7 @@ } .xterm-find-result-decoration { - opacity: 60%; + opacity: 0.6; } .xterm-decoration-overview-ruler { From 8328fcac52ec64320a57a41defd93a4d5634e037 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 18 Mar 2022 15:32:24 -0400 Subject: [PATCH 130/245] track disposable --- addons/xterm-addon-search/src/SearchAddon.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/addons/xterm-addon-search/src/SearchAddon.ts b/addons/xterm-addon-search/src/SearchAddon.ts index 201c7420..f0d64eb9 100644 --- a/addons/xterm-addon-search/src/SearchAddon.ts +++ b/addons/xterm-addon-search/src/SearchAddon.ts @@ -50,6 +50,7 @@ export class SearchAddon implements ITerminalAddon { private _selectedDecoration: IDecoration | undefined; private _resultDecorations: Map = new Map(); private _searchResults: Map = new Map(); + private _onDataDisposable: IDisposable | undefined; /** * translateBufferLineToStringWithWrap is a fairly expensive call. * We memoize the calls into an array that has a time based ttl. @@ -62,10 +63,13 @@ export class SearchAddon implements ITerminalAddon { public activate(terminal: Terminal): void { this._terminal = terminal; - this._terminal.onData(() => this._dataChanged = true); + this._onDataDisposable = this._terminal.onData(() => this._dataChanged = true); } - public dispose(): void { } + public dispose(): void { + this.clear(); + this._onDataDisposable?.dispose(); + } public clear(): void { this._terminal?.clearSelection(); @@ -576,7 +580,6 @@ export class SearchAddon implements ITerminalAddon { element.style.left = `${element.clientWidth * result.col}px`; element.style.width = `${element.clientWidth * result.term.length}px`; element.style.backgroundColor = color; - element.style.color = color; } } From fef30bdba457b03e2d0f7c204b1e127449086dba Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 18 Mar 2022 15:38:31 -0400 Subject: [PATCH 131/245] revert row register marker changes --- addons/xterm-addon-search/src/SearchAddon.ts | 4 ++-- src/browser/Terminal.ts | 5 +---- src/browser/Types.d.ts | 2 +- src/browser/public/Terminal.ts | 7 ++----- typings/xterm.d.ts | 3 +-- 5 files changed, 7 insertions(+), 14 deletions(-) diff --git a/addons/xterm-addon-search/src/SearchAddon.ts b/addons/xterm-addon-search/src/SearchAddon.ts index f0d64eb9..90749e92 100644 --- a/addons/xterm-addon-search/src/SearchAddon.ts +++ b/addons/xterm-addon-search/src/SearchAddon.ts @@ -547,7 +547,7 @@ export class SearchAddon implements ITerminalAddon { } terminal.select(result.col, result.row, result.size); if (color) { - const marker = terminal.registerMarker(undefined, result.row); + const marker = terminal.registerMarker(-terminal.buffer.active.baseY - terminal.buffer.active.cursorY + result.row); if (marker) { this._selectedDecoration = terminal.registerDecoration({ marker, overviewRulerOptions: { color } }); this._selectedDecoration?.onRender((e) => this._applyStyles(e, color, result)); @@ -591,7 +591,7 @@ export class SearchAddon implements ITerminalAddon { */ private _createResultDecoration(result: ISearchResult, color: string): IDecoration | undefined { const terminal = this._terminal!; - const marker = terminal.registerMarker(undefined, result.row); + const marker = terminal.registerMarker(-terminal.buffer.active.baseY - terminal.buffer.active.cursorY + result.row); if (!marker) { return undefined; } diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index 496b8dfa..5803e8c4 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -1012,14 +1012,11 @@ export class Terminal extends CoreTerminal implements ITerminal { return this.buffer.markers; } - public addMarker(cursorYOffset: number, row?: number): IMarker | undefined { + public addMarker(cursorYOffset: number): IMarker | undefined { // Disallow markers on the alt buffer if (this.buffer !== this.buffers.normal) { return; } - if (row) { - return this.buffer.addMarker(row); - } return this.buffer.addMarker(this.buffer.ybase + this.buffer.y + cursorYOffset); } diff --git a/src/browser/Types.d.ts b/src/browser/Types.d.ts index 6e98eff6..8860bb41 100644 --- a/src/browser/Types.d.ts +++ b/src/browser/Types.d.ts @@ -61,7 +61,7 @@ export interface IPublicTerminal extends IDisposable { registerLinkProvider(linkProvider: ILinkProvider): IDisposable; registerCharacterJoiner(handler: (text: string) => [number, number][]): number; deregisterCharacterJoiner(joinerId: number): void; - addMarker(cursorYOffset: number, col?: number, row?: number): IMarker | undefined; + addMarker(cursorYOffset: number): IMarker | undefined; registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined; hasSelection(): boolean; getSelection(): string; diff --git a/src/browser/public/Terminal.ts b/src/browser/public/Terminal.ts index 8c290fda..1acde934 100644 --- a/src/browser/public/Terminal.ts +++ b/src/browser/public/Terminal.ts @@ -166,13 +166,10 @@ export class Terminal implements ITerminalApi { this._checkProposedApi(); this._core.deregisterCharacterJoiner(joinerId); } - public registerMarker(cursorYOffset: number = 0, row?: number): IMarker | undefined { + public registerMarker(cursorYOffset: number = 0): IMarker | undefined { this._checkProposedApi(); this._verifyIntegers(cursorYOffset); - if (row) { - this._verifyPositiveIntegers(row); - } - return this._core.addMarker(cursorYOffset, row); + return this._core.addMarker(cursorYOffset); } public registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined { this._checkProposedApi(); diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 451da2e8..d1eb3890 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -954,10 +954,9 @@ declare module 'xterm' { * (EXPERIMENTAL) Adds a marker to the normal buffer and returns it. If the * alt buffer is active, undefined is returned. * @param cursorYOffset The y position offset of the marker from the cursor. - * @param row The y position of the marker. * @returns The new marker or undefined. */ - registerMarker(cursorYOffset?: number, row?: number): IMarker | undefined; + registerMarker(cursorYOffset?: number): IMarker | undefined; /** * @deprecated use `registerMarker` instead. From 61e5c10fd30b73f73f9ebb6714582e4f26dd0907 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 18 Mar 2022 19:07:39 -0400 Subject: [PATCH 132/245] pull apart and refactor findNext --- addons/xterm-addon-search/src/SearchAddon.ts | 96 ++++++++++---------- demo/client.ts | 8 +- demo/index.html | 2 +- typings/xterm.d.ts | 12 +++ 4 files changed, 63 insertions(+), 55 deletions(-) diff --git a/addons/xterm-addon-search/src/SearchAddon.ts b/addons/xterm-addon-search/src/SearchAddon.ts index 90749e92..f592b78e 100644 --- a/addons/xterm-addon-search/src/SearchAddon.ts +++ b/addons/xterm-addon-search/src/SearchAddon.ts @@ -10,10 +10,9 @@ export interface ISearchOptions { wholeWord?: boolean; caseSensitive?: boolean; incremental?: boolean; + highlightAllMatches?: boolean; startRow?: number; startCol?: number; - overviewRulerResultDecorationColor?: string; - overviewRulerSelectionDecorationColor?: string; } export interface ISearchPosition { @@ -72,6 +71,7 @@ export class SearchAddon implements ITerminalAddon { } public clear(): void { + this._selectedDecoration?.dispose(); this._terminal?.clearSelection(); this._searchResults.clear(); this._resultDecorations.forEach(decorations => decorations.forEach(d=> d.dispose())); @@ -81,17 +81,23 @@ export class SearchAddon implements ITerminalAddon { } /** - * Find all instances of the term, selecting the next one with each - * enter. If it doesn't exist, do nothing. + * Find the next instance of the term, then scroll to and select it. If it + * doesn't exist, do nothing. * @param term The search term. * @param searchOptions Search options. * @return Whether a result was found. */ - public find(term: string, searchOptions?: ISearchOptions): boolean { + public findNext(term: string, searchOptions?: ISearchOptions): boolean { if (!this._terminal) { throw new Error('Cannot use addon until it has been loaded'); } + return searchOptions?.highlightAllMatches ? this._highlightAllMatches(term, searchOptions) : this._findAndSelectNext(term, searchOptions); + } + private _highlightAllMatches(term: string, searchOptions: ISearchOptions): boolean { + if (!this._terminal) { + throw new Error('cannot find all matches with no terminal'); + } if (!term || term.length === 0) { this.clear(); return false; @@ -99,7 +105,7 @@ export class SearchAddon implements ITerminalAddon { searchOptions = searchOptions || {}; if (term === this._cachedSearchTerm) { if (!this._dataChanged) { - return this.findNext(term, searchOptions); + return this._findAndSelectNext(term, searchOptions); } // set start row and col to avoid redoing work const key = Array.from(this._searchResults.keys()).pop()?.split('-'); @@ -113,51 +119,45 @@ export class SearchAddon implements ITerminalAddon { this._resultDecorations.clear(); this._searchResults.clear(); } - + if (!this._terminal.options.overviewRulerWidth) { + this._terminal.options.overviewRulerWidth = 10; + } + if (!this._terminal.options.findResultDecorationColor) { + this._terminal.options.findResultDecorationColor = '#555753'; + } + if (!this._terminal.options.findResultSelectedDecorationColor) { + this._terminal.options.findResultSelectedDecorationColor = '#ef2929'; + } searchOptions.incremental = false; - let found = this.findNext(term, searchOptions); + let found = this._findAndSelectNext(term, searchOptions); while (found && (!this._result || !this._searchResults.get(`${this._result.row}-${this._result.col}`))) { if (this._result) { this._searchResults.set(`${this._result.row}-${this._result.col}`, this._result); } - found = this.findNext(term, searchOptions); + found = this._findAndSelectNext(term, searchOptions); } - if (searchOptions.overviewRulerResultDecorationColor && searchOptions.overviewRulerSelectionDecorationColor) { - this._searchResults.forEach(result => { - const resultDecoration = this._createResultDecoration(result, searchOptions!.overviewRulerResultDecorationColor!); - if (resultDecoration) { - const decorationsForLine = this._resultDecorations.get(resultDecoration.marker.line) || []; - decorationsForLine.push(resultDecoration); - this._resultDecorations.set(resultDecoration.marker.line, decorationsForLine); - } - }); - } - + this._searchResults.forEach(result => { + const resultDecoration = this._createResultDecoration(result); + if (resultDecoration) { + const decorationsForLine = this._resultDecorations.get(resultDecoration.marker.line) || []; + decorationsForLine.push(resultDecoration); + this._resultDecorations.set(resultDecoration.marker.line, decorationsForLine); + } + }); if (this._dataChanged) { this._dataChanged = false; } if (this._searchResults.size > 0) { this._cachedSearchTerm = term; } - return true; + return this._searchResults.size > 0; } - - /** - * Find the next instance of the term, then scroll to and select it. If it - * doesn't exist, do nothing. - * @param term The search term. - * @param searchOptions Search options. - * @return Whether a result was found. - */ - public findNext(term: string, searchOptions?: ISearchOptions): boolean { - if (!this._terminal) { - throw new Error('Cannot use addon until it has been loaded'); - } - - if (!term || term.length === 0) { + private _findAndSelectNext(term: string, searchOptions?: ISearchOptions): boolean { + if (!this._terminal || !term || term.length === 0) { this._result = undefined; - this._terminal.clearSelection(); + this._terminal?.clearSelection(); + this.clear(); return false; } @@ -219,9 +219,8 @@ export class SearchAddon implements ITerminalAddon { } // Set selection and scroll if a result was found - return this._selectResult(this._result, searchOptions?.overviewRulerSelectionDecorationColor); + return this._selectResult(this._result, searchOptions?.highlightAllMatches); } - /** * Find the previous instance of the term, then scroll to and select it. If it * doesn't exist, do nothing. @@ -300,7 +299,7 @@ export class SearchAddon implements ITerminalAddon { if (!result && currentSelection) return true; // Set selection and scroll if a result was found - return this._selectResult(result, searchOptions?.overviewRulerSelectionDecorationColor); + return this._selectResult(result, searchOptions?.highlightAllMatches); } @@ -538,7 +537,7 @@ export class SearchAddon implements ITerminalAddon { * @param result The result to select. * @return Whether a result was selected. */ - private _selectResult(result: ISearchResult | undefined, color?: string): boolean { + private _selectResult(result: ISearchResult | undefined, highlightAllMatches?: boolean): boolean { const terminal = this._terminal!; this._selectedDecoration?.dispose(); if (!result) { @@ -546,11 +545,11 @@ export class SearchAddon implements ITerminalAddon { return false; } terminal.select(result.col, result.row, result.size); - if (color) { + if (this._terminal?.options.findResultSelectedDecorationColor && highlightAllMatches) { const marker = terminal.registerMarker(-terminal.buffer.active.baseY - terminal.buffer.active.cursorY + result.row); if (marker) { - this._selectedDecoration = terminal.registerDecoration({ marker, overviewRulerOptions: { color } }); - this._selectedDecoration?.onRender((e) => this._applyStyles(e, color, result)); + this._selectedDecoration = terminal.registerDecoration({ marker, overviewRulerOptions: { color: this._terminal!.options.findResultSelectedDecorationColor } }); + this._selectedDecoration?.onRender((e) => this._applyStyles(e, this._terminal!.options.findResultSelectedDecorationColor!, result)); } } @@ -589,14 +588,17 @@ export class SearchAddon implements ITerminalAddon { * @param color the color to use for the decoration * @returns the {@link IDecoration} or undefined if the marker has already been disposed of */ - private _createResultDecoration(result: ISearchResult, color: string): IDecoration | undefined { + private _createResultDecoration(result: ISearchResult): IDecoration | undefined { const terminal = this._terminal!; const marker = terminal.registerMarker(-terminal.buffer.active.baseY - terminal.buffer.active.cursorY + result.row); - if (!marker) { + if (!marker || !this._terminal?.options.findResultDecorationColor) { return undefined; } - const findResultDecoration = terminal.registerDecoration({ marker, overviewRulerOptions: this._resultDecorations.get(marker.line) && !this._dataChanged ? undefined : { color, position: 'center' } }); - findResultDecoration?.onRender((e) => this._applyStyles(e, color, result)); + const findResultDecoration = terminal.registerDecoration( + { marker, + overviewRulerOptions: this._resultDecorations.get(marker.line) && !this._dataChanged ? undefined : { color: this._terminal.options.findResultDecorationColor, position: 'center' } + }); + findResultDecoration?.onRender((e) => this._applyStyles(e, this._terminal!.options.findResultDecorationColor!, result)); return findResultDecoration; } } diff --git a/demo/client.ts b/demo/client.ts index 404435c4..98aa2e31 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -109,8 +109,7 @@ function getSearchOptions(e: KeyboardEvent): ISearchOptions { wholeWord: (document.getElementById('whole-word') as HTMLInputElement).checked, caseSensitive: (document.getElementById('case-sensitive') as HTMLInputElement).checked, incremental: e.key !== `Enter`, - overviewRulerResultDecorationColor: '#555753', - overviewRulerSelectionDecorationColor: '#ef2929' + highlightAllMatches: (document.getElementById('highlight-all-matches') as HTMLInputElement).checked, }; } @@ -203,11 +202,6 @@ function createTerminal(): void { addDomListener(paddingElement, 'change', setPadding); - addDomListener(actionElements.find, 'keyup', (e) => { - term.options.overviewRulerWidth = 10; - addons.search.instance.find(actionElements.find.value, getSearchOptions(e)); - }); - addDomListener(actionElements.findNext, 'keyup', (e) => { addons.search.instance.findNext(actionElements.findNext.value, getSearchOptions(e)); }); diff --git a/demo/index.html b/demo/index.html index 13748e9b..b89726de 100644 --- a/demo/index.html +++ b/demo/index.html @@ -38,12 +38,12 @@

Addons Control

SearchAddon

- +

SerializeAddon

diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index d1eb3890..3513feb7 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -272,6 +272,18 @@ declare module 'xterm' { * ruler will be hidden when not set. */ overviewRulerWidth?: number; + + /** + * The color for all find result decorations + * in the overview ruler + */ + findResultDecorationColor?: string; + + /** + * The color for the currently selected decoration + * when all matches are displayed + */ + findResultSelectedDecorationColor?: string; } /** From e79c21d4b2c490f0d9a70263a91f20efe6fe5007 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 18 Mar 2022 19:24:27 -0400 Subject: [PATCH 133/245] get it to work for previous too --- addons/xterm-addon-search/src/SearchAddon.ts | 53 +++++++++++--------- 1 file changed, 30 insertions(+), 23 deletions(-) diff --git a/addons/xterm-addon-search/src/SearchAddon.ts b/addons/xterm-addon-search/src/SearchAddon.ts index f592b78e..627c4a63 100644 --- a/addons/xterm-addon-search/src/SearchAddon.ts +++ b/addons/xterm-addon-search/src/SearchAddon.ts @@ -91,10 +91,10 @@ export class SearchAddon implements ITerminalAddon { if (!this._terminal) { throw new Error('Cannot use addon until it has been loaded'); } - return searchOptions?.highlightAllMatches ? this._highlightAllMatches(term, searchOptions) : this._findAndSelectNext(term, searchOptions); + return searchOptions?.highlightAllMatches ? this._highlightAllMatches(term, searchOptions, 'next') : this._findAndSelectNext(term, searchOptions); } - private _highlightAllMatches(term: string, searchOptions: ISearchOptions): boolean { + private _highlightAllMatches(term: string, searchOptions: ISearchOptions, type: 'next' | 'previous'): boolean { if (!this._terminal) { throw new Error('cannot find all matches with no terminal'); } @@ -129,12 +129,12 @@ export class SearchAddon implements ITerminalAddon { this._terminal.options.findResultSelectedDecorationColor = '#ef2929'; } searchOptions.incremental = false; - let found = this._findAndSelectNext(term, searchOptions); + let found = type === 'next' ? this._findAndSelectNext(term, searchOptions) : this._findAndSelectPrevious(term, searchOptions); while (found && (!this._result || !this._searchResults.get(`${this._result.row}-${this._result.col}`))) { if (this._result) { this._searchResults.set(`${this._result.row}-${this._result.col}`, this._result); } - found = this._findAndSelectNext(term, searchOptions); + found = type === 'next' ? this._findAndSelectNext(term, searchOptions) : this._findAndSelectPrevious(term, searchOptions); } this._searchResults.forEach(result => { const resultDecoration = this._createResultDecoration(result); @@ -232,16 +232,24 @@ export class SearchAddon implements ITerminalAddon { if (!this._terminal) { throw new Error('Cannot use addon until it has been loaded'); } + return searchOptions?.highlightAllMatches ? this._highlightAllMatches(term, searchOptions, 'previous') : this._findAndSelectPrevious(term, searchOptions); + } - if (!term || term.length === 0) { - this._terminal.clearSelection(); + private _findAndSelectPrevious(term: string, searchOptions?: ISearchOptions): boolean { + if (!this._terminal) { + throw new Error('Cannot use addon until it has been loaded'); + } + + if (!this._terminal || !term || term.length === 0) { + this._result = undefined; + this._terminal?.clearSelection(); + this.clear(); return false; } const isReverseSearch = true; - let startRow = this._terminal.buffer.active.baseY + this._terminal.rows; - let startCol = this._terminal.cols; - let result: ISearchResult | undefined; + let startRow = searchOptions?.startRow || this._terminal.buffer.active.baseY + this._terminal.rows; + let startCol = searchOptions?.startCol || this._terminal.cols; const incremental = searchOptions ? searchOptions.incremental : false; let currentSelection: ISelectionPosition | undefined; if (this._terminal.hasSelection()) { @@ -259,50 +267,49 @@ export class SearchAddon implements ITerminalAddon { if (incremental) { // Try to expand selection to right first. - result = this._findInLine(term, searchPosition, searchOptions, false); - const isOldResultHighlighted = result && result.row === startRow && result.col === startCol; + this._result = this._findInLine(term, searchPosition, searchOptions, false); + const isOldResultHighlighted = this._result && this._result.row === startRow && this._result.col === startCol; if (!isOldResultHighlighted) { // If selection was not able to be expanded to the right, then try reverse search if (currentSelection) { searchPosition.startRow = currentSelection.endRow; searchPosition.startCol = currentSelection.endColumn; } - result = this._findInLine(term, searchPosition, searchOptions, true); + this._result = this._findInLine(term, searchPosition, searchOptions, true); } } else { - result = this._findInLine(term, searchPosition, searchOptions, isReverseSearch); + this._result = this._findInLine(term, searchPosition, searchOptions, isReverseSearch); } // Search from startRow - 1 to top - if (!result) { + if (!this._result) { searchPosition.startCol = Math.max(searchPosition.startCol, this._terminal.cols); for (let y = startRow - 1; y >= 0; y--) { searchPosition.startRow = y; - result = this._findInLine(term, searchPosition, searchOptions, isReverseSearch); - if (result) { + this._result = this._findInLine(term, searchPosition, searchOptions, isReverseSearch); + if (this._result) { break; } } } // If we hit the top and didn't search from the very bottom wrap back down - if (!result && startRow !== (this._terminal.buffer.active.baseY + this._terminal.rows)) { + if (!this._result && startRow !== (this._terminal.buffer.active.baseY + this._terminal.rows)) { for (let y = (this._terminal.buffer.active.baseY + this._terminal.rows); y >= startRow; y--) { searchPosition.startRow = y; - result = this._findInLine(term, searchPosition, searchOptions, isReverseSearch); - if (result) { + this._result = this._findInLine(term, searchPosition, searchOptions, isReverseSearch); + if (this._result) { break; } } } // If there is only one result, return true. - if (!result && currentSelection) return true; + if (!this._result && currentSelection) return true; - // Set selection and scroll if a result was found - return this._selectResult(result, searchOptions?.highlightAllMatches); + // Set selection and scroll if a this._result was found + return this._selectResult(this._result, searchOptions?.highlightAllMatches); } - /** * Sets up a line cache with a ttl */ From a0aae62cb7f1344bed280bbe15602cc9f6fe7ec4 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 18 Mar 2022 19:43:00 -0400 Subject: [PATCH 134/245] get rid of start col end col --- addons/xterm-addon-search/src/SearchAddon.ts | 39 ++++++++++++++------ 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/addons/xterm-addon-search/src/SearchAddon.ts b/addons/xterm-addon-search/src/SearchAddon.ts index 627c4a63..422f060b 100644 --- a/addons/xterm-addon-search/src/SearchAddon.ts +++ b/addons/xterm-addon-search/src/SearchAddon.ts @@ -11,8 +11,6 @@ export interface ISearchOptions { caseSensitive?: boolean; incremental?: boolean; highlightAllMatches?: boolean; - startRow?: number; - startCol?: number; } export interface ISearchPosition { @@ -50,6 +48,7 @@ export class SearchAddon implements ITerminalAddon { private _resultDecorations: Map = new Map(); private _searchResults: Map = new Map(); private _onDataDisposable: IDisposable | undefined; + private _addToPrior: boolean = false; /** * translateBufferLineToStringWithWrap is a fairly expensive call. * We memoize the calls into an array that has a time based ttl. @@ -107,12 +106,7 @@ export class SearchAddon implements ITerminalAddon { if (!this._dataChanged) { return this._findAndSelectNext(term, searchOptions); } - // set start row and col to avoid redoing work - const key = Array.from(this._searchResults.keys()).pop()?.split('-'); - if (key?.length === 2) { - searchOptions.startRow = Number.parseInt(key[0]); - searchOptions.startCol = Number.parseInt(key[1]) + 1; - } + this._addToPrior = true; } else { // new search, clear out the old decorations this._resultDecorations.forEach(decorations => decorations.forEach(d=> d.dispose())); @@ -147,6 +141,9 @@ export class SearchAddon implements ITerminalAddon { if (this._dataChanged) { this._dataChanged = false; } + if (this._addToPrior) { + this._addToPrior = false; + } if (this._searchResults.size > 0) { this._cachedSearchTerm = term; } @@ -161,8 +158,16 @@ export class SearchAddon implements ITerminalAddon { return false; } - let startCol = searchOptions?.startCol || 0; - let startRow = searchOptions?.startRow || 0; + let startCol = 0; + let startRow = 0; + if (searchOptions?.highlightAllMatches && this._addToPrior) { + // set start row and col to avoid redoing work + const key = Array.from(this._searchResults.keys()).pop()?.split('-'); + if (key?.length === 2) { + startRow = Number.parseInt(key[0]); + startCol = Number.parseInt(key[1]) + 1; + } + } let currentSelection: ISelectionPosition | undefined; if (this._terminal.hasSelection()) { const incremental = searchOptions ? searchOptions.incremental : false; @@ -247,9 +252,19 @@ export class SearchAddon implements ITerminalAddon { return false; } + let startRow = this._terminal.buffer.active.baseY + this._terminal.rows; + let startCol = this._terminal.cols; const isReverseSearch = true; - let startRow = searchOptions?.startRow || this._terminal.buffer.active.baseY + this._terminal.rows; - let startCol = searchOptions?.startCol || this._terminal.cols; + // if (searchOptions?.highlightAllMatches && this._addToPrior) { + // // set start row and col to avoid redoing work + // // TODO: fix this will mess with the order that they're iterated through + // const key = Array.from(this._searchResults.keys()).pop()?.split('-'); + // if (key?.length === 2) { + // startRow = Number.parseInt(key[0]); + // startCol = Number.parseInt(key[1]) + 1; + // } + // } + const incremental = searchOptions ? searchOptions.incremental : false; let currentSelection: ISelectionPosition | undefined; if (this._terminal.hasSelection()) { From 8225ecd3bad9b66df1e7341822c1a34c54bf8e96 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 21 Mar 2022 10:24:46 -0400 Subject: [PATCH 135/245] refactor from highlightAllMatches -> decorations --- addons/xterm-addon-search/src/SearchAddon.ts | 41 ++++++++++---------- demo/client.ts | 2 +- typings/xterm.d.ts | 12 ------ 3 files changed, 21 insertions(+), 34 deletions(-) diff --git a/addons/xterm-addon-search/src/SearchAddon.ts b/addons/xterm-addon-search/src/SearchAddon.ts index 422f060b..f7cf5175 100644 --- a/addons/xterm-addon-search/src/SearchAddon.ts +++ b/addons/xterm-addon-search/src/SearchAddon.ts @@ -10,7 +10,12 @@ export interface ISearchOptions { wholeWord?: boolean; caseSensitive?: boolean; incremental?: boolean; - highlightAllMatches?: boolean; + decorations?: IDecorationColor; +} + +interface IDecorationColor { + matchColor: string; + selectedColor: string; } export interface ISearchPosition { @@ -90,7 +95,7 @@ export class SearchAddon implements ITerminalAddon { if (!this._terminal) { throw new Error('Cannot use addon until it has been loaded'); } - return searchOptions?.highlightAllMatches ? this._highlightAllMatches(term, searchOptions, 'next') : this._findAndSelectNext(term, searchOptions); + return searchOptions?.decorations ? this._highlightAllMatches(term, searchOptions, 'next') : this._findAndSelectNext(term, searchOptions); } private _highlightAllMatches(term: string, searchOptions: ISearchOptions, type: 'next' | 'previous'): boolean { @@ -116,12 +121,6 @@ export class SearchAddon implements ITerminalAddon { if (!this._terminal.options.overviewRulerWidth) { this._terminal.options.overviewRulerWidth = 10; } - if (!this._terminal.options.findResultDecorationColor) { - this._terminal.options.findResultDecorationColor = '#555753'; - } - if (!this._terminal.options.findResultSelectedDecorationColor) { - this._terminal.options.findResultSelectedDecorationColor = '#ef2929'; - } searchOptions.incremental = false; let found = type === 'next' ? this._findAndSelectNext(term, searchOptions) : this._findAndSelectPrevious(term, searchOptions); while (found && (!this._result || !this._searchResults.get(`${this._result.row}-${this._result.col}`))) { @@ -131,7 +130,7 @@ export class SearchAddon implements ITerminalAddon { found = type === 'next' ? this._findAndSelectNext(term, searchOptions) : this._findAndSelectPrevious(term, searchOptions); } this._searchResults.forEach(result => { - const resultDecoration = this._createResultDecoration(result); + const resultDecoration = this._createResultDecoration(result, searchOptions.decorations); if (resultDecoration) { const decorationsForLine = this._resultDecorations.get(resultDecoration.marker.line) || []; decorationsForLine.push(resultDecoration); @@ -160,7 +159,7 @@ export class SearchAddon implements ITerminalAddon { let startCol = 0; let startRow = 0; - if (searchOptions?.highlightAllMatches && this._addToPrior) { + if (searchOptions?.decorations && this._addToPrior) { // set start row and col to avoid redoing work const key = Array.from(this._searchResults.keys()).pop()?.split('-'); if (key?.length === 2) { @@ -224,7 +223,7 @@ export class SearchAddon implements ITerminalAddon { } // Set selection and scroll if a result was found - return this._selectResult(this._result, searchOptions?.highlightAllMatches); + return this._selectResult(this._result, searchOptions?.decorations); } /** * Find the previous instance of the term, then scroll to and select it. If it @@ -237,7 +236,7 @@ export class SearchAddon implements ITerminalAddon { if (!this._terminal) { throw new Error('Cannot use addon until it has been loaded'); } - return searchOptions?.highlightAllMatches ? this._highlightAllMatches(term, searchOptions, 'previous') : this._findAndSelectPrevious(term, searchOptions); + return searchOptions?.decorations ? this._highlightAllMatches(term, searchOptions, 'previous') : this._findAndSelectPrevious(term, searchOptions); } private _findAndSelectPrevious(term: string, searchOptions?: ISearchOptions): boolean { @@ -322,7 +321,7 @@ export class SearchAddon implements ITerminalAddon { if (!this._result && currentSelection) return true; // Set selection and scroll if a this._result was found - return this._selectResult(this._result, searchOptions?.highlightAllMatches); + return this._selectResult(this._result, searchOptions?.decorations); } /** @@ -559,7 +558,7 @@ export class SearchAddon implements ITerminalAddon { * @param result The result to select. * @return Whether a result was selected. */ - private _selectResult(result: ISearchResult | undefined, highlightAllMatches?: boolean): boolean { + private _selectResult(result: ISearchResult | undefined, decorations?: IDecorationColor): boolean { const terminal = this._terminal!; this._selectedDecoration?.dispose(); if (!result) { @@ -567,11 +566,11 @@ export class SearchAddon implements ITerminalAddon { return false; } terminal.select(result.col, result.row, result.size); - if (this._terminal?.options.findResultSelectedDecorationColor && highlightAllMatches) { + if (decorations?.selectedColor) { const marker = terminal.registerMarker(-terminal.buffer.active.baseY - terminal.buffer.active.cursorY + result.row); if (marker) { - this._selectedDecoration = terminal.registerDecoration({ marker, overviewRulerOptions: { color: this._terminal!.options.findResultSelectedDecorationColor } }); - this._selectedDecoration?.onRender((e) => this._applyStyles(e, this._terminal!.options.findResultSelectedDecorationColor!, result)); + this._selectedDecoration = terminal.registerDecoration({ marker, overviewRulerOptions: { color: decorations.selectedColor } }); + this._selectedDecoration?.onRender((e) => this._applyStyles(e, decorations.selectedColor, result)); } } @@ -610,17 +609,17 @@ export class SearchAddon implements ITerminalAddon { * @param color the color to use for the decoration * @returns the {@link IDecoration} or undefined if the marker has already been disposed of */ - private _createResultDecoration(result: ISearchResult): IDecoration | undefined { + private _createResultDecoration(result: ISearchResult, decorations?: IDecorationColor): IDecoration | undefined { const terminal = this._terminal!; const marker = terminal.registerMarker(-terminal.buffer.active.baseY - terminal.buffer.active.cursorY + result.row); - if (!marker || !this._terminal?.options.findResultDecorationColor) { + if (!marker || !decorations?.matchColor) { return undefined; } const findResultDecoration = terminal.registerDecoration( { marker, - overviewRulerOptions: this._resultDecorations.get(marker.line) && !this._dataChanged ? undefined : { color: this._terminal.options.findResultDecorationColor, position: 'center' } + overviewRulerOptions: this._resultDecorations.get(marker.line) && !this._dataChanged ? undefined : { color: decorations.matchColor, position: 'center' } }); - findResultDecoration?.onRender((e) => this._applyStyles(e, this._terminal!.options.findResultDecorationColor!, result)); + findResultDecoration?.onRender((e) => this._applyStyles(e, decorations.matchColor, result)); return findResultDecoration; } } diff --git a/demo/client.ts b/demo/client.ts index 98aa2e31..faf3e0be 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -109,7 +109,7 @@ function getSearchOptions(e: KeyboardEvent): ISearchOptions { wholeWord: (document.getElementById('whole-word') as HTMLInputElement).checked, caseSensitive: (document.getElementById('case-sensitive') as HTMLInputElement).checked, incremental: e.key !== `Enter`, - highlightAllMatches: (document.getElementById('highlight-all-matches') as HTMLInputElement).checked, + decorations: (document.getElementById('highlight-all-matches') as HTMLInputElement).checked ? { matchColor: '#555753', selectedColor: '#ef2929' } : undefined }; } diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 3513feb7..d1eb3890 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -272,18 +272,6 @@ declare module 'xterm' { * ruler will be hidden when not set. */ overviewRulerWidth?: number; - - /** - * The color for all find result decorations - * in the overview ruler - */ - findResultDecorationColor?: string; - - /** - * The color for the currently selected decoration - * when all matches are displayed - */ - findResultSelectedDecorationColor?: string; } /** From 12a999dfe051422006bcd6a69b85dc004d79b8fb Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 21 Mar 2022 10:54:01 -0400 Subject: [PATCH 136/245] get rid of start row/col code --- addons/xterm-addon-search/src/SearchAddon.ts | 42 ++++---------------- 1 file changed, 8 insertions(+), 34 deletions(-) diff --git a/addons/xterm-addon-search/src/SearchAddon.ts b/addons/xterm-addon-search/src/SearchAddon.ts index f7cf5175..7ec081f5 100644 --- a/addons/xterm-addon-search/src/SearchAddon.ts +++ b/addons/xterm-addon-search/src/SearchAddon.ts @@ -53,7 +53,6 @@ export class SearchAddon implements ITerminalAddon { private _resultDecorations: Map = new Map(); private _searchResults: Map = new Map(); private _onDataDisposable: IDisposable | undefined; - private _addToPrior: boolean = false; /** * translateBufferLineToStringWithWrap is a fairly expensive call. * We memoize the calls into an array that has a time based ttl. @@ -107,17 +106,14 @@ export class SearchAddon implements ITerminalAddon { return false; } searchOptions = searchOptions || {}; - if (term === this._cachedSearchTerm) { - if (!this._dataChanged) { - return this._findAndSelectNext(term, searchOptions); - } - this._addToPrior = true; - } else { - // new search, clear out the old decorations - this._resultDecorations.forEach(decorations => decorations.forEach(d=> d.dispose())); - this._resultDecorations.clear(); - this._searchResults.clear(); + if (term === this._cachedSearchTerm && !this._dataChanged) { + return type === 'next' ? this._findAndSelectNext(term, searchOptions) : this._findAndSelectPrevious(term, searchOptions); } + // new search, clear out the old decorations + this._resultDecorations.forEach(decorations => decorations.forEach(d=> d.dispose())); + this._resultDecorations.clear(); + this._searchResults.clear(); + if (!this._terminal.options.overviewRulerWidth) { this._terminal.options.overviewRulerWidth = 10; } @@ -137,12 +133,7 @@ export class SearchAddon implements ITerminalAddon { this._resultDecorations.set(resultDecoration.marker.line, decorationsForLine); } }); - if (this._dataChanged) { - this._dataChanged = false; - } - if (this._addToPrior) { - this._addToPrior = false; - } + this._dataChanged = false; if (this._searchResults.size > 0) { this._cachedSearchTerm = term; } @@ -159,14 +150,6 @@ export class SearchAddon implements ITerminalAddon { let startCol = 0; let startRow = 0; - if (searchOptions?.decorations && this._addToPrior) { - // set start row and col to avoid redoing work - const key = Array.from(this._searchResults.keys()).pop()?.split('-'); - if (key?.length === 2) { - startRow = Number.parseInt(key[0]); - startCol = Number.parseInt(key[1]) + 1; - } - } let currentSelection: ISelectionPosition | undefined; if (this._terminal.hasSelection()) { const incremental = searchOptions ? searchOptions.incremental : false; @@ -254,15 +237,6 @@ export class SearchAddon implements ITerminalAddon { let startRow = this._terminal.buffer.active.baseY + this._terminal.rows; let startCol = this._terminal.cols; const isReverseSearch = true; - // if (searchOptions?.highlightAllMatches && this._addToPrior) { - // // set start row and col to avoid redoing work - // // TODO: fix this will mess with the order that they're iterated through - // const key = Array.from(this._searchResults.keys()).pop()?.split('-'); - // if (key?.length === 2) { - // startRow = Number.parseInt(key[0]); - // startCol = Number.parseInt(key[1]) + 1; - // } - // } const incremental = searchOptions ? searchOptions.incremental : false; let currentSelection: ISelectionPosition | undefined; From bec4cb37d4031dadbbeff495d0d41436d2d03e06 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 21 Mar 2022 10:58:44 -0400 Subject: [PATCH 137/245] set data changed to false if true --- addons/xterm-addon-search/src/SearchAddon.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/addons/xterm-addon-search/src/SearchAddon.ts b/addons/xterm-addon-search/src/SearchAddon.ts index 7ec081f5..33e38e95 100644 --- a/addons/xterm-addon-search/src/SearchAddon.ts +++ b/addons/xterm-addon-search/src/SearchAddon.ts @@ -133,7 +133,9 @@ export class SearchAddon implements ITerminalAddon { this._resultDecorations.set(resultDecoration.marker.line, decorationsForLine); } }); - this._dataChanged = false; + if (this._dataChanged) { + this._dataChanged = false; + } if (this._searchResults.size > 0) { this._cachedSearchTerm = term; } From 8ff34a6aee1e2f083056af15dc4c06822d3c7003 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 21 Mar 2022 11:17:24 -0400 Subject: [PATCH 138/245] update decorations dynamically when the buffer changes --- addons/xterm-addon-search/src/SearchAddon.ts | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/addons/xterm-addon-search/src/SearchAddon.ts b/addons/xterm-addon-search/src/SearchAddon.ts index 33e38e95..457e0853 100644 --- a/addons/xterm-addon-search/src/SearchAddon.ts +++ b/addons/xterm-addon-search/src/SearchAddon.ts @@ -53,6 +53,7 @@ export class SearchAddon implements ITerminalAddon { private _resultDecorations: Map = new Map(); private _searchResults: Map = new Map(); private _onDataDisposable: IDisposable | undefined; + private _lastSearchOptions: ISearchOptions | undefined; /** * translateBufferLineToStringWithWrap is a fairly expensive call. * We memoize the calls into an array that has a time based ttl. @@ -65,7 +66,13 @@ export class SearchAddon implements ITerminalAddon { public activate(terminal: Terminal): void { this._terminal = terminal; - this._onDataDisposable = this._terminal.onData(() => this._dataChanged = true); + this._onDataDisposable = this._terminal.onData(() => { + //TODO: debounce + this._dataChanged = true; + if (this._cachedSearchTerm && this._resultDecorations.size > 0 && this._lastSearchOptions) { + this._highlightAllMatches(this._cachedSearchTerm, this._lastSearchOptions,'previous'); + } + }); } public dispose(): void { @@ -94,10 +101,11 @@ export class SearchAddon implements ITerminalAddon { if (!this._terminal) { throw new Error('Cannot use addon until it has been loaded'); } + this._lastSearchOptions = searchOptions; return searchOptions?.decorations ? this._highlightAllMatches(term, searchOptions, 'next') : this._findAndSelectNext(term, searchOptions); } - private _highlightAllMatches(term: string, searchOptions: ISearchOptions, type: 'next' | 'previous'): boolean { + private _highlightAllMatches(term: string, searchOptions: ISearchOptions, selectionType: 'next' | 'previous'): boolean { if (!this._terminal) { throw new Error('cannot find all matches with no terminal'); } @@ -107,7 +115,7 @@ export class SearchAddon implements ITerminalAddon { } searchOptions = searchOptions || {}; if (term === this._cachedSearchTerm && !this._dataChanged) { - return type === 'next' ? this._findAndSelectNext(term, searchOptions) : this._findAndSelectPrevious(term, searchOptions); + return selectionType === 'next' ? this._findAndSelectNext(term, searchOptions) : this._findAndSelectPrevious(term, searchOptions); } // new search, clear out the old decorations this._resultDecorations.forEach(decorations => decorations.forEach(d=> d.dispose())); @@ -118,12 +126,12 @@ export class SearchAddon implements ITerminalAddon { this._terminal.options.overviewRulerWidth = 10; } searchOptions.incremental = false; - let found = type === 'next' ? this._findAndSelectNext(term, searchOptions) : this._findAndSelectPrevious(term, searchOptions); + let found = selectionType === 'next' ? this._findAndSelectNext(term, searchOptions) : this._findAndSelectPrevious(term, searchOptions); while (found && (!this._result || !this._searchResults.get(`${this._result.row}-${this._result.col}`))) { if (this._result) { this._searchResults.set(`${this._result.row}-${this._result.col}`, this._result); } - found = type === 'next' ? this._findAndSelectNext(term, searchOptions) : this._findAndSelectPrevious(term, searchOptions); + found = selectionType === 'next' ? this._findAndSelectNext(term, searchOptions) : this._findAndSelectPrevious(term, searchOptions); } this._searchResults.forEach(result => { const resultDecoration = this._createResultDecoration(result, searchOptions.decorations); @@ -221,6 +229,7 @@ export class SearchAddon implements ITerminalAddon { if (!this._terminal) { throw new Error('Cannot use addon until it has been loaded'); } + this._lastSearchOptions = searchOptions; return searchOptions?.decorations ? this._highlightAllMatches(term, searchOptions, 'previous') : this._findAndSelectPrevious(term, searchOptions); } From 1098173908af0894e2637467ea9b233debe3c6c5 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 21 Mar 2022 11:45:45 -0400 Subject: [PATCH 139/245] use set timeout --- addons/xterm-addon-search/src/SearchAddon.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/addons/xterm-addon-search/src/SearchAddon.ts b/addons/xterm-addon-search/src/SearchAddon.ts index 457e0853..4bca88cc 100644 --- a/addons/xterm-addon-search/src/SearchAddon.ts +++ b/addons/xterm-addon-search/src/SearchAddon.ts @@ -67,11 +67,12 @@ export class SearchAddon implements ITerminalAddon { public activate(terminal: Terminal): void { this._terminal = terminal; this._onDataDisposable = this._terminal.onData(() => { - //TODO: debounce this._dataChanged = true; - if (this._cachedSearchTerm && this._resultDecorations.size > 0 && this._lastSearchOptions) { - this._highlightAllMatches(this._cachedSearchTerm, this._lastSearchOptions,'previous'); - } + setTimeout(() => { + if (this._cachedSearchTerm && this._resultDecorations.size > 0 && this._lastSearchOptions) { + this._highlightAllMatches(this._cachedSearchTerm, this._lastSearchOptions,'previous'); + } + }, 200); }); } From 97e4af3707e2343d0d8770d3dbed2a03929bbc02 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 21 Mar 2022 13:07:25 -0400 Subject: [PATCH 140/245] only highlight all matches when decorations are requested --- addons/xterm-addon-search/src/SearchAddon.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/xterm-addon-search/src/SearchAddon.ts b/addons/xterm-addon-search/src/SearchAddon.ts index 4bca88cc..6e4b5f9b 100644 --- a/addons/xterm-addon-search/src/SearchAddon.ts +++ b/addons/xterm-addon-search/src/SearchAddon.ts @@ -69,7 +69,7 @@ export class SearchAddon implements ITerminalAddon { this._onDataDisposable = this._terminal.onData(() => { this._dataChanged = true; setTimeout(() => { - if (this._cachedSearchTerm && this._resultDecorations.size > 0 && this._lastSearchOptions) { + if (this._lastSearchOptions?.decorations && this._cachedSearchTerm && this._resultDecorations.size > 0 && this._lastSearchOptions) { this._highlightAllMatches(this._cachedSearchTerm, this._lastSearchOptions,'previous'); } }, 200); From 4cea6b3f4136f5626a00229b68ade83076776971 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 21 Mar 2022 13:35:56 -0400 Subject: [PATCH 141/245] revert a breaking change --- addons/xterm-addon-search/src/SearchAddon.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/addons/xterm-addon-search/src/SearchAddon.ts b/addons/xterm-addon-search/src/SearchAddon.ts index 6e4b5f9b..6b84421d 100644 --- a/addons/xterm-addon-search/src/SearchAddon.ts +++ b/addons/xterm-addon-search/src/SearchAddon.ts @@ -169,9 +169,6 @@ export class SearchAddon implements ITerminalAddon { currentSelection = this._terminal.getSelectionPosition()!; startRow = incremental ? currentSelection.startRow : currentSelection.endRow; startCol = incremental ? currentSelection.startColumn : currentSelection.endColumn; - } else if (!startRow) { - startRow = this._terminal.buffer.active.cursorY; - startCol = this._terminal.buffer.active.cursorX; } this._initLinesCache(); From 64d5af38f5fbea10958c205ebb7556d19f107da0 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Mon, 21 Mar 2022 15:35:01 -0400 Subject: [PATCH 142/245] Update addons/xterm-addon-search/src/SearchAddon.ts Co-authored-by: Daniel Imms <2193314+Tyriar@users.noreply.github.com> --- addons/xterm-addon-search/src/SearchAddon.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/xterm-addon-search/src/SearchAddon.ts b/addons/xterm-addon-search/src/SearchAddon.ts index 6b84421d..c2089f11 100644 --- a/addons/xterm-addon-search/src/SearchAddon.ts +++ b/addons/xterm-addon-search/src/SearchAddon.ts @@ -70,7 +70,7 @@ export class SearchAddon implements ITerminalAddon { this._dataChanged = true; setTimeout(() => { if (this._lastSearchOptions?.decorations && this._cachedSearchTerm && this._resultDecorations.size > 0 && this._lastSearchOptions) { - this._highlightAllMatches(this._cachedSearchTerm, this._lastSearchOptions,'previous'); + this._highlightAllMatches(this._cachedSearchTerm, this._lastSearchOptions, 'previous'); } }, 200); }); From e8aa4eba75522877af05799020b91b7b2829ab76 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 21 Mar 2022 15:36:39 -0400 Subject: [PATCH 143/245] IDecorationColor -> ISearchDecorationOptions --- addons/xterm-addon-search/src/SearchAddon.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/addons/xterm-addon-search/src/SearchAddon.ts b/addons/xterm-addon-search/src/SearchAddon.ts index 6b84421d..bf4d6330 100644 --- a/addons/xterm-addon-search/src/SearchAddon.ts +++ b/addons/xterm-addon-search/src/SearchAddon.ts @@ -10,10 +10,10 @@ export interface ISearchOptions { wholeWord?: boolean; caseSensitive?: boolean; incremental?: boolean; - decorations?: IDecorationColor; + decorations?: ISearchDecorationOptions; } -interface IDecorationColor { +interface ISearchDecorationOptions { matchColor: string; selectedColor: string; } @@ -541,7 +541,7 @@ export class SearchAddon implements ITerminalAddon { * @param result The result to select. * @return Whether a result was selected. */ - private _selectResult(result: ISearchResult | undefined, decorations?: IDecorationColor): boolean { + private _selectResult(result: ISearchResult | undefined, decorations?: ISearchDecorationOptions): boolean { const terminal = this._terminal!; this._selectedDecoration?.dispose(); if (!result) { @@ -592,7 +592,7 @@ export class SearchAddon implements ITerminalAddon { * @param color the color to use for the decoration * @returns the {@link IDecoration} or undefined if the marker has already been disposed of */ - private _createResultDecoration(result: ISearchResult, decorations?: IDecorationColor): IDecoration | undefined { + private _createResultDecoration(result: ISearchResult, decorations?: ISearchDecorationOptions): IDecoration | undefined { const terminal = this._terminal!; const marker = terminal.registerMarker(-terminal.buffer.active.baseY - terminal.buffer.active.cursorY + result.row); if (!marker || !decorations?.matchColor) { From 7a502c6b83ff1fdd32c9f8d76343b680cfa926e8 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 21 Mar 2022 15:43:08 -0400 Subject: [PATCH 144/245] use const enum --- addons/xterm-addon-search/src/SearchAddon.ts | 19 ++++++++++++------- .../typings/xterm-addon-search.d.ts | 8 -------- 2 files changed, 12 insertions(+), 15 deletions(-) diff --git a/addons/xterm-addon-search/src/SearchAddon.ts b/addons/xterm-addon-search/src/SearchAddon.ts index 46c32c7f..2f3fbb6e 100644 --- a/addons/xterm-addon-search/src/SearchAddon.ts +++ b/addons/xterm-addon-search/src/SearchAddon.ts @@ -5,6 +5,11 @@ import { Terminal, IDisposable, ITerminalAddon, ISelectionPosition, IDecoration } from 'xterm'; +const enum SelectionType { + NEXT = 0, + PREVIOUS = 1 +} + export interface ISearchOptions { regex?: boolean; wholeWord?: boolean; @@ -70,7 +75,7 @@ export class SearchAddon implements ITerminalAddon { this._dataChanged = true; setTimeout(() => { if (this._lastSearchOptions?.decorations && this._cachedSearchTerm && this._resultDecorations.size > 0 && this._lastSearchOptions) { - this._highlightAllMatches(this._cachedSearchTerm, this._lastSearchOptions, 'previous'); + this._highlightAllMatches(this._cachedSearchTerm, this._lastSearchOptions, SelectionType.PREVIOUS); } }, 200); }); @@ -103,10 +108,10 @@ export class SearchAddon implements ITerminalAddon { throw new Error('Cannot use addon until it has been loaded'); } this._lastSearchOptions = searchOptions; - return searchOptions?.decorations ? this._highlightAllMatches(term, searchOptions, 'next') : this._findAndSelectNext(term, searchOptions); + return searchOptions?.decorations ? this._highlightAllMatches(term, searchOptions, SelectionType.NEXT) : this._findAndSelectNext(term, searchOptions); } - private _highlightAllMatches(term: string, searchOptions: ISearchOptions, selectionType: 'next' | 'previous'): boolean { + private _highlightAllMatches(term: string, searchOptions: ISearchOptions, selectionType: SelectionType): boolean { if (!this._terminal) { throw new Error('cannot find all matches with no terminal'); } @@ -116,7 +121,7 @@ export class SearchAddon implements ITerminalAddon { } searchOptions = searchOptions || {}; if (term === this._cachedSearchTerm && !this._dataChanged) { - return selectionType === 'next' ? this._findAndSelectNext(term, searchOptions) : this._findAndSelectPrevious(term, searchOptions); + return selectionType === SelectionType.NEXT ? this._findAndSelectNext(term, searchOptions) : this._findAndSelectPrevious(term, searchOptions); } // new search, clear out the old decorations this._resultDecorations.forEach(decorations => decorations.forEach(d=> d.dispose())); @@ -127,12 +132,12 @@ export class SearchAddon implements ITerminalAddon { this._terminal.options.overviewRulerWidth = 10; } searchOptions.incremental = false; - let found = selectionType === 'next' ? this._findAndSelectNext(term, searchOptions) : this._findAndSelectPrevious(term, searchOptions); + let found = selectionType === SelectionType.NEXT ? this._findAndSelectNext(term, searchOptions) : this._findAndSelectPrevious(term, searchOptions); while (found && (!this._result || !this._searchResults.get(`${this._result.row}-${this._result.col}`))) { if (this._result) { this._searchResults.set(`${this._result.row}-${this._result.col}`, this._result); } - found = selectionType === 'next' ? this._findAndSelectNext(term, searchOptions) : this._findAndSelectPrevious(term, searchOptions); + found = selectionType === SelectionType.NEXT ? this._findAndSelectNext(term, searchOptions) : this._findAndSelectPrevious(term, searchOptions); } this._searchResults.forEach(result => { const resultDecoration = this._createResultDecoration(result, searchOptions.decorations); @@ -228,7 +233,7 @@ export class SearchAddon implements ITerminalAddon { throw new Error('Cannot use addon until it has been loaded'); } this._lastSearchOptions = searchOptions; - return searchOptions?.decorations ? this._highlightAllMatches(term, searchOptions, 'previous') : this._findAndSelectPrevious(term, searchOptions); + return searchOptions?.decorations ? this._highlightAllMatches(term, searchOptions, SelectionType.PREVIOUS) : this._findAndSelectPrevious(term, searchOptions); } private _findAndSelectPrevious(term: string, searchOptions?: ISearchOptions): boolean { diff --git a/addons/xterm-addon-search/typings/xterm-addon-search.d.ts b/addons/xterm-addon-search/typings/xterm-addon-search.d.ts index 228a5ffe..ef2948a5 100644 --- a/addons/xterm-addon-search/typings/xterm-addon-search.d.ts +++ b/addons/xterm-addon-search/typings/xterm-addon-search.d.ts @@ -49,14 +49,6 @@ declare module 'xterm-addon-search' { */ public dispose(): void; - /** - * Find all instances of the term, selecting the next one with each - * enter. If it doesn't exist, do nothing. - * @param term The search term. - * @param searchOptions The options for the search. - */ - public find(term: string, searchOptions?: ISearchOptions): boolean; - /** * Search forwards for the next result that matches the search term and * options. From fefdd8a4e9cbb8f8f2216b32b0a5558aafa3f05b Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 21 Mar 2022 15:44:17 -0400 Subject: [PATCH 145/245] clearDecorations --- addons/xterm-addon-search/src/SearchAddon.ts | 10 +++++----- .../xterm-addon-search/typings/xterm-addon-search.d.ts | 5 +++++ 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/addons/xterm-addon-search/src/SearchAddon.ts b/addons/xterm-addon-search/src/SearchAddon.ts index 2f3fbb6e..bc27e4e4 100644 --- a/addons/xterm-addon-search/src/SearchAddon.ts +++ b/addons/xterm-addon-search/src/SearchAddon.ts @@ -82,11 +82,11 @@ export class SearchAddon implements ITerminalAddon { } public dispose(): void { - this.clear(); + this.clearDecorations(); this._onDataDisposable?.dispose(); } - public clear(): void { + public clearDecorations(): void { this._selectedDecoration?.dispose(); this._terminal?.clearSelection(); this._searchResults.clear(); @@ -116,7 +116,7 @@ export class SearchAddon implements ITerminalAddon { throw new Error('cannot find all matches with no terminal'); } if (!term || term.length === 0) { - this.clear(); + this.clearDecorations(); return false; } searchOptions = searchOptions || {}; @@ -160,7 +160,7 @@ export class SearchAddon implements ITerminalAddon { if (!this._terminal || !term || term.length === 0) { this._result = undefined; this._terminal?.clearSelection(); - this.clear(); + this.clearDecorations(); return false; } @@ -244,7 +244,7 @@ export class SearchAddon implements ITerminalAddon { if (!this._terminal || !term || term.length === 0) { this._result = undefined; this._terminal?.clearSelection(); - this.clear(); + this.clearDecorations(); return false; } diff --git a/addons/xterm-addon-search/typings/xterm-addon-search.d.ts b/addons/xterm-addon-search/typings/xterm-addon-search.d.ts index ef2948a5..e58afec0 100644 --- a/addons/xterm-addon-search/typings/xterm-addon-search.d.ts +++ b/addons/xterm-addon-search/typings/xterm-addon-search.d.ts @@ -64,5 +64,10 @@ declare module 'xterm-addon-search' { * @param searchOptions The options for the search. */ public findPrevious(term: string, searchOptions?: ISearchOptions): boolean; + + /** + * Clears the decorations and selection + */ + public clearDecorations(): void; } } From 866ee23df9ab7dd0bd3185255307e6cbba15eb14 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 21 Mar 2022 15:45:45 -0400 Subject: [PATCH 146/245] add disposeDecorations helper --- addons/xterm-addon-search/src/SearchAddon.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/addons/xterm-addon-search/src/SearchAddon.ts b/addons/xterm-addon-search/src/SearchAddon.ts index bc27e4e4..5451b832 100644 --- a/addons/xterm-addon-search/src/SearchAddon.ts +++ b/addons/xterm-addon-search/src/SearchAddon.ts @@ -90,12 +90,16 @@ export class SearchAddon implements ITerminalAddon { this._selectedDecoration?.dispose(); this._terminal?.clearSelection(); this._searchResults.clear(); - this._resultDecorations.forEach(decorations => decorations.forEach(d=> d.dispose())); - this._resultDecorations.clear(); + this._disposeDecorations(); this._cachedSearchTerm = undefined; this._dataChanged = true; } + private _disposeDecorations(): void { + this._resultDecorations.forEach(decorations => decorations.forEach(d=> d.dispose())); + this._resultDecorations.clear(); + } + /** * Find the next instance of the term, then scroll to and select it. If it * doesn't exist, do nothing. @@ -124,8 +128,7 @@ export class SearchAddon implements ITerminalAddon { return selectionType === SelectionType.NEXT ? this._findAndSelectNext(term, searchOptions) : this._findAndSelectPrevious(term, searchOptions); } // new search, clear out the old decorations - this._resultDecorations.forEach(decorations => decorations.forEach(d=> d.dispose())); - this._resultDecorations.clear(); + this._disposeDecorations(); this._searchResults.clear(); if (!this._terminal.options.overviewRulerWidth) { From 4645ef706ee524cf96de0916c5842547cd4ff2c8 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 21 Mar 2022 18:13:08 -0400 Subject: [PATCH 147/245] refactor --- addons/xterm-addon-search/src/SearchAddon.ts | 145 ++++++++++++------- 1 file changed, 92 insertions(+), 53 deletions(-) diff --git a/addons/xterm-addon-search/src/SearchAddon.ts b/addons/xterm-addon-search/src/SearchAddon.ts index 5451b832..7a20c5c9 100644 --- a/addons/xterm-addon-search/src/SearchAddon.ts +++ b/addons/xterm-addon-search/src/SearchAddon.ts @@ -5,11 +5,6 @@ import { Terminal, IDisposable, ITerminalAddon, ISelectionPosition, IDecoration } from 'xterm'; -const enum SelectionType { - NEXT = 0, - PREVIOUS = 1 -} - export interface ISearchOptions { regex?: boolean; wholeWord?: boolean; @@ -51,7 +46,6 @@ const LINES_CACHE_TIME_TO_LIVE = 15 * 1000; // 15 secs export class SearchAddon implements ITerminalAddon { private _terminal: Terminal | undefined; - private _result: ISearchResult | undefined; private _dataChanged: boolean = false; private _cachedSearchTerm: string | undefined; private _selectedDecoration: IDecoration | undefined; @@ -75,7 +69,7 @@ export class SearchAddon implements ITerminalAddon { this._dataChanged = true; setTimeout(() => { if (this._lastSearchOptions?.decorations && this._cachedSearchTerm && this._resultDecorations.size > 0 && this._lastSearchOptions) { - this._highlightAllMatches(this._cachedSearchTerm, this._lastSearchOptions, SelectionType.PREVIOUS); + this._highlightAllMatches(this._cachedSearchTerm, this._lastSearchOptions); } }, 200); }); @@ -96,7 +90,11 @@ export class SearchAddon implements ITerminalAddon { } private _disposeDecorations(): void { - this._resultDecorations.forEach(decorations => decorations.forEach(d=> d.dispose())); + this._resultDecorations.forEach(decorations => { + for (const d of decorations) { + d.dispose(); + } + }); this._resultDecorations.clear(); } @@ -112,38 +110,38 @@ export class SearchAddon implements ITerminalAddon { throw new Error('Cannot use addon until it has been loaded'); } this._lastSearchOptions = searchOptions; - return searchOptions?.decorations ? this._highlightAllMatches(term, searchOptions, SelectionType.NEXT) : this._findAndSelectNext(term, searchOptions); + const findNextResult = this._findNextAndSelect(term, searchOptions); + if (searchOptions?.decorations) { + this._highlightAllMatches(term, searchOptions); + } + return findNextResult; } - private _highlightAllMatches(term: string, searchOptions: ISearchOptions, selectionType: SelectionType): boolean { + private _highlightAllMatches(term: string, searchOptions: ISearchOptions): void { if (!this._terminal) { - throw new Error('cannot find all matches with no terminal'); + throw new Error('Cannot use addon until it has been loaded'); } if (!term || term.length === 0) { this.clearDecorations(); - return false; + return; } searchOptions = searchOptions || {}; if (term === this._cachedSearchTerm && !this._dataChanged) { - return selectionType === SelectionType.NEXT ? this._findAndSelectNext(term, searchOptions) : this._findAndSelectPrevious(term, searchOptions); + return; } // new search, clear out the old decorations this._disposeDecorations(); this._searchResults.clear(); - + let result = this._find(term, 0, 0, searchOptions); + while (result && !this._searchResults.get(`${result.row}-${result.col}`)) { + this._searchResults.set(`${result.row}-${result.col}`, result); + result = this._find(term, result.row, result.col + 1, searchOptions); + } if (!this._terminal.options.overviewRulerWidth) { this._terminal.options.overviewRulerWidth = 10; } - searchOptions.incremental = false; - let found = selectionType === SelectionType.NEXT ? this._findAndSelectNext(term, searchOptions) : this._findAndSelectPrevious(term, searchOptions); - while (found && (!this._result || !this._searchResults.get(`${this._result.row}-${this._result.col}`))) { - if (this._result) { - this._searchResults.set(`${this._result.row}-${this._result.col}`, this._result); - } - found = selectionType === SelectionType.NEXT ? this._findAndSelectNext(term, searchOptions) : this._findAndSelectPrevious(term, searchOptions); - } this._searchResults.forEach(result => { - const resultDecoration = this._createResultDecoration(result, searchOptions.decorations); + const resultDecoration = this._createResultDecoration(result, searchOptions.decorations!); if (resultDecoration) { const decorationsForLine = this._resultDecorations.get(resultDecoration.marker.line) || []; decorationsForLine.push(resultDecoration); @@ -156,12 +154,49 @@ export class SearchAddon implements ITerminalAddon { if (this._searchResults.size > 0) { this._cachedSearchTerm = term; } - return this._searchResults.size > 0; } - private _findAndSelectNext(term: string, searchOptions?: ISearchOptions): boolean { + private _find(term: string, startRow?: number, startCol?: number, searchOptions?: ISearchOptions): ISearchResult | undefined { + if (!this._terminal || !term || term.length === 0) { + this._terminal?.clearSelection(); + this.clearDecorations(); + return undefined; + } + let result: ISearchResult | undefined = undefined; + startCol = startCol || 0; + startRow = startRow ?? 0; + + this._initLinesCache(); + + const searchPosition: ISearchPosition = { + startRow, + startCol + }; + + // Search startRow + result = this._findInLine(term, searchPosition, searchOptions); + // Search from startRow + 1 to end + if (!result) { + + for (let y = startRow + 1; y < this._terminal.buffer.active.baseY + this._terminal.rows; y++) { + searchPosition.startRow = y; + searchPosition.startCol = 0; + // If the current line is wrapped line, increase index of column to ignore the previous scan + // Otherwise, reset beginning column index to zero with set new unwrapped line index + result = this._findInLine(term, searchPosition, searchOptions); + if (result) { + break; + } + } + } + if (result && searchOptions?.decorations) { + this._createResultDecoration(result, searchOptions?.decorations); + } + return result; + } + + private _findNextAndSelect(term: string, searchOptions?: ISearchOptions): boolean { if (!this._terminal || !term || term.length === 0) { - this._result = undefined; this._terminal?.clearSelection(); this.clearDecorations(); return false; @@ -187,42 +222,42 @@ export class SearchAddon implements ITerminalAddon { }; // Search startRow - this._result = this._findInLine(term, searchPosition, searchOptions); + let result = this._findInLine(term, searchPosition, searchOptions); // Search from startRow + 1 to end - if (!this._result) { + if (!result) { for (let y = startRow + 1; y < this._terminal.buffer.active.baseY + this._terminal.rows; y++) { searchPosition.startRow = y; searchPosition.startCol = 0; // If the current line is wrapped line, increase index of column to ignore the previous scan // Otherwise, reset beginning column index to zero with set new unwrapped line index - this._result = this._findInLine(term, searchPosition, searchOptions); - if (this._result) { + result = this._findInLine(term, searchPosition, searchOptions); + if (result) { break; } } } // If we hit the bottom and didn't search from the very top wrap back up - if (!this._result && startRow !== 0) { + if (!result && startRow !== 0) { for (let y = 0; y < startRow; y++) { searchPosition.startRow = y; searchPosition.startCol = 0; - this._result = this._findInLine(term, searchPosition, searchOptions); - if (this._result) { + result = this._findInLine(term, searchPosition, searchOptions); + if (result) { break; } } } // If there is only one result, wrap back and return selection if it exists. - if (!this._result && currentSelection) { + if (!result && currentSelection) { searchPosition.startRow = currentSelection.startRow; searchPosition.startCol = 0; - this._result = this._findInLine(term, searchPosition, searchOptions); + result = this._findInLine(term, searchPosition, searchOptions); } // Set selection and scroll if a result was found - return this._selectResult(this._result, searchOptions?.decorations); + return this._selectResult(result, searchOptions?.decorations); } /** * Find the previous instance of the term, then scroll to and select it. If it @@ -236,16 +271,20 @@ export class SearchAddon implements ITerminalAddon { throw new Error('Cannot use addon until it has been loaded'); } this._lastSearchOptions = searchOptions; - return searchOptions?.decorations ? this._highlightAllMatches(term, searchOptions, SelectionType.PREVIOUS) : this._findAndSelectPrevious(term, searchOptions); + const findPreviousResult = this._findAndSelectPrevious(term, searchOptions); + if (searchOptions?.decorations) { + this._highlightAllMatches(term, searchOptions); + } + return findPreviousResult; } private _findAndSelectPrevious(term: string, searchOptions?: ISearchOptions): boolean { if (!this._terminal) { throw new Error('Cannot use addon until it has been loaded'); } - + let result: ISearchResult | undefined; if (!this._terminal || !term || term.length === 0) { - this._result = undefined; + result = undefined; this._terminal?.clearSelection(); this.clearDecorations(); return false; @@ -272,47 +311,47 @@ export class SearchAddon implements ITerminalAddon { if (incremental) { // Try to expand selection to right first. - this._result = this._findInLine(term, searchPosition, searchOptions, false); - const isOldResultHighlighted = this._result && this._result.row === startRow && this._result.col === startCol; + result = this._findInLine(term, searchPosition, searchOptions, false); + const isOldResultHighlighted = result && result.row === startRow && result.col === startCol; if (!isOldResultHighlighted) { // If selection was not able to be expanded to the right, then try reverse search if (currentSelection) { searchPosition.startRow = currentSelection.endRow; searchPosition.startCol = currentSelection.endColumn; } - this._result = this._findInLine(term, searchPosition, searchOptions, true); + result = this._findInLine(term, searchPosition, searchOptions, true); } } else { - this._result = this._findInLine(term, searchPosition, searchOptions, isReverseSearch); + result = this._findInLine(term, searchPosition, searchOptions, isReverseSearch); } // Search from startRow - 1 to top - if (!this._result) { + if (!result) { searchPosition.startCol = Math.max(searchPosition.startCol, this._terminal.cols); for (let y = startRow - 1; y >= 0; y--) { searchPosition.startRow = y; - this._result = this._findInLine(term, searchPosition, searchOptions, isReverseSearch); - if (this._result) { + result = this._findInLine(term, searchPosition, searchOptions, isReverseSearch); + if (result) { break; } } } // If we hit the top and didn't search from the very bottom wrap back down - if (!this._result && startRow !== (this._terminal.buffer.active.baseY + this._terminal.rows)) { + if (!result && startRow !== (this._terminal.buffer.active.baseY + this._terminal.rows)) { for (let y = (this._terminal.buffer.active.baseY + this._terminal.rows); y >= startRow; y--) { searchPosition.startRow = y; - this._result = this._findInLine(term, searchPosition, searchOptions, isReverseSearch); - if (this._result) { + result = this._findInLine(term, searchPosition, searchOptions, isReverseSearch); + if (result) { break; } } } // If there is only one result, return true. - if (!this._result && currentSelection) return true; + if (!result && currentSelection) return true; - // Set selection and scroll if a this._result was found - return this._selectResult(this._result, searchOptions?.decorations); + // Set selection and scroll if a result was found + return this._selectResult(result, searchOptions?.decorations); } /** @@ -600,7 +639,7 @@ export class SearchAddon implements ITerminalAddon { * @param color the color to use for the decoration * @returns the {@link IDecoration} or undefined if the marker has already been disposed of */ - private _createResultDecoration(result: ISearchResult, decorations?: ISearchDecorationOptions): IDecoration | undefined { + private _createResultDecoration(result: ISearchResult, decorations: ISearchDecorationOptions): IDecoration | undefined { const terminal = this._terminal!; const marker = terminal.registerMarker(-terminal.buffer.active.baseY - terminal.buffer.active.cursorY + result.row); if (!marker || !decorations?.matchColor) { From 7f131aa19a526164cf9d738c04efd33c0828b97c Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 21 Mar 2022 18:16:35 -0400 Subject: [PATCH 148/245] fix error --- src/browser/Decorations/OverviewRulerRenderer.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/browser/Decorations/OverviewRulerRenderer.ts b/src/browser/Decorations/OverviewRulerRenderer.ts index aa09db56..b15d0d01 100644 --- a/src/browser/Decorations/OverviewRulerRenderer.ts +++ b/src/browser/Decorations/OverviewRulerRenderer.ts @@ -143,12 +143,10 @@ export class OverviewRulerRenderer extends Disposable { } this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height); for (const decoration of this._decorationService.decorations) { - if (decoration.options.overviewRulerOptions!.position !== 'full') { - this._renderDecoration(decoration, updateAnchor); + if (!decoration.options.overviewRulerOptions?.position) { + continue; } - } - for (const decoration of this._decorationService.decorations) { - if (decoration.options.overviewRulerOptions!.position === 'full') { + if (decoration.options.overviewRulerOptions.position !== 'full') { this._renderDecoration(decoration, updateAnchor); } } From 7a70db709c98db5da16de106da27e4cbb4ef840a Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 21 Mar 2022 15:51:51 -0700 Subject: [PATCH 149/245] Fix API indentation --- .../typings/xterm-addon-search.d.ts | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/addons/xterm-addon-search/typings/xterm-addon-search.d.ts b/addons/xterm-addon-search/typings/xterm-addon-search.d.ts index e58afec0..794d49c1 100644 --- a/addons/xterm-addon-search/typings/xterm-addon-search.d.ts +++ b/addons/xterm-addon-search/typings/xterm-addon-search.d.ts @@ -55,19 +55,19 @@ declare module 'xterm-addon-search' { * @param term The search term. * @param searchOptions The options for the search. */ - public findNext(term: string, searchOptions?: ISearchOptions): boolean; + public findNext(term: string, searchOptions?: ISearchOptions): boolean; - /** - * Search backwards for the previous result that matches the search term and - * options. - * @param term The search term. - * @param searchOptions The options for the search. - */ - public findPrevious(term: string, searchOptions?: ISearchOptions): boolean; + /** + * Search backwards for the previous result that matches the search term and + * options. + * @param term The search term. + * @param searchOptions The options for the search. + */ + public findPrevious(term: string, searchOptions?: ISearchOptions): boolean; - /** - * Clears the decorations and selection - */ - public clearDecorations(): void; + /** + * Clears the decorations and selection + */ + public clearDecorations(): void; } } From 09d1a01126d9f53a6c6411ab6f9737752339dd83 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 21 Mar 2022 16:01:13 -0700 Subject: [PATCH 150/245] Add ISearchDecorationOptions to the d.ts --- .../typings/xterm-addon-search.d.ts | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/addons/xterm-addon-search/typings/xterm-addon-search.d.ts b/addons/xterm-addon-search/typings/xterm-addon-search.d.ts index 794d49c1..e620a254 100644 --- a/addons/xterm-addon-search/typings/xterm-addon-search.d.ts +++ b/addons/xterm-addon-search/typings/xterm-addon-search.d.ts @@ -32,6 +32,27 @@ declare module 'xterm-addon-search' { * `findNext`, not `findPrevious`. */ incremental?: boolean; + + /** + * When set, will highlight all instances of the word on search and show + * them in the overview ruler if it's enabled. + */ + decorations?: ISearchDecorationOptions; + } + + /** + * Options for showing decorations when searching. + */ + interface ISearchDecorationOptions { + /** + * The color of a match. + */ + matchColor: string; + + /** + * The color for the currently selected match. + */ + selectedColor: string; } /** From ef3695f727f6a0cc9a02ed0f386ffab2f63b4fb1 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 21 Mar 2022 16:03:10 -0700 Subject: [PATCH 151/245] Move setting opacity into js --- addons/xterm-addon-search/src/SearchAddon.ts | 2 +- css/xterm.css | 4 ---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/addons/xterm-addon-search/src/SearchAddon.ts b/addons/xterm-addon-search/src/SearchAddon.ts index 7a20c5c9..982be048 100644 --- a/addons/xterm-addon-search/src/SearchAddon.ts +++ b/addons/xterm-addon-search/src/SearchAddon.ts @@ -626,10 +626,10 @@ export class SearchAddon implements ITerminalAddon { } if (!element.classList.contains('xterm-find-result-decoration')) { element.classList.add('xterm-find-result-decoration'); - // decoration's clientWidth = actualCellWidth element.style.left = `${element.clientWidth * result.col}px`; element.style.width = `${element.clientWidth * result.term.length}px`; element.style.backgroundColor = color; + element.style.opacity = '0.6'; } } diff --git a/css/xterm.css b/css/xterm.css index b9382ccc..7432fbb1 100644 --- a/css/xterm.css +++ b/css/xterm.css @@ -179,10 +179,6 @@ position: absolute; } -.xterm-find-result-decoration { - opacity: 0.6; -} - .xterm-decoration-overview-ruler { z-index: 7; position: absolute; From 87dd275a51ff0b8cf52a28805c8df1d369b66187 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 21 Mar 2022 16:03:59 -0700 Subject: [PATCH 152/245] Undo change to Terminal.ts --- src/browser/Terminal.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index 5803e8c4..8cb116ae 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -1017,6 +1017,7 @@ export class Terminal extends CoreTerminal implements ITerminal { if (this.buffer !== this.buffers.normal) { return; } + return this.buffer.addMarker(this.buffer.ybase + this.buffer.y + cursorYOffset); } From efda0e77a9f68e59f3ed3b862a7255479df85afd Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 21 Mar 2022 16:04:23 -0700 Subject: [PATCH 153/245] Fix indentation properly this time --- addons/xterm-addon-search/typings/xterm-addon-search.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/xterm-addon-search/typings/xterm-addon-search.d.ts b/addons/xterm-addon-search/typings/xterm-addon-search.d.ts index e620a254..67ed2985 100644 --- a/addons/xterm-addon-search/typings/xterm-addon-search.d.ts +++ b/addons/xterm-addon-search/typings/xterm-addon-search.d.ts @@ -76,7 +76,7 @@ declare module 'xterm-addon-search' { * @param term The search term. * @param searchOptions The options for the search. */ - public findNext(term: string, searchOptions?: ISearchOptions): boolean; + public findNext(term: string, searchOptions?: ISearchOptions): boolean; /** * Search backwards for the previous result that matches the search term and From fe530d92ecc467adcd5c43301971f65dd275a726 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 21 Mar 2022 16:11:31 -0700 Subject: [PATCH 154/245] Ensure marker is disposed when decoration is --- addons/xterm-addon-search/src/SearchAddon.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/addons/xterm-addon-search/src/SearchAddon.ts b/addons/xterm-addon-search/src/SearchAddon.ts index 982be048..d8266e02 100644 --- a/addons/xterm-addon-search/src/SearchAddon.ts +++ b/addons/xterm-addon-search/src/SearchAddon.ts @@ -601,6 +601,7 @@ export class SearchAddon implements ITerminalAddon { if (marker) { this._selectedDecoration = terminal.registerDecoration({ marker, overviewRulerOptions: { color: decorations.selectedColor } }); this._selectedDecoration?.onRender((e) => this._applyStyles(e, decorations.selectedColor, result)); + this._selectedDecoration?.onDispose(() => marker.dispose()); } } @@ -650,6 +651,7 @@ export class SearchAddon implements ITerminalAddon { overviewRulerOptions: this._resultDecorations.get(marker.line) && !this._dataChanged ? undefined : { color: decorations.matchColor, position: 'center' } }); findResultDecoration?.onRender((e) => this._applyStyles(e, decorations.matchColor, result)); + findResultDecoration?.onDispose(() => marker.dispose()); return findResultDecoration; } } From 23076ba6ccc73c91872bb4bc4bfd29f64920ce3a Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 21 Mar 2022 16:20:51 -0700 Subject: [PATCH 155/245] Fix duplicate decorations getting created --- addons/xterm-addon-search/src/SearchAddon.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/addons/xterm-addon-search/src/SearchAddon.ts b/addons/xterm-addon-search/src/SearchAddon.ts index d8266e02..c3fc3ecc 100644 --- a/addons/xterm-addon-search/src/SearchAddon.ts +++ b/addons/xterm-addon-search/src/SearchAddon.ts @@ -92,6 +92,7 @@ export class SearchAddon implements ITerminalAddon { private _disposeDecorations(): void { this._resultDecorations.forEach(decorations => { for (const d of decorations) { + console.log('dispose', d); d.dispose(); } }); @@ -189,9 +190,6 @@ export class SearchAddon implements ITerminalAddon { } } } - if (result && searchOptions?.decorations) { - this._createResultDecoration(result, searchOptions?.decorations); - } return result; } @@ -646,6 +644,7 @@ export class SearchAddon implements ITerminalAddon { if (!marker || !decorations?.matchColor) { return undefined; } + const findResultDecoration = terminal.registerDecoration( { marker, overviewRulerOptions: this._resultDecorations.get(marker.line) && !this._dataChanged ? undefined : { color: decorations.matchColor, position: 'center' } From f9deb2f8409de532c9ed4e10004c1f0830be6c3d Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 21 Mar 2022 16:34:48 -0700 Subject: [PATCH 156/245] Don't set overview ruler width when using find An embedder may not want to enable it --- addons/xterm-addon-search/src/SearchAddon.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/addons/xterm-addon-search/src/SearchAddon.ts b/addons/xterm-addon-search/src/SearchAddon.ts index c3fc3ecc..12e82103 100644 --- a/addons/xterm-addon-search/src/SearchAddon.ts +++ b/addons/xterm-addon-search/src/SearchAddon.ts @@ -92,7 +92,6 @@ export class SearchAddon implements ITerminalAddon { private _disposeDecorations(): void { this._resultDecorations.forEach(decorations => { for (const d of decorations) { - console.log('dispose', d); d.dispose(); } }); @@ -138,9 +137,6 @@ export class SearchAddon implements ITerminalAddon { this._searchResults.set(`${result.row}-${result.col}`, result); result = this._find(term, result.row, result.col + 1, searchOptions); } - if (!this._terminal.options.overviewRulerWidth) { - this._terminal.options.overviewRulerWidth = 10; - } this._searchResults.forEach(result => { const resultDecoration = this._createResultDecoration(result, searchOptions.decorations!); if (resultDecoration) { @@ -644,7 +640,6 @@ export class SearchAddon implements ITerminalAddon { if (!marker || !decorations?.matchColor) { return undefined; } - const findResultDecoration = terminal.registerDecoration( { marker, overviewRulerOptions: this._resultDecorations.get(marker.line) && !this._dataChanged ? undefined : { color: decorations.matchColor, position: 'center' } From 6ef1bdd7ce04d733100e57eca0d989447bdad1d9 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 21 Mar 2022 16:37:46 -0700 Subject: [PATCH 157/245] Debounce onData listener --- addons/xterm-addon-search/src/SearchAddon.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/addons/xterm-addon-search/src/SearchAddon.ts b/addons/xterm-addon-search/src/SearchAddon.ts index 12e82103..3dd2992d 100644 --- a/addons/xterm-addon-search/src/SearchAddon.ts +++ b/addons/xterm-addon-search/src/SearchAddon.ts @@ -53,6 +53,7 @@ export class SearchAddon implements ITerminalAddon { private _searchResults: Map = new Map(); private _onDataDisposable: IDisposable | undefined; private _lastSearchOptions: ISearchOptions | undefined; + private _highlightTimeout: number | undefined; /** * translateBufferLineToStringWithWrap is a fairly expensive call. * We memoize the calls into an array that has a time based ttl. @@ -67,7 +68,10 @@ export class SearchAddon implements ITerminalAddon { this._terminal = terminal; this._onDataDisposable = this._terminal.onData(() => { this._dataChanged = true; - setTimeout(() => { + if (this._highlightTimeout) { + window.clearTimeout(this._highlightTimeout); + } + this._highlightTimeout = setTimeout(() => { if (this._lastSearchOptions?.decorations && this._cachedSearchTerm && this._resultDecorations.size > 0 && this._lastSearchOptions) { this._highlightAllMatches(this._cachedSearchTerm, this._lastSearchOptions); } From b39477d81acb67a0a061a9586a2db59706afb732 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 21 Mar 2022 16:45:48 -0700 Subject: [PATCH 158/245] Fix full decorations --- src/browser/Decorations/OverviewRulerRenderer.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/browser/Decorations/OverviewRulerRenderer.ts b/src/browser/Decorations/OverviewRulerRenderer.ts index b15d0d01..9cdf99ca 100644 --- a/src/browser/Decorations/OverviewRulerRenderer.ts +++ b/src/browser/Decorations/OverviewRulerRenderer.ts @@ -143,10 +143,12 @@ export class OverviewRulerRenderer extends Disposable { } this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height); for (const decoration of this._decorationService.decorations) { - if (!decoration.options.overviewRulerOptions?.position) { - continue; + if (decoration.options.overviewRulerOptions && decoration.options.overviewRulerOptions.position !== 'full') { + this._renderDecoration(decoration, updateAnchor); } - if (decoration.options.overviewRulerOptions.position !== 'full') { + } + for (const decoration of this._decorationService.decorations) { + if (decoration.options.overviewRulerOptions && decoration.options.overviewRulerOptions.position === 'full') { this._renderDecoration(decoration, updateAnchor); } } From 43a014e8c1e178c5647fa467363e6ac64e6ad8cb Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 21 Mar 2022 16:57:49 -0700 Subject: [PATCH 159/245] Fix decoration lifecycle issues --- src/common/services/DecorationService.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/common/services/DecorationService.ts b/src/common/services/DecorationService.ts index 6c750236..fba5fc35 100644 --- a/src/common/services/DecorationService.ts +++ b/src/common/services/DecorationService.ts @@ -32,7 +32,10 @@ export class DecorationService extends Disposable implements IDecorationService if (decoration) { decoration.onDispose(() => { if (decoration) { - this._decorations.splice(this._decorations.indexOf(decoration), 1); + const index = this._decorations.indexOf(decoration); + if (index >= 0) { + this._decorations.splice(this._decorations.indexOf(decoration), 1); + } } }); this._decorations.push(decoration); @@ -70,6 +73,10 @@ class Decoration extends Disposable implements IInternalDecoration { } } public override dispose(): void { + if (this._isDisposed) { + return; + } + this._isDisposed = true; this._onDispose.fire(); super.dispose(); } From 21e1c5ef584737a5a27d517fbd41a5f1f2df270a Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 22 Mar 2022 01:53:10 -0400 Subject: [PATCH 160/245] refresh canvas dimensions on refresh --- src/browser/Decorations/OverviewRulerRenderer.ts | 6 +++--- src/browser/Terminal.ts | 8 ++++++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/browser/Decorations/OverviewRulerRenderer.ts b/src/browser/Decorations/OverviewRulerRenderer.ts index 9cdf99ca..8c63f5e3 100644 --- a/src/browser/Decorations/OverviewRulerRenderer.ts +++ b/src/browser/Decorations/OverviewRulerRenderer.ts @@ -49,7 +49,7 @@ export class OverviewRulerRenderer extends Disposable { super(); this._canvas = document.createElement('canvas'); this._canvas.classList.add('xterm-decoration-overview-ruler'); - this._refreshCanvasDimensions(); + this.refreshCanvasDimensions(); this._viewportElement.parentElement?.insertBefore(this._canvas, this._viewportElement); const ctx = this._canvas.getContext('2d'); if (!ctx) { @@ -129,7 +129,7 @@ export class OverviewRulerRenderer extends Disposable { ); } - private _refreshCanvasDimensions(): void { + public refreshCanvasDimensions(): void { this._canvas.style.width = `${this._width}px`; this._canvas.style.height = `${this._screenElement.clientHeight}px`; this._canvas.width = Math.round(this._width * window.devicePixelRatio); @@ -139,7 +139,7 @@ export class OverviewRulerRenderer extends Disposable { private _refreshDecorations(updateCanvasDimensions?: boolean, updateAnchor?: boolean): void { if (updateCanvasDimensions) { - this._refreshCanvasDimensions(); + this.refreshCanvasDimensions(); } this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height); for (const decoration of this._decorationService.decorations) { diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index 8cb116ae..6560a976 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -175,7 +175,9 @@ export class Terminal extends CoreTerminal implements ITerminal { // Setup InputHandler listeners this.register(this._inputHandler.onRequestBell(() => this.bell())); - this.register(this._inputHandler.onRequestRefreshRows((start, end) => this.refresh(start, end))); + this.register(this._inputHandler.onRequestRefreshRows((start, end) => { + this.refresh(start, end); + })); this.register(this._inputHandler.onRequestSendFocus(() => this._reportFocus())); this.register(this._inputHandler.onRequestReset(() => this.reset())); this.register(this._inputHandler.onRequestWindowsOptionsReport(type => this._reportWindowsOptions(type))); @@ -613,7 +615,8 @@ export class Terminal extends CoreTerminal implements ITerminal { this.optionsService.onOptionChange(() => { if (!this._overviewRulerRenderer && this.options.overviewRulerWidth && this._viewportElement && this.screenElement) { this._overviewRulerRenderer = this._instantiationService.createInstance(OverviewRulerRenderer, this._viewportElement, this.screenElement); - }}); + } + }); // Measure the character size this._charSizeService.measure(); @@ -906,6 +909,7 @@ export class Terminal extends CoreTerminal implements ITerminal { */ public refresh(start: number, end: number): void { this._renderService?.refreshRows(start, end); + this._overviewRulerRenderer?.refreshCanvasDimensions(); } /** From 1f60a398b277f2daaac994285db081730a6ae2c1 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 22 Mar 2022 15:04:22 -0400 Subject: [PATCH 161/245] on render, fire when height has changed (was 0) --- .../Decorations/OverviewRulerRenderer.ts | 46 ++++++++++++++----- src/browser/Terminal.ts | 5 +- src/browser/TestUtils.test.ts | 1 + src/browser/services/RenderService.ts | 7 ++- src/browser/services/Services.ts | 4 ++ 5 files changed, 45 insertions(+), 18 deletions(-) diff --git a/src/browser/Decorations/OverviewRulerRenderer.ts b/src/browser/Decorations/OverviewRulerRenderer.ts index 8c63f5e3..9d6e4904 100644 --- a/src/browser/Decorations/OverviewRulerRenderer.ts +++ b/src/browser/Decorations/OverviewRulerRenderer.ts @@ -38,6 +38,10 @@ export class OverviewRulerRenderer extends Disposable { } private _animationFrame: number | undefined; + private _canvasHeight: number | undefined; + private _canvasWidth: number | undefined; + private _shouldUpdateDimensions: boolean | undefined = true; + constructor( private readonly _viewportElement: HTMLElement, private readonly _screenElement: HTMLElement, @@ -49,7 +53,7 @@ export class OverviewRulerRenderer extends Disposable { super(); this._canvas = document.createElement('canvas'); this._canvas.classList.add('xterm-decoration-overview-ruler'); - this.refreshCanvasDimensions(); + this._refreshCanvasDimensions(); this._viewportElement.parentElement?.insertBefore(this._canvas, this._viewportElement); const ctx = this._canvas.getContext('2d'); if (!ctx) { @@ -62,6 +66,11 @@ export class OverviewRulerRenderer extends Disposable { this._canvas!.style.display = this._bufferService.buffer === this._bufferService.buffers.alt ? 'none' : 'block'; })); this.register(this._renderService.onRenderedBufferChange(() => this._queueRefresh())); + this.register(this._renderService.onRender(() => { + if (this._canvasHeight !== this._screenElement.clientHeight) { + this._queueRefresh(true); + } + })); this.register(this._renderService.onDimensionsChange(() => this._queueRefresh(true, true))); this.register(addDisposableDomListener(window, 'resize', () => this._queueRefresh(true))); this.register(this._decorationService.onDecorationRegistered(() => this._queueRefresh(undefined, true))); @@ -102,6 +111,7 @@ export class OverviewRulerRenderer extends Disposable { drawX.left = 0; drawX.center = drawWidth.left; drawX.right = drawWidth.left + drawWidth.center; + this._shouldUpdateDimensions = false; } private _refreshStyle(decoration: IInternalDecoration, updateAnchor?: boolean): void { @@ -122,24 +132,35 @@ export class OverviewRulerRenderer extends Disposable { /* x */ drawX[decoration.options.overviewRulerOptions.position!], /* y */ Math.round( (this._canvas.height - 1) * // -1 to ensure at least 2px are allowed for decoration on last line - (decoration.options.marker.line / this._bufferService.buffers.active.lines.length) - drawHeight[decoration.options.overviewRulerOptions.position!] / 2 + (decoration.options.marker.line / this._bufferService.buffers.active.lines.length) - drawHeight[decoration.options.overviewRulerOptions.position!] / 2 ), /* w */ drawWidth[decoration.options.overviewRulerOptions.position!], /* h */ drawHeight[decoration.options.overviewRulerOptions.position!] ); } - public refreshCanvasDimensions(): void { - this._canvas.style.width = `${this._width}px`; - this._canvas.style.height = `${this._screenElement.clientHeight}px`; - this._canvas.width = Math.round(this._width * window.devicePixelRatio); - this._canvas.height = Math.round(this._screenElement.clientHeight * window.devicePixelRatio); - this._refreshDrawConstants(); + private _refreshCanvasDimensions(): void { + let updated = false; + if (this._canvasWidth !== this._width) { + this._canvas.style.width = `${this._width}px`; + this._canvas.width = Math.round(this._width * window.devicePixelRatio); + this._canvasWidth = this._canvas.width; + updated = true; + } + if (this._canvasHeight !== Math.round(this._screenElement.clientHeight * window.devicePixelRatio)) { + this._canvas.style.height = `${this._screenElement.clientHeight}px`; + this._canvas.height = Math.round(this._screenElement.clientHeight * window.devicePixelRatio); + this._canvasHeight = this._canvas.height; + updated = true; + } + if (updated) { + this._refreshDrawConstants(); + } } - private _refreshDecorations(updateCanvasDimensions?: boolean, updateAnchor?: boolean): void { - if (updateCanvasDimensions) { - this.refreshCanvasDimensions(); + private _refreshDecorations(updateAnchor?: boolean): void { + if (this._shouldUpdateDimensions) { + this._refreshCanvasDimensions(); } this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height); for (const decoration of this._decorationService.decorations) { @@ -165,10 +186,11 @@ export class OverviewRulerRenderer extends Disposable { private _queueRefresh(updateCanvasDimensions?: boolean, updateAnchor?: boolean): void { if (this._animationFrame !== undefined) { + this._shouldUpdateDimensions = updateCanvasDimensions || this._shouldUpdateDimensions; return; } this._animationFrame = window.requestAnimationFrame(() => { - this._refreshDecorations(updateCanvasDimensions, updateAnchor); + this._refreshDecorations(updateAnchor); this._animationFrame = undefined; }); } diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index 6560a976..f14b5217 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -175,9 +175,7 @@ export class Terminal extends CoreTerminal implements ITerminal { // Setup InputHandler listeners this.register(this._inputHandler.onRequestBell(() => this.bell())); - this.register(this._inputHandler.onRequestRefreshRows((start, end) => { - this.refresh(start, end); - })); + this.register(this._inputHandler.onRequestRefreshRows((start, end) => this.refresh(start, end))); this.register(this._inputHandler.onRequestSendFocus(() => this._reportFocus())); this.register(this._inputHandler.onRequestReset(() => this.reset())); this.register(this._inputHandler.onRequestWindowsOptionsReport(type => this._reportWindowsOptions(type))); @@ -909,7 +907,6 @@ export class Terminal extends CoreTerminal implements ITerminal { */ public refresh(start: number, end: number): void { this._renderService?.refreshRows(start, end); - this._overviewRulerRenderer?.refreshCanvasDimensions(); } /** diff --git a/src/browser/TestUtils.test.ts b/src/browser/TestUtils.test.ts index ac048be6..85e2bb55 100644 --- a/src/browser/TestUtils.test.ts +++ b/src/browser/TestUtils.test.ts @@ -371,6 +371,7 @@ export class MockRenderService implements IRenderService { public serviceBrand: undefined; public onDimensionsChange: IEvent = new EventEmitter().event; public onRenderedBufferChange: IEvent<{ start: number, end: number }, void> = new EventEmitter<{ start: number, end: number }>().event; + public onRender: IEvent<{ start: number, end: number }, void> = new EventEmitter<{ start: number, end: number }>().event; public onRefreshRequest: IEvent<{ start: number, end: number}, void> = new EventEmitter<{ start: number, end: number }>().event; public dimensions: IRenderDimensions = { scaledCharWidth: 0, diff --git a/src/browser/services/RenderService.ts b/src/browser/services/RenderService.ts index da458abc..91b510a3 100644 --- a/src/browser/services/RenderService.ts +++ b/src/browser/services/RenderService.ts @@ -39,8 +39,10 @@ export class RenderService extends Disposable implements IRenderService { private _onDimensionsChange = new EventEmitter(); public get onDimensionsChange(): IEvent { return this._onDimensionsChange.event; } + private _onRenderedBufferChange = new EventEmitter<{ start: number, end: number }>(); + public get onRenderedBufferChange(): IEvent<{ start: number, end: number }> { return this._onRenderedBufferChange.event; } private _onRender = new EventEmitter<{ start: number, end: number }>(); - public get onRenderedBufferChange(): IEvent<{ start: number, end: number }> { return this._onRender.event; } + public get onRender(): IEvent<{ start: number, end: number }> { return this._onRender.event; } private _onRefreshRequest = new EventEmitter<{ start: number, end: number }>(); public get onRefreshRequest(): IEvent<{ start: number, end: number }> { return this._onRefreshRequest.event; } @@ -122,8 +124,9 @@ export class RenderService extends Disposable implements IRenderService { // Fire render event only if it was not a redraw if (!this._isNextRenderRedrawOnly) { - this._onRender.fire({ start, end }); + this._onRenderedBufferChange.fire({ start, end }); } + this._onRender.fire({ start, end }); this._isNextRenderRedrawOnly = true; } diff --git a/src/browser/services/Services.ts b/src/browser/services/Services.ts index 1598ef02..7191d0ed 100644 --- a/src/browser/services/Services.ts +++ b/src/browser/services/Services.ts @@ -50,6 +50,10 @@ export interface IRenderService extends IDisposable { * or selections are rendered. */ onRenderedBufferChange: IEvent<{ start: number, end: number }>; + /** + * Fires on render + */ + onRender: IEvent<{ start: number, end: number }>; onRefreshRequest: IEvent<{ start: number, end: number }>; dimensions: IRenderDimensions; From 74409da947c59088c45901d35d0c233f28773203 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 22 Mar 2022 15:09:54 -0400 Subject: [PATCH 162/245] move resetting of var --- src/browser/Decorations/OverviewRulerRenderer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/browser/Decorations/OverviewRulerRenderer.ts b/src/browser/Decorations/OverviewRulerRenderer.ts index 9d6e4904..07d3546a 100644 --- a/src/browser/Decorations/OverviewRulerRenderer.ts +++ b/src/browser/Decorations/OverviewRulerRenderer.ts @@ -111,7 +111,6 @@ export class OverviewRulerRenderer extends Disposable { drawX.left = 0; drawX.center = drawWidth.left; drawX.right = drawWidth.left + drawWidth.center; - this._shouldUpdateDimensions = false; } private _refreshStyle(decoration: IInternalDecoration, updateAnchor?: boolean): void { @@ -161,6 +160,7 @@ export class OverviewRulerRenderer extends Disposable { private _refreshDecorations(updateAnchor?: boolean): void { if (this._shouldUpdateDimensions) { this._refreshCanvasDimensions(); + this._shouldUpdateDimensions = false; } this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height); for (const decoration of this._decorationService.decorations) { From 550b19f334f672e5367383ce0784ce4c5e4e8b15 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 22 Mar 2022 16:41:50 -0400 Subject: [PATCH 163/245] refactor --- .../Decorations/OverviewRulerRenderer.ts | 102 ++++++++++-------- 1 file changed, 60 insertions(+), 42 deletions(-) diff --git a/src/browser/Decorations/OverviewRulerRenderer.ts b/src/browser/Decorations/OverviewRulerRenderer.ts index 07d3546a..950e2ac8 100644 --- a/src/browser/Decorations/OverviewRulerRenderer.ts +++ b/src/browser/Decorations/OverviewRulerRenderer.ts @@ -38,9 +38,10 @@ export class OverviewRulerRenderer extends Disposable { } private _animationFrame: number | undefined; - private _canvasHeight: number | undefined; - private _canvasWidth: number | undefined; private _shouldUpdateDimensions: boolean | undefined = true; + private _shouldUpdateAnchor: boolean | undefined = true; + + private _containerHeight: number | undefined; constructor( private readonly _viewportElement: HTMLElement, @@ -61,27 +62,53 @@ export class OverviewRulerRenderer extends Disposable { } else { this._ctx = ctx; } - this._queueRefresh(true); + this._registerDecorationListeners(); + this._registerBufferChangeListeners(); + this._registerDimensionChangeListeners(); + } + + /** + * On decoration add or remove, redraw + */ + private _registerDecorationListeners(): void { + this.register(this._decorationService.onDecorationRegistered(() => this._queueRefresh(undefined, true))); + this.register(this._decorationService.onDecorationRemoved(decoration => this._removeDecoration(decoration))); + } + + /** + * On buffer change, redraw + * and hide the canvas if the alt buffer is active + */ + private _registerBufferChangeListeners(): void { + this.register(this._renderService.onRenderedBufferChange(() => this._queueRefresh())); this.register(this._bufferService.buffers.onBufferActivate(() => { this._canvas!.style.display = this._bufferService.buffer === this._bufferService.buffers.alt ? 'none' : 'block'; })); - this.register(this._renderService.onRenderedBufferChange(() => this._queueRefresh())); - this.register(this._renderService.onRender(() => { - if (this._canvasHeight !== this._screenElement.clientHeight) { + } + /** + * On dimension change, update canvas dimensions + * and then redraw + */ + private _registerDimensionChangeListeners(): void { + // container height changed + this.register(this._renderService.onRender((): void => { + if (!this._containerHeight || this._containerHeight !== this._screenElement.clientHeight) { + this._queueRefresh(true); + this._containerHeight = this._screenElement.clientHeight; + } + })); + // overview ruler width changed + this.register(this._optionsService.onOptionChange(o => { + if (o === 'overviewRulerWidth') { this._queueRefresh(true); } })); - this.register(this._renderService.onDimensionsChange(() => this._queueRefresh(true, true))); - this.register(addDisposableDomListener(window, 'resize', () => this._queueRefresh(true))); - this.register(this._decorationService.onDecorationRegistered(() => this._queueRefresh(undefined, true))); - this.register(this._decorationService.onDecorationRemoved(decoration => this._removeDecoration(decoration))); - this.register(this._optionsService.onOptionChange(o => { - if (o === 'overviewRulerWidth') { - this._refreshDrawConstants(); - this._queueRefresh(); - } + // device pixel ratio changed + this.register(addDisposableDomListener(window, 'resize', () => { + this._queueRefresh(true); })); - this._refreshDrawConstants(); + // set the canvas dimensions + this._queueRefresh(true); } public override dispose(): void { @@ -113,8 +140,8 @@ export class OverviewRulerRenderer extends Disposable { drawX.right = drawWidth.left + drawWidth.center; } - private _refreshStyle(decoration: IInternalDecoration, updateAnchor?: boolean): void { - if (updateAnchor) { + private _refreshStyle(decoration: IInternalDecoration): void { + if (this._shouldUpdateAnchor) { if (decoration.options.anchor === 'right') { this._canvas.style.right = decoration.options.x ? `${decoration.options.x * this._renderService.dimensions.actualCellWidth}px` : ''; } else { @@ -139,58 +166,49 @@ export class OverviewRulerRenderer extends Disposable { } private _refreshCanvasDimensions(): void { - let updated = false; - if (this._canvasWidth !== this._width) { - this._canvas.style.width = `${this._width}px`; - this._canvas.width = Math.round(this._width * window.devicePixelRatio); - this._canvasWidth = this._canvas.width; - updated = true; - } - if (this._canvasHeight !== Math.round(this._screenElement.clientHeight * window.devicePixelRatio)) { - this._canvas.style.height = `${this._screenElement.clientHeight}px`; - this._canvas.height = Math.round(this._screenElement.clientHeight * window.devicePixelRatio); - this._canvasHeight = this._canvas.height; - updated = true; - } - if (updated) { - this._refreshDrawConstants(); - } + this._canvas.style.width = `${this._width}px`; + this._canvas.width = Math.round(this._width * window.devicePixelRatio); + this._canvas.style.height = `${this._screenElement.clientHeight}px`; + this._canvas.height = Math.round(this._screenElement.clientHeight * window.devicePixelRatio); + this._refreshDrawConstants(); } - private _refreshDecorations(updateAnchor?: boolean): void { + private _refreshDecorations(): void { if (this._shouldUpdateDimensions) { this._refreshCanvasDimensions(); - this._shouldUpdateDimensions = false; } this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height); for (const decoration of this._decorationService.decorations) { if (decoration.options.overviewRulerOptions && decoration.options.overviewRulerOptions.position !== 'full') { - this._renderDecoration(decoration, updateAnchor); + this._renderDecoration(decoration); } } for (const decoration of this._decorationService.decorations) { if (decoration.options.overviewRulerOptions && decoration.options.overviewRulerOptions.position === 'full') { - this._renderDecoration(decoration, updateAnchor); + this._renderDecoration(decoration); } } + this._shouldUpdateDimensions = false; + this._shouldUpdateAnchor = false; } - private _renderDecoration(decoration: IInternalDecoration, updateAnchor?: boolean): void { + private _renderDecoration(decoration: IInternalDecoration): void { const element = this._decorationElements.get(decoration); if (!element) { this._decorationElements.set(decoration, this._canvas); decoration.onDispose(() => this._queueRefresh()); } - this._refreshStyle(decoration, updateAnchor); + this._refreshStyle(decoration); } private _queueRefresh(updateCanvasDimensions?: boolean, updateAnchor?: boolean): void { + this._shouldUpdateDimensions = updateCanvasDimensions || this._shouldUpdateDimensions; + this._shouldUpdateAnchor = updateAnchor || this._shouldUpdateAnchor; if (this._animationFrame !== undefined) { - this._shouldUpdateDimensions = updateCanvasDimensions || this._shouldUpdateDimensions; return; } this._animationFrame = window.requestAnimationFrame(() => { - this._refreshDecorations(updateAnchor); + this._refreshDecorations(); this._animationFrame = undefined; }); } From dfe35a93d549c7802ee1d25750372a407a241268 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 23 Mar 2022 11:48:02 -0400 Subject: [PATCH 164/245] add ST sequence --- src/browser/Terminal.ts | 4 ++-- src/common/data/EscapeSequences.ts | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index 8cb116ae..89245244 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -26,7 +26,7 @@ import { IRenderer } from 'browser/renderer/Types'; import { CompositionHelper } from 'browser/input/CompositionHelper'; import { Viewport } from 'browser/Viewport'; import { rightClickHandler, moveTextAreaUnderMouseCursor, handlePasteEvent, copyHandler, paste } from 'browser/Clipboard'; -import { C0 } from 'common/data/EscapeSequences'; +import { C0, C1 } from 'common/data/EscapeSequences'; import { WindowsOptionsReportType } from '../common/InputHandler'; import { Renderer } from 'browser/renderer/Renderer'; import { Linkifier } from 'browser/Linkifier'; @@ -224,7 +224,7 @@ export class Terminal extends CoreTerminal implements ITerminal { const channels = color.toColorRGB(acc === 'ansi' ? this._colorManager.colors.ansi[req.index] : this._colorManager.colors[acc]); - this.coreService.triggerDataEvent(`${C0.ESC}]${ident};${toRgbString(channels)}${C0.BEL}`); + this.coreService.triggerDataEvent(`${C0.ESC}]${ident};${toRgbString(channels)}${C1.ST}`); break; case ColorRequestType.SET: if (acc === 'ansi') this._colorManager.colors.ansi[req.index] = rgba.toColor(...req.color); diff --git a/src/common/data/EscapeSequences.ts b/src/common/data/EscapeSequences.ts index e35f01dd..8444c35b 100644 --- a/src/common/data/EscapeSequences.ts +++ b/src/common/data/EscapeSequences.ts @@ -119,6 +119,8 @@ export namespace C1 { export const DCS = '\x90'; /** Private Use 1 */ export const PU1 = '\x91'; + /** String Terminator */ + export const ST = '\x912'; /** Private Use 2 */ export const PU2 = '\x92'; /** Set Transmit State */ From 078d88fbbe643eaa1aa0d569c5873afd4f776ce4 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 23 Mar 2022 12:54:19 -0400 Subject: [PATCH 165/245] pass in escape sequence with ESC notation" --- src/browser/Terminal.ts | 2 +- src/common/data/EscapeSequences.ts | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index 89245244..a00d72fd 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -224,7 +224,7 @@ export class Terminal extends CoreTerminal implements ITerminal { const channels = color.toColorRGB(acc === 'ansi' ? this._colorManager.colors.ansi[req.index] : this._colorManager.colors[acc]); - this.coreService.triggerDataEvent(`${C0.ESC}]${ident};${toRgbString(channels)}${C1.ST}`); + this.coreService.triggerDataEvent(`${C0.ESC}]${ident};${toRgbString(channels)}ESC \9c`); break; case ColorRequestType.SET: if (acc === 'ansi') this._colorManager.colors.ansi[req.index] = rgba.toColor(...req.color); diff --git a/src/common/data/EscapeSequences.ts b/src/common/data/EscapeSequences.ts index 8444c35b..e35f01dd 100644 --- a/src/common/data/EscapeSequences.ts +++ b/src/common/data/EscapeSequences.ts @@ -119,8 +119,6 @@ export namespace C1 { export const DCS = '\x90'; /** Private Use 1 */ export const PU1 = '\x91'; - /** String Terminator */ - export const ST = '\x912'; /** Private Use 2 */ export const PU2 = '\x92'; /** Set Transmit State */ From b9d10f1453e2c4e3bc0da2755a3e53253e3735e6 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 23 Mar 2022 13:54:01 -0400 Subject: [PATCH 166/245] use correct start col/row --- addons/xterm-addon-search/src/SearchAddon.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/addons/xterm-addon-search/src/SearchAddon.ts b/addons/xterm-addon-search/src/SearchAddon.ts index 3dd2992d..0e0fcef1 100644 --- a/addons/xterm-addon-search/src/SearchAddon.ts +++ b/addons/xterm-addon-search/src/SearchAddon.ts @@ -139,7 +139,12 @@ export class SearchAddon implements ITerminalAddon { let result = this._find(term, 0, 0, searchOptions); while (result && !this._searchResults.get(`${result.row}-${result.col}`)) { this._searchResults.set(`${result.row}-${result.col}`, result); - result = this._find(term, result.row, result.col + 1, searchOptions); + result = this._find( + term, + result.col + result.term.length >= this._terminal.cols ? result.row + 1 : result.row, + result.col + result.term.length >= this._terminal.cols ? 0 : result.col + 1, + searchOptions + ); } this._searchResults.forEach(result => { const resultDecoration = this._createResultDecoration(result, searchOptions.decorations!); @@ -157,15 +162,13 @@ export class SearchAddon implements ITerminalAddon { } } - private _find(term: string, startRow?: number, startCol?: number, searchOptions?: ISearchOptions): ISearchResult | undefined { + private _find(term: string, startRow: number, startCol: number, searchOptions?: ISearchOptions): ISearchResult | undefined { if (!this._terminal || !term || term.length === 0) { this._terminal?.clearSelection(); this.clearDecorations(); return undefined; } let result: ISearchResult | undefined = undefined; - startCol = startCol || 0; - startRow = startRow ?? 0; this._initLinesCache(); From 3a0f64545ecc4dfeaa4c48f8eba23df65f6f434c Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 23 Mar 2022 14:01:56 -0400 Subject: [PATCH 167/245] throw for invalid row or col --- addons/xterm-addon-search/src/SearchAddon.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/addons/xterm-addon-search/src/SearchAddon.ts b/addons/xterm-addon-search/src/SearchAddon.ts index 0e0fcef1..0f677072 100644 --- a/addons/xterm-addon-search/src/SearchAddon.ts +++ b/addons/xterm-addon-search/src/SearchAddon.ts @@ -168,6 +168,10 @@ export class SearchAddon implements ITerminalAddon { this.clearDecorations(); return undefined; } + if (startRow > this._terminal.rows || startCol > this._terminal.cols) { + throw new Error(`Invalid row: ${startRow} or col: ${startCol} to search in terminal with ${this._terminal.rows} rows and ${this._terminal.cols} cols`); + } + let result: ISearchResult | undefined = undefined; this._initLinesCache(); From 8e17cace6fa2ed84faf6d129b245ec63554367b6 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 23 Mar 2022 14:17:54 -0400 Subject: [PATCH 168/245] use escaped format Co-authored-by: jerch --- src/browser/Terminal.ts | 4 ++-- src/common/data/EscapeSequences.ts | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index a00d72fd..4202501b 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -26,7 +26,7 @@ import { IRenderer } from 'browser/renderer/Types'; import { CompositionHelper } from 'browser/input/CompositionHelper'; import { Viewport } from 'browser/Viewport'; import { rightClickHandler, moveTextAreaUnderMouseCursor, handlePasteEvent, copyHandler, paste } from 'browser/Clipboard'; -import { C0, C1 } from 'common/data/EscapeSequences'; +import { C0, C1_ESCAPED } from 'common/data/EscapeSequences'; import { WindowsOptionsReportType } from '../common/InputHandler'; import { Renderer } from 'browser/renderer/Renderer'; import { Linkifier } from 'browser/Linkifier'; @@ -224,7 +224,7 @@ export class Terminal extends CoreTerminal implements ITerminal { const channels = color.toColorRGB(acc === 'ansi' ? this._colorManager.colors.ansi[req.index] : this._colorManager.colors[acc]); - this.coreService.triggerDataEvent(`${C0.ESC}]${ident};${toRgbString(channels)}ESC \9c`); + this.coreService.triggerDataEvent(`${C0.ESC}]${ident};${toRgbString(channels)}${C1_ESCAPED.ST}`); break; case ColorRequestType.SET: if (acc === 'ansi') this._colorManager.colors.ansi[req.index] = rgba.toColor(...req.color); diff --git a/src/common/data/EscapeSequences.ts b/src/common/data/EscapeSequences.ts index e35f01dd..9713f64b 100644 --- a/src/common/data/EscapeSequences.ts +++ b/src/common/data/EscapeSequences.ts @@ -148,3 +148,6 @@ export namespace C1 { /** Application Program Command */ export const APC = '\x9f'; } +export const C1_ESCAPED = { + ST: `{C0.ESC}\\` +}; From becab08b6ac2e54c7acdc4ad5c907cefaa1ba586 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 23 Mar 2022 15:12:00 -0400 Subject: [PATCH 169/245] fix most of tests --- test/api/InputHandler.api.ts | 50 ++++++++++++++++++------------------ 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/test/api/InputHandler.api.ts b/test/api/InputHandler.api.ts index 4d3a15dd..8663c465 100644 --- a/test/api/InputHandler.api.ts +++ b/test/api/InputHandler.api.ts @@ -399,13 +399,13 @@ describe('InputHandler Integration Tests', function(): void { }); it('query single color', async () => { await writeSync(page, '\x1b]4;0;?\x07'); - assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]4;0;rgb:2e2e/3434/3636\x07']); + assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]4;0;rgb:2e2e/3434/3636{C0.ESC}\\']); await writeSync(page, '\x1b]4;77;?\x07'); - assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]4;0;rgb:2e2e/3434/3636\x07', '\x1b]4;77;rgb:5f5f/d7d7/5f5f\x07']); + assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]4;0;rgb:2e2e/3434/3636{C0.ESC}\\', '\x1b]4;77;rgb:5f5f/d7d7/5f5f{C0.ESC}\\']); }); it('query multiple colors', async () => { await writeSync(page, '\x1b]4;0;?;77;?\x07'); - assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]4;0;rgb:2e2e/3434/3636\x07', '\x1b]4;77;rgb:5f5f/d7d7/5f5f\x07']); + assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]4;0;rgb:2e2e/3434/3636{C0.ESC}\\', '\x1b]4;77;rgb:5f5f/d7d7/5f5f{C0.ESC}\\']); }); it('set & query single color', async () => { await writeSync(page, '\x1b]4;0;?\x07'); @@ -413,10 +413,10 @@ describe('InputHandler Integration Tests', function(): void { assert.deepEqual(await page.evaluate('window._recordedData'), restore); // set new color & query await writeSync(page, '\x1b]4;0;rgb:01/02/03\x07\x1b]4;0;?\x07'); - assert.deepEqual(await page.evaluate('window._recordedData'), [restore[0], '\x1b]4;0;rgb:0101/0202/0303\x07']); + assert.deepEqual(await page.evaluate('window._recordedData'), [restore[0], '\x1b]4;0;rgb:0101/0202/0303{C0.ESC}\\']); // restore should set old color await writeSync(page, restore[0] + '\x1b]4;0;?\x07'); - assert.deepEqual(await page.evaluate('window._recordedData'), [restore[0], '\x1b]4;0;rgb:0101/0202/0303\x07', restore[0]]); + assert.deepEqual(await page.evaluate('window._recordedData'), [restore[0], '\x1b]4;0;rgb:0101/0202/0303{C0.ESC}\\', restore[0]]); }); it('query & set colors mixed', async () => { await writeSync(page, '\x1b]4;0;?;77;?\x07'); @@ -424,11 +424,11 @@ describe('InputHandler Integration Tests', function(): void { await page.evaluate('window._recordedData.length = 0;'); // mixed call - change 0, query 43, change 77 await writeSync(page, '\x1b]4;0;rgb:01/02/03;43;?;77;#aabbcc\x07'); - assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]4;43;rgb:0000/d7d7/afaf\x07']); + assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]4;43;rgb:0000/d7d7/afaf{C0.ESC}\\']); await page.evaluate('window._recordedData.length = 0;'); // query new values for 0 + 77 await writeSync(page, '\x1b]4;0;?;77;?\x07'); - assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]4;0;rgb:0101/0202/0303\x07', '\x1b]4;77;rgb:aaaa/bbbb/cccc\x07']); + assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]4;0;rgb:0101/0202/0303{C0.ESC}\\', '\x1b]4;77;rgb:aaaa/bbbb/cccc{C0.ESC}\\']); await page.evaluate('window._recordedData.length = 0;'); // restore old values for 0 + 77 await writeSync(page, restore[0] + restore[1] + '\x1b]4;0;?;77;?\x07'); @@ -451,10 +451,10 @@ describe('InputHandler Integration Tests', function(): void { await writeSync(page, `\x1b]4;${i};?\x07`); const restore: string[] = await page.evaluate('window._recordedData'); await writeSync(page, `\x1b]4;${i};rgb:01/02/03\x07\x1b]4;${i};?\x07`); - assert.deepEqual(await page.evaluate('window._recordedData'), [restore[0], `\x1b]4;${i};rgb:0101/0202/0303\x07`]); + assert.deepEqual(await page.evaluate('window._recordedData'), [restore[0], `\x1b]4;${i};rgb:0101/0202/0303{C0.ESC}\\`]); // restore slot color await writeSync(page, `\x1b]104;${i}\x07\x1b]4;${i};?\x07`); - assert.deepEqual(await page.evaluate('window._recordedData'), [restore[0], `\x1b]4;${i};rgb:0101/0202/0303\x07`, restore[0]]); + assert.deepEqual(await page.evaluate('window._recordedData'), [restore[0], `\x1b]4;${i};rgb:0101/0202/0303{C0.ESC}\\`, restore[0]]); await page.evaluate('window._recordedData.length = 0;'); } }); @@ -491,62 +491,62 @@ describe('InputHandler Integration Tests', function(): void { }); it('query FG color', async () => { await writeSync(page, '\x1b]10;?\x07'); - assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]10;rgb:ffff/ffff/ffff\x07']); + assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]10;rgb:ffff/ffff/ffff{C0.ESC}\\']); }); it('query BG color', async () => { await writeSync(page, '\x1b]11;?\x07'); - assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]11;rgb:0000/0000/0000\x07']); + assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]11;rgb:0000/0000/0000{C0.ESC}\\']); }); it('query FG & BG color in one call', async () => { await writeSync(page, '\x1b]10;?;?\x07'); - assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]10;rgb:ffff/ffff/ffff\x07', '\x1b]11;rgb:0000/0000/0000\x07']); + assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]10;rgb:ffff/ffff/ffff{C0.ESC}\\', '\x1b]11;rgb:0000/0000/0000{C0.ESC}\\']); }); it('set & query FG', async () => { await writeSync(page, '\x1b]10;rgb:1/2/3\x07\x1b]10;?\x07'); - assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]10;rgb:1111/2222/3333\x07']); + assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]10;rgb:1111/2222/3333{C0.ESC}\\']); await writeSync(page, '\x1b]10;#ffffff\x07\x1b]10;?\x07'); - assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]10;rgb:1111/2222/3333\x07', '\x1b]10;rgb:ffff/ffff/ffff\x07']); + assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]10;rgb:1111/2222/3333{C0.ESC}\\', '\x1b]10;rgb:ffff/ffff/ffff{C0.ESC}\\']); }); it('set & query BG', async () => { await writeSync(page, '\x1b]11;rgb:1/2/3\x07\x1b]11;?\x07'); - assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]11;rgb:1111/2222/3333\x07']); + assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]11;rgb:1111/2222/3333{C0.ESC}\\']); await writeSync(page, '\x1b]11;#000000\x07\x1b]11;?\x07'); - assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]11;rgb:1111/2222/3333\x07', '\x1b]11;rgb:0000/0000/0000\x07']); + assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]11;rgb:1111/2222/3333{C0.ESC}\\', '\x1b]11;rgb:0000/0000/0000{C0.ESC}\\']); }); it('set & query cursor color', async () => { await writeSync(page, '\x1b]12;rgb:1/2/3\x07\x1b]12;?\x07'); - assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]12;rgb:1111/2222/3333\x07']); + assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]12;rgb:1111/2222/3333{C0.ESC}\\']); await writeSync(page, '\x1b]12;#ffffff\x07\x1b]12;?\x07'); - assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]12;rgb:1111/2222/3333\x07', '\x1b]12;rgb:ffff/ffff/ffff\x07']); + assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]12;rgb:1111/2222/3333{C0.ESC}\\', '\x1b]12;rgb:ffff/ffff/ffff{C0.ESC}\\']); }); it('set & query FG & BG color in one call', async () => { await writeSync(page, '\x1b]10;#123456;rgb:aa/bb/cc\x07\x1b]10;?;?\x07'); - assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]10;rgb:1212/3434/5656\x07', '\x1b]11;rgb:aaaa/bbbb/cccc\x07']); + assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]10;rgb:1212/3434/5656{C0.ESC}\\', '\x1b]11;rgb:aaaa/bbbb/cccc{C0.ESC}\\']); await writeSync(page, '\x1b]10;#ffffff;#000000\x07'); }); it('OSC 110: restore FG color', async () => { await writeSync(page, '\x1b]10;rgb:1/2/3\x07\x1b]10;?\x07'); - assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]10;rgb:1111/2222/3333\x07']); + assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]10;rgb:1111/2222/3333{C0.ESC}\\']); await page.evaluate('window._recordedData.length = 0;'); // restore await writeSync(page, '\x1b]110\x07\x1b]10;?\x07'); - assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]10;rgb:ffff/ffff/ffff\x07']); + assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]10;rgb:ffff/ffff/ffff{C0.ESC}\\']); }); it('OSC 111: restore BG color', async () => { await writeSync(page, '\x1b]11;rgb:1/2/3\x07\x1b]11;?\x07'); - assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]11;rgb:1111/2222/3333\x07']); + assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]11;rgb:1111/2222/3333{C0.ESC}\\']); await page.evaluate('window._recordedData.length = 0;'); // restore await writeSync(page, '\x1b]111\x07\x1b]11;?\x07'); - assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]11;rgb:0000/0000/0000\x07']); + assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]11;rgb:0000/0000/0000{C0.ESC}\\']); }); it('OSC 112: restore cursor color', async () => { await writeSync(page, '\x1b]12;rgb:1/2/3\x07\x1b]12;?\x07'); - assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]12;rgb:1111/2222/3333\x07']); + assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]12;rgb:1111/2222/3333{C0.ESC}\\']); await page.evaluate('window._recordedData.length = 0;'); // restore await writeSync(page, '\x1b]112\x07\x1b]12;?\x07'); - assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]12;rgb:ffff/ffff/ffff\x07']); + assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]12;rgb:ffff/ffff/ffff{C0.ESC}\\']); }); }); }); From 79c990d73e5c633738a256fe4acff0172c6f5b1e Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 23 Mar 2022 15:43:12 -0400 Subject: [PATCH 170/245] Revert "fix most of tests" This reverts commit becab08b6ac2e54c7acdc4ad5c907cefaa1ba586. --- test/api/InputHandler.api.ts | 50 ++++++++++++++++++------------------ 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/test/api/InputHandler.api.ts b/test/api/InputHandler.api.ts index 8663c465..4d3a15dd 100644 --- a/test/api/InputHandler.api.ts +++ b/test/api/InputHandler.api.ts @@ -399,13 +399,13 @@ describe('InputHandler Integration Tests', function(): void { }); it('query single color', async () => { await writeSync(page, '\x1b]4;0;?\x07'); - assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]4;0;rgb:2e2e/3434/3636{C0.ESC}\\']); + assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]4;0;rgb:2e2e/3434/3636\x07']); await writeSync(page, '\x1b]4;77;?\x07'); - assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]4;0;rgb:2e2e/3434/3636{C0.ESC}\\', '\x1b]4;77;rgb:5f5f/d7d7/5f5f{C0.ESC}\\']); + assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]4;0;rgb:2e2e/3434/3636\x07', '\x1b]4;77;rgb:5f5f/d7d7/5f5f\x07']); }); it('query multiple colors', async () => { await writeSync(page, '\x1b]4;0;?;77;?\x07'); - assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]4;0;rgb:2e2e/3434/3636{C0.ESC}\\', '\x1b]4;77;rgb:5f5f/d7d7/5f5f{C0.ESC}\\']); + assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]4;0;rgb:2e2e/3434/3636\x07', '\x1b]4;77;rgb:5f5f/d7d7/5f5f\x07']); }); it('set & query single color', async () => { await writeSync(page, '\x1b]4;0;?\x07'); @@ -413,10 +413,10 @@ describe('InputHandler Integration Tests', function(): void { assert.deepEqual(await page.evaluate('window._recordedData'), restore); // set new color & query await writeSync(page, '\x1b]4;0;rgb:01/02/03\x07\x1b]4;0;?\x07'); - assert.deepEqual(await page.evaluate('window._recordedData'), [restore[0], '\x1b]4;0;rgb:0101/0202/0303{C0.ESC}\\']); + assert.deepEqual(await page.evaluate('window._recordedData'), [restore[0], '\x1b]4;0;rgb:0101/0202/0303\x07']); // restore should set old color await writeSync(page, restore[0] + '\x1b]4;0;?\x07'); - assert.deepEqual(await page.evaluate('window._recordedData'), [restore[0], '\x1b]4;0;rgb:0101/0202/0303{C0.ESC}\\', restore[0]]); + assert.deepEqual(await page.evaluate('window._recordedData'), [restore[0], '\x1b]4;0;rgb:0101/0202/0303\x07', restore[0]]); }); it('query & set colors mixed', async () => { await writeSync(page, '\x1b]4;0;?;77;?\x07'); @@ -424,11 +424,11 @@ describe('InputHandler Integration Tests', function(): void { await page.evaluate('window._recordedData.length = 0;'); // mixed call - change 0, query 43, change 77 await writeSync(page, '\x1b]4;0;rgb:01/02/03;43;?;77;#aabbcc\x07'); - assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]4;43;rgb:0000/d7d7/afaf{C0.ESC}\\']); + assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]4;43;rgb:0000/d7d7/afaf\x07']); await page.evaluate('window._recordedData.length = 0;'); // query new values for 0 + 77 await writeSync(page, '\x1b]4;0;?;77;?\x07'); - assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]4;0;rgb:0101/0202/0303{C0.ESC}\\', '\x1b]4;77;rgb:aaaa/bbbb/cccc{C0.ESC}\\']); + assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]4;0;rgb:0101/0202/0303\x07', '\x1b]4;77;rgb:aaaa/bbbb/cccc\x07']); await page.evaluate('window._recordedData.length = 0;'); // restore old values for 0 + 77 await writeSync(page, restore[0] + restore[1] + '\x1b]4;0;?;77;?\x07'); @@ -451,10 +451,10 @@ describe('InputHandler Integration Tests', function(): void { await writeSync(page, `\x1b]4;${i};?\x07`); const restore: string[] = await page.evaluate('window._recordedData'); await writeSync(page, `\x1b]4;${i};rgb:01/02/03\x07\x1b]4;${i};?\x07`); - assert.deepEqual(await page.evaluate('window._recordedData'), [restore[0], `\x1b]4;${i};rgb:0101/0202/0303{C0.ESC}\\`]); + assert.deepEqual(await page.evaluate('window._recordedData'), [restore[0], `\x1b]4;${i};rgb:0101/0202/0303\x07`]); // restore slot color await writeSync(page, `\x1b]104;${i}\x07\x1b]4;${i};?\x07`); - assert.deepEqual(await page.evaluate('window._recordedData'), [restore[0], `\x1b]4;${i};rgb:0101/0202/0303{C0.ESC}\\`, restore[0]]); + assert.deepEqual(await page.evaluate('window._recordedData'), [restore[0], `\x1b]4;${i};rgb:0101/0202/0303\x07`, restore[0]]); await page.evaluate('window._recordedData.length = 0;'); } }); @@ -491,62 +491,62 @@ describe('InputHandler Integration Tests', function(): void { }); it('query FG color', async () => { await writeSync(page, '\x1b]10;?\x07'); - assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]10;rgb:ffff/ffff/ffff{C0.ESC}\\']); + assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]10;rgb:ffff/ffff/ffff\x07']); }); it('query BG color', async () => { await writeSync(page, '\x1b]11;?\x07'); - assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]11;rgb:0000/0000/0000{C0.ESC}\\']); + assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]11;rgb:0000/0000/0000\x07']); }); it('query FG & BG color in one call', async () => { await writeSync(page, '\x1b]10;?;?\x07'); - assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]10;rgb:ffff/ffff/ffff{C0.ESC}\\', '\x1b]11;rgb:0000/0000/0000{C0.ESC}\\']); + assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]10;rgb:ffff/ffff/ffff\x07', '\x1b]11;rgb:0000/0000/0000\x07']); }); it('set & query FG', async () => { await writeSync(page, '\x1b]10;rgb:1/2/3\x07\x1b]10;?\x07'); - assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]10;rgb:1111/2222/3333{C0.ESC}\\']); + assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]10;rgb:1111/2222/3333\x07']); await writeSync(page, '\x1b]10;#ffffff\x07\x1b]10;?\x07'); - assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]10;rgb:1111/2222/3333{C0.ESC}\\', '\x1b]10;rgb:ffff/ffff/ffff{C0.ESC}\\']); + assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]10;rgb:1111/2222/3333\x07', '\x1b]10;rgb:ffff/ffff/ffff\x07']); }); it('set & query BG', async () => { await writeSync(page, '\x1b]11;rgb:1/2/3\x07\x1b]11;?\x07'); - assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]11;rgb:1111/2222/3333{C0.ESC}\\']); + assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]11;rgb:1111/2222/3333\x07']); await writeSync(page, '\x1b]11;#000000\x07\x1b]11;?\x07'); - assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]11;rgb:1111/2222/3333{C0.ESC}\\', '\x1b]11;rgb:0000/0000/0000{C0.ESC}\\']); + assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]11;rgb:1111/2222/3333\x07', '\x1b]11;rgb:0000/0000/0000\x07']); }); it('set & query cursor color', async () => { await writeSync(page, '\x1b]12;rgb:1/2/3\x07\x1b]12;?\x07'); - assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]12;rgb:1111/2222/3333{C0.ESC}\\']); + assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]12;rgb:1111/2222/3333\x07']); await writeSync(page, '\x1b]12;#ffffff\x07\x1b]12;?\x07'); - assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]12;rgb:1111/2222/3333{C0.ESC}\\', '\x1b]12;rgb:ffff/ffff/ffff{C0.ESC}\\']); + assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]12;rgb:1111/2222/3333\x07', '\x1b]12;rgb:ffff/ffff/ffff\x07']); }); it('set & query FG & BG color in one call', async () => { await writeSync(page, '\x1b]10;#123456;rgb:aa/bb/cc\x07\x1b]10;?;?\x07'); - assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]10;rgb:1212/3434/5656{C0.ESC}\\', '\x1b]11;rgb:aaaa/bbbb/cccc{C0.ESC}\\']); + assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]10;rgb:1212/3434/5656\x07', '\x1b]11;rgb:aaaa/bbbb/cccc\x07']); await writeSync(page, '\x1b]10;#ffffff;#000000\x07'); }); it('OSC 110: restore FG color', async () => { await writeSync(page, '\x1b]10;rgb:1/2/3\x07\x1b]10;?\x07'); - assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]10;rgb:1111/2222/3333{C0.ESC}\\']); + assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]10;rgb:1111/2222/3333\x07']); await page.evaluate('window._recordedData.length = 0;'); // restore await writeSync(page, '\x1b]110\x07\x1b]10;?\x07'); - assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]10;rgb:ffff/ffff/ffff{C0.ESC}\\']); + assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]10;rgb:ffff/ffff/ffff\x07']); }); it('OSC 111: restore BG color', async () => { await writeSync(page, '\x1b]11;rgb:1/2/3\x07\x1b]11;?\x07'); - assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]11;rgb:1111/2222/3333{C0.ESC}\\']); + assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]11;rgb:1111/2222/3333\x07']); await page.evaluate('window._recordedData.length = 0;'); // restore await writeSync(page, '\x1b]111\x07\x1b]11;?\x07'); - assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]11;rgb:0000/0000/0000{C0.ESC}\\']); + assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]11;rgb:0000/0000/0000\x07']); }); it('OSC 112: restore cursor color', async () => { await writeSync(page, '\x1b]12;rgb:1/2/3\x07\x1b]12;?\x07'); - assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]12;rgb:1111/2222/3333{C0.ESC}\\']); + assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]12;rgb:1111/2222/3333\x07']); await page.evaluate('window._recordedData.length = 0;'); // restore await writeSync(page, '\x1b]112\x07\x1b]12;?\x07'); - assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]12;rgb:ffff/ffff/ffff{C0.ESC}\\']); + assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]12;rgb:ffff/ffff/ffff\x07']); }); }); }); From f6a384b40198c29f165002b62514015e79f3c7f4 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 23 Mar 2022 17:39:10 -0400 Subject: [PATCH 171/245] fix tests Co-authored-by: jerch --- src/common/data/EscapeSequences.ts | 6 ++-- test/api/InputHandler.api.ts | 50 +++++++++++++++--------------- 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/src/common/data/EscapeSequences.ts b/src/common/data/EscapeSequences.ts index 9713f64b..0e034620 100644 --- a/src/common/data/EscapeSequences.ts +++ b/src/common/data/EscapeSequences.ts @@ -148,6 +148,6 @@ export namespace C1 { /** Application Program Command */ export const APC = '\x9f'; } -export const C1_ESCAPED = { - ST: `{C0.ESC}\\` -}; +export namespace C1_ESCAPED { + export const ST = `${C0.ESC}\\`; +} diff --git a/test/api/InputHandler.api.ts b/test/api/InputHandler.api.ts index 4d3a15dd..2981e134 100644 --- a/test/api/InputHandler.api.ts +++ b/test/api/InputHandler.api.ts @@ -399,13 +399,13 @@ describe('InputHandler Integration Tests', function(): void { }); it('query single color', async () => { await writeSync(page, '\x1b]4;0;?\x07'); - assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]4;0;rgb:2e2e/3434/3636\x07']); + assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]4;0;rgb:2e2e/3434/3636\x1b\\']); await writeSync(page, '\x1b]4;77;?\x07'); - assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]4;0;rgb:2e2e/3434/3636\x07', '\x1b]4;77;rgb:5f5f/d7d7/5f5f\x07']); + assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]4;0;rgb:2e2e/3434/3636\x1b\\', '\x1b]4;77;rgb:5f5f/d7d7/5f5f\x1b\\']); }); it('query multiple colors', async () => { await writeSync(page, '\x1b]4;0;?;77;?\x07'); - assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]4;0;rgb:2e2e/3434/3636\x07', '\x1b]4;77;rgb:5f5f/d7d7/5f5f\x07']); + assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]4;0;rgb:2e2e/3434/3636\x1b\\', '\x1b]4;77;rgb:5f5f/d7d7/5f5f\x1b\\']); }); it('set & query single color', async () => { await writeSync(page, '\x1b]4;0;?\x07'); @@ -413,10 +413,10 @@ describe('InputHandler Integration Tests', function(): void { assert.deepEqual(await page.evaluate('window._recordedData'), restore); // set new color & query await writeSync(page, '\x1b]4;0;rgb:01/02/03\x07\x1b]4;0;?\x07'); - assert.deepEqual(await page.evaluate('window._recordedData'), [restore[0], '\x1b]4;0;rgb:0101/0202/0303\x07']); + assert.deepEqual(await page.evaluate('window._recordedData'), [restore[0], '\x1b]4;0;rgb:0101/0202/0303\x1b\\']); // restore should set old color await writeSync(page, restore[0] + '\x1b]4;0;?\x07'); - assert.deepEqual(await page.evaluate('window._recordedData'), [restore[0], '\x1b]4;0;rgb:0101/0202/0303\x07', restore[0]]); + assert.deepEqual(await page.evaluate('window._recordedData'), [restore[0], '\x1b]4;0;rgb:0101/0202/0303\x1b\\', restore[0]]); }); it('query & set colors mixed', async () => { await writeSync(page, '\x1b]4;0;?;77;?\x07'); @@ -424,11 +424,11 @@ describe('InputHandler Integration Tests', function(): void { await page.evaluate('window._recordedData.length = 0;'); // mixed call - change 0, query 43, change 77 await writeSync(page, '\x1b]4;0;rgb:01/02/03;43;?;77;#aabbcc\x07'); - assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]4;43;rgb:0000/d7d7/afaf\x07']); + assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]4;43;rgb:0000/d7d7/afaf\x1b\\']); await page.evaluate('window._recordedData.length = 0;'); // query new values for 0 + 77 await writeSync(page, '\x1b]4;0;?;77;?\x07'); - assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]4;0;rgb:0101/0202/0303\x07', '\x1b]4;77;rgb:aaaa/bbbb/cccc\x07']); + assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]4;0;rgb:0101/0202/0303\x1b\\', '\x1b]4;77;rgb:aaaa/bbbb/cccc\x1b\\']); await page.evaluate('window._recordedData.length = 0;'); // restore old values for 0 + 77 await writeSync(page, restore[0] + restore[1] + '\x1b]4;0;?;77;?\x07'); @@ -451,10 +451,10 @@ describe('InputHandler Integration Tests', function(): void { await writeSync(page, `\x1b]4;${i};?\x07`); const restore: string[] = await page.evaluate('window._recordedData'); await writeSync(page, `\x1b]4;${i};rgb:01/02/03\x07\x1b]4;${i};?\x07`); - assert.deepEqual(await page.evaluate('window._recordedData'), [restore[0], `\x1b]4;${i};rgb:0101/0202/0303\x07`]); + assert.deepEqual(await page.evaluate('window._recordedData'), [restore[0], `\x1b]4;${i};rgb:0101/0202/0303\x1b\\`]); // restore slot color await writeSync(page, `\x1b]104;${i}\x07\x1b]4;${i};?\x07`); - assert.deepEqual(await page.evaluate('window._recordedData'), [restore[0], `\x1b]4;${i};rgb:0101/0202/0303\x07`, restore[0]]); + assert.deepEqual(await page.evaluate('window._recordedData'), [restore[0], `\x1b]4;${i};rgb:0101/0202/0303\x1b\\`, restore[0]]); await page.evaluate('window._recordedData.length = 0;'); } }); @@ -491,62 +491,62 @@ describe('InputHandler Integration Tests', function(): void { }); it('query FG color', async () => { await writeSync(page, '\x1b]10;?\x07'); - assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]10;rgb:ffff/ffff/ffff\x07']); + assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]10;rgb:ffff/ffff/ffff\x1b\\']); }); it('query BG color', async () => { await writeSync(page, '\x1b]11;?\x07'); - assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]11;rgb:0000/0000/0000\x07']); + assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]11;rgb:0000/0000/0000\x1b\\']); }); it('query FG & BG color in one call', async () => { await writeSync(page, '\x1b]10;?;?\x07'); - assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]10;rgb:ffff/ffff/ffff\x07', '\x1b]11;rgb:0000/0000/0000\x07']); + assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]10;rgb:ffff/ffff/ffff\x1b\\', '\x1b]11;rgb:0000/0000/0000\x1b\\']); }); it('set & query FG', async () => { await writeSync(page, '\x1b]10;rgb:1/2/3\x07\x1b]10;?\x07'); - assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]10;rgb:1111/2222/3333\x07']); + assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]10;rgb:1111/2222/3333\x1b\\']); await writeSync(page, '\x1b]10;#ffffff\x07\x1b]10;?\x07'); - assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]10;rgb:1111/2222/3333\x07', '\x1b]10;rgb:ffff/ffff/ffff\x07']); + assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]10;rgb:1111/2222/3333\x1b\\', '\x1b]10;rgb:ffff/ffff/ffff\x1b\\']); }); it('set & query BG', async () => { await writeSync(page, '\x1b]11;rgb:1/2/3\x07\x1b]11;?\x07'); - assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]11;rgb:1111/2222/3333\x07']); + assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]11;rgb:1111/2222/3333\x1b\\']); await writeSync(page, '\x1b]11;#000000\x07\x1b]11;?\x07'); - assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]11;rgb:1111/2222/3333\x07', '\x1b]11;rgb:0000/0000/0000\x07']); + assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]11;rgb:1111/2222/3333\x1b\\', '\x1b]11;rgb:0000/0000/0000\x1b\\']); }); it('set & query cursor color', async () => { await writeSync(page, '\x1b]12;rgb:1/2/3\x07\x1b]12;?\x07'); - assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]12;rgb:1111/2222/3333\x07']); + assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]12;rgb:1111/2222/3333\x1b\\']); await writeSync(page, '\x1b]12;#ffffff\x07\x1b]12;?\x07'); - assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]12;rgb:1111/2222/3333\x07', '\x1b]12;rgb:ffff/ffff/ffff\x07']); + assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]12;rgb:1111/2222/3333\x1b\\', '\x1b]12;rgb:ffff/ffff/ffff\x1b\\']); }); it('set & query FG & BG color in one call', async () => { await writeSync(page, '\x1b]10;#123456;rgb:aa/bb/cc\x07\x1b]10;?;?\x07'); - assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]10;rgb:1212/3434/5656\x07', '\x1b]11;rgb:aaaa/bbbb/cccc\x07']); + assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]10;rgb:1212/3434/5656\x1b\\', '\x1b]11;rgb:aaaa/bbbb/cccc\x1b\\']); await writeSync(page, '\x1b]10;#ffffff;#000000\x07'); }); it('OSC 110: restore FG color', async () => { await writeSync(page, '\x1b]10;rgb:1/2/3\x07\x1b]10;?\x07'); - assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]10;rgb:1111/2222/3333\x07']); + assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]10;rgb:1111/2222/3333\x1b\\']); await page.evaluate('window._recordedData.length = 0;'); // restore await writeSync(page, '\x1b]110\x07\x1b]10;?\x07'); - assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]10;rgb:ffff/ffff/ffff\x07']); + assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]10;rgb:ffff/ffff/ffff\x1b\\']); }); it('OSC 111: restore BG color', async () => { await writeSync(page, '\x1b]11;rgb:1/2/3\x07\x1b]11;?\x07'); - assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]11;rgb:1111/2222/3333\x07']); + assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]11;rgb:1111/2222/3333\x1b\\']); await page.evaluate('window._recordedData.length = 0;'); // restore await writeSync(page, '\x1b]111\x07\x1b]11;?\x07'); - assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]11;rgb:0000/0000/0000\x07']); + assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]11;rgb:0000/0000/0000\x1b\\']); }); it('OSC 112: restore cursor color', async () => { await writeSync(page, '\x1b]12;rgb:1/2/3\x07\x1b]12;?\x07'); - assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]12;rgb:1111/2222/3333\x07']); + assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]12;rgb:1111/2222/3333\x1b\\']); await page.evaluate('window._recordedData.length = 0;'); // restore await writeSync(page, '\x1b]112\x07\x1b]12;?\x07'); - assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]12;rgb:ffff/ffff/ffff\x07']); + assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]12;rgb:ffff/ffff/ffff\x1b\\']); }); }); }); From 23ed3327ce1f12555d50606c647e5fa17d3d8ddf Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 23 Mar 2022 15:04:13 -0700 Subject: [PATCH 172/245] Allow configuring search overview ruler and border color Part of microsoft/vscode#145744 Part of microsoft/vscode#145742 Part of microsoft/vscode#145746 --- addons/xterm-addon-search/src/SearchAddon.ts | 46 +++++++++++++------ .../typings/xterm-addon-search.d.ts | 28 +++++++++-- demo/client.ts | 9 +++- 3 files changed, 63 insertions(+), 20 deletions(-) diff --git a/addons/xterm-addon-search/src/SearchAddon.ts b/addons/xterm-addon-search/src/SearchAddon.ts index 0f677072..8ce92bf2 100644 --- a/addons/xterm-addon-search/src/SearchAddon.ts +++ b/addons/xterm-addon-search/src/SearchAddon.ts @@ -14,8 +14,12 @@ export interface ISearchOptions { } interface ISearchDecorationOptions { - matchColor: string; - selectedColor: string; + matchBackground?: string; + matchBorder?: string; + matchOverviewRuler: string; + selectedBackground?: string; + selectedBorder?: string; + selectedColorOverviewRuler: string; } export interface ISearchPosition { @@ -601,11 +605,16 @@ export class SearchAddon implements ITerminalAddon { return false; } terminal.select(result.col, result.row, result.size); - if (decorations?.selectedColor) { + if (decorations?.selectedColorOverviewRuler) { const marker = terminal.registerMarker(-terminal.buffer.active.baseY - terminal.buffer.active.cursorY + result.row); if (marker) { - this._selectedDecoration = terminal.registerDecoration({ marker, overviewRulerOptions: { color: decorations.selectedColor } }); - this._selectedDecoration?.onRender((e) => this._applyStyles(e, decorations.selectedColor, result)); + this._selectedDecoration = terminal.registerDecoration({ + marker, + overviewRulerOptions: { + color: decorations.selectedColorOverviewRuler + } + }); + this._selectedDecoration?.onRender((e) => this._applyStyles(e, decorations.selectedBackground, decorations.selectedBorder, result)); this._selectedDecoration?.onDispose(() => marker.dispose()); } } @@ -622,11 +631,12 @@ export class SearchAddon implements ITerminalAddon { /** * Applies styles to the decoration when it is rendered * @param element the decoration's element - * @param color the color to apply + * @param backgroundColor the background color to apply + * @param borderColor the border color to apply * @param result the search result associated with the decoration * @returns */ - private _applyStyles(element: HTMLElement, color: string, result: ISearchResult): void { + private _applyStyles(element: HTMLElement, backgroundColor: string | undefined, borderColor: string | undefined, result: ISearchResult): void { if (element.clientWidth <= 0) { return; } @@ -634,8 +644,12 @@ export class SearchAddon implements ITerminalAddon { element.classList.add('xterm-find-result-decoration'); element.style.left = `${element.clientWidth * result.col}px`; element.style.width = `${element.clientWidth * result.term.length}px`; - element.style.backgroundColor = color; - element.style.opacity = '0.6'; + if (backgroundColor) { + element.style.backgroundColor = backgroundColor; + } + if (borderColor) { + element.style.outline = `1px solid ${borderColor}`; + } } } @@ -648,14 +662,16 @@ export class SearchAddon implements ITerminalAddon { private _createResultDecoration(result: ISearchResult, decorations: ISearchDecorationOptions): IDecoration | undefined { const terminal = this._terminal!; const marker = terminal.registerMarker(-terminal.buffer.active.baseY - terminal.buffer.active.cursorY + result.row); - if (!marker || !decorations?.matchColor) { + if (!marker || !decorations?.matchOverviewRuler) { return undefined; } - const findResultDecoration = terminal.registerDecoration( - { marker, - overviewRulerOptions: this._resultDecorations.get(marker.line) && !this._dataChanged ? undefined : { color: decorations.matchColor, position: 'center' } - }); - findResultDecoration?.onRender((e) => this._applyStyles(e, decorations.matchColor, result)); + const findResultDecoration = terminal.registerDecoration({ + marker, + overviewRulerOptions: this._resultDecorations.get(marker.line) && !this._dataChanged ? undefined : { + color: decorations.matchOverviewRuler, position: 'center' + } + }); + findResultDecoration?.onRender((e) => this._applyStyles(e, decorations.matchBackground, decorations.matchBorder, result)); findResultDecoration?.onDispose(() => marker.dispose()); return findResultDecoration; } diff --git a/addons/xterm-addon-search/typings/xterm-addon-search.d.ts b/addons/xterm-addon-search/typings/xterm-addon-search.d.ts index 67ed2985..ab5e9321 100644 --- a/addons/xterm-addon-search/typings/xterm-addon-search.d.ts +++ b/addons/xterm-addon-search/typings/xterm-addon-search.d.ts @@ -45,14 +45,34 @@ declare module 'xterm-addon-search' { */ interface ISearchDecorationOptions { /** - * The color of a match. + * The background color of a match. */ - matchColor: string; + matchBackground?: string; /** - * The color for the currently selected match. + * The border color of a match */ - selectedColor: string; + matchBorder?: string; + + /** + * The overview ruler color of a match. + */ + matchOverviewRuler: string; + + /** + * The background color for the currently selected match. + */ + selectedBackground?: string; + + /** + * The border color of the currently selected match. + */ + selectedBorder?: string; + + /** + * The overview ruler color of the currently selected match. + */ + selectedColorOverviewRuler: string; } /** diff --git a/demo/client.ts b/demo/client.ts index 830a44a0..5c669db3 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -109,7 +109,14 @@ function getSearchOptions(e: KeyboardEvent): ISearchOptions { wholeWord: (document.getElementById('whole-word') as HTMLInputElement).checked, caseSensitive: (document.getElementById('case-sensitive') as HTMLInputElement).checked, incremental: e.key !== `Enter`, - decorations: (document.getElementById('highlight-all-matches') as HTMLInputElement).checked ? { matchColor: '#555753', selectedColor: '#ef2929' } : undefined + decorations: (document.getElementById('highlight-all-matches') as HTMLInputElement).checked ? { + matchBackground: '#55575380', + matchBorder: '#555753', + matchOverviewRuler: '#555753', + selectedBackground: '#ef292980', + selectedBorder: '#ef2929', + selectedColorOverviewRuler: '#ef2929' + } : undefined }; } From b55c7e342ae3283364f34b71701b102df1841f49 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 23 Mar 2022 15:09:35 -0700 Subject: [PATCH 173/245] Improve setting names --- addons/xterm-addon-search/src/SearchAddon.ts | 12 ++++++------ .../typings/xterm-addon-search.d.ts | 12 ++++++------ demo/client.ts | 6 +++--- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/addons/xterm-addon-search/src/SearchAddon.ts b/addons/xterm-addon-search/src/SearchAddon.ts index 8ce92bf2..d8b5a17e 100644 --- a/addons/xterm-addon-search/src/SearchAddon.ts +++ b/addons/xterm-addon-search/src/SearchAddon.ts @@ -17,9 +17,9 @@ interface ISearchDecorationOptions { matchBackground?: string; matchBorder?: string; matchOverviewRuler: string; - selectedBackground?: string; - selectedBorder?: string; - selectedColorOverviewRuler: string; + activeMatchBackground?: string; + activeMatchBorder?: string; + activeMatchColorOverviewRuler: string; } export interface ISearchPosition { @@ -605,16 +605,16 @@ export class SearchAddon implements ITerminalAddon { return false; } terminal.select(result.col, result.row, result.size); - if (decorations?.selectedColorOverviewRuler) { + if (decorations?.activeMatchColorOverviewRuler) { const marker = terminal.registerMarker(-terminal.buffer.active.baseY - terminal.buffer.active.cursorY + result.row); if (marker) { this._selectedDecoration = terminal.registerDecoration({ marker, overviewRulerOptions: { - color: decorations.selectedColorOverviewRuler + color: decorations.activeMatchColorOverviewRuler } }); - this._selectedDecoration?.onRender((e) => this._applyStyles(e, decorations.selectedBackground, decorations.selectedBorder, result)); + this._selectedDecoration?.onRender((e) => this._applyStyles(e, decorations.activeMatchBackground, decorations.activeMatchBorder, result)); this._selectedDecoration?.onDispose(() => marker.dispose()); } } diff --git a/addons/xterm-addon-search/typings/xterm-addon-search.d.ts b/addons/xterm-addon-search/typings/xterm-addon-search.d.ts index ab5e9321..aedb6af9 100644 --- a/addons/xterm-addon-search/typings/xterm-addon-search.d.ts +++ b/addons/xterm-addon-search/typings/xterm-addon-search.d.ts @@ -60,19 +60,19 @@ declare module 'xterm-addon-search' { matchOverviewRuler: string; /** - * The background color for the currently selected match. + * The background color for the currently active match. */ - selectedBackground?: string; + activeMatchBackground?: string; /** - * The border color of the currently selected match. + * The border color of the currently active match. */ - selectedBorder?: string; + activeMatchBorder?: string; /** - * The overview ruler color of the currently selected match. + * The overview ruler color of the currently active match. */ - selectedColorOverviewRuler: string; + activeMatchColorOverviewRuler: string; } /** diff --git a/demo/client.ts b/demo/client.ts index 5c669db3..55ff8d62 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -113,9 +113,9 @@ function getSearchOptions(e: KeyboardEvent): ISearchOptions { matchBackground: '#55575380', matchBorder: '#555753', matchOverviewRuler: '#555753', - selectedBackground: '#ef292980', - selectedBorder: '#ef2929', - selectedColorOverviewRuler: '#ef2929' + activeMatchBackground: '#ef292980', + activeMatchBorder: '#ef2929', + activeMatchColorOverviewRuler: '#ef2929' } : undefined }; } From 86f80b344bed688e25cab9521722acc0b16fbee6 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 23 Mar 2022 19:20:16 -0400 Subject: [PATCH 174/245] don't throw if row > terminal.rows --- addons/xterm-addon-search/src/SearchAddon.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/addons/xterm-addon-search/src/SearchAddon.ts b/addons/xterm-addon-search/src/SearchAddon.ts index d8b5a17e..3b00d7eb 100644 --- a/addons/xterm-addon-search/src/SearchAddon.ts +++ b/addons/xterm-addon-search/src/SearchAddon.ts @@ -172,8 +172,8 @@ export class SearchAddon implements ITerminalAddon { this.clearDecorations(); return undefined; } - if (startRow > this._terminal.rows || startCol > this._terminal.cols) { - throw new Error(`Invalid row: ${startRow} or col: ${startCol} to search in terminal with ${this._terminal.rows} rows and ${this._terminal.cols} cols`); + if (startCol > this._terminal.cols) { + throw new Error(`Invalid col: ${startCol} to search in terminal of ${this._terminal.cols} cols`); } let result: ISearchResult | undefined = undefined; From 24fb49875719dbf72d86e346464c127fea193cb0 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 24 Mar 2022 07:05:16 -0700 Subject: [PATCH 175/245] Don't override decoration positions This also fixes an exception that was throwing where we only allowed links within the terminal's 'rows', which should have been buffer length Fixes #3705 Part of microsoft/vscode#145808 --- addons/xterm-addon-search/src/SearchAddon.ts | 23 ++++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/addons/xterm-addon-search/src/SearchAddon.ts b/addons/xterm-addon-search/src/SearchAddon.ts index 0f677072..a46fb712 100644 --- a/addons/xterm-addon-search/src/SearchAddon.ts +++ b/addons/xterm-addon-search/src/SearchAddon.ts @@ -168,8 +168,8 @@ export class SearchAddon implements ITerminalAddon { this.clearDecorations(); return undefined; } - if (startRow > this._terminal.rows || startCol > this._terminal.cols) { - throw new Error(`Invalid row: ${startRow} or col: ${startCol} to search in terminal with ${this._terminal.rows} rows and ${this._terminal.cols} cols`); + if (startRow > this._terminal.buffer.active.baseY + this._terminal.rows || startCol > this._terminal.cols) { + throw new Error(`Invalid row: ${startRow} or col: ${startCol} to search in terminal with ${this._terminal.buffer.active.baseY + this._terminal.rows} rows and ${this._terminal.cols} cols`); } let result: ISearchResult | undefined = undefined; @@ -604,7 +604,12 @@ export class SearchAddon implements ITerminalAddon { if (decorations?.selectedColor) { const marker = terminal.registerMarker(-terminal.buffer.active.baseY - terminal.buffer.active.cursorY + result.row); if (marker) { - this._selectedDecoration = terminal.registerDecoration({ marker, overviewRulerOptions: { color: decorations.selectedColor } }); + this._selectedDecoration = terminal.registerDecoration({ + marker, + x: result.col, + width: result.size, + overviewRulerOptions: { color: decorations.selectedColor } + }); this._selectedDecoration?.onRender((e) => this._applyStyles(e, decorations.selectedColor, result)); this._selectedDecoration?.onDispose(() => marker.dispose()); } @@ -632,8 +637,6 @@ export class SearchAddon implements ITerminalAddon { } if (!element.classList.contains('xterm-find-result-decoration')) { element.classList.add('xterm-find-result-decoration'); - element.style.left = `${element.clientWidth * result.col}px`; - element.style.width = `${element.clientWidth * result.term.length}px`; element.style.backgroundColor = color; element.style.opacity = '0.6'; } @@ -651,10 +654,12 @@ export class SearchAddon implements ITerminalAddon { if (!marker || !decorations?.matchColor) { return undefined; } - const findResultDecoration = terminal.registerDecoration( - { marker, - overviewRulerOptions: this._resultDecorations.get(marker.line) && !this._dataChanged ? undefined : { color: decorations.matchColor, position: 'center' } - }); + const findResultDecoration = terminal.registerDecoration({ + marker, + x: result.col, + width: result.size, + overviewRulerOptions: this._resultDecorations.get(marker.line) && !this._dataChanged ? undefined : { color: decorations.matchColor, position: 'center' } + }); findResultDecoration?.onRender((e) => this._applyStyles(e, decorations.matchColor, result)); findResultDecoration?.onDispose(() => marker.dispose()); return findResultDecoration; From 02795f8803224e431a019ab058ad2855c2a43e1d Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 24 Mar 2022 08:02:52 -0700 Subject: [PATCH 176/245] Don't change overview ruler position based on decorations The original fix for #3705 ended up revealing this other bug; the canvas shouldn't be set based on the anchor of its decorations. Fixes #3705 --- src/browser/Decorations/OverviewRulerRenderer.ts | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/browser/Decorations/OverviewRulerRenderer.ts b/src/browser/Decorations/OverviewRulerRenderer.ts index 950e2ac8..1407f4bb 100644 --- a/src/browser/Decorations/OverviewRulerRenderer.ts +++ b/src/browser/Decorations/OverviewRulerRenderer.ts @@ -141,13 +141,6 @@ export class OverviewRulerRenderer extends Disposable { } private _refreshStyle(decoration: IInternalDecoration): void { - if (this._shouldUpdateAnchor) { - if (decoration.options.anchor === 'right') { - this._canvas.style.right = decoration.options.x ? `${decoration.options.x * this._renderService.dimensions.actualCellWidth}px` : ''; - } else { - this._canvas.style.left = decoration.options.x ? `${decoration.options.x * this._renderService.dimensions.actualCellWidth}px` : ''; - } - } if (!decoration.options.overviewRulerOptions) { this._decorationElements.delete(decoration); return; From 28dcd38a53665aa63bcb4425dfd5f5f34462f4fb Mon Sep 17 00:00:00 2001 From: Tobias Speicher Date: Fri, 25 Mar 2022 20:41:06 +0100 Subject: [PATCH 177/245] refactor: replace deprecated String.prototype.substr() .substr() is deprecated so we replace it with .slice() which works similarily but isn't deprecated Signed-off-by: Tobias Speicher --- .../src/atlas/WebglCharAtlas.ts | 2 +- .../test/WebglRenderer.api.ts | 30 +++++++++---------- bin/publish.js | 6 ++-- src/browser/Terminal.test.ts | 4 +-- src/browser/renderer/CustomGlyphs.ts | 8 ++--- 5 files changed, 25 insertions(+), 25 deletions(-) diff --git a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts index dd95f177..e409f51e 100644 --- a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts +++ b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts @@ -238,7 +238,7 @@ export class WebglCharAtlas implements IDisposable { const bg = this._config.colors.background.css; if (bg.length === 9) { // Remove bg alpha channel if present - return bg.substr(0, 7); + return bg.slice(0, 7); } return bg; } diff --git a/addons/xterm-addon-webgl/test/WebglRenderer.api.ts b/addons/xterm-addon-webgl/test/WebglRenderer.api.ts index e6942d1c..7c6755da 100644 --- a/addons/xterm-addon-webgl/test/WebglRenderer.api.ts +++ b/addons/xterm-addon-webgl/test/WebglRenderer.api.ts @@ -260,9 +260,9 @@ describe('WebGL Renderer Integration Tests', async () => { 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.substr(1, 2), 16); - const g = parseInt(cssColor.substr(3, 2), 16); - const b = parseInt(cssColor.substr(5, 2), 16); + 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]); } } @@ -280,9 +280,9 @@ describe('WebGL Renderer Integration Tests', async () => { 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.substr(1, 2), 16); - const g = parseInt(cssColor.substr(3, 2), 16); - const b = parseInt(cssColor.substr(5, 2), 16); + 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]); } } @@ -300,9 +300,9 @@ describe('WebGL Renderer Integration Tests', async () => { 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.substr(1, 2), 16); - const g = parseInt(cssColor.substr(3, 2), 16); - const b = parseInt(cssColor.substr(5, 2), 16); + 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]); } } @@ -320,9 +320,9 @@ describe('WebGL Renderer Integration Tests', async () => { 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.substr(1, 2), 16); - const g = parseInt(cssColor.substr(3, 2), 16); - const b = parseInt(cssColor.substr(5, 2), 16); + 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]); } } @@ -356,9 +356,9 @@ describe('WebGL Renderer Integration Tests', async () => { 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.substr(1, 2), 16); - const g = parseInt(cssColor.substr(3, 2), 16); - const b = parseInt(cssColor.substr(5, 2), 16); + 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]); } } diff --git a/bin/publish.js b/bin/publish.js index 75de4b68..a43b8cd4 100644 --- a/bin/publish.js +++ b/bin/publish.js @@ -104,11 +104,11 @@ function getNextBetaVersion(packageJson) { return `${nextStableVersion}-${tag}.1`; } const latestPublishedVersion = publishedVersions.sort((a, b) => { - const aVersion = parseInt(a.substr(a.search(/\d+$/))); - const bVersion = parseInt(b.substr(b.search(/\d+$/))); + const aVersion = parseInt(a.slice(a.search(/\d+$/))); + const bVersion = parseInt(b.slice(b.search(/\d+$/))); return aVersion > bVersion ? -1 : 1; })[0]; - const latestTagVersion = parseInt(latestPublishedVersion.substr(latestPublishedVersion.search(/\d+$/)), 10); + const latestTagVersion = parseInt(latestPublishedVersion.slice(latestPublishedVersion.search(/\d+$/)), 10); return `${nextStableVersion}-${tag}.${latestTagVersion + 1}`; } diff --git a/src/browser/Terminal.test.ts b/src/browser/Terminal.test.ts index 872bfdc7..d039b17b 100644 --- a/src/browser/Terminal.test.ts +++ b/src/browser/Terminal.test.ts @@ -1332,8 +1332,8 @@ describe('Terminal', () => { (!(i % 3)) ? input[i] : (i % 3 === 1) - ? input.substr(i, 2) - : input.substr(i - 1, 2), + ? input.slice(i, i + 2) + : input.slice(i - 1, i + 1), terminal.buffer.lines.get(bufferIndex[0])!.loadCell(bufferIndex[1], new CellData()).getChars()); } }); diff --git a/src/browser/renderer/CustomGlyphs.ts b/src/browser/renderer/CustomGlyphs.ts index 77562790..c2bfc210 100644 --- a/src/browser/renderer/CustomGlyphs.ts +++ b/src/browser/renderer/CustomGlyphs.ts @@ -414,10 +414,10 @@ function drawPatternChar( let b: number; let a: number; if (fillStyle.startsWith('#')) { - r = parseInt(fillStyle.substr(1, 2), 16); - g = parseInt(fillStyle.substr(3, 2), 16); - b = parseInt(fillStyle.substr(5, 2), 16); - a = fillStyle.length > 7 && parseInt(fillStyle.substr(7, 2), 16) || 1; + r = parseInt(fillStyle.slice(1, 3), 16); + g = parseInt(fillStyle.slice(3, 5), 16); + b = parseInt(fillStyle.slice(5, 7), 16); + a = fillStyle.length > 7 && parseInt(fillStyle.slice(7, 9), 16) || 1; } else if (fillStyle.startsWith('rgba')) { ([r, g, b, a] = fillStyle.substring(5, fillStyle.length - 1).split(',').map(e => parseFloat(e))); } else { From 433732616994e430c22169b605de044924733895 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 26 Mar 2022 15:33:57 +0000 Subject: [PATCH 178/245] Bump minimist from 1.2.5 to 1.2.6 in /addons/xterm-addon-ligatures Bumps [minimist](https://github.com/substack/minimist) from 1.2.5 to 1.2.6. - [Release notes](https://github.com/substack/minimist/releases) - [Commits](https://github.com/substack/minimist/compare/1.2.5...1.2.6) --- updated-dependencies: - dependency-name: minimist dependency-type: indirect ... Signed-off-by: dependabot[bot] --- addons/xterm-addon-ligatures/yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/addons/xterm-addon-ligatures/yarn.lock b/addons/xterm-addon-ligatures/yarn.lock index 64117742..6ba3eccb 100644 --- a/addons/xterm-addon-ligatures/yarn.lock +++ b/addons/xterm-addon-ligatures/yarn.lock @@ -141,9 +141,9 @@ lru-cache@^6.0.0: yallist "^4.0.0" minimist@^1.2.5: - version "1.2.5" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" - integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== + version "1.2.6" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44" + integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q== mkdirp@0.5.5: version "0.5.5" From 06f3f8f26732e1dfbb542c18693345de8c427421 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Mar 2022 13:30:37 +0000 Subject: [PATCH 179/245] Bump minimist from 1.2.5 to 1.2.6 Bumps [minimist](https://github.com/substack/minimist) from 1.2.5 to 1.2.6. - [Release notes](https://github.com/substack/minimist/releases) - [Commits](https://github.com/substack/minimist/compare/1.2.5...1.2.6) --- updated-dependencies: - dependency-name: minimist dependency-type: indirect ... Signed-off-by: dependabot[bot] --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index c85bca27..1177f49a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2948,9 +2948,9 @@ minimatch@3.0.4, minimatch@^3.0.4: brace-expansion "^1.1.7" minimist@^1.2.5: - version "1.2.5" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" - integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== + version "1.2.6" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44" + integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q== mkdirp@^0.5.3: version "0.5.5" From 32ae29be37ec4e601082dc3e366522660f169250 Mon Sep 17 00:00:00 2001 From: Pavel Sychev Date: Wed, 30 Mar 2022 14:27:02 +0200 Subject: [PATCH 180/245] Fix WebLinkProvider to handle wrapped lines properly. --- addons/xterm-addon-web-links/src/WebLinkProvider.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/addons/xterm-addon-web-links/src/WebLinkProvider.ts b/addons/xterm-addon-web-links/src/WebLinkProvider.ts index f0caf974..04098741 100644 --- a/addons/xterm-addon-web-links/src/WebLinkProvider.ts +++ b/addons/xterm-addon-web-links/src/WebLinkProvider.ts @@ -78,10 +78,17 @@ export class LinkComputer { endY++; } + let startX = stringIndex + 1; + let startY = startLineIndex + 1; + while (startX > terminal.cols) { + startX -= terminal.cols; + startY++; + } + const range = { start: { - x: stringIndex + 1, - y: startLineIndex + 1 + x: startX, + y: startY }, end: { x: endX, From 7f87b4632857c6a1d2a40582800e5ef3d0260e53 Mon Sep 17 00:00:00 2001 From: Pavel Sychev Date: Wed, 30 Mar 2022 14:30:56 +0200 Subject: [PATCH 181/245] Expose urlRegex in the public .d.ts file. --- .../xterm-addon-web-links/typings/xterm-addon-web-links.d.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/addons/xterm-addon-web-links/typings/xterm-addon-web-links.d.ts b/addons/xterm-addon-web-links/typings/xterm-addon-web-links.d.ts index 78a258e5..5e6c266d 100644 --- a/addons/xterm-addon-web-links/typings/xterm-addon-web-links.d.ts +++ b/addons/xterm-addon-web-links/typings/xterm-addon-web-links.d.ts @@ -49,5 +49,10 @@ declare module 'xterm-addon-web-links' { * happen even when tooltipCallback hasn't fired for the link yet. */ leave?(event: MouseEvent, text: string): void; + + /** + * A callback to use instead of the default one. + */ + urlRegex?: RegExp; } } From bd3bd7fda8f25b8bb79930c6336b67c936338491 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 30 Mar 2022 07:57:42 -0700 Subject: [PATCH 182/245] Remove duplicate interface --- addons/xterm-addon-web-links/src/WebLinkProvider.ts | 3 ++- addons/xterm-addon-web-links/src/WebLinksAddon.ts | 10 ++-------- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/addons/xterm-addon-web-links/src/WebLinkProvider.ts b/addons/xterm-addon-web-links/src/WebLinkProvider.ts index f0caf974..a620bc5e 100644 --- a/addons/xterm-addon-web-links/src/WebLinkProvider.ts +++ b/addons/xterm-addon-web-links/src/WebLinkProvider.ts @@ -5,9 +5,10 @@ import { ILinkProvider, ILink, Terminal, IViewportRange } from 'xterm'; -interface ILinkProviderOptions { +export interface ILinkProviderOptions { hover?(event: MouseEvent, text: string, location: IViewportRange): void; leave?(event: MouseEvent, text: string): void; + urlRegex?: RegExp; } export class WebLinkProvider implements ILinkProvider { diff --git a/addons/xterm-addon-web-links/src/WebLinksAddon.ts b/addons/xterm-addon-web-links/src/WebLinksAddon.ts index aae921ec..285ef5dc 100644 --- a/addons/xterm-addon-web-links/src/WebLinksAddon.ts +++ b/addons/xterm-addon-web-links/src/WebLinksAddon.ts @@ -3,8 +3,8 @@ * @license MIT */ -import { Terminal, ILinkMatcherOptions, ITerminalAddon, IDisposable, IViewportRange } from 'xterm'; -import { WebLinkProvider } from './WebLinkProvider'; +import { Terminal, ILinkMatcherOptions, ITerminalAddon, IDisposable } from 'xterm'; +import { ILinkProviderOptions, WebLinkProvider } from './WebLinkProvider'; const protocolClause = '(https?:\\/\\/)'; const domainCharacterSet = '[\\da-z\\.-]+'; @@ -40,12 +40,6 @@ function handleLink(event: MouseEvent, uri: string): void { } } -interface ILinkProviderOptions { - hover?(event: MouseEvent, text: string, location: IViewportRange): void; - leave?(event: MouseEvent, text: string): void; - urlRegex?: RegExp; -} - export class WebLinksAddon implements ITerminalAddon { private _linkMatcherId: number | undefined; private _terminal: Terminal | undefined; From 842f4c20888b308d18aa976b34904567d953ad36 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Wed, 30 Mar 2022 20:08:55 -0400 Subject: [PATCH 183/245] Add search result count to SearchAddon (#3716) --- addons/xterm-addon-search/src/SearchAddon.ts | 98 ++++++++++++++----- addons/xterm-addon-search/src/tsconfig.json | 12 ++- .../typings/xterm-addon-search.d.ts | 9 +- addons/xterm-addon-search/webpack.config.js | 7 ++ 4 files changed, 97 insertions(+), 29 deletions(-) diff --git a/addons/xterm-addon-search/src/SearchAddon.ts b/addons/xterm-addon-search/src/SearchAddon.ts index c261ca6a..d2ee05b4 100644 --- a/addons/xterm-addon-search/src/SearchAddon.ts +++ b/addons/xterm-addon-search/src/SearchAddon.ts @@ -4,6 +4,7 @@ */ import { Terminal, IDisposable, ITerminalAddon, ISelectionPosition, IDecoration } from 'xterm'; +import { EventEmitter } from 'common/EventEmitter'; export interface ISearchOptions { regex?: boolean; @@ -53,8 +54,8 @@ export class SearchAddon implements ITerminalAddon { private _dataChanged: boolean = false; private _cachedSearchTerm: string | undefined; private _selectedDecoration: IDecoration | undefined; - private _resultDecorations: Map = new Map(); - private _searchResults: Map = new Map(); + private _resultDecorations: Map | undefined; + private _searchResults: Map | undefined; private _onDataDisposable: IDisposable | undefined; private _lastSearchOptions: ISearchOptions | undefined; private _highlightTimeout: number | undefined; @@ -68,6 +69,11 @@ export class SearchAddon implements ITerminalAddon { private _cursorMoveListener: IDisposable | undefined; private _resizeListener: IDisposable | undefined; + private _resultIndex: number | undefined; + + private readonly _onDidChangeResults = new EventEmitter<{resultIndex: number, resultCount: number} | undefined>(); + public readonly onDidChangeResults = this._onDidChangeResults.event; + public activate(terminal: Terminal): void { this._terminal = terminal; this._onDataDisposable = this._terminal.onData(() => { @@ -75,11 +81,11 @@ export class SearchAddon implements ITerminalAddon { if (this._highlightTimeout) { window.clearTimeout(this._highlightTimeout); } - this._highlightTimeout = setTimeout(() => { - if (this._lastSearchOptions?.decorations && this._cachedSearchTerm && this._resultDecorations.size > 0 && this._lastSearchOptions) { - this._highlightAllMatches(this._cachedSearchTerm, this._lastSearchOptions); - } - }, 200); + if (this._cachedSearchTerm && this._lastSearchOptions?.decorations) { + this._highlightTimeout = setTimeout(() => { + this.findPrevious(this._cachedSearchTerm!, { ...this._lastSearchOptions, incremental: true }); + }, 200); + } }); } @@ -90,20 +96,18 @@ export class SearchAddon implements ITerminalAddon { public clearDecorations(): void { this._selectedDecoration?.dispose(); - this._terminal?.clearSelection(); - this._searchResults.clear(); - this._disposeDecorations(); - this._cachedSearchTerm = undefined; - this._dataChanged = true; - } - - private _disposeDecorations(): void { - this._resultDecorations.forEach(decorations => { + this._searchResults?.clear(); + this._resultDecorations?.forEach(decorations => { for (const d of decorations) { d.dispose(); } }); - this._resultDecorations.clear(); + this._resultDecorations?.clear(); + this._cachedSearchTerm = undefined; + this._searchResults = undefined; + this._resultDecorations = undefined; + this._dataChanged = true; + this._resultIndex = undefined; } /** @@ -118,11 +122,18 @@ export class SearchAddon implements ITerminalAddon { throw new Error('Cannot use addon until it has been loaded'); } this._lastSearchOptions = searchOptions; - const findNextResult = this._findNextAndSelect(term, searchOptions); if (searchOptions?.decorations) { this._highlightAllMatches(term, searchOptions); } - return findNextResult; + const next = this._findNextAndSelect(term, searchOptions); + if (searchOptions?.decorations) { + if (next && this._resultIndex !== undefined && this._searchResults?.size) { + this._onDidChangeResults.fire({ resultIndex: this._resultIndex, resultCount: this._searchResults.size }); + } else { + this._onDidChangeResults.fire(undefined); + } + } + return next; } private _highlightAllMatches(term: string, searchOptions: ISearchOptions): void { @@ -137,9 +148,12 @@ export class SearchAddon implements ITerminalAddon { if (term === this._cachedSearchTerm && !this._dataChanged) { return; } + // new search, clear out the old decorations - this._disposeDecorations(); - this._searchResults.clear(); + this.clearDecorations(); + this._searchResults = new Map(); + this._resultDecorations = new Map(); + const resultDecorations = this._resultDecorations; let result = this._find(term, 0, 0, searchOptions); while (result && !this._searchResults.get(`${result.row}-${result.col}`)) { this._searchResults.set(`${result.row}-${result.col}`, result); @@ -153,9 +167,9 @@ export class SearchAddon implements ITerminalAddon { this._searchResults.forEach(result => { const resultDecoration = this._createResultDecoration(result, searchOptions.decorations!); if (resultDecoration) { - const decorationsForLine = this._resultDecorations.get(resultDecoration.marker.line) || []; + const decorationsForLine = resultDecorations.get(resultDecoration.marker.line) || []; decorationsForLine.push(resultDecoration); - this._resultDecorations.set(resultDecoration.marker.line, decorationsForLine); + resultDecorations.set(resultDecoration.marker.line, decorationsForLine); } }); if (this._dataChanged) { @@ -211,6 +225,7 @@ export class SearchAddon implements ITerminalAddon { return false; } + let startCol = 0; let startRow = 0; let currentSelection: ISelectionPosition | undefined; @@ -265,6 +280,17 @@ export class SearchAddon implements ITerminalAddon { result = this._findInLine(term, searchPosition, searchOptions); } + if (this._searchResults) { + if (this._resultIndex === undefined) { + this._resultIndex = 0; + } else { + this._resultIndex++; + if (this._resultIndex >= this._searchResults.size) { + this._resultIndex = 0; + } + } + } + // Set selection and scroll if a result was found return this._selectResult(result, searchOptions?.decorations); } @@ -280,14 +306,21 @@ export class SearchAddon implements ITerminalAddon { throw new Error('Cannot use addon until it has been loaded'); } this._lastSearchOptions = searchOptions; - const findPreviousResult = this._findAndSelectPrevious(term, searchOptions); if (searchOptions?.decorations) { this._highlightAllMatches(term, searchOptions); } - return findPreviousResult; + const previous = this._findPreviousAndSelect(term, searchOptions); + if (searchOptions?.decorations) { + if (previous && this._resultIndex !== undefined && this._searchResults?.size) { + this._onDidChangeResults.fire({ resultIndex: this._resultIndex, resultCount: this._searchResults.size }); + } else { + this._onDidChangeResults.fire(undefined); + } + } + return previous; } - private _findAndSelectPrevious(term: string, searchOptions?: ISearchOptions): boolean { + private _findPreviousAndSelect(term: string, searchOptions?: ISearchOptions): boolean { if (!this._terminal) { throw new Error('Cannot use addon until it has been loaded'); } @@ -356,6 +389,17 @@ export class SearchAddon implements ITerminalAddon { } } + if (this._searchResults) { + if (this._resultIndex === undefined) { + this._resultIndex = this._searchResults?.size - 1; + } else { + this._resultIndex--; + if (this._resultIndex === -1) { + this._resultIndex = this._searchResults?.size - 1; + } + } + } + // If there is only one result, return true. if (!result && currentSelection) return true; @@ -669,7 +713,7 @@ export class SearchAddon implements ITerminalAddon { marker, x: result.col, width: result.size, - overviewRulerOptions: this._resultDecorations.get(marker.line) && !this._dataChanged ? undefined : { + overviewRulerOptions: this._resultDecorations?.get(marker.line) && !this._dataChanged ? undefined : { color: decorations.matchOverviewRuler, position: 'center' } }); diff --git a/addons/xterm-addon-search/src/tsconfig.json b/addons/xterm-addon-search/src/tsconfig.json index d264abe0..5a5e671f 100644 --- a/addons/xterm-addon-search/src/tsconfig.json +++ b/addons/xterm-addon-search/src/tsconfig.json @@ -13,10 +13,20 @@ "strict": true, "types": [ "../../../node_modules/@types/mocha" - ] + ], + "paths": { + "common/*": [ + "../../../src/common/*" + ] + } }, "include": [ "./**/*", "../../../typings/xterm.d.ts" + ], + "references": [ + { + "path": "../../../src/common" + } ] } diff --git a/addons/xterm-addon-search/typings/xterm-addon-search.d.ts b/addons/xterm-addon-search/typings/xterm-addon-search.d.ts index aedb6af9..1cb9740e 100644 --- a/addons/xterm-addon-search/typings/xterm-addon-search.d.ts +++ b/addons/xterm-addon-search/typings/xterm-addon-search.d.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { Terminal, ILinkMatcherOptions, IDisposable, ITerminalAddon } from 'xterm'; +import { Terminal, ITerminalAddon, IEvent } from 'xterm'; declare module 'xterm-addon-search' { /** @@ -110,5 +110,12 @@ declare module 'xterm-addon-search' { * Clears the decorations and selection */ public clearDecorations(): void; + + /** + * When decorations are enabled, fires when + * the search results or the selected result changes, + * returning undefined if there are no matches. + */ + readonly onDidChangeResults: IEvent<{ resultIndex: number, resultCount: number } | undefined>; } } diff --git a/addons/xterm-addon-search/webpack.config.js b/addons/xterm-addon-search/webpack.config.js index 726dceb6..30526812 100644 --- a/addons/xterm-addon-search/webpack.config.js +++ b/addons/xterm-addon-search/webpack.config.js @@ -21,6 +21,13 @@ module.exports = { } ] }, + resolve: { + modules: ['./node_modules'], + extensions: [ '.js' ], + alias: { + common: path.resolve('../../out/common') + } + }, output: { filename: mainFile, path: path.resolve('./lib'), From 7c5853bf0051b4938690546fb8d67c717b160979 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 1 Apr 2022 05:30:01 -0700 Subject: [PATCH 184/245] Create color zone system Part of #3703 --- src/browser/Decorations/ColorZoneStore.ts | 94 +++++++++++++++++++ .../Decorations/OverviewRulerRenderer.ts | 25 ++++- 2 files changed, 118 insertions(+), 1 deletion(-) create mode 100644 src/browser/Decorations/ColorZoneStore.ts diff --git a/src/browser/Decorations/ColorZoneStore.ts b/src/browser/Decorations/ColorZoneStore.ts new file mode 100644 index 00000000..c5823234 --- /dev/null +++ b/src/browser/Decorations/ColorZoneStore.ts @@ -0,0 +1,94 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IInternalDecoration } from 'common/services/Services'; +import { IDecorationOverviewRulerOptions } from 'xterm'; + +export interface IColorZoneStore { + readonly zones: IColorZone[]; + clear(): void; + addDecoration(decoration: IInternalDecoration): void; + /** + * Sets the amount of padding in lines that will be added between zones, if new lines intersect + * the padding they will be merged into the same zone. + */ + setPadding(padding: { [position: string]: number }): void; +} + +export interface IColorZone { + /** Color in a format supported by canvas' fillStyle. */ + color: string; + position: 'full' | 'left' | 'center' | 'right' | undefined; + startBufferLine: number; + endBufferLine: number; +} + +export class ColorZoneStore implements IColorZoneStore { + private _zones: IColorZone[] = []; + public get zones(): IColorZone[] { return this._zones; } + + private _linePadding: { [position: string]: number } = { + full: 0, + left: 0, + center: 0, + right: 0 + }; + + public clear(): void { + this._zones.length = 0; + } + + public addDecoration(decoration: IInternalDecoration): void { + if (!decoration.options.overviewRulerOptions) { + return; + } + for (const z of this._zones) { + if (z.color === decoration.options.overviewRulerOptions.color && + z.position === decoration.options.overviewRulerOptions.position) { + if (this._lineIntersectsZone(z, decoration.marker.line)) { + console.log('intersects, skip'); + // this._addLineToZone(z, decoration.marker.line); + return; + } + if (this._lineAdjacentToZone(z, decoration.marker.line, decoration.options.overviewRulerOptions.position)) { + this._addLineToZone(z, decoration.marker.line); + console.log('adjacent, add'); + return; + } + } + } + // TODO: Track zones in an object pool to reduce GC + this._zones.push({ + color: decoration.options.overviewRulerOptions.color, + position: decoration.options.overviewRulerOptions.position, + startBufferLine: decoration.marker.line, + endBufferLine: decoration.marker.line + }); + } + + public setPadding(padding: { [position: string]: number }): void { + console.log('padding', padding); + this._linePadding = padding; + } + + private _lineIntersectsZone(zone: IColorZone, line: number): boolean { + return ( + line >= zone.startBufferLine && + line <= zone.endBufferLine + ); + } + + private _lineAdjacentToZone(zone: IColorZone, line: number, position: IColorZone['position']): boolean { + return ( + (line >= zone.startBufferLine - 1 - this._linePadding[position || 'full'] * 2) && + (line <= zone.endBufferLine + 1 + this._linePadding[position || 'full'] * 2) + ); + } + + private _addLineToZone(zone: IColorZone, line: number): void { + zone.startBufferLine = Math.min(zone.startBufferLine, line); + zone.endBufferLine = Math.max(zone.endBufferLine, line); + } +} diff --git a/src/browser/Decorations/OverviewRulerRenderer.ts b/src/browser/Decorations/OverviewRulerRenderer.ts index 1407f4bb..8e55dec5 100644 --- a/src/browser/Decorations/OverviewRulerRenderer.ts +++ b/src/browser/Decorations/OverviewRulerRenderer.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { ColorZoneStore, IColorZoneStore } from 'browser/Decorations/ColorZoneStore'; import { addDisposableDomListener } from 'browser/Lifecycle'; import { IRenderService } from 'browser/services/Services'; import { Disposable } from 'common/Lifecycle'; @@ -33,6 +34,7 @@ export class OverviewRulerRenderer extends Disposable { private readonly _canvas: HTMLCanvasElement; private readonly _ctx: CanvasRenderingContext2D; private readonly _decorationElements: Map = new Map(); + private readonly _colorZoneStore: IColorZoneStore = new ColorZoneStore(); private get _width(): number { return this._optionsService.options.overviewRulerWidth || 0; } @@ -40,6 +42,7 @@ export class OverviewRulerRenderer extends Disposable { private _shouldUpdateDimensions: boolean | undefined = true; private _shouldUpdateAnchor: boolean | undefined = true; + private _lastKnownBufferLength: number = 0; private _containerHeight: number | undefined; @@ -84,6 +87,11 @@ export class OverviewRulerRenderer extends Disposable { this.register(this._bufferService.buffers.onBufferActivate(() => { this._canvas!.style.display = this._bufferService.buffer === this._bufferService.buffers.alt ? 'none' : 'block'; })); + this.register(this._bufferService.onScroll(() => { + if (this._lastKnownBufferLength !== this._bufferService.buffers.normal.lines.length) { + this._refreshColorZonePadding(); + } + })); } /** * On dimension change, update canvas dimensions @@ -140,6 +148,17 @@ export class OverviewRulerRenderer extends Disposable { drawX.right = drawWidth.left + drawWidth.center; } + private _refreshColorZonePadding(): void { + const nonFullPadding = Math.ceil(this._bufferService.buffers.active.lines.length / (this._canvas.height - 1) * (drawHeight.full / 2)); + this._colorZoneStore.setPadding({ + full: Math.ceil(this._bufferService.buffers.active.lines.length / (this._canvas.height - 1) * (drawHeight.full / 2)), + left: nonFullPadding, + center: nonFullPadding, + right: nonFullPadding + }); + this._lastKnownBufferLength = this._bufferService.buffers.normal.lines.length; + } + private _refreshStyle(decoration: IInternalDecoration): void { if (!decoration.options.overviewRulerOptions) { this._decorationElements.delete(decoration); @@ -151,7 +170,7 @@ export class OverviewRulerRenderer extends Disposable { /* x */ drawX[decoration.options.overviewRulerOptions.position!], /* y */ Math.round( (this._canvas.height - 1) * // -1 to ensure at least 2px are allowed for decoration on last line - (decoration.options.marker.line / this._bufferService.buffers.active.lines.length) - drawHeight[decoration.options.overviewRulerOptions.position!] / 2 + (decoration.options.marker.line / this._bufferService.buffers.active.lines.length) - drawHeight[decoration.options.overviewRulerOptions.position!] / 2 ), /* w */ drawWidth[decoration.options.overviewRulerOptions.position!], /* h */ drawHeight[decoration.options.overviewRulerOptions.position!] @@ -164,6 +183,7 @@ export class OverviewRulerRenderer extends Disposable { this._canvas.style.height = `${this._screenElement.clientHeight}px`; this._canvas.height = Math.round(this._screenElement.clientHeight * window.devicePixelRatio); this._refreshDrawConstants(); + this._refreshColorZonePadding(); } private _refreshDecorations(): void { @@ -171,7 +191,9 @@ export class OverviewRulerRenderer extends Disposable { this._refreshCanvasDimensions(); } this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height); + this._colorZoneStore.clear(); for (const decoration of this._decorationService.decorations) { + this._colorZoneStore.addDecoration(decoration); if (decoration.options.overviewRulerOptions && decoration.options.overviewRulerOptions.position !== 'full') { this._renderDecoration(decoration); } @@ -181,6 +203,7 @@ export class OverviewRulerRenderer extends Disposable { this._renderDecoration(decoration); } } + console.log('zones', this._colorZoneStore.zones); this._shouldUpdateDimensions = false; this._shouldUpdateAnchor = false; } From 8524c23deb34c10e06ea0428996afa81f4d0c55a Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 1 Apr 2022 05:55:53 -0700 Subject: [PATCH 185/245] Render overview ruler using color zones Fixes #3703 --- src/browser/Decorations/ColorZoneStore.ts | 8 +-- .../Decorations/OverviewRulerRenderer.ts | 64 ++++++++----------- 2 files changed, 27 insertions(+), 45 deletions(-) diff --git a/src/browser/Decorations/ColorZoneStore.ts b/src/browser/Decorations/ColorZoneStore.ts index c5823234..45774965 100644 --- a/src/browser/Decorations/ColorZoneStore.ts +++ b/src/browser/Decorations/ColorZoneStore.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import { IInternalDecoration } from 'common/services/Services'; -import { IDecorationOverviewRulerOptions } from 'xterm'; export interface IColorZoneStore { readonly zones: IColorZone[]; @@ -48,13 +47,10 @@ export class ColorZoneStore implements IColorZoneStore { if (z.color === decoration.options.overviewRulerOptions.color && z.position === decoration.options.overviewRulerOptions.position) { if (this._lineIntersectsZone(z, decoration.marker.line)) { - console.log('intersects, skip'); - // this._addLineToZone(z, decoration.marker.line); return; } if (this._lineAdjacentToZone(z, decoration.marker.line, decoration.options.overviewRulerOptions.position)) { this._addLineToZone(z, decoration.marker.line); - console.log('adjacent, add'); return; } } @@ -82,8 +78,8 @@ export class ColorZoneStore implements IColorZoneStore { private _lineAdjacentToZone(zone: IColorZone, line: number, position: IColorZone['position']): boolean { return ( - (line >= zone.startBufferLine - 1 - this._linePadding[position || 'full'] * 2) && - (line <= zone.endBufferLine + 1 + this._linePadding[position || 'full'] * 2) + (line >= zone.startBufferLine - this._linePadding[position || 'full'] * 2) && + (line <= zone.endBufferLine + this._linePadding[position || 'full'] * 2) ); } diff --git a/src/browser/Decorations/OverviewRulerRenderer.ts b/src/browser/Decorations/OverviewRulerRenderer.ts index 8e55dec5..d2ffdc2d 100644 --- a/src/browser/Decorations/OverviewRulerRenderer.ts +++ b/src/browser/Decorations/OverviewRulerRenderer.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ColorZoneStore, IColorZoneStore } from 'browser/Decorations/ColorZoneStore'; +import { ColorZoneStore, IColorZone, IColorZoneStore } from 'browser/Decorations/ColorZoneStore'; import { addDisposableDomListener } from 'browser/Lifecycle'; import { IRenderService } from 'browser/services/Services'; import { Disposable } from 'common/Lifecycle'; @@ -149,34 +149,15 @@ export class OverviewRulerRenderer extends Disposable { } private _refreshColorZonePadding(): void { - const nonFullPadding = Math.ceil(this._bufferService.buffers.active.lines.length / (this._canvas.height - 1) * (drawHeight.full / 2)); this._colorZoneStore.setPadding({ - full: Math.ceil(this._bufferService.buffers.active.lines.length / (this._canvas.height - 1) * (drawHeight.full / 2)), - left: nonFullPadding, - center: nonFullPadding, - right: nonFullPadding + full: this._bufferService.buffers.active.lines.length / (this._canvas.height - 1) * (drawHeight.full / 2), + left: this._bufferService.buffers.active.lines.length / (this._canvas.height - 1) * (drawHeight.left / 2), + center: this._bufferService.buffers.active.lines.length / (this._canvas.height - 1) * (drawHeight.center / 2), + right: this._bufferService.buffers.active.lines.length / (this._canvas.height - 1) * (drawHeight.right / 2) }); this._lastKnownBufferLength = this._bufferService.buffers.normal.lines.length; } - private _refreshStyle(decoration: IInternalDecoration): void { - if (!decoration.options.overviewRulerOptions) { - this._decorationElements.delete(decoration); - return; - } - this._ctx.lineWidth = 1; - this._ctx.fillStyle = decoration.options.overviewRulerOptions.color; - this._ctx.fillRect( - /* x */ drawX[decoration.options.overviewRulerOptions.position!], - /* y */ Math.round( - (this._canvas.height - 1) * // -1 to ensure at least 2px are allowed for decoration on last line - (decoration.options.marker.line / this._bufferService.buffers.active.lines.length) - drawHeight[decoration.options.overviewRulerOptions.position!] / 2 - ), - /* w */ drawWidth[decoration.options.overviewRulerOptions.position!], - /* h */ drawHeight[decoration.options.overviewRulerOptions.position!] - ); - } - private _refreshCanvasDimensions(): void { this._canvas.style.width = `${this._width}px`; this._canvas.width = Math.round(this._width * window.devicePixelRatio); @@ -194,27 +175,32 @@ export class OverviewRulerRenderer extends Disposable { this._colorZoneStore.clear(); for (const decoration of this._decorationService.decorations) { this._colorZoneStore.addDecoration(decoration); - if (decoration.options.overviewRulerOptions && decoration.options.overviewRulerOptions.position !== 'full') { - this._renderDecoration(decoration); - } } - for (const decoration of this._decorationService.decorations) { - if (decoration.options.overviewRulerOptions && decoration.options.overviewRulerOptions.position === 'full') { - this._renderDecoration(decoration); - } + this._ctx.lineWidth = 1; + for (const zone of this._colorZoneStore.zones) { + this._renderColorZone(zone); } - console.log('zones', this._colorZoneStore.zones); this._shouldUpdateDimensions = false; this._shouldUpdateAnchor = false; } - private _renderDecoration(decoration: IInternalDecoration): void { - const element = this._decorationElements.get(decoration); - if (!element) { - this._decorationElements.set(decoration, this._canvas); - decoration.onDispose(() => this._queueRefresh()); - } - this._refreshStyle(decoration); + private _renderColorZone(zone: IColorZone): void { + // TODO: Is _decorationElements needed? + + this._ctx.fillStyle = zone.color; + console.log('zone height', drawHeight[zone.position || 'full']); + this._ctx.fillRect( + /* x */ drawX[zone.position || 'full'], + /* y */ Math.round( + (this._canvas.height - 1) * // -1 to ensure at least 2px are allowed for decoration on last line + (zone.startBufferLine / this._bufferService.buffers.active.lines.length) - drawHeight[zone.position || 'full'] / 2 + ), + /* w */ drawWidth[zone.position || 'full'], + /* h */ Math.round( + (this._canvas.height - 1) * // -1 to ensure at least 2px are allowed for decoration on last line + ((zone.endBufferLine - zone.startBufferLine) / this._bufferService.buffers.active.lines.length) + drawHeight[zone.position || 'full'] + ) + ); } private _queueRefresh(updateCanvasDimensions?: boolean, updateAnchor?: boolean): void { From 6b26a1bcf8bda287b269884df80ba8f8206bf489 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 1 Apr 2022 06:05:30 -0700 Subject: [PATCH 186/245] Stick to integers for padding and render full after --- .../Decorations/OverviewRulerRenderer.ts | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/browser/Decorations/OverviewRulerRenderer.ts b/src/browser/Decorations/OverviewRulerRenderer.ts index d2ffdc2d..de2342f1 100644 --- a/src/browser/Decorations/OverviewRulerRenderer.ts +++ b/src/browser/Decorations/OverviewRulerRenderer.ts @@ -150,10 +150,10 @@ export class OverviewRulerRenderer extends Disposable { private _refreshColorZonePadding(): void { this._colorZoneStore.setPadding({ - full: this._bufferService.buffers.active.lines.length / (this._canvas.height - 1) * (drawHeight.full / 2), - left: this._bufferService.buffers.active.lines.length / (this._canvas.height - 1) * (drawHeight.left / 2), - center: this._bufferService.buffers.active.lines.length / (this._canvas.height - 1) * (drawHeight.center / 2), - right: this._bufferService.buffers.active.lines.length / (this._canvas.height - 1) * (drawHeight.right / 2) + full: Math.floor(this._bufferService.buffers.active.lines.length / (this._canvas.height - 1) * (drawHeight.full / 2)), + left: Math.floor(this._bufferService.buffers.active.lines.length / (this._canvas.height - 1) * (drawHeight.left / 2)), + center: Math.floor(this._bufferService.buffers.active.lines.length / (this._canvas.height - 1) * (drawHeight.center / 2)), + right: Math.floor(this._bufferService.buffers.active.lines.length / (this._canvas.height - 1) * (drawHeight.right / 2)) }); this._lastKnownBufferLength = this._bufferService.buffers.normal.lines.length; } @@ -177,8 +177,16 @@ export class OverviewRulerRenderer extends Disposable { this._colorZoneStore.addDecoration(decoration); } this._ctx.lineWidth = 1; - for (const zone of this._colorZoneStore.zones) { - this._renderColorZone(zone); + const zones = this._colorZoneStore.zones; + for (const zone of zones) { + if (zone.position !== 'full') { + this._renderColorZone(zone); + } + } + for (const zone of zones) { + if (zone.position === 'full') { + this._renderColorZone(zone); + } } this._shouldUpdateDimensions = false; this._shouldUpdateAnchor = false; From d072a628ca9bcafac4ae4b68b9eabc413e1164fc Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 1 Apr 2022 06:25:55 -0700 Subject: [PATCH 187/245] Object pool, correct merging of zones --- demo/client.ts | 4 +-- src/browser/Decorations/ColorZoneStore.ts | 33 ++++++++++++++++--- .../Decorations/OverviewRulerRenderer.ts | 9 +++-- 3 files changed, 34 insertions(+), 12 deletions(-) diff --git a/demo/client.ts b/demo/client.ts index 55ff8d62..7c21956a 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -556,8 +556,8 @@ function loadTest() { function addDecoration() { term.options['overviewRulerWidth'] = 15; const marker = term.addMarker(1); - const decoration = term.registerDecoration({ marker, overviewRulerOptions: { color: '#ef2929'} }); - decoration.onRender((e) => e.style.backgroundColor = '#ef2929'); + const decoration = term.registerDecoration({ marker, overviewRulerOptions: { color: '#ef292980', position: 'left' } }); + decoration.onRender((e) => e.style.backgroundColor = '#ef292980'); } function addOverviewRuler() { diff --git a/src/browser/Decorations/ColorZoneStore.ts b/src/browser/Decorations/ColorZoneStore.ts index 45774965..bf68dc14 100644 --- a/src/browser/Decorations/ColorZoneStore.ts +++ b/src/browser/Decorations/ColorZoneStore.ts @@ -26,7 +26,12 @@ export interface IColorZone { export class ColorZoneStore implements IColorZoneStore { private _zones: IColorZone[] = []; - public get zones(): IColorZone[] { return this._zones; } + + // The zone pool is used to keep zone objects from being freed between clearing the color zone + // store and fetching the zones. This helps reduce GC pressure since the color zones are + // accumulated on potentially every scroll event. + private _zonePool: IColorZone[] = []; + private _zonePoolIndex = 0; private _linePadding: { [position: string]: number } = { full: 0, @@ -35,8 +40,15 @@ export class ColorZoneStore implements IColorZoneStore { right: 0 }; + public get zones(): IColorZone[] { + // Trim the zone pool to free unused memory + this._zonePool.length = Math.min(this._zonePool.length, this._zones.length); + return this._zones; + } + public clear(): void { this._zones.length = 0; + this._zonePoolIndex = 0; } public addDecoration(decoration: IInternalDecoration): void { @@ -50,22 +62,33 @@ export class ColorZoneStore implements IColorZoneStore { return; } if (this._lineAdjacentToZone(z, decoration.marker.line, decoration.options.overviewRulerOptions.position)) { + console.log('add line to zone'); this._addLineToZone(z, decoration.marker.line); return; } } } - // TODO: Track zones in an object pool to reduce GC + // Create using zone pool if possible + if (this._zonePoolIndex < this._zonePool.length) { + this._zonePool[this._zonePoolIndex].color = decoration.options.overviewRulerOptions.color; + this._zonePool[this._zonePoolIndex].position = decoration.options.overviewRulerOptions.position; + this._zonePool[this._zonePoolIndex].startBufferLine = decoration.marker.line; + this._zonePool[this._zonePoolIndex].endBufferLine = decoration.marker.line; + this._zones.push(this._zonePool[this._zonePoolIndex++]); + return; + } + // Create this._zones.push({ color: decoration.options.overviewRulerOptions.color, position: decoration.options.overviewRulerOptions.position, startBufferLine: decoration.marker.line, endBufferLine: decoration.marker.line }); + this._zonePool.push(this._zones[this._zones.length - 1]); + this._zonePoolIndex++; } public setPadding(padding: { [position: string]: number }): void { - console.log('padding', padding); this._linePadding = padding; } @@ -78,8 +101,8 @@ export class ColorZoneStore implements IColorZoneStore { private _lineAdjacentToZone(zone: IColorZone, line: number, position: IColorZone['position']): boolean { return ( - (line >= zone.startBufferLine - this._linePadding[position || 'full'] * 2) && - (line <= zone.endBufferLine + this._linePadding[position || 'full'] * 2) + (line >= zone.startBufferLine - this._linePadding[position || 'full']) && + (line <= zone.endBufferLine + this._linePadding[position || 'full']) ); } diff --git a/src/browser/Decorations/OverviewRulerRenderer.ts b/src/browser/Decorations/OverviewRulerRenderer.ts index de2342f1..2b5f2dc4 100644 --- a/src/browser/Decorations/OverviewRulerRenderer.ts +++ b/src/browser/Decorations/OverviewRulerRenderer.ts @@ -150,10 +150,10 @@ export class OverviewRulerRenderer extends Disposable { private _refreshColorZonePadding(): void { this._colorZoneStore.setPadding({ - full: Math.floor(this._bufferService.buffers.active.lines.length / (this._canvas.height - 1) * (drawHeight.full / 2)), - left: Math.floor(this._bufferService.buffers.active.lines.length / (this._canvas.height - 1) * (drawHeight.left / 2)), - center: Math.floor(this._bufferService.buffers.active.lines.length / (this._canvas.height - 1) * (drawHeight.center / 2)), - right: Math.floor(this._bufferService.buffers.active.lines.length / (this._canvas.height - 1) * (drawHeight.right / 2)) + full: Math.floor(this._bufferService.buffers.active.lines.length / (this._canvas.height - 1) * drawHeight.full), + left: Math.floor(this._bufferService.buffers.active.lines.length / (this._canvas.height - 1) * drawHeight.left), + center: Math.floor(this._bufferService.buffers.active.lines.length / (this._canvas.height - 1) * drawHeight.center), + right: Math.floor(this._bufferService.buffers.active.lines.length / (this._canvas.height - 1) * drawHeight.right) }); this._lastKnownBufferLength = this._bufferService.buffers.normal.lines.length; } @@ -196,7 +196,6 @@ export class OverviewRulerRenderer extends Disposable { // TODO: Is _decorationElements needed? this._ctx.fillStyle = zone.color; - console.log('zone height', drawHeight[zone.position || 'full']); this._ctx.fillRect( /* x */ drawX[zone.position || 'full'], /* y */ Math.round( From e98a20ba9ed29e8d1313151a238903b14bfe254c Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 1 Apr 2022 07:04:52 -0700 Subject: [PATCH 188/245] Fix typo in test name --- src/browser/Terminal.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/browser/Terminal.test.ts b/src/browser/Terminal.test.ts index d039b17b..3eafb261 100644 --- a/src/browser/Terminal.test.ts +++ b/src/browser/Terminal.test.ts @@ -231,7 +231,7 @@ describe('Terminal', () => { }); term.paste('foo'); }); - it('should sanitize \n chars', done => { + it('should sanitize \\n chars', done => { term.onData(e => { assert.equal(e, '\rfoo\rbar\r'); done(); From 7eace4d4799516f3f563cb3da5066c2eca6f700f Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 1 Apr 2022 07:05:05 -0700 Subject: [PATCH 189/245] Add tests for ColorZoneStore --- .../Decorations/ColorZoneStore.test.ts | 88 +++++++++++++++++++ src/browser/Decorations/ColorZoneStore.ts | 8 +- 2 files changed, 94 insertions(+), 2 deletions(-) create mode 100644 src/browser/Decorations/ColorZoneStore.test.ts diff --git a/src/browser/Decorations/ColorZoneStore.test.ts b/src/browser/Decorations/ColorZoneStore.test.ts new file mode 100644 index 00000000..73e3402f --- /dev/null +++ b/src/browser/Decorations/ColorZoneStore.test.ts @@ -0,0 +1,88 @@ +/** + * Copyright (c) 2017 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { assert } from 'chai'; +import { ColorZoneStore } from 'browser/Decorations/ColorZoneStore'; + +const optionsRedFull = { + overviewRulerOptions: { + color: 'red', + position: 'full' as 'full' + } +}; + +describe('ColorZoneStore', () => { + let store: ColorZoneStore; + + beforeEach(() => { + store = new ColorZoneStore(); + store.setPadding({ + full: 1, + left: 1, + center: 1, + right: 1 + }); + }); + + it('should merge adjacent zones', () => { + store.addDecoration({ + marker: { line: 0 }, + options: optionsRedFull + }); + store.addDecoration({ + marker: { line: 1 }, + options: optionsRedFull + }); + assert.deepStrictEqual(store.zones, [ + { + color: 'red', + position: 'full', + startBufferLine: 0, + endBufferLine: 1 + } + ]); + }); + + it('should not merge non-adjacent zones', () => { + store.addDecoration({ + marker: { line: 0 }, + options: optionsRedFull + }); + store.addDecoration({ + marker: { line: 2 }, + options: optionsRedFull + }); + assert.deepStrictEqual(store.zones, [ + { + color: 'red', + position: 'full', + startBufferLine: 0, + endBufferLine: 0 + }, + { + color: 'red', + position: 'full', + startBufferLine: 2, + endBufferLine: 2 + } + ]); + }); + + it('should reuse zone objects', () => { + const obj = { + marker: { line: 0 }, + options: optionsRedFull + }; + store.addDecoration(obj); + const zone = store.zones[0]; + store.clear(); + store.addDecoration({ + marker: { line: 1 }, + options: optionsRedFull + }); + // The object reference should be the same + assert.equal(zone, store.zones[0]); + }); +}); diff --git a/src/browser/Decorations/ColorZoneStore.ts b/src/browser/Decorations/ColorZoneStore.ts index bf68dc14..d066bedb 100644 --- a/src/browser/Decorations/ColorZoneStore.ts +++ b/src/browser/Decorations/ColorZoneStore.ts @@ -24,6 +24,11 @@ export interface IColorZone { endBufferLine: number; } +interface IMinimalDecorationForColorZone { + marker: Pick; + options: Pick; +} + export class ColorZoneStore implements IColorZoneStore { private _zones: IColorZone[] = []; @@ -51,7 +56,7 @@ export class ColorZoneStore implements IColorZoneStore { this._zonePoolIndex = 0; } - public addDecoration(decoration: IInternalDecoration): void { + public addDecoration(decoration: IMinimalDecorationForColorZone): void { if (!decoration.options.overviewRulerOptions) { return; } @@ -62,7 +67,6 @@ export class ColorZoneStore implements IColorZoneStore { return; } if (this._lineAdjacentToZone(z, decoration.marker.line, decoration.options.overviewRulerOptions.position)) { - console.log('add line to zone'); this._addLineToZone(z, decoration.marker.line); return; } From 8158a16629dd1da39e43e953c5b24a4b8c7204b6 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 1 Apr 2022 07:07:13 -0700 Subject: [PATCH 190/245] Remove unused _decorationElements --- src/browser/Decorations/OverviewRulerRenderer.ts | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/src/browser/Decorations/OverviewRulerRenderer.ts b/src/browser/Decorations/OverviewRulerRenderer.ts index 2b5f2dc4..f34338ce 100644 --- a/src/browser/Decorations/OverviewRulerRenderer.ts +++ b/src/browser/Decorations/OverviewRulerRenderer.ts @@ -33,7 +33,6 @@ const drawX = { export class OverviewRulerRenderer extends Disposable { private readonly _canvas: HTMLCanvasElement; private readonly _ctx: CanvasRenderingContext2D; - private readonly _decorationElements: Map = new Map(); private readonly _colorZoneStore: IColorZoneStore = new ColorZoneStore(); private get _width(): number { return this._optionsService.options.overviewRulerWidth || 0; @@ -75,7 +74,6 @@ export class OverviewRulerRenderer extends Disposable { */ private _registerDecorationListeners(): void { this.register(this._decorationService.onDecorationRegistered(() => this._queueRefresh(undefined, true))); - this.register(this._decorationService.onDecorationRemoved(decoration => this._removeDecoration(decoration))); } /** @@ -120,10 +118,6 @@ export class OverviewRulerRenderer extends Disposable { } public override dispose(): void { - for (const decoration of this._decorationElements) { - decoration[0].dispose(); - } - this._decorationElements.clear(); this._canvas?.remove(); super.dispose(); } @@ -221,9 +215,4 @@ export class OverviewRulerRenderer extends Disposable { this._animationFrame = undefined; }); } - - private _removeDecoration(decoration: IInternalDecoration): void { - this._decorationElements.get(decoration)?.remove(); - this._decorationElements.delete(decoration); - } } From 2b916a24aa3ae2b1a2ff5eb28745b751a0ea4586 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Fri, 8 Apr 2022 13:35:55 -0700 Subject: [PATCH 191/245] get overview ruler to update when color is changed (#3730) --- typings/xterm.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index d1eb3890..76c228b2 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -444,7 +444,7 @@ declare module 'xterm' { * This will only take effect when {@link IDecorationOptions.overviewRulerOptions} * were provided initially. */ - overviewRulerOptions?: Pick; + options: Pick; } From 85b5161380757b8d21fc35155818e1166259e28b Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Fri, 8 Apr 2022 14:07:46 -0700 Subject: [PATCH 192/245] don't return if results are the same bc color might have changed (#3731) --- addons/xterm-addon-search/src/SearchAddon.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/addons/xterm-addon-search/src/SearchAddon.ts b/addons/xterm-addon-search/src/SearchAddon.ts index d2ee05b4..0192532c 100644 --- a/addons/xterm-addon-search/src/SearchAddon.ts +++ b/addons/xterm-addon-search/src/SearchAddon.ts @@ -145,9 +145,6 @@ export class SearchAddon implements ITerminalAddon { return; } searchOptions = searchOptions || {}; - if (term === this._cachedSearchTerm && !this._dataChanged) { - return; - } // new search, clear out the old decorations this.clearDecorations(); From e98b6315e400f6f3c43595131a403c5063be07d3 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Wed, 13 Apr 2022 11:19:22 -0700 Subject: [PATCH 193/245] always update overview ruler decorations (#3733) --- addons/xterm-addon-search/src/SearchAddon.ts | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/addons/xterm-addon-search/src/SearchAddon.ts b/addons/xterm-addon-search/src/SearchAddon.ts index 0192532c..41fd19b9 100644 --- a/addons/xterm-addon-search/src/SearchAddon.ts +++ b/addons/xterm-addon-search/src/SearchAddon.ts @@ -51,7 +51,6 @@ const LINES_CACHE_TIME_TO_LIVE = 15 * 1000; // 15 secs export class SearchAddon implements ITerminalAddon { private _terminal: Terminal | undefined; - private _dataChanged: boolean = false; private _cachedSearchTerm: string | undefined; private _selectedDecoration: IDecoration | undefined; private _resultDecorations: Map | undefined; @@ -77,7 +76,6 @@ export class SearchAddon implements ITerminalAddon { public activate(terminal: Terminal): void { this._terminal = terminal; this._onDataDisposable = this._terminal.onData(() => { - this._dataChanged = true; if (this._highlightTimeout) { window.clearTimeout(this._highlightTimeout); } @@ -106,7 +104,6 @@ export class SearchAddon implements ITerminalAddon { this._cachedSearchTerm = undefined; this._searchResults = undefined; this._resultDecorations = undefined; - this._dataChanged = true; this._resultIndex = undefined; } @@ -169,9 +166,6 @@ export class SearchAddon implements ITerminalAddon { resultDecorations.set(resultDecoration.marker.line, decorationsForLine); } }); - if (this._dataChanged) { - this._dataChanged = false; - } if (this._searchResults.size > 0) { this._cachedSearchTerm = term; } @@ -710,7 +704,7 @@ export class SearchAddon implements ITerminalAddon { marker, x: result.col, width: result.size, - overviewRulerOptions: this._resultDecorations?.get(marker.line) && !this._dataChanged ? undefined : { + overviewRulerOptions: this._resultDecorations?.get(marker.line) ? undefined : { color: decorations.matchOverviewRuler, position: 'center' } }); From 0785a1bf24348f0e18c5f289ec591bc69a9ecda8 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Tue, 19 Apr 2022 08:25:17 -0700 Subject: [PATCH 194/245] fix #3735 (#3736) --- addons/xterm-addon-search/src/SearchAddon.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/xterm-addon-search/src/SearchAddon.ts b/addons/xterm-addon-search/src/SearchAddon.ts index 41fd19b9..536e4873 100644 --- a/addons/xterm-addon-search/src/SearchAddon.ts +++ b/addons/xterm-addon-search/src/SearchAddon.ts @@ -81,7 +81,7 @@ export class SearchAddon implements ITerminalAddon { } if (this._cachedSearchTerm && this._lastSearchOptions?.decorations) { this._highlightTimeout = setTimeout(() => { - this.findPrevious(this._cachedSearchTerm!, { ...this._lastSearchOptions, incremental: true }); + this._highlightAllMatches(this._cachedSearchTerm!, { ...this._lastSearchOptions, incremental: true }); }, 200); } }); From 3bd37115d1f3ff0649ddc992e94fad197518278c Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Tue, 19 Apr 2022 08:49:16 -0700 Subject: [PATCH 195/245] queue refresh when deco is removed (#3738) --- src/browser/Decorations/OverviewRulerRenderer.ts | 1 + src/common/services/DecorationService.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/src/browser/Decorations/OverviewRulerRenderer.ts b/src/browser/Decorations/OverviewRulerRenderer.ts index f34338ce..dc35b901 100644 --- a/src/browser/Decorations/OverviewRulerRenderer.ts +++ b/src/browser/Decorations/OverviewRulerRenderer.ts @@ -74,6 +74,7 @@ export class OverviewRulerRenderer extends Disposable { */ private _registerDecorationListeners(): void { this.register(this._decorationService.onDecorationRegistered(() => this._queueRefresh(undefined, true))); + this.register(this._decorationService.onDecorationRemoved(() => this._queueRefresh(undefined, true))); } /** diff --git a/src/common/services/DecorationService.ts b/src/common/services/DecorationService.ts index fba5fc35..03cfab4d 100644 --- a/src/common/services/DecorationService.ts +++ b/src/common/services/DecorationService.ts @@ -35,6 +35,7 @@ export class DecorationService extends Disposable implements IDecorationService const index = this._decorations.indexOf(decoration); if (index >= 0) { this._decorations.splice(this._decorations.indexOf(decoration), 1); + this._onDecorationRemoved.fire(decoration); } } }); From 6d801311cd1a30f7c2bd708d26959d879fa7583c Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Tue, 19 Apr 2022 15:23:26 -0700 Subject: [PATCH 196/245] exclude powerline char range from contrast demands (#3740) --- src/browser/renderer/BaseRenderLayer.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/browser/renderer/BaseRenderLayer.ts b/src/browser/renderer/BaseRenderLayer.ts index 629e9436..a95df5fa 100644 --- a/src/browser/renderer/BaseRenderLayer.ts +++ b/src/browser/renderer/BaseRenderLayer.ts @@ -428,6 +428,11 @@ export abstract class BaseRenderLayer implements IRenderLayer { } private _getContrastColor(cell: CellData): IColor | undefined { + const codepoint = cell.getCode(); + if (57344 <= codepoint && codepoint <= 63743) { + // powerline chars #3739 + return undefined; + } if (this._optionsService.rawOptions.minimumContrastRatio === 1) { return undefined; } From 38f0dd9be27bfcf626afa94629eeb7174778a55d Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Tue, 19 Apr 2022 18:46:39 -0700 Subject: [PATCH 197/245] apply to dom and webgl too (#3742) --- .../src/atlas/WebglCharAtlas.ts | 21 +++++++++---------- .../renderer/dom/DomRendererRowFactory.ts | 13 ++++++------ 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts index e409f51e..911ea3c0 100644 --- a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts +++ b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts @@ -216,8 +216,8 @@ export class WebglCharAtlas implements IDisposable { } } - private _getForegroundCss(bg: number, bgColorMode: number, bgColor: number, fg: number, fgColorMode: number, fgColor: number, inverse: boolean, bold: boolean): string { - const minimumContrastCss = this._getMinimumContrastCss(bg, bgColorMode, bgColor, fg, fgColorMode, fgColor, inverse, bold); + private _getForegroundCss(bg: number, bgColorMode: number, bgColor: number, fg: number, fgColorMode: number, fgColor: number, inverse: boolean, bold: boolean, isPowerLineGlyph: boolean): string { + const minimumContrastCss = this._getMinimumContrastCss(bg, bgColorMode, bgColor, fg, fgColorMode, fgColor, inverse, bold, isPowerLineGlyph); if (minimumContrastCss) { return minimumContrastCss; } @@ -281,8 +281,8 @@ export class WebglCharAtlas implements IDisposable { } } - private _getMinimumContrastCss(bg: number, bgColorMode: number, bgColor: number, fg: number, fgColorMode: number, fgColor: number, inverse: boolean, bold: boolean): string | undefined { - if (this._config.minimumContrastRatio === 1) { + private _getMinimumContrastCss(bg: number, bgColorMode: number, bgColor: number, fg: number, fgColorMode: number, fgColor: number, inverse: boolean, bold: boolean, isPowerLineGlyph: boolean): string | undefined { + if (this._config.minimumContrastRatio === 1 || isPowerLineGlyph) { return undefined; } @@ -370,13 +370,6 @@ export class WebglCharAtlas implements IDisposable { `${fontStyle} ${fontWeight} ${this._config.fontSize * this._config.devicePixelRatio}px ${this._config.fontFamily}`; this._tmpCtx.textBaseline = TEXT_BASELINE; - this._tmpCtx.fillStyle = this._getForegroundCss(bg, bgColorMode, bgColor, fg, fgColorMode, fgColor, inverse, bold); - - // Apply alpha to dim the character - if (dim) { - this._tmpCtx.globalAlpha = DIM_OPACITY; - } - // Check if the char is a powerline glyph, these will be restricted to a single cell glyph, no // padding on either side that are allowed for other glyphs since they are designed to be pixel // perfect but may render with "bad" anti-aliasing @@ -387,6 +380,12 @@ export class WebglCharAtlas implements IDisposable { isPowerlineGlyph = true; } } + this._tmpCtx.fillStyle = this._getForegroundCss(bg, bgColorMode, bgColor, fg, fgColorMode, fgColor, inverse, bold, isPowerlineGlyph); + + // Apply alpha to dim the character + if (dim) { + this._tmpCtx.globalAlpha = DIM_OPACITY; + } // For powerline glyphs left/top padding is excluded (https://github.com/microsoft/vscode/issues/120129) const padding = isPowerlineGlyph ? 0 : TMP_CANVAS_GLYPH_PADDING; diff --git a/src/browser/renderer/dom/DomRendererRowFactory.ts b/src/browser/renderer/dom/DomRendererRowFactory.ts index fda800ae..e24c5fe5 100644 --- a/src/browser/renderer/dom/DomRendererRowFactory.ts +++ b/src/browser/renderer/dom/DomRendererRowFactory.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { IBufferLine } from 'common/Types'; +import { IBufferLine, ICellData } from 'common/Types'; import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants'; import { NULL_CELL_CODE, WHITESPACE_CELL_CHAR, Attributes } from 'common/buffer/Constants'; import { CellData } from 'common/buffer/CellData'; @@ -178,7 +178,7 @@ export class DomRendererRowFactory { if (cell.isBold() && fg < 8 && this._optionsService.rawOptions.drawBoldTextInBrightColors) { fg += 8; } - if (!this._applyMinimumContrast(charElement, this._colors.background, this._colors.ansi[fg])) { + if (!this._applyMinimumContrast(charElement, this._colors.background, this._colors.ansi[fg], cell)) { charElement.classList.add(`xterm-fg-${fg}`); } break; @@ -188,13 +188,13 @@ export class DomRendererRowFactory { (fg >> 8) & 0xFF, (fg ) & 0xFF ); - if (!this._applyMinimumContrast(charElement, this._colors.background, color)) { + if (!this._applyMinimumContrast(charElement, this._colors.background, color, cell)) { this._addStyle(charElement, `color:#${padStart(fg.toString(16), '0', 6)}`); } break; case Attributes.CM_DEFAULT: default: - if (!this._applyMinimumContrast(charElement, this._colors.background, this._colors.foreground)) { + if (!this._applyMinimumContrast(charElement, this._colors.background, this._colors.foreground, cell)) { if (isInverse) { charElement.classList.add(`xterm-fg-${INVERTED_DEFAULT_COLOR}`); } @@ -224,8 +224,9 @@ export class DomRendererRowFactory { return fragment; } - private _applyMinimumContrast(element: HTMLElement, bg: IColor, fg: IColor): boolean { - if (this._optionsService.rawOptions.minimumContrastRatio === 1) { + private _applyMinimumContrast(element: HTMLElement, bg: IColor, fg: IColor, cell: ICellData): boolean { + const codepoint = cell.getCode(); + if (this._optionsService.rawOptions.minimumContrastRatio === 1 || 57344 <= codepoint && codepoint <= 63743) { return false; } From da33543ffb96f43e1904bc335d8cacd4eed407d6 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Wed, 20 Apr 2022 14:25:46 -0700 Subject: [PATCH 198/245] align isPowerlineGlyph across renderers (#3743) --- .../src/atlas/WebglCharAtlas.ts | 18 +++++------------- src/browser/renderer/BaseRenderLayer.ts | 9 ++------- src/browser/renderer/RendererUtils.ts | 6 ++++++ .../renderer/dom/DomRendererRowFactory.ts | 4 ++-- 4 files changed, 15 insertions(+), 22 deletions(-) diff --git a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts index 911ea3c0..3194d397 100644 --- a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts +++ b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts @@ -13,6 +13,7 @@ import { IDisposable } from 'xterm'; import { AttributeData } from 'common/buffer/AttributeData'; import { channels, rgba } from 'browser/Color'; import { tryDrawCustomChar } from 'browser/renderer/CustomGlyphs'; +import { isPowerlineGlyph } from 'browser/renderer/RendererUtils'; // For debugging purposes, it can be useful to set this to a really tiny value, // to verify that LRU eviction works. @@ -370,17 +371,8 @@ export class WebglCharAtlas implements IDisposable { `${fontStyle} ${fontWeight} ${this._config.fontSize * this._config.devicePixelRatio}px ${this._config.fontFamily}`; this._tmpCtx.textBaseline = TEXT_BASELINE; - // Check if the char is a powerline glyph, these will be restricted to a single cell glyph, no - // padding on either side that are allowed for other glyphs since they are designed to be pixel - // perfect but may render with "bad" anti-aliasing - let isPowerlineGlyph = false; - if (chars.length === 1) { - const code = chars.charCodeAt(0); - if (code >= 0xE0A0 && code <= 0xE0D6) { - isPowerlineGlyph = true; - } - } - this._tmpCtx.fillStyle = this._getForegroundCss(bg, bgColorMode, bgColor, fg, fgColorMode, fgColor, inverse, bold, isPowerlineGlyph); + const powerLineGlyph = chars.length === 1 && isPowerlineGlyph(chars.charCodeAt(0)); + this._tmpCtx.fillStyle = this._getForegroundCss(bg, bgColorMode, bgColor, fg, fgColorMode, fgColor, inverse, bold, powerLineGlyph); // Apply alpha to dim the character if (dim) { @@ -388,7 +380,7 @@ export class WebglCharAtlas implements IDisposable { } // For powerline glyphs left/top padding is excluded (https://github.com/microsoft/vscode/issues/120129) - const padding = isPowerlineGlyph ? 0 : TMP_CANVAS_GLYPH_PADDING; + const padding = powerLineGlyph ? 0 : TMP_CANVAS_GLYPH_PADDING; // Draw custom characters if applicable let drawSuccess = false; @@ -458,7 +450,7 @@ export class WebglCharAtlas implements IDisposable { return NULL_RASTERIZED_GLYPH; } - const rasterizedGlyph = this._findGlyphBoundingBox(imageData, this._workBoundingBox, allowedWidth, isPowerlineGlyph, drawSuccess); + const rasterizedGlyph = this._findGlyphBoundingBox(imageData, this._workBoundingBox, allowedWidth, powerLineGlyph, drawSuccess); const clippedImageData = this._clipImageData(imageData, this._workBoundingBox); // Check if there is enough room in the current row and go to next if needed diff --git a/src/browser/renderer/BaseRenderLayer.ts b/src/browser/renderer/BaseRenderLayer.ts index a95df5fa..90d4f82f 100644 --- a/src/browser/renderer/BaseRenderLayer.ts +++ b/src/browser/renderer/BaseRenderLayer.ts @@ -14,7 +14,7 @@ import { AttributeData } from 'common/buffer/AttributeData'; import { IColorSet, IColor } from 'browser/Types'; import { CellData } from 'common/buffer/CellData'; import { IBufferService, IOptionsService } from 'common/services/Services'; -import { throwIfFalsy } from 'browser/renderer/RendererUtils'; +import { isPowerlineGlyph, throwIfFalsy } from 'browser/renderer/RendererUtils'; import { channels, color, rgba } from 'browser/Color'; import { removeElementFromParent } from 'browser/Dom'; import { tryDrawCustomChar } from 'browser/renderer/CustomGlyphs'; @@ -428,12 +428,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { } private _getContrastColor(cell: CellData): IColor | undefined { - const codepoint = cell.getCode(); - if (57344 <= codepoint && codepoint <= 63743) { - // powerline chars #3739 - return undefined; - } - if (this._optionsService.rawOptions.minimumContrastRatio === 1) { + if (this._optionsService.rawOptions.minimumContrastRatio === 1 || isPowerlineGlyph(cell.getCode())) { return undefined; } diff --git a/src/browser/renderer/RendererUtils.ts b/src/browser/renderer/RendererUtils.ts index 48fd26a4..174556bd 100644 --- a/src/browser/renderer/RendererUtils.ts +++ b/src/browser/renderer/RendererUtils.ts @@ -9,3 +9,9 @@ export function throwIfFalsy(value: T | undefined | null): T { } return value; } + +export function isPowerlineGlyph(codepoint: number): boolean { + // This range was established via + // https://apw-bash-settings.readthedocs.io/en/latest/fontpatching.html + return 0xE000 <= codepoint && codepoint <= 0xF8FF; +} diff --git a/src/browser/renderer/dom/DomRendererRowFactory.ts b/src/browser/renderer/dom/DomRendererRowFactory.ts index e24c5fe5..9822ec36 100644 --- a/src/browser/renderer/dom/DomRendererRowFactory.ts +++ b/src/browser/renderer/dom/DomRendererRowFactory.ts @@ -12,6 +12,7 @@ import { color, rgba } from 'browser/Color'; import { IColorSet, IColor } from 'browser/Types'; import { ICharacterJoinerService } from 'browser/services/Services'; import { JoinedCellData } from 'browser/services/CharacterJoinerService'; +import { isPowerlineGlyph } from 'browser/renderer/RendererUtils'; export const BOLD_CLASS = 'xterm-bold'; export const DIM_CLASS = 'xterm-dim'; @@ -225,8 +226,7 @@ export class DomRendererRowFactory { } private _applyMinimumContrast(element: HTMLElement, bg: IColor, fg: IColor, cell: ICellData): boolean { - const codepoint = cell.getCode(); - if (this._optionsService.rawOptions.minimumContrastRatio === 1 || 57344 <= codepoint && codepoint <= 63743) { + if (this._optionsService.rawOptions.minimumContrastRatio === 1 || isPowerlineGlyph(cell.getCode())) { return false; } From 239f669041395f5ef80cffd48a94debbd171ba10 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Thu, 21 Apr 2022 16:04:08 -0700 Subject: [PATCH 199/245] add max find result count for which to show decorations (#3745) --- addons/xterm-addon-search/src/SearchAddon.ts | 26 ++++++++++--------- .../typings/xterm-addon-search.d.ts | 2 ++ 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/addons/xterm-addon-search/src/SearchAddon.ts b/addons/xterm-addon-search/src/SearchAddon.ts index 536e4873..c1de5653 100644 --- a/addons/xterm-addon-search/src/SearchAddon.ts +++ b/addons/xterm-addon-search/src/SearchAddon.ts @@ -122,15 +122,7 @@ export class SearchAddon implements ITerminalAddon { if (searchOptions?.decorations) { this._highlightAllMatches(term, searchOptions); } - const next = this._findNextAndSelect(term, searchOptions); - if (searchOptions?.decorations) { - if (next && this._resultIndex !== undefined && this._searchResults?.size) { - this._onDidChangeResults.fire({ resultIndex: this._resultIndex, resultCount: this._searchResults.size }); - } else { - this._onDidChangeResults.fire(undefined); - } - } - return next; + return this._fireResults(this._findNextAndSelect(term, searchOptions), searchOptions); } private _highlightAllMatches(term: string, searchOptions: ISearchOptions): void { @@ -157,6 +149,11 @@ export class SearchAddon implements ITerminalAddon { result.col + result.term.length >= this._terminal.cols ? 0 : result.col + 1, searchOptions ); + if (this._searchResults.size > 2000) { + this.clearDecorations(); + this._resultIndex = -1; + return; + } } this._searchResults.forEach(result => { const resultDecoration = this._createResultDecoration(result, searchOptions.decorations!); @@ -300,15 +297,20 @@ export class SearchAddon implements ITerminalAddon { if (searchOptions?.decorations) { this._highlightAllMatches(term, searchOptions); } - const previous = this._findPreviousAndSelect(term, searchOptions); + return this._fireResults(this._findPreviousAndSelect(term, searchOptions), searchOptions); + } + + private _fireResults(found: boolean, searchOptions?: ISearchOptions): boolean { if (searchOptions?.decorations) { - if (previous && this._resultIndex !== undefined && this._searchResults?.size) { + if (found && this._resultIndex !== undefined && this._searchResults?.size) { this._onDidChangeResults.fire({ resultIndex: this._resultIndex, resultCount: this._searchResults.size }); + } else if (this._resultIndex === -1) { + this._onDidChangeResults.fire({ resultIndex: -1, resultCount: -1 }); } else { this._onDidChangeResults.fire(undefined); } } - return previous; + return found; } private _findPreviousAndSelect(term: string, searchOptions?: ISearchOptions): boolean { diff --git a/addons/xterm-addon-search/typings/xterm-addon-search.d.ts b/addons/xterm-addon-search/typings/xterm-addon-search.d.ts index 1cb9740e..5dafb449 100644 --- a/addons/xterm-addon-search/typings/xterm-addon-search.d.ts +++ b/addons/xterm-addon-search/typings/xterm-addon-search.d.ts @@ -115,6 +115,8 @@ declare module 'xterm-addon-search' { * When decorations are enabled, fires when * the search results or the selected result changes, * returning undefined if there are no matches. + * -1 is returned for resultCount/resultIndex when the threshold of 2k results + * is exceeded and decorations are disposed of. */ readonly onDidChangeResults: IEvent<{ resultIndex: number, resultCount: number } | undefined>; } From fa92e6074de025084bda35a9bd1517e9e6078428 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Tue, 26 Apr 2022 15:45:30 -0700 Subject: [PATCH 200/245] search fixes (#3748) --- addons/xterm-addon-search/src/SearchAddon.ts | 47 +++++++++++++------ .../typings/xterm-addon-search.d.ts | 6 +-- 2 files changed, 36 insertions(+), 17 deletions(-) diff --git a/addons/xterm-addon-search/src/SearchAddon.ts b/addons/xterm-addon-search/src/SearchAddon.ts index c1de5653..ef3ba2fc 100644 --- a/addons/xterm-addon-search/src/SearchAddon.ts +++ b/addons/xterm-addon-search/src/SearchAddon.ts @@ -85,6 +85,16 @@ export class SearchAddon implements ITerminalAddon { }, 200); } }); + this._terminal.onResize(() => { + if (this._highlightTimeout) { + window.clearTimeout(this._highlightTimeout); + } + if (this._cachedSearchTerm && this._lastSearchOptions?.decorations) { + this._highlightTimeout = setTimeout(() => { + this._highlightAllMatches(this._cachedSearchTerm!, { ...this._lastSearchOptions, incremental: true }); + }, 200); + } + }); } public dispose(): void { @@ -101,10 +111,8 @@ export class SearchAddon implements ITerminalAddon { } }); this._resultDecorations?.clear(); - this._cachedSearchTerm = undefined; this._searchResults = undefined; this._resultDecorations = undefined; - this._resultIndex = undefined; } /** @@ -120,9 +128,11 @@ export class SearchAddon implements ITerminalAddon { } this._lastSearchOptions = searchOptions; if (searchOptions?.decorations) { - this._highlightAllMatches(term, searchOptions); + if (this._resultIndex || this._cachedSearchTerm && term !== this._cachedSearchTerm) { + this._highlightAllMatches(term, searchOptions); + } } - return this._fireResults(this._findNextAndSelect(term, searchOptions), searchOptions); + return this._fireResults(term, this._findNextAndSelect(term, searchOptions), searchOptions); } private _highlightAllMatches(term: string, searchOptions: ISearchOptions): void { @@ -149,9 +159,9 @@ export class SearchAddon implements ITerminalAddon { result.col + result.term.length >= this._terminal.cols ? 0 : result.col + 1, searchOptions ); - if (this._searchResults.size > 2000) { + if (this._searchResults.size > 1000) { this.clearDecorations(); - this._resultIndex = -1; + this._resultIndex = undefined; return; } } @@ -163,9 +173,6 @@ export class SearchAddon implements ITerminalAddon { resultDecorations.set(resultDecoration.marker.line, decorationsForLine); } }); - if (this._searchResults.size > 0) { - this._cachedSearchTerm = term; - } } private _find(term: string, startRow: number, startCol: number, searchOptions?: ISearchOptions): ISearchResult | undefined { @@ -210,9 +217,15 @@ export class SearchAddon implements ITerminalAddon { if (!this._terminal || !term || term.length === 0) { this._terminal?.clearSelection(); this.clearDecorations(); + this._cachedSearchTerm = undefined; + this._resultIndex = -1; return false; } + if (this._cachedSearchTerm !== term) { + this._resultIndex = undefined; + this._terminal.clearSelection(); + } let startCol = 0; let startRow = 0; @@ -278,7 +291,6 @@ export class SearchAddon implements ITerminalAddon { } } } - // Set selection and scroll if a result was found return this._selectResult(result, searchOptions?.decorations); } @@ -294,13 +306,13 @@ export class SearchAddon implements ITerminalAddon { throw new Error('Cannot use addon until it has been loaded'); } this._lastSearchOptions = searchOptions; - if (searchOptions?.decorations) { + if (searchOptions?.decorations && (this._resultIndex || term !== this._cachedSearchTerm)) { this._highlightAllMatches(term, searchOptions); } - return this._fireResults(this._findPreviousAndSelect(term, searchOptions), searchOptions); + return this._fireResults(term, this._findPreviousAndSelect(term, searchOptions), searchOptions); } - private _fireResults(found: boolean, searchOptions?: ISearchOptions): boolean { + private _fireResults(term: string, found: boolean, searchOptions?: ISearchOptions): boolean { if (searchOptions?.decorations) { if (found && this._resultIndex !== undefined && this._searchResults?.size) { this._onDidChangeResults.fire({ resultIndex: this._resultIndex, resultCount: this._searchResults.size }); @@ -310,6 +322,7 @@ export class SearchAddon implements ITerminalAddon { this._onDidChangeResults.fire(undefined); } } + this._cachedSearchTerm = term; return found; } @@ -322,9 +335,15 @@ export class SearchAddon implements ITerminalAddon { result = undefined; this._terminal?.clearSelection(); this.clearDecorations(); + this._resultIndex = -1; return false; } + if (this._cachedSearchTerm !== term) { + this._resultIndex = undefined; + this._terminal.clearSelection(); + } + let startRow = this._terminal.buffer.active.baseY + this._terminal.rows; let startCol = this._terminal.cols; const isReverseSearch = true; @@ -383,7 +402,7 @@ export class SearchAddon implements ITerminalAddon { } if (this._searchResults) { - if (this._resultIndex === undefined) { + if (this._resultIndex === undefined || this._resultIndex < 0) { this._resultIndex = this._searchResults?.size - 1; } else { this._resultIndex--; diff --git a/addons/xterm-addon-search/typings/xterm-addon-search.d.ts b/addons/xterm-addon-search/typings/xterm-addon-search.d.ts index 5dafb449..4d683db0 100644 --- a/addons/xterm-addon-search/typings/xterm-addon-search.d.ts +++ b/addons/xterm-addon-search/typings/xterm-addon-search.d.ts @@ -113,9 +113,9 @@ declare module 'xterm-addon-search' { /** * When decorations are enabled, fires when - * the search results or the selected result changes, - * returning undefined if there are no matches. - * -1 is returned for resultCount/resultIndex when the threshold of 2k results + * the search results change. + * @returns -1 if there are no matches and + * @returns undefined when the threshold of 1k results * is exceeded and decorations are disposed of. */ readonly onDidChangeResults: IEvent<{ resultIndex: number, resultCount: number } | undefined>; From 7fa32af0513cb40c7033b4dcd89e861caecc9b59 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Wed, 27 Apr 2022 11:20:00 -0700 Subject: [PATCH 201/245] clear `cachedSearchTerm` when decorations are cleared (#3750) --- addons/xterm-addon-search/src/SearchAddon.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/addons/xterm-addon-search/src/SearchAddon.ts b/addons/xterm-addon-search/src/SearchAddon.ts index ef3ba2fc..241e5e99 100644 --- a/addons/xterm-addon-search/src/SearchAddon.ts +++ b/addons/xterm-addon-search/src/SearchAddon.ts @@ -102,7 +102,7 @@ export class SearchAddon implements ITerminalAddon { this._onDataDisposable?.dispose(); } - public clearDecorations(): void { + public clearDecorations(internal?: boolean): void { this._selectedDecoration?.dispose(); this._searchResults?.clear(); this._resultDecorations?.forEach(decorations => { @@ -113,6 +113,13 @@ export class SearchAddon implements ITerminalAddon { this._resultDecorations?.clear(); this._searchResults = undefined; this._resultDecorations = undefined; + if (!internal) { + // we want to keep _cachedSearchTerm defined if this is + // an internal call + // so that when the buffer changes, + // we can use that to search for new matches + this._cachedSearchTerm = undefined; + } } /** @@ -146,7 +153,7 @@ export class SearchAddon implements ITerminalAddon { searchOptions = searchOptions || {}; // new search, clear out the old decorations - this.clearDecorations(); + this.clearDecorations(true); this._searchResults = new Map(); this._resultDecorations = new Map(); const resultDecorations = this._resultDecorations; From 656912c9348fd356e99f28ffcab0536daf91c28f Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Wed, 27 Apr 2022 12:10:00 -0700 Subject: [PATCH 202/245] on data/ buffer change, update the match count (#3752) --- addons/xterm-addon-search/src/SearchAddon.ts | 59 ++++++++++---------- 1 file changed, 29 insertions(+), 30 deletions(-) diff --git a/addons/xterm-addon-search/src/SearchAddon.ts b/addons/xterm-addon-search/src/SearchAddon.ts index 241e5e99..801da30a 100644 --- a/addons/xterm-addon-search/src/SearchAddon.ts +++ b/addons/xterm-addon-search/src/SearchAddon.ts @@ -12,6 +12,7 @@ export interface ISearchOptions { caseSensitive?: boolean; incremental?: boolean; decorations?: ISearchDecorationOptions; + noScroll?: boolean; } interface ISearchDecorationOptions { @@ -56,6 +57,7 @@ export class SearchAddon implements ITerminalAddon { private _resultDecorations: Map | undefined; private _searchResults: Map | undefined; private _onDataDisposable: IDisposable | undefined; + private _onResizeDisposable: IDisposable | undefined; private _lastSearchOptions: ISearchOptions | undefined; private _highlightTimeout: number | undefined; /** @@ -75,31 +77,26 @@ export class SearchAddon implements ITerminalAddon { public activate(terminal: Terminal): void { this._terminal = terminal; - this._onDataDisposable = this._terminal.onData(() => { - if (this._highlightTimeout) { - window.clearTimeout(this._highlightTimeout); - } - if (this._cachedSearchTerm && this._lastSearchOptions?.decorations) { - this._highlightTimeout = setTimeout(() => { - this._highlightAllMatches(this._cachedSearchTerm!, { ...this._lastSearchOptions, incremental: true }); - }, 200); - } - }); - this._terminal.onResize(() => { - if (this._highlightTimeout) { - window.clearTimeout(this._highlightTimeout); - } - if (this._cachedSearchTerm && this._lastSearchOptions?.decorations) { - this._highlightTimeout = setTimeout(() => { - this._highlightAllMatches(this._cachedSearchTerm!, { ...this._lastSearchOptions, incremental: true }); - }, 200); - } - }); + this._onDataDisposable = this._terminal.onData(() => this._updateMatches()); + this._onResizeDisposable = this._terminal.onResize(() => this._updateMatches()); + } + + private _updateMatches(): void { + if (this._highlightTimeout) { + window.clearTimeout(this._highlightTimeout); + } + if (this._cachedSearchTerm && this._lastSearchOptions?.decorations) { + this._highlightTimeout = setTimeout(() => { + this.findPrevious(this._cachedSearchTerm!, { ...this._lastSearchOptions, incremental: true, noScroll: true }); + this._onDidChangeResults.fire({ resultIndex: this._searchResults ? this._searchResults.size - 1 : -1, resultCount: this._searchResults ? this._searchResults.size : -1 }); + }, 200); + } } public dispose(): void { this.clearDecorations(); this._onDataDisposable?.dispose(); + this._onResizeDisposable?.dispose(); } public clearDecorations(internal?: boolean): void { @@ -299,7 +296,7 @@ export class SearchAddon implements ITerminalAddon { } } // Set selection and scroll if a result was found - return this._selectResult(result, searchOptions?.decorations); + return this._selectResult(result, searchOptions?.decorations, searchOptions?.noScroll); } /** * Find the previous instance of the term, then scroll to and select it. If it @@ -423,7 +420,7 @@ export class SearchAddon implements ITerminalAddon { if (!result && currentSelection) return true; // Set selection and scroll if a result was found - return this._selectResult(result, searchOptions?.decorations); + return this._selectResult(result, searchOptions?.decorations, searchOptions?.noScroll); } /** @@ -660,7 +657,7 @@ export class SearchAddon implements ITerminalAddon { * @param result The result to select. * @return Whether a result was selected. */ - private _selectResult(result: ISearchResult | undefined, decorations?: ISearchDecorationOptions): boolean { + private _selectResult(result: ISearchResult | undefined, decorations?: ISearchDecorationOptions, noScroll?: boolean): boolean { const terminal = this._terminal!; this._selectedDecoration?.dispose(); if (!result) { @@ -679,16 +676,18 @@ export class SearchAddon implements ITerminalAddon { color: decorations.activeMatchColorOverviewRuler } }); - this._selectedDecoration?.onRender((e) => this._applyStyles(e, decorations.activeMatchBackground, decorations.activeMatchBorder, result)); + this._selectedDecoration?.onRender((e) => this._applyStyles(e, decorations.activeMatchBackground, decorations.activeMatchBorder)); this._selectedDecoration?.onDispose(() => marker.dispose()); } } + if (!noScroll) { // If it is not in the viewport then we scroll else it just gets selected - if (result.row >= (terminal.buffer.active.viewportY + terminal.rows) || result.row < terminal.buffer.active.viewportY) { - let scroll = result.row - terminal.buffer.active.viewportY; - scroll -= Math.floor(terminal.rows / 2); - terminal.scrollLines(scroll); + if (result.row >= (terminal.buffer.active.viewportY + terminal.rows) || result.row < terminal.buffer.active.viewportY) { + let scroll = result.row - terminal.buffer.active.viewportY; + scroll -= Math.floor(terminal.rows / 2); + terminal.scrollLines(scroll); + } } return true; } @@ -701,7 +700,7 @@ export class SearchAddon implements ITerminalAddon { * @param result the search result associated with the decoration * @returns */ - private _applyStyles(element: HTMLElement, backgroundColor: string | undefined, borderColor: string | undefined, result: ISearchResult): void { + private _applyStyles(element: HTMLElement, backgroundColor: string | undefined, borderColor: string | undefined): void { if (element.clientWidth <= 0) { return; } @@ -736,7 +735,7 @@ export class SearchAddon implements ITerminalAddon { color: decorations.matchOverviewRuler, position: 'center' } }); - findResultDecoration?.onRender((e) => this._applyStyles(e, decorations.matchBackground, decorations.matchBorder, result)); + findResultDecoration?.onRender((e) => this._applyStyles(e, decorations.matchBackground, decorations.matchBorder)); findResultDecoration?.onDispose(() => marker.dispose()); return findResultDecoration; } From 908e84a1f8d1be77519bd0fcb13dd954c7ac083d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Sun, 1 May 2022 17:55:04 +0200 Subject: [PATCH 203/245] update benchmark version to silence vul warning --- package.json | 2 +- yarn.lock | 344 +++++++++++++-------------------------------------- 2 files changed, 84 insertions(+), 262 deletions(-) diff --git a/package.json b/package.json index 03811997..c3e29be5 100644 --- a/package.json +++ b/package.json @@ -85,6 +85,6 @@ "webpack": "^5.61.0", "webpack-cli": "^4.9.1", "ws": "^8.2.3", - "xterm-benchmark": "^0.3.0" + "xterm-benchmark": "^0.3.1" } } diff --git a/yarn.lock b/yarn.lock index 1177f49a..ca2a0b91 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,7 +2,7 @@ # yarn lockfile v1 -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.8.3": +"@babel/code-frame@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.8.3.tgz#33e25903d7481181534e12ec0a25f16b6fcf419e" integrity sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g== @@ -144,6 +144,13 @@ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.9.4.tgz#68a35e6b0319bbc014465be43828300113f2f2e8" integrity sha512-bC49otXX6N0/VYhgOMh4gnP26E9xnDZK3TmbNpxYzzz9BQLBosQwfyOe9/cXUU3txYhTzLCbcqd5c8y/OmCjHA== +"@babel/runtime@^7.15.4": + version "7.17.9" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.17.9.tgz#d19fbf802d01a8cb6cf053a64e472d42c434ba72" + integrity sha512-lSiBBvodq29uShpWGNbgFdKYNiFDo5/HIYsaCEY9ff4sb10x9jizo2+pRrSyF4jKZCXqgzuqBOQKbUm90gQwJg== + dependencies: + regenerator-runtime "^0.13.4" + "@babel/template@^7.7.4", "@babel/template@^7.8.3", "@babel/template@^7.8.6": version "7.8.6" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.8.6.tgz#86b22af15f828dfb086474f964dcc3e39c43ce2b" @@ -355,9 +362,9 @@ integrity sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ== "@types/mocha@^8.2.1": - version "8.2.2" - resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-8.2.2.tgz#91daa226eb8c2ff261e6a8cbf8c7304641e095e0" - integrity sha512-Lwh0lzzqT5Pqh6z61P3c3P5nm6fzQK/MMHl9UKeneAeInVflBSz1O2EkX6gM6xfJd7FBXBY5purtLx7fUiZ7Hw== + version "8.2.3" + resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-8.2.3.tgz#bbeb55fbc73f28ea6de601fbfa4613f58d785323" + integrity sha512-ekGvFhFgrc2zYQoX4JeZPmVzZxw6Dtllga7iGHzfbYIYkAMUx/sAFP2GdFpLff+vdHXu5fl7WX9AT+TtqYcsyw== "@types/mocha@^9.0.0": version "9.0.0" @@ -375,9 +382,9 @@ integrity sha512-hkzMMD3xu6BrJpGVLeQ3htQQNAcOrJjX7WFmtK8zWQpz2UJf13LCFF2ALA7c9OVdvc2vQJeDdjfR35M0sBCxvw== "@types/node@^12.12.37": - version "12.20.12" - resolved "https://registry.yarnpkg.com/@types/node/-/node-12.20.12.tgz#fd9c1c2cfab536a2383ed1ef70f94adea743a226" - integrity sha512-KQZ1al2hKOONAs2MFv+yTQP1LkDWMrRJ9YCVRalXltOfXsBmH5IownLxQaiq0lnAHwAViLnh2aTYqrPcRGEbgg== + version "12.20.50" + resolved "https://registry.yarnpkg.com/@types/node/-/node-12.20.50.tgz#14ba5198f1754ffd0472a2f84ab433b45ee0b65e" + integrity sha512-+9axpWx2b2JCVovr7Ilgt96uc6C1zBKOQMpGtRbWT9IoR/8ue32GGMfGA4woP8QyP2gBs6GQWEVM3tCybGCxDA== "@types/node@^14.14.44": version "14.14.44" @@ -390,9 +397,9 @@ integrity sha512-+hQX+WyJAOne7Fh3zF5CxPemILIbuhNcqHHodzK9caYOLnC8pD5efmPleRnw0z++LfKUC/sVNMwk0Gap+B0baA== "@types/puppeteer@^5.4.3": - version "5.4.3" - resolved "https://registry.yarnpkg.com/@types/puppeteer/-/puppeteer-5.4.3.tgz#cdca84aa7751d77448d8a477dbfa0af1f11485f2" - integrity sha512-3nE8YgR9DIsgttLW+eJf6mnXxq8Ge+27m5SU3knWmrlfl6+KOG0Bf9f7Ua7K+C4BnaTMAh3/UpySqdAYvrsvjg== + version "5.4.6" + resolved "https://registry.yarnpkg.com/@types/puppeteer/-/puppeteer-5.4.6.tgz#afc438e41dcbc27ca1ba0235ea464a372db2b21c" + integrity sha512-98Kghehs7+/GD9b56qryhqdqVCXUTbetTv3PlvDnmFRTHQH0j9DIp1f7rkAW3BAj4U3yoeSEQnKgdW8bDq0Y0Q== dependencies: "@types/node" "*" @@ -869,21 +876,16 @@ ansi-colors@4.1.1, ansi-colors@^4.1.1: resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348" integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA== -ansi-regex@^2.0.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" - integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8= - -ansi-regex@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" - integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg= - ansi-regex@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75" integrity sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg== +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + ansi-styles@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" @@ -899,14 +901,6 @@ ansi-styles@^4.0.0, ansi-styles@^4.1.0: "@types/color-name" "^1.1.1" color-convert "^2.0.1" -anymatch@~3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.1.tgz#c55ecf02185e2469259399310c173ce31233b142" - integrity sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg== - dependencies: - normalize-path "^3.0.0" - picomatch "^2.0.4" - anymatch@~3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716" @@ -1048,11 +1042,6 @@ buffer-from@^1.0.0: resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== -builtin-modules@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f" - integrity sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8= - bytes@3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6" @@ -1108,7 +1097,7 @@ chai@^4.3.4: pathval "^1.1.1" type-detect "^4.0.5" -chalk@^2.0.0, chalk@^2.3.0: +chalk@^2.0.0: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== @@ -1138,21 +1127,6 @@ check-error@^1.0.2: resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.2.tgz#574d312edd88bb5dd8912e9286dd6c0aed4aac82" integrity sha1-V00xLt2Iu13YkS6Sht1sCu1KrII= -chokidar@3.5.1: - version "3.5.1" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.1.tgz#ee9ce7bbebd2b79f49f304799d5468e31e14e68a" - integrity sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw== - dependencies: - anymatch "~3.1.1" - braces "~3.0.2" - glob-parent "~5.1.0" - is-binary-path "~2.1.0" - is-glob "~4.0.1" - normalize-path "~3.0.0" - readdirp "~3.5.0" - optionalDependencies: - fsevents "~2.3.1" - chokidar@3.5.3: version "3.5.3" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" @@ -1179,9 +1153,9 @@ clean-stack@^2.0.0: integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== cli-table@^0.3.6: - version "0.3.6" - resolved "https://registry.yarnpkg.com/cli-table/-/cli-table-0.3.6.tgz#e9d6aa859c7fe636981fd3787378c2a20bce92fc" - integrity sha512-ZkNZbnZjKERTY5NwC2SeMeLeifSPq/pubeRoTpdr3WchLlnZg6hEgvHkK5zL7KNFdd9PmHN8lxrENUwI3cE8vQ== + version "0.3.11" + resolved "https://registry.yarnpkg.com/cli-table/-/cli-table-0.3.11.tgz#ac69cdecbe81dccdba4889b9a18b7da312a9d3ee" + integrity sha512-IqLQi4lO0nIB4tcdTpN4LCB9FI3uqrJZK7RC515EnhZ6qBaglkIgICb1wjeAqpdoOabm1+SuQtkXIPdYC93jhQ== dependencies: colors "1.0.3" @@ -1257,11 +1231,11 @@ colors@1.0.3: integrity sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs= columnify@^1.5.4: - version "1.5.4" - resolved "https://registry.yarnpkg.com/columnify/-/columnify-1.5.4.tgz#4737ddf1c7b69a8a7c340570782e947eec8e78bb" - integrity sha1-Rzfd8ce2mop8NAVweC6UfuyOeLs= + version "1.6.0" + resolved "https://registry.yarnpkg.com/columnify/-/columnify-1.6.0.tgz#6989531713c9008bb29735e61e37acf5bd553cf3" + integrity sha512-lomjuFZKfM6MSAnV9aCZC9sc0qGbmZdfygNv+nCpqVkSKdCxCklLtd16O0EILGkImHw9ZpHkAnHaB+8Zxq5W6Q== dependencies: - strip-ansi "^3.0.0" + strip-ansi "^6.0.1" wcwidth "^1.0.0" combined-stream@^1.0.8: @@ -1271,7 +1245,7 @@ combined-stream@^1.0.8: dependencies: delayed-stream "~1.0.0" -commander@^2.12.1, commander@^2.20.0: +commander@^2.20.0: version "2.20.3" resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== @@ -1296,10 +1270,10 @@ commondir@^1.0.1: resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" integrity sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs= -complex.js@^2.0.11: - version "2.0.12" - resolved "https://registry.yarnpkg.com/complex.js/-/complex.js-2.0.12.tgz#fa4df97d8928e5f7b6a86b35bdeecc3a3eda8a22" - integrity sha512-oQX99fwL6LrTVg82gDY1dIWXy6qZRnRL35N+YhIX0N7tSwsa0KFy6IEMHTNuCW4mP7FS7MEqZ/2I/afzYwPldw== +complex.js@^2.0.15: + version "2.1.1" + resolved "https://registry.yarnpkg.com/complex.js/-/complex.js-2.1.1.tgz#0675dac8e464ec431fb2ab7d30f41d889fb25c31" + integrity sha512-8njCHOTtFFLtegk6zQo0kkVX1rngygb/KQI6z1qZxlFI3scluC+LVTCFbrkWjBv4vvLlbQ9t88IPMC6k95VTTg== concat-map@0.0.1: version "0.0.1" @@ -1391,13 +1365,6 @@ debug@4, debug@^4.1.0, debug@^4.1.1: dependencies: ms "2.1.2" -debug@4.3.1: - version "4.3.1" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee" - integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ== - dependencies: - ms "2.1.2" - debug@4.3.3: version "4.3.3" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.3.tgz#04266e0b70a98d4462e6e288e38259213332b664" @@ -1422,12 +1389,7 @@ decamelize@^4.0.0: resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-4.0.0.tgz#aa472d7bf660eb15f3494efd531cab7f2a709837" integrity sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ== -decimal.js@^10.0.0, decimal.js@^10.2.1: - version "10.2.1" - resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.2.1.tgz#238ae7b0f0c793d3e3cea410108b35a2c01426a3" - integrity sha512-KaL7+6Fw6i5A2XSnsbhm/6B+NuEA7TZ4vqxnd5tXz9sbKtrN9Srj8ab4vKVdK8YAqZO9P1kg45Y6YLoduPf+kw== - -decimal.js@^10.3.1: +decimal.js@^10.0.0, decimal.js@^10.3.1: version "10.3.1" resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.3.1.tgz#d8c3a444a9c6774ba60ca6ad7261c3a94fd5e783" integrity sha512-V0pfhfr8suzyPGOx3nmq4aHqabehUZn6Ch9kyFpV79TGDTWFmHqUqXdabR7QHqxzrYolF4+tVmJhUG4OURg5dQ== @@ -1506,11 +1468,6 @@ diff@5.0.0: resolved "https://registry.yarnpkg.com/diff/-/diff-5.0.0.tgz#7ed6ad76d859d030787ec35855f5b1daf31d852b" integrity sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w== -diff@^4.0.1: - version "4.0.2" - resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" - integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== - dir-glob@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" @@ -2040,10 +1997,10 @@ forwarded@~0.1.2: resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84" integrity sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ= -fraction.js@^4.0.13: - version "4.0.13" - resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.0.13.tgz#3c1c315fa16b35c85fffa95725a36fa729c69dfe" - integrity sha512-E1fz2Xs9ltlUp+qbiyx9wmt2n9dRzPsS11Jtdb8D2o+cC7wr9xkkKsVKJuBX0ST+LVS+LhLO+SbLJNtfWcJvXA== +fraction.js@^4.1.1: + version "4.2.0" + resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.2.0.tgz#448e5109a313a3527f5a3ab2119ec4cf0e0e2950" + integrity sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA== fresh@0.5.2: version "0.5.2" @@ -2060,7 +2017,7 @@ fs.realpath@^1.0.0: resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= -fsevents@~2.3.1, fsevents@~2.3.2: +fsevents@~2.3.2: version "2.3.2" resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== @@ -2116,7 +2073,7 @@ get-stream@^6.0.0: resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== -glob-parent@^5.1.2, glob-parent@~5.1.0, glob-parent@~5.1.2: +glob-parent@^5.1.2, glob-parent@~5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== @@ -2135,18 +2092,6 @@ glob-to-regexp@^0.4.1: resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e" integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw== -glob@7.1.6, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: - version "7.1.6" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" - integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.0.4" - once "^1.3.0" - path-is-absolute "^1.0.0" - glob@7.2.0, glob@^7.2.0: version "7.2.0" resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023" @@ -2159,10 +2104,10 @@ glob@7.2.0, glob@^7.2.0: once "^1.3.0" path-is-absolute "^1.0.0" -glob@^7.1.1: - version "7.1.7" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.7.tgz#3b193e9233f01d42d0b3f78294bbeeb418f94a90" - integrity sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ== +glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: + version "7.1.6" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" + integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== dependencies: fs.realpath "^1.0.0" inflight "^1.0.4" @@ -2437,11 +2382,6 @@ is-extglob@^2.1.1: resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= -is-fullwidth-code-point@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" - integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= - is-fullwidth-code-point@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" @@ -2676,13 +2616,6 @@ js-tokens@^4.0.0: resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== -js-yaml@4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.0.0.tgz#f426bc0ff4b4051926cd588c71113183409a121f" - integrity sha512-pqon0s+4ScYUvX30wxQi3PogGFAlUyH0awepWvwkj4jD4v+ova3RiYw8bmA6x2rDrEaj8i/oWKoRxpVNW+Re8Q== - dependencies: - argparse "^2.0.1" - js-yaml@4.1.0, js-yaml@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" @@ -2813,13 +2746,6 @@ lodash@^4.17.13: resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== -log-symbols@4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.0.0.tgz#69b3cc46d20f448eccdb75ea1fa733d9e821c920" - integrity sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA== - dependencies: - chalk "^4.0.0" - log-symbols@4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503" @@ -2843,14 +2769,15 @@ make-dir@^3.0.0, make-dir@^3.0.2: semver "^6.0.0" mathjs@^9.3.0: - version "9.3.2" - resolved "https://registry.yarnpkg.com/mathjs/-/mathjs-9.3.2.tgz#6523dd5c963d200ff1cea0ff7963b10521b82185" - integrity sha512-0YKSKAeN9OkbIQrxfxnBT4kk/KlH71piWOsvVvAasyRIj/Xd/zlpc5VP/aFxwr+llOq2F3f6booPEu2fWv3yjQ== + version "9.5.2" + resolved "https://registry.yarnpkg.com/mathjs/-/mathjs-9.5.2.tgz#e0f3279320dc6f49e45d99c4fcdd8b52231f0462" + integrity sha512-c0erTq0GP503/Ch2OtDOAn50GIOsuxTMjmE00NI/vKJFSWrDaQHRjx6ai+16xYv70yBSnnpUgHZGNf9FR9IwmA== dependencies: - complex.js "^2.0.11" - decimal.js "^10.2.1" + "@babel/runtime" "^7.15.4" + complex.js "^2.0.15" + decimal.js "^10.3.1" escape-latex "^1.2.0" - fraction.js "^4.0.13" + fraction.js "^4.1.1" javascript-natural-sort "^0.7.1" seedrandom "^3.0.5" tiny-emitter "^2.1.0" @@ -2952,44 +2879,6 @@ minimist@^1.2.5: resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44" integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q== -mkdirp@^0.5.3: - version "0.5.5" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" - integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== - dependencies: - minimist "^1.2.5" - -mocha@^8.3.2: - version "8.4.0" - resolved "https://registry.yarnpkg.com/mocha/-/mocha-8.4.0.tgz#677be88bf15980a3cae03a73e10a0fc3997f0cff" - integrity sha512-hJaO0mwDXmZS4ghXsvPVriOhsxQ7ofcpQdm8dE+jISUOKopitvnXFQmpRR7jd2K6VBG6E26gU3IAbXXGIbu4sQ== - dependencies: - "@ungap/promise-all-settled" "1.1.2" - ansi-colors "4.1.1" - browser-stdout "1.3.1" - chokidar "3.5.1" - debug "4.3.1" - diff "5.0.0" - escape-string-regexp "4.0.0" - find-up "5.0.0" - glob "7.1.6" - growl "1.10.5" - he "1.2.0" - js-yaml "4.0.0" - log-symbols "4.0.0" - minimatch "3.0.4" - ms "2.1.3" - nanoid "3.1.20" - serialize-javascript "5.0.1" - strip-json-comments "3.1.1" - supports-color "8.1.1" - which "2.0.2" - wide-align "1.1.3" - workerpool "6.1.0" - yargs "16.2.0" - yargs-parser "20.2.4" - yargs-unparser "2.0.0" - mocha@^9.2.0: version "9.2.0" resolved "https://registry.yarnpkg.com/mocha/-/mocha-9.2.0.tgz#2bfba73d46e392901f877ab9a47b7c9c5d0275cc" @@ -3050,11 +2939,6 @@ nan@^2.14.0: resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.2.tgz#f5376400695168f4cc694ac9393d0c9585eeea19" integrity sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ== -nanoid@3.1.20: - version "3.1.20" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.20.tgz#badc263c6b1dcf14b71efaa85f6ab4c1d6cfc788" - integrity sha512-a1cQNyczgKbLX9jwbS/+d7W8fX/RfgYR7lVWwWOGIPNgK2m0MWvrGF6/m4kk6U3QcFMnZf3RIhL0v2Jgh/0Uxw== - nanoid@3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.2.0.tgz#62667522da6673971cca916a6d3eff3f415ff80c" @@ -3476,13 +3360,6 @@ raw-body@2.4.0: iconv-lite "0.4.24" unpipe "1.0.0" -readdirp@~3.5.0: - version "3.5.0" - resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.5.0.tgz#9ba74c019b15d365278d2e91bb8c48d7b4d42c9e" - integrity sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ== - dependencies: - picomatch "^2.2.1" - readdirp@~3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" @@ -3497,6 +3374,11 @@ rechoir@^0.7.0: dependencies: resolve "^1.9.0" +regenerator-runtime@^0.13.4: + version "0.13.9" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz#8925742a98ffd90814988d7566ad30ca3b263b52" + integrity sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA== + regexp.prototype.flags@^1.3.0: version "1.3.1" resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.3.1.tgz#7ef352ae8d159e758c0eadca6f8fcb4eef07be26" @@ -3628,7 +3510,7 @@ seedrandom@^3.0.5: resolved "https://registry.yarnpkg.com/seedrandom/-/seedrandom-3.0.5.tgz#54edc85c95222525b0c7a6f6b3543d8e0b3aa0a7" integrity sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg== -semver@^5.3.0, semver@^5.4.1: +semver@^5.4.1: version "5.7.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== @@ -3664,13 +3546,6 @@ send@0.17.1: range-parser "~1.2.1" statuses "~1.5.0" -serialize-javascript@5.0.1, serialize-javascript@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-5.0.1.tgz#7886ec848049a462467a97d3d918ebb2aaf934f4" - integrity sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA== - dependencies: - randombytes "^2.1.0" - serialize-javascript@6.0.0, serialize-javascript@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.0.tgz#efae5d88f45d7924141da8b5c3a7a7e663fefeb8" @@ -3678,6 +3553,13 @@ serialize-javascript@6.0.0, serialize-javascript@^6.0.0: dependencies: randombytes "^2.1.0" +serialize-javascript@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-5.0.1.tgz#7886ec848049a462467a97d3d918ebb2aaf934f4" + integrity sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA== + dependencies: + randombytes "^2.1.0" + serve-static@1.14.1: version "1.14.1" resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.14.1.tgz#666e636dc4f010f7ef29970a88a674320898b2f9" @@ -3842,14 +3724,6 @@ stack-utils@^2.0.3: resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= -"string-width@^1.0.2 || 2": - version "2.1.1" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" - integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== - dependencies: - is-fullwidth-code-point "^2.0.0" - strip-ansi "^4.0.0" - string-width@^4.1.0, string-width@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.0.tgz#952182c46cc7b2c313d1596e623992bd163b72b5" @@ -3875,20 +3749,6 @@ string.prototype.trimstart@^1.0.4: call-bind "^1.0.2" define-properties "^1.1.3" -strip-ansi@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" - integrity sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8= - dependencies: - ansi-regex "^2.0.0" - -strip-ansi@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" - integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8= - dependencies: - ansi-regex "^3.0.0" - strip-ansi@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.0.tgz#0b1571dd7669ccd4f3e06e14ef1eed26225ae532" @@ -3896,6 +3756,13 @@ strip-ansi@^6.0.0: dependencies: ansi-regex "^5.0.0" +strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + strip-bom@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878" @@ -4046,42 +3913,11 @@ ts-loader@^9.1.2: micromatch "^4.0.0" semver "^7.3.4" -tslib@^1.13.0: - version "1.14.1" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" - integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== - tslib@^1.8.1: version "1.11.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.11.1.tgz#eb15d128827fbee2841549e171f45ed338ac7e35" integrity sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA== -tslint@^6.1.3: - version "6.1.3" - resolved "https://registry.yarnpkg.com/tslint/-/tslint-6.1.3.tgz#5c23b2eccc32487d5523bd3a470e9aa31789d904" - integrity sha512-IbR4nkT96EQOvKE2PW/djGz8iGNeJ4rF2mBfiYaR/nvUWYKJhLwimoJKgjIFEIDibBtOevj7BqCRL4oHeWWUCg== - dependencies: - "@babel/code-frame" "^7.0.0" - builtin-modules "^1.1.1" - chalk "^2.3.0" - commander "^2.12.1" - diff "^4.0.1" - glob "^7.1.1" - js-yaml "^3.13.1" - minimatch "^3.0.4" - mkdirp "^0.5.3" - resolve "^1.3.2" - semver "^5.3.0" - tslib "^1.13.0" - tsutils "^2.29.0" - -tsutils@^2.29.0: - version "2.29.0" - resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-2.29.0.tgz#32b488501467acbedd4b85498673a0812aca0b99" - integrity sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA== - dependencies: - tslib "^1.8.1" - tsutils@^3.21.0: version "3.21.0" resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623" @@ -4127,9 +3963,9 @@ type-is@~1.6.17, type-is@~1.6.18: mime-types "~2.1.24" typed-function@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/typed-function/-/typed-function-2.0.0.tgz#15ab3825845138a8b1113bd89e60cd6a435739e8" - integrity sha512-Hhy1Iwo/e4AtLZNK10ewVVcP2UEs408DS35ubP825w/YgSBK1KVLwALvvIG4yX75QJrxjCpcWkzkVRB0BwwYlA== + version "2.1.0" + resolved "https://registry.yarnpkg.com/typed-function/-/typed-function-2.1.0.tgz#ded6f8a442ba8749ff3fe75bc41419c8d46ccc3f" + integrity sha512-bctQIOqx2iVbWGDGPWwIm18QScpu2XRmkC19D8rQGFsjKSgteq/o1hTZvIG/wuDq8fanpBDrLkLq+aEN/6y5XQ== typedarray-to-buffer@^3.1.5: version "3.1.5" @@ -4139,9 +3975,9 @@ typedarray-to-buffer@^3.1.5: is-typedarray "^1.0.0" typescript@^4.2.3: - version "4.2.4" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.2.4.tgz#8610b59747de028fda898a8aef0e103f156d0961" - integrity sha512-V+evlYHZnQkaz8TRBuxTA92yZBPotr5H+WhQ7bD3hZUndx5tGOa1fuCgeSjxAzM1RiN5IzvadIXTVefuuwZCRg== + version "4.6.4" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.6.4.tgz#caa78bbc3a59e6a5c510d35703f6a09877ce45e9" + integrity sha512-9ia/jWHIEbo49HfjrLGfKbZSuWo9iTMwXO+Ca3pRsSpbsMbc7/IU8NKdCZVRRBafVPGnoJeFL76ZOAA84I9fEg== typescript@^4.4.4: version "4.4.4" @@ -4406,13 +4242,6 @@ which@2.0.2, which@^2.0.1: dependencies: isexe "^2.0.0" -wide-align@1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457" - integrity sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA== - dependencies: - string-width "^1.0.2 || 2" - wildcard@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/wildcard/-/wildcard-2.0.0.tgz#a77d20e5200c6faaac979e4b3aadc7b3dd7f8fec" @@ -4423,11 +4252,6 @@ word-wrap@^1.2.3, word-wrap@~1.2.3: resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== -workerpool@6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.1.0.tgz#a8e038b4c94569596852de7a8ea4228eefdeb37b" - integrity sha512-toV7q9rWNYha963Pl/qyeZ6wG+3nnsyvolaNUS8+R5Wtw6qJPTxIlOP1ZSvcGhEJw+l3HMMmtiNo9Gl61G4GVg== - workerpool@6.2.0: version "6.2.0" resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.2.0.tgz#827d93c9ba23ee2019c3ffaff5c27fccea289e8b" @@ -4486,10 +4310,10 @@ xmlchars@^2.2.0: resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb" integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw== -xterm-benchmark@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/xterm-benchmark/-/xterm-benchmark-0.3.0.tgz#8702ae41672ff1e656423336f4e54699a3ab74e8" - integrity sha512-JTC1NjaqAWRHA3vPwbAocOJvz42ysYelrq7z2PS9egly8Hi5W7zfJIZlpkzEsZ8dXo0fcEm/6azmoYFy+TMjig== +xterm-benchmark@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/xterm-benchmark/-/xterm-benchmark-0.3.1.tgz#dcaaf808e40605c7c27a83b5a5b81f9c45045e24" + integrity sha512-JjsCrSxkYKWf5CmBt2BeXm83KQdStyoGWREWQ0jSFF5N8CYVbdKQoWgs56mmy6qWD5GDKxO+V89Cvnbc8YUFjw== dependencies: "@types/app-root-path" "^1.2.4" "@types/cli-table" "^0.3.0" @@ -4502,8 +4326,6 @@ xterm-benchmark@^0.3.0: columnify "^1.5.4" commander "^6.2.1" mathjs "^9.3.0" - mocha "^8.3.2" - tslint "^6.1.3" typescript "^4.2.3" y18n@^4.0.0: From bb55cd288154d1ae21133b36a70fd932d654d4e2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 1 May 2022 16:05:46 +0000 Subject: [PATCH 204/245] Bump ansi-regex from 5.0.0 to 5.0.1 Bumps [ansi-regex](https://github.com/chalk/ansi-regex) from 5.0.0 to 5.0.1. - [Release notes](https://github.com/chalk/ansi-regex/releases) - [Commits](https://github.com/chalk/ansi-regex/compare/v5.0.0...v5.0.1) --- updated-dependencies: - dependency-name: ansi-regex dependency-type: indirect ... Signed-off-by: dependabot[bot] --- yarn.lock | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/yarn.lock b/yarn.lock index ca2a0b91..2523d316 100644 --- a/yarn.lock +++ b/yarn.lock @@ -876,12 +876,7 @@ ansi-colors@4.1.1, ansi-colors@^4.1.1: resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348" integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA== -ansi-regex@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75" - integrity sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg== - -ansi-regex@^5.0.1: +ansi-regex@^5.0.0, ansi-regex@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== From 1687d06f1c6b4f6ad073304f0bdd64afd30c2adc Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Mon, 2 May 2022 13:50:11 -0700 Subject: [PATCH 205/245] use correct powerline glyph range (#3756) --- src/browser/renderer/RendererUtils.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/browser/renderer/RendererUtils.ts b/src/browser/renderer/RendererUtils.ts index 174556bd..3fc2bb3b 100644 --- a/src/browser/renderer/RendererUtils.ts +++ b/src/browser/renderer/RendererUtils.ts @@ -11,7 +11,8 @@ export function throwIfFalsy(value: T | undefined | null): T { } export function isPowerlineGlyph(codepoint: number): boolean { - // This range was established via - // https://apw-bash-settings.readthedocs.io/en/latest/fontpatching.html - return 0xE000 <= codepoint && codepoint <= 0xF8FF; + // Only return true for Powerline symbols which require + // different padding and should be excluded from minimum contrast + // ratio standards + return 0xE0A0 <= codepoint && codepoint <= 0xE0D6; } From 218745ab85fe1b6a6278b64b022302f299ba969c Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Mon, 2 May 2022 14:20:25 -0700 Subject: [PATCH 206/245] refine name for clearDecorations param (#3757) --- addons/xterm-addon-search/src/SearchAddon.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/addons/xterm-addon-search/src/SearchAddon.ts b/addons/xterm-addon-search/src/SearchAddon.ts index 801da30a..345b9153 100644 --- a/addons/xterm-addon-search/src/SearchAddon.ts +++ b/addons/xterm-addon-search/src/SearchAddon.ts @@ -99,7 +99,7 @@ export class SearchAddon implements ITerminalAddon { this._onResizeDisposable?.dispose(); } - public clearDecorations(internal?: boolean): void { + public clearDecorations(retainCachedSearchTerm?: boolean): void { this._selectedDecoration?.dispose(); this._searchResults?.clear(); this._resultDecorations?.forEach(decorations => { @@ -110,11 +110,7 @@ export class SearchAddon implements ITerminalAddon { this._resultDecorations?.clear(); this._searchResults = undefined; this._resultDecorations = undefined; - if (!internal) { - // we want to keep _cachedSearchTerm defined if this is - // an internal call - // so that when the buffer changes, - // we can use that to search for new matches + if (!retainCachedSearchTerm) { this._cachedSearchTerm = undefined; } } From 873e3745ec043cea97f219d00980477435785983 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 6 May 2022 07:36:22 -0700 Subject: [PATCH 207/245] Send ctrl modifier in page up/down sequence Part of microsoft/vscode#148685 --- src/common/input/Keyboard.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/common/input/Keyboard.ts b/src/common/input/Keyboard.ts index b4b3dce4..6d916a38 100644 --- a/src/common/input/Keyboard.ts +++ b/src/common/input/Keyboard.ts @@ -230,6 +230,8 @@ export function evaluateKeyboardEvent( // page up if (ev.shiftKey) { result.type = KeyboardResultType.PAGE_UP; + } else if (ev.ctrlKey) { + result.key = C0.ESC + '[5;' + (modifiers + 1) + '~'; } else { result.key = C0.ESC + '[5~'; } @@ -238,6 +240,8 @@ export function evaluateKeyboardEvent( // page down if (ev.shiftKey) { result.type = KeyboardResultType.PAGE_DOWN; + } else if (ev.ctrlKey) { + result.key = C0.ESC + '[6;' + (modifiers + 1) + '~'; } else { result.key = C0.ESC + '[6~'; } From 95979c4a7da47a100133398a03d9ad082a393351 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 9 May 2022 10:34:15 -0700 Subject: [PATCH 208/245] Correct param names/jsdoc in SearchAddon --- addons/xterm-addon-search/src/SearchAddon.ts | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/addons/xterm-addon-search/src/SearchAddon.ts b/addons/xterm-addon-search/src/SearchAddon.ts index 345b9153..a56b3ab3 100644 --- a/addons/xterm-addon-search/src/SearchAddon.ts +++ b/addons/xterm-addon-search/src/SearchAddon.ts @@ -693,7 +693,6 @@ export class SearchAddon implements ITerminalAddon { * @param element the decoration's element * @param backgroundColor the background color to apply * @param borderColor the border color to apply - * @param result the search result associated with the decoration * @returns */ private _applyStyles(element: HTMLElement, backgroundColor: string | undefined, borderColor: string | undefined): void { @@ -714,13 +713,13 @@ export class SearchAddon implements ITerminalAddon { /** * Creates a decoration for the result and applies styles * @param result the search result for which to create the decoration - * @param color the color to use for the decoration + * @param options the options for the decoration * @returns the {@link IDecoration} or undefined if the marker has already been disposed of */ - private _createResultDecoration(result: ISearchResult, decorations: ISearchDecorationOptions): IDecoration | undefined { + private _createResultDecoration(result: ISearchResult, options: ISearchDecorationOptions): IDecoration | undefined { const terminal = this._terminal!; const marker = terminal.registerMarker(-terminal.buffer.active.baseY - terminal.buffer.active.cursorY + result.row); - if (!marker || !decorations?.matchOverviewRuler) { + if (!marker || !options?.matchOverviewRuler) { return undefined; } const findResultDecoration = terminal.registerDecoration({ @@ -728,10 +727,10 @@ export class SearchAddon implements ITerminalAddon { x: result.col, width: result.size, overviewRulerOptions: this._resultDecorations?.get(marker.line) ? undefined : { - color: decorations.matchOverviewRuler, position: 'center' + color: options.matchOverviewRuler, position: 'center' } }); - findResultDecoration?.onRender((e) => this._applyStyles(e, decorations.matchBackground, decorations.matchBorder)); + findResultDecoration?.onRender((e) => this._applyStyles(e, options.matchBackground, options.matchBorder)); findResultDecoration?.onDispose(() => marker.dispose()); return findResultDecoration; } From b7c0332626f4667aba7705f8d7e867820f4195a0 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 9 May 2022 10:49:58 -0700 Subject: [PATCH 209/245] Only create decoration elements when they are actually rendered Part of microsoft/vscode#145751 --- .../Decorations/BufferDecorationRenderer.ts | 28 ++++++++++--------- src/common/services/DecorationService.ts | 2 ++ 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/src/browser/Decorations/BufferDecorationRenderer.ts b/src/browser/Decorations/BufferDecorationRenderer.ts index 22dc73e9..dbe30187 100644 --- a/src/browser/Decorations/BufferDecorationRenderer.ts +++ b/src/browser/Decorations/BufferDecorationRenderer.ts @@ -60,17 +60,7 @@ export class BufferDecorationRenderer extends Disposable { } private _renderDecoration(decoration: IInternalDecoration): void { - let element = this._decorationElements.get(decoration); - if (!element) { - element = this._createElement(decoration); - decoration.onDispose(() => this._removeDecoration(decoration)); - decoration.marker.onDispose(() => decoration.dispose()); - decoration.element = element; - this._decorationElements.set(decoration, element); - this._container.appendChild(element); - } - this._refreshStyle(decoration, element); - decoration.onRenderEmitter.fire(element); + this._refreshStyle(decoration); } private _createElement(decoration: IInternalDecoration): HTMLElement { @@ -95,14 +85,26 @@ export class BufferDecorationRenderer extends Disposable { return element; } - private _refreshStyle(decoration: IInternalDecoration, element: HTMLElement): void { + private _refreshStyle(decoration: IInternalDecoration): void { const line = decoration.marker.line - this._bufferService.buffers.active.ydisp; if (line < 0 || line >= this._bufferService.rows) { // outside of viewport - element.style.display = 'none'; + if (decoration.element) { + decoration.element.style.display = 'none'; + decoration.onRenderEmitter.fire(decoration.element); + } } else { + let element = this._decorationElements.get(decoration); + if (!element) { + decoration.onDispose(() => this._removeDecoration(decoration)); + element = this._createElement(decoration); + decoration.element = element; + this._decorationElements.set(decoration, element); + this._container.appendChild(element); + } element.style.top = `${line * this._renderService.dimensions.actualCellHeight}px`; element.style.display = this._altBufferIsActive ? 'none' : 'block'; + decoration.onRenderEmitter.fire(element); } } diff --git a/src/common/services/DecorationService.ts b/src/common/services/DecorationService.ts index 03cfab4d..61936e15 100644 --- a/src/common/services/DecorationService.ts +++ b/src/common/services/DecorationService.ts @@ -30,6 +30,7 @@ export class DecorationService extends Disposable implements IDecorationService } const decoration = new Decoration(options); if (decoration) { + const markerDispose = decoration.marker.onDispose(() => decoration.dispose()); decoration.onDispose(() => { if (decoration) { const index = this._decorations.indexOf(decoration); @@ -37,6 +38,7 @@ export class DecorationService extends Disposable implements IDecorationService this._decorations.splice(this._decorations.indexOf(decoration), 1); this._onDecorationRemoved.fire(decoration); } + markerDispose.dispose(); } }); this._decorations.push(decoration); From 63eb9a45555d728bd320b6dd371c190e1c8fdef1 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 9 May 2022 12:44:51 -0700 Subject: [PATCH 210/245] Initial bg/fg renderer decoration proof of concept Part of #3770 --- demo/client.ts | 12 ++++- src/browser/renderer/dom/DomRenderer.ts | 11 +++-- .../dom/DomRendererRowFactory.test.ts | 5 ++- .../renderer/dom/DomRendererRowFactory.ts | 23 +++++++++- src/common/TestUtils.test.ts | 13 +++++- src/common/services/DecorationService.ts | 45 +++++++++++++++++++ src/common/services/Services.ts | 6 ++- typings/xterm.d.ts | 15 ++++++- 8 files changed, 115 insertions(+), 15 deletions(-) diff --git a/demo/client.ts b/demo/client.ts index 7c21956a..2590d4b0 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -556,8 +556,16 @@ function loadTest() { function addDecoration() { term.options['overviewRulerWidth'] = 15; const marker = term.addMarker(1); - const decoration = term.registerDecoration({ marker, overviewRulerOptions: { color: '#ef292980', position: 'left' } }); - decoration.onRender((e) => e.style.backgroundColor = '#ef292980'); + const decoration = term.registerDecoration({ + marker, + backgroundColor: '#00FF00', + foregroundColor: '#000000', + overviewRulerOptions: { color: '#ef292980', position: 'left' } + }); + decoration.onRender((e: HTMLElement) => { + e.style.right = '100%'; + e.style.backgroundColor = '#ef292980'; + }); } function addOverviewRuler() { diff --git a/src/browser/renderer/dom/DomRenderer.ts b/src/browser/renderer/dom/DomRenderer.ts index ee283399..540da240 100644 --- a/src/browser/renderer/dom/DomRenderer.ts +++ b/src/browser/renderer/dom/DomRenderer.ts @@ -9,7 +9,7 @@ import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants'; import { Disposable } from 'common/Lifecycle'; import { IColorSet, ILinkifierEvent, ILinkifier, ILinkifier2 } from 'browser/Types'; import { ICharSizeService } from 'browser/services/Services'; -import { IOptionsService, IBufferService, IInstantiationService } from 'common/services/Services'; +import { IOptionsService, IBufferService, IInstantiationService, IDecorationService } from 'common/services/Services'; import { EventEmitter, IEvent } from 'common/EventEmitter'; import { color } from 'browser/Color'; import { removeElementFromParent } from 'browser/Dom'; @@ -87,11 +87,11 @@ export class DomRenderer extends Disposable implements IRenderer { this._screenElement.appendChild(this._rowContainer); this._screenElement.appendChild(this._selectionContainer); - this._linkifier.onShowLinkUnderline(e => this._onLinkHover(e)); - this._linkifier.onHideLinkUnderline(e => this._onLinkLeave(e)); + this.register(this._linkifier.onShowLinkUnderline(e => this._onLinkHover(e))); + this.register(this._linkifier.onHideLinkUnderline(e => this._onLinkLeave(e))); - this._linkifier2.onShowLinkUnderline(e => this._onLinkHover(e)); - this._linkifier2.onHideLinkUnderline(e => this._onLinkLeave(e)); + this.register(this._linkifier2.onShowLinkUnderline(e => this._onLinkHover(e))); + this.register(this._linkifier2.onHideLinkUnderline(e => this._onLinkLeave(e))); } public dispose(): void { @@ -361,7 +361,6 @@ export class DomRenderer extends Disposable implements IRenderer { for (let y = start; y <= end; y++) { const rowElement = this._rowElements[y]; rowElement.innerText = ''; - const row = y + this._bufferService.buffer.ydisp; const lineData = this._bufferService.buffer.lines.get(row); const cursorStyle = this._optionsService.rawOptions.cursorStyle; diff --git a/src/browser/renderer/dom/DomRendererRowFactory.test.ts b/src/browser/renderer/dom/DomRendererRowFactory.test.ts index f41e5d44..61cd6f40 100644 --- a/src/browser/renderer/dom/DomRendererRowFactory.test.ts +++ b/src/browser/renderer/dom/DomRendererRowFactory.test.ts @@ -10,7 +10,7 @@ import { NULL_CELL_CODE, NULL_CELL_WIDTH, NULL_CELL_CHAR, DEFAULT_ATTR, FgFlags, import { BufferLine, DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine'; import { IBufferLine } from 'common/Types'; import { CellData } from 'common/buffer/CellData'; -import { MockCoreService, MockOptionsService } from 'common/TestUtils.test'; +import { MockCoreService, MockDecorationService, MockOptionsService } from 'common/TestUtils.test'; import { css } from 'browser/Color'; import { MockCharacterJoinerService } from 'browser/TestUtils.test'; @@ -49,7 +49,8 @@ describe('DomRendererRowFactory', () => { } as any, new MockCharacterJoinerService(), new MockOptionsService({ drawBoldTextInBrightColors: true }), - new MockCoreService() + new MockCoreService(), + new MockDecorationService() ); lineData = createEmptyLineData(2); }); diff --git a/src/browser/renderer/dom/DomRendererRowFactory.ts b/src/browser/renderer/dom/DomRendererRowFactory.ts index 9822ec36..025cfd33 100644 --- a/src/browser/renderer/dom/DomRendererRowFactory.ts +++ b/src/browser/renderer/dom/DomRendererRowFactory.ts @@ -7,7 +7,7 @@ import { IBufferLine, ICellData } from 'common/Types'; import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants'; import { NULL_CELL_CODE, WHITESPACE_CELL_CHAR, Attributes } from 'common/buffer/Constants'; import { CellData } from 'common/buffer/CellData'; -import { ICoreService, IOptionsService } from 'common/services/Services'; +import { ICoreService, IDecorationService, IOptionsService } from 'common/services/Services'; import { color, rgba } from 'browser/Color'; import { IColorSet, IColor } from 'browser/Types'; import { ICharacterJoinerService } from 'browser/services/Services'; @@ -33,7 +33,8 @@ export class DomRendererRowFactory { private _colors: IColorSet, @ICharacterJoinerService private readonly _characterJoinerService: ICharacterJoinerService, @IOptionsService private readonly _optionsService: IOptionsService, - @ICoreService private readonly _coreService: ICoreService + @ICoreService private readonly _coreService: ICoreService, + @IDecorationService private readonly _decorationService: IDecorationService ) { } @@ -172,6 +173,23 @@ export class DomRendererRowFactory { bgColorMode = temp2; } + // Apply any decoration foreground/background overrides + const decorations = this._decorationService.getDecorationsOnLine(row); + for (const d of decorations) { + const xmin = d.options.x ?? 0; + const xmax = xmin + (d.options.width ?? 1); + if (x >= xmin && x < xmax) { + if (d.backgroundColorRGB) { + bgColorMode = Attributes.CM_RGB; + bg = (d.backgroundColorRGB[0] << 16) | (d.backgroundColorRGB[1]) << 8 | d.backgroundColorRGB[2]; + } + if (d.foregroundColorRGB) { + fgColorMode = Attributes.CM_RGB; + fg = (d.foregroundColorRGB[0] << 16) | (d.foregroundColorRGB[1]) << 8 | d.foregroundColorRGB[2]; + } + } + } + // Foreground switch (fgColorMode) { case Attributes.CM_P16: @@ -179,6 +197,7 @@ export class DomRendererRowFactory { if (cell.isBold() && fg < 8 && this._optionsService.rawOptions.drawBoldTextInBrightColors) { fg += 8; } + // TODO: Pass in bg override if (!this._applyMinimumContrast(charElement, this._colors.background, this._colors.ansi[fg], cell)) { charElement.classList.add(`xterm-fg-${fg}`); } diff --git a/src/common/TestUtils.test.ts b/src/common/TestUtils.test.ts index 58f6c709..c937f70c 100644 --- a/src/common/TestUtils.test.ts +++ b/src/common/TestUtils.test.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { IBufferService, ICoreService, ILogService, IOptionsService, ITerminalOptions, IDirtyRowService, ICoreMouseService, ICharsetService, IUnicodeService, IUnicodeVersionProvider, LogLevelEnum } from 'common/services/Services'; +import { IBufferService, ICoreService, ILogService, IOptionsService, ITerminalOptions, IDirtyRowService, ICoreMouseService, ICharsetService, IUnicodeService, IUnicodeVersionProvider, LogLevelEnum, IDecorationService, IInternalDecoration } from 'common/services/Services'; import { IEvent, EventEmitter } from 'common/EventEmitter'; import { clone } from 'common/Clone'; import { DEFAULT_OPTIONS } from 'common/services/OptionsService'; @@ -11,6 +11,7 @@ import { IBufferSet, IBuffer } from 'common/buffer/Types'; import { BufferSet } from 'common/buffer/BufferSet'; import { IDecPrivateModes, ICoreMouseEvent, CoreMouseEventType, ICharset, IModes, IAttributeData } from 'common/Types'; import { UnicodeV6 } from 'common/input/UnicodeV6'; +import { IDecorationOptions, IDecoration } from 'xterm'; export class MockBufferService implements IBufferService { public serviceBrand: any; @@ -158,3 +159,13 @@ export class MockUnicodeService implements IUnicodeService { throw new Error('Method not implemented.'); } } + +export class MockDecorationService implements IDecorationService { + public serviceBrand: any; + public get decorations(): IterableIterator { return [].values(); }; + public onDecorationRegistered = new EventEmitter().event; + public onDecorationRemoved = new EventEmitter().event; + public registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined { return undefined; } + public *getDecorationsOnLine(line: number): IterableIterator { } + public dispose(): void { } +} diff --git a/src/common/services/DecorationService.ts b/src/common/services/DecorationService.ts index 03cfab4d..16cd2421 100644 --- a/src/common/services/DecorationService.ts +++ b/src/common/services/DecorationService.ts @@ -6,6 +6,7 @@ import { EventEmitter } from 'common/EventEmitter'; import { Disposable } from 'common/Lifecycle'; import { IDecorationService, IInternalDecoration } from 'common/services/Services'; +import { IColorRGB } from 'common/Types'; import { IDecorationOptions, IDecoration, IMarker, IEvent } from 'xterm'; export class DecorationService extends Disposable implements IDecorationService { @@ -45,6 +46,15 @@ export class DecorationService extends Disposable implements IDecorationService return decoration; } + public *getDecorationsOnLine(line: number): IterableIterator { + // TODO: This could be made much faster if _decorations was sorted by line (and col?) + for (const d of this.decorations) { + if (d.marker.line === line) { + yield d; + } + } + } + public dispose(): void { for (const decoration of this._decorations) { this._onDecorationRemoved.fire(decoration); @@ -64,6 +74,32 @@ class Decoration extends Disposable implements IInternalDecoration { private _onDispose = this.register(new EventEmitter()); public readonly onDispose = this._onDispose.event; + // TODO: React to changes on options + private _cachedBg: IColorRGB | undefined | null = null; + public get backgroundColorRGB(): IColorRGB | undefined { + if (this._cachedBg === null) { + if (this.options.backgroundColor) { + this._cachedBg = toColorRGB(this.options.backgroundColor); + } else { + this._cachedBg = undefined; + } + } + return this._cachedBg; + } + + // TODO: React to changes on options + private _cachedFg: IColorRGB | undefined | null = null; + public get foregroundColorRGB(): IColorRGB | undefined { + if (this._cachedFg === null) { + if (this.options.foregroundColor) { + this._cachedFg = toColorRGB(this.options.foregroundColor); + } else { + this._cachedFg = undefined; + } + } + return this._cachedFg; + } + constructor( public readonly options: IDecorationOptions ) { @@ -73,6 +109,7 @@ class Decoration extends Disposable implements IInternalDecoration { this.options.overviewRulerOptions.position = 'full'; } } + public override dispose(): void { if (this._isDisposed) { return; @@ -82,3 +119,11 @@ class Decoration extends Disposable implements IInternalDecoration { super.dispose(); } } + +function toColorRGB(css: string): IColorRGB { + // #rrggbb + if (css.length === 7) { + return [parseInt(css.slice(1, 3), 16), parseInt(css.slice(3, 5), 16), parseInt(css.slice(5, 7), 16)]; + } + throw new Error('css.toColor: Unsupported css format'); +} diff --git a/src/common/services/Services.ts b/src/common/services/Services.ts index 876d90bc..4e306e63 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 } from 'common/Types'; +import { IDecPrivateModes, ICoreMouseEvent, CoreMouseEncoding, ICoreMouseProtocol, CoreMouseEventType, ICharset, IWindowOptions, IModes, IAttributeData, ScrollSource, IDisposable, IColorRGB } from 'common/Types'; import { createDecorator } from 'common/services/ServiceRegistry'; import { IDecorationOptions, IDecoration } from 'xterm'; @@ -308,8 +308,12 @@ export interface IDecorationService extends IDisposable { readonly onDecorationRegistered: IEvent; readonly onDecorationRemoved: IEvent; registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined; + /** Iterates over the decorations on a line (in no particular order). */ + getDecorationsOnLine(line: number): IterableIterator; } export interface IInternalDecoration extends IDecoration { readonly options: IDecorationOptions; + readonly backgroundColorRGB: IColorRGB | undefined; + readonly foregroundColorRGB: IColorRGB | undefined; readonly onRenderEmitter: IEventEmitter; } diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 76c228b2..fe1bb979 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -444,7 +444,8 @@ declare module 'xterm' { * This will only take effect when {@link IDecorationOptions.overviewRulerOptions} * were provided initially. */ - options: Pick; + options: Pick; + // options: Pick; } @@ -488,6 +489,18 @@ declare module 'xterm' { */ readonly height?: number; + /** + * The background color of the cell(s). When 2 decorations both set the foreground color the + * last registered decoration will be used. Only the `#RRGGBB` format is supported. + */ + backgroundColor?: string; + + /** + * The foreground color of the cell(s). When 2 decorations both set the foreground color the + * last registered decoration will be used. Only the `#RRGGBB` format is supported. + */ + foregroundColor?: string; + /** * When defined, renders the decoration in the overview ruler to the right * of the terminal. {@link ITerminalOptions.overviewRulerWidth} must be set From 0c6877f46f3bcb4584dd5fadc72bcda671bbe85e Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 9 May 2022 14:00:39 -0700 Subject: [PATCH 211/245] Move color to common, work with IColor IColor is needed for the minimum contrast ratio function currently. --- addons/xterm-addon-webgl/src/GlyphRenderer.ts | 3 ++- .../src/RectangleRenderer.ts | 3 ++- .../src/atlas/CharAtlasUtils.ts | 3 ++- .../src/atlas/WebglCharAtlas.ts | 4 ++-- src/browser/ColorContrastCache.ts | 3 ++- src/browser/ColorManager.ts | 6 ++--- src/browser/Terminal.ts | 2 +- src/browser/Types.d.ts | 8 +------ src/browser/renderer/BaseRenderLayer.ts | 6 ++--- .../renderer/atlas/DynamicCharAtlas.ts | 4 ++-- src/browser/renderer/dom/DomRenderer.ts | 2 +- .../dom/DomRendererRowFactory.test.ts | 2 +- .../renderer/dom/DomRendererRowFactory.ts | 10 ++++---- src/{browser => common}/Color.test.ts | 2 +- src/{browser => common}/Color.ts | 3 +-- src/common/TestUtils.test.ts | 2 +- src/common/Types.d.ts | 5 ++++ src/common/services/DecorationService.ts | 23 +++++++------------ src/common/services/Services.ts | 6 ++--- 19 files changed, 46 insertions(+), 51 deletions(-) rename src/{browser => common}/Color.test.ts (99%) rename src/{browser => common}/Color.ts (98%) diff --git a/addons/xterm-addon-webgl/src/GlyphRenderer.ts b/addons/xterm-addon-webgl/src/GlyphRenderer.ts index e2c37be2..6b9faf88 100644 --- a/addons/xterm-addon-webgl/src/GlyphRenderer.ts +++ b/addons/xterm-addon-webgl/src/GlyphRenderer.ts @@ -11,7 +11,8 @@ import { fill } from 'common/TypedArrayUtils'; import { slice } from './TypedArray'; import { NULL_CELL_CODE, WHITESPACE_CELL_CODE, Attributes, FgFlags } from 'common/buffer/Constants'; import { Terminal, IBufferLine } from 'xterm'; -import { IColorSet, IColor } from 'browser/Types'; +import { IColor } from 'common/Types'; +import { IColorSet } from 'browser/Types'; import { IRenderDimensions } from 'browser/renderer/Types'; import { AttributeData } from 'common/buffer/AttributeData'; diff --git a/addons/xterm-addon-webgl/src/RectangleRenderer.ts b/addons/xterm-addon-webgl/src/RectangleRenderer.ts index c96cc6bc..ab0b34e9 100644 --- a/addons/xterm-addon-webgl/src/RectangleRenderer.ts +++ b/addons/xterm-addon-webgl/src/RectangleRenderer.ts @@ -8,7 +8,8 @@ import { IRenderModel, IWebGLVertexArrayObject, IWebGL2RenderingContext, ISelect import { fill } from 'common/TypedArrayUtils'; import { Attributes, FgFlags } from 'common/buffer/Constants'; import { Terminal } from 'xterm'; -import { IColorSet, IColor } from 'browser/Types'; +import { IColor } from 'common/Types'; +import { IColorSet } from 'browser/Types'; import { IRenderDimensions } from 'browser/renderer/Types'; import { RENDER_MODEL_BG_OFFSET, RENDER_MODEL_FG_OFFSET, RENDER_MODEL_INDICIES_PER_CELL } from './RenderModel'; diff --git a/addons/xterm-addon-webgl/src/atlas/CharAtlasUtils.ts b/addons/xterm-addon-webgl/src/atlas/CharAtlasUtils.ts index 4705796a..0ce893df 100644 --- a/addons/xterm-addon-webgl/src/atlas/CharAtlasUtils.ts +++ b/addons/xterm-addon-webgl/src/atlas/CharAtlasUtils.ts @@ -6,7 +6,8 @@ import { ICharAtlasConfig } from './Types'; import { Attributes } from 'common/buffer/Constants'; import { Terminal, FontWeight } from 'xterm'; -import { IColorSet, IColor } from 'browser/Types'; +import { IColorSet } from 'browser/Types'; +import { IColor } from 'common/Types'; const NULL_COLOR: IColor = { css: '', diff --git a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts index 3194d397..34107fc5 100644 --- a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts +++ b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts @@ -8,10 +8,10 @@ import { DIM_OPACITY, TEXT_BASELINE } from 'browser/renderer/atlas/Constants'; import { IRasterizedGlyph, IBoundingBox, IRasterizedGlyphSet } from '../Types'; import { DEFAULT_COLOR, Attributes } from 'common/buffer/Constants'; import { throwIfFalsy } from '../WebglUtils'; -import { IColor } from 'browser/Types'; +import { IColor } from 'common/Types'; import { IDisposable } from 'xterm'; import { AttributeData } from 'common/buffer/AttributeData'; -import { channels, rgba } from 'browser/Color'; +import { channels, rgba } from 'common/Color'; import { tryDrawCustomChar } from 'browser/renderer/CustomGlyphs'; import { isPowerlineGlyph } from 'browser/renderer/RendererUtils'; diff --git a/src/browser/ColorContrastCache.ts b/src/browser/ColorContrastCache.ts index b96b66cc..73b7a0b7 100644 --- a/src/browser/ColorContrastCache.ts +++ b/src/browser/ColorContrastCache.ts @@ -3,7 +3,8 @@ * @license MIT */ -import { IColor, IColorContrastCache } from 'browser/Types'; +import { IColorContrastCache } from 'browser/Types'; +import { IColor } from 'common/Types'; export class ColorContrastCache implements IColorContrastCache { private _color: { [bg: number]: { [fg: number]: IColor | null | undefined } | undefined } = {}; diff --git a/src/browser/ColorManager.ts b/src/browser/ColorManager.ts index e7ac10ba..2d6e4ea5 100644 --- a/src/browser/ColorManager.ts +++ b/src/browser/ColorManager.ts @@ -3,11 +3,11 @@ * @license MIT */ -import { IColorManager, IColor, IColorSet, IColorContrastCache } from 'browser/Types'; +import { IColorManager, IColorSet, IColorContrastCache } from 'browser/Types'; import { ITheme } from 'common/services/Services'; -import { channels, color, css } from 'browser/Color'; +import { channels, color, css } from 'common/Color'; import { ColorContrastCache } from 'browser/ColorContrastCache'; -import { ColorIndex } from 'common/Types'; +import { ColorIndex, IColor } from 'common/Types'; interface IRestoreColorSet { diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index f08d8581..491a209e 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -52,7 +52,7 @@ import { MouseService } from 'browser/services/MouseService'; import { Linkifier2 } from 'browser/Linkifier2'; import { CoreBrowserService } from 'browser/services/CoreBrowserService'; import { CoreTerminal } from 'common/CoreTerminal'; -import { color, rgba } from 'browser/Color'; +import { color, rgba } from 'common/Color'; import { CharacterJoinerService } from 'browser/services/CharacterJoinerService'; import { toRgbString } from 'common/input/XParseColor'; import { BufferDecorationRenderer } from 'browser/Decorations/BufferDecorationRenderer'; diff --git a/src/browser/Types.d.ts b/src/browser/Types.d.ts index 8860bb41..0e83c213 100644 --- a/src/browser/Types.d.ts +++ b/src/browser/Types.d.ts @@ -5,11 +5,10 @@ import { IDecorationOptions, IDecoration, IDisposable, IMarker, ISelectionPosition } from 'xterm'; import { IEvent } from 'common/EventEmitter'; -import { ICoreTerminal, CharData, ITerminalOptions } from 'common/Types'; +import { ICoreTerminal, CharData, ITerminalOptions, IColor } from 'common/Types'; import { IMouseService, IRenderService } from './services/Services'; import { IBuffer } from 'common/buffer/Types'; import { IFunctionIdentifier, IParams } from 'common/parser/Types'; -import { createDecorator } from 'common/services/ServiceRegistry'; export interface ITerminal extends IPublicTerminal, ICoreTerminal { element: HTMLElement | undefined; @@ -113,11 +112,6 @@ export interface IColorManager { onOptionsChange(key: string): void; } -export interface IColor { - css: string; - rgba: number; // 32-bit int with rgba in each byte -} - export interface IColorSet { foreground: IColor; background: IColor; diff --git a/src/browser/renderer/BaseRenderLayer.ts b/src/browser/renderer/BaseRenderLayer.ts index 90d4f82f..67edcb02 100644 --- a/src/browser/renderer/BaseRenderLayer.ts +++ b/src/browser/renderer/BaseRenderLayer.ts @@ -4,18 +4,18 @@ */ import { IRenderDimensions, IRenderLayer } from 'browser/renderer/Types'; -import { ICellData } from 'common/Types'; +import { ICellData, IColor } from 'common/Types'; import { DEFAULT_COLOR, WHITESPACE_CELL_CHAR, WHITESPACE_CELL_CODE, Attributes } from 'common/buffer/Constants'; import { IGlyphIdentifier } from 'browser/renderer/atlas/Types'; import { DIM_OPACITY, INVERTED_DEFAULT_COLOR, TEXT_BASELINE } from 'browser/renderer/atlas/Constants'; import { BaseCharAtlas } from 'browser/renderer/atlas/BaseCharAtlas'; import { acquireCharAtlas } from 'browser/renderer/atlas/CharAtlasCache'; import { AttributeData } from 'common/buffer/AttributeData'; -import { IColorSet, IColor } from 'browser/Types'; +import { IColorSet } from 'browser/Types'; import { CellData } from 'common/buffer/CellData'; import { IBufferService, IOptionsService } from 'common/services/Services'; import { isPowerlineGlyph, throwIfFalsy } from 'browser/renderer/RendererUtils'; -import { channels, color, rgba } from 'browser/Color'; +import { channels, color, rgba } from 'common/Color'; import { removeElementFromParent } from 'browser/Dom'; import { tryDrawCustomChar } from 'browser/renderer/CustomGlyphs'; diff --git a/src/browser/renderer/atlas/DynamicCharAtlas.ts b/src/browser/renderer/atlas/DynamicCharAtlas.ts index 118dbcd2..88194615 100644 --- a/src/browser/renderer/atlas/DynamicCharAtlas.ts +++ b/src/browser/renderer/atlas/DynamicCharAtlas.ts @@ -9,9 +9,9 @@ import { BaseCharAtlas } from 'browser/renderer/atlas/BaseCharAtlas'; import { DEFAULT_ANSI_COLORS } from 'browser/ColorManager'; import { LRUMap } from 'browser/renderer/atlas/LRUMap'; import { isFirefox, isSafari } from 'common/Platform'; -import { IColor } from 'browser/Types'; +import { IColor } from 'common/Types'; import { throwIfFalsy } from 'browser/renderer/RendererUtils'; -import { color } from 'browser/Color'; +import { color } from 'common/Color'; // In practice we're probably never going to exhaust a texture this large. For debugging purposes, // however, it can be useful to set this to a really tiny value, to verify that LRU eviction works. diff --git a/src/browser/renderer/dom/DomRenderer.ts b/src/browser/renderer/dom/DomRenderer.ts index 540da240..d15d7eac 100644 --- a/src/browser/renderer/dom/DomRenderer.ts +++ b/src/browser/renderer/dom/DomRenderer.ts @@ -11,7 +11,7 @@ import { IColorSet, ILinkifierEvent, ILinkifier, ILinkifier2 } from 'browser/Typ import { ICharSizeService } from 'browser/services/Services'; import { IOptionsService, IBufferService, IInstantiationService, IDecorationService } from 'common/services/Services'; import { EventEmitter, IEvent } from 'common/EventEmitter'; -import { color } from 'browser/Color'; +import { color } from 'common/Color'; import { removeElementFromParent } from 'browser/Dom'; const TERMINAL_CLASS_PREFIX = 'xterm-dom-renderer-owner-'; diff --git a/src/browser/renderer/dom/DomRendererRowFactory.test.ts b/src/browser/renderer/dom/DomRendererRowFactory.test.ts index 61cd6f40..bb511a47 100644 --- a/src/browser/renderer/dom/DomRendererRowFactory.test.ts +++ b/src/browser/renderer/dom/DomRendererRowFactory.test.ts @@ -11,7 +11,7 @@ import { BufferLine, DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine'; import { IBufferLine } from 'common/Types'; import { CellData } from 'common/buffer/CellData'; import { MockCoreService, MockDecorationService, MockOptionsService } from 'common/TestUtils.test'; -import { css } from 'browser/Color'; +import { css } from 'common/Color'; import { MockCharacterJoinerService } from 'browser/TestUtils.test'; describe('DomRendererRowFactory', () => { diff --git a/src/browser/renderer/dom/DomRendererRowFactory.ts b/src/browser/renderer/dom/DomRendererRowFactory.ts index 025cfd33..300a8fb9 100644 --- a/src/browser/renderer/dom/DomRendererRowFactory.ts +++ b/src/browser/renderer/dom/DomRendererRowFactory.ts @@ -3,13 +3,13 @@ * @license MIT */ -import { IBufferLine, ICellData } from 'common/Types'; +import { IBufferLine, ICellData, IColor } from 'common/Types'; import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants'; import { NULL_CELL_CODE, WHITESPACE_CELL_CHAR, Attributes } from 'common/buffer/Constants'; import { CellData } from 'common/buffer/CellData'; import { ICoreService, IDecorationService, IOptionsService } from 'common/services/Services'; -import { color, rgba } from 'browser/Color'; -import { IColorSet, IColor } from 'browser/Types'; +import { color, rgba } from 'common/Color'; +import { IColorSet } from 'browser/Types'; import { ICharacterJoinerService } from 'browser/services/Services'; import { JoinedCellData } from 'browser/services/CharacterJoinerService'; import { isPowerlineGlyph } from 'browser/renderer/RendererUtils'; @@ -181,11 +181,11 @@ export class DomRendererRowFactory { if (x >= xmin && x < xmax) { if (d.backgroundColorRGB) { bgColorMode = Attributes.CM_RGB; - bg = (d.backgroundColorRGB[0] << 16) | (d.backgroundColorRGB[1]) << 8 | d.backgroundColorRGB[2]; + bg = d.backgroundColorRGB.rgba >> 8; } if (d.foregroundColorRGB) { fgColorMode = Attributes.CM_RGB; - fg = (d.foregroundColorRGB[0] << 16) | (d.foregroundColorRGB[1]) << 8 | d.foregroundColorRGB[2]; + fg = d.foregroundColorRGB.rgba >> 8; } } } diff --git a/src/browser/Color.test.ts b/src/common/Color.test.ts similarity index 99% rename from src/browser/Color.test.ts rename to src/common/Color.test.ts index 0d410930..f16e6ffb 100644 --- a/src/browser/Color.test.ts +++ b/src/common/Color.test.ts @@ -4,7 +4,7 @@ */ import { assert } from 'chai'; -import { channels, color, css, rgb, rgba, toPaddedHex, contrastRatio } from 'browser/Color'; +import { channels, color, css, rgb, rgba, toPaddedHex, contrastRatio } from 'common/Color'; describe('Color', () => { diff --git a/src/browser/Color.ts b/src/common/Color.ts similarity index 98% rename from src/browser/Color.ts rename to src/common/Color.ts index 32e311db..1d00b730 100644 --- a/src/browser/Color.ts +++ b/src/common/Color.ts @@ -3,8 +3,7 @@ * @license MIT */ -import { IColor } from 'browser/Types'; -import { IColorRGB } from 'common/Types'; +import { IColor, IColorRGB } from 'common/Types'; /** * Helper functions where the source type is "channels" (individual color channels as numbers). diff --git a/src/common/TestUtils.test.ts b/src/common/TestUtils.test.ts index c937f70c..1ec5a05a 100644 --- a/src/common/TestUtils.test.ts +++ b/src/common/TestUtils.test.ts @@ -162,7 +162,7 @@ export class MockUnicodeService implements IUnicodeService { export class MockDecorationService implements IDecorationService { public serviceBrand: any; - public get decorations(): IterableIterator { return [].values(); }; + public get decorations(): IterableIterator { return [].values(); } public onDecorationRegistered = new EventEmitter().event; public onDecorationRemoved = new EventEmitter().event; public registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined { return undefined; } diff --git a/src/common/Types.d.ts b/src/common/Types.d.ts index fee426e1..c48b23ea 100644 --- a/src/common/Types.d.ts +++ b/src/common/Types.d.ts @@ -102,6 +102,11 @@ export interface ICharset { } export type CharData = [number, string, number, number]; + +export interface IColor { + css: string; + rgba: number; // 32-bit int with rgba in each byte +} export type IColorRGB = [number, number, number]; export interface IExtendedAttrs { diff --git a/src/common/services/DecorationService.ts b/src/common/services/DecorationService.ts index 16cd2421..d251dc35 100644 --- a/src/common/services/DecorationService.ts +++ b/src/common/services/DecorationService.ts @@ -3,10 +3,11 @@ * @license MIT */ +import { css } from 'common/Color'; import { EventEmitter } from 'common/EventEmitter'; import { Disposable } from 'common/Lifecycle'; import { IDecorationService, IInternalDecoration } from 'common/services/Services'; -import { IColorRGB } from 'common/Types'; +import { IColor } from 'common/Types'; import { IDecorationOptions, IDecoration, IMarker, IEvent } from 'xterm'; export class DecorationService extends Disposable implements IDecorationService { @@ -75,11 +76,11 @@ class Decoration extends Disposable implements IInternalDecoration { public readonly onDispose = this._onDispose.event; // TODO: React to changes on options - private _cachedBg: IColorRGB | undefined | null = null; - public get backgroundColorRGB(): IColorRGB | undefined { + private _cachedBg: IColor | undefined | null = null; + public get backgroundColorRGB(): IColor | undefined { if (this._cachedBg === null) { if (this.options.backgroundColor) { - this._cachedBg = toColorRGB(this.options.backgroundColor); + this._cachedBg = css.toColor(this.options.backgroundColor); } else { this._cachedBg = undefined; } @@ -88,11 +89,11 @@ class Decoration extends Disposable implements IInternalDecoration { } // TODO: React to changes on options - private _cachedFg: IColorRGB | undefined | null = null; - public get foregroundColorRGB(): IColorRGB | undefined { + private _cachedFg: IColor | undefined | null = null; + public get foregroundColorRGB(): IColor | undefined { if (this._cachedFg === null) { if (this.options.foregroundColor) { - this._cachedFg = toColorRGB(this.options.foregroundColor); + this._cachedFg = css.toColor(this.options.foregroundColor); } else { this._cachedFg = undefined; } @@ -119,11 +120,3 @@ class Decoration extends Disposable implements IInternalDecoration { super.dispose(); } } - -function toColorRGB(css: string): IColorRGB { - // #rrggbb - if (css.length === 7) { - return [parseInt(css.slice(1, 3), 16), parseInt(css.slice(3, 5), 16), parseInt(css.slice(5, 7), 16)]; - } - throw new Error('css.toColor: Unsupported css format'); -} diff --git a/src/common/services/Services.ts b/src/common/services/Services.ts index 4e306e63..e086ff56 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, IColorRGB } from 'common/Types'; +import { IDecPrivateModes, ICoreMouseEvent, CoreMouseEncoding, ICoreMouseProtocol, CoreMouseEventType, ICharset, IWindowOptions, IModes, IAttributeData, ScrollSource, IDisposable, IColorRGB, IColor } from 'common/Types'; import { createDecorator } from 'common/services/ServiceRegistry'; import { IDecorationOptions, IDecoration } from 'xterm'; @@ -313,7 +313,7 @@ export interface IDecorationService extends IDisposable { } export interface IInternalDecoration extends IDecoration { readonly options: IDecorationOptions; - readonly backgroundColorRGB: IColorRGB | undefined; - readonly foregroundColorRGB: IColorRGB | undefined; + readonly backgroundColorRGB: IColor | undefined; + readonly foregroundColorRGB: IColor | undefined; readonly onRenderEmitter: IEventEmitter; } From 10598462d00e36d7b5e7121ad86ca334b5e839ea Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 9 May 2022 15:36:54 -0700 Subject: [PATCH 212/245] Fix color channel used by luminance functions --- src/common/Color.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/common/Color.ts b/src/common/Color.ts index 1d00b730..b197cd66 100644 --- a/src/common/Color.ts +++ b/src/common/Color.ts @@ -172,13 +172,13 @@ export namespace rgba { let fgR = (fgRgba >> 24) & 0xFF; let fgG = (fgRgba >> 16) & 0xFF; let fgB = (fgRgba >> 8) & 0xFF; - let cr = contrastRatio(rgb.relativeLuminance2(fgR, fgB, fgG), rgb.relativeLuminance2(bgR, bgG, bgB)); + let cr = contrastRatio(rgb.relativeLuminance2(fgR, fgG, fgB), rgb.relativeLuminance2(bgR, bgG, bgB)); while (cr < ratio && (fgR > 0 || fgG > 0 || fgB > 0)) { // Reduce by 10% until the ratio is hit fgR -= Math.max(0, Math.ceil(fgR * 0.1)); fgG -= Math.max(0, Math.ceil(fgG * 0.1)); fgB -= Math.max(0, Math.ceil(fgB * 0.1)); - cr = contrastRatio(rgb.relativeLuminance2(fgR, fgB, fgG), rgb.relativeLuminance2(bgR, bgG, bgB)); + cr = contrastRatio(rgb.relativeLuminance2(fgR, fgG, fgB), rgb.relativeLuminance2(bgR, bgG, bgB)); } return (fgR << 24 | fgG << 16 | fgB << 8 | 0xFF) >>> 0; } @@ -192,13 +192,13 @@ export namespace rgba { let fgR = (fgRgba >> 24) & 0xFF; let fgG = (fgRgba >> 16) & 0xFF; let fgB = (fgRgba >> 8) & 0xFF; - let cr = contrastRatio(rgb.relativeLuminance2(fgR, fgB, fgG), rgb.relativeLuminance2(bgR, bgG, bgB)); + let cr = contrastRatio(rgb.relativeLuminance2(fgR, fgG, fgB), rgb.relativeLuminance2(bgR, bgG, bgB)); while (cr < ratio && (fgR < 0xFF || fgG < 0xFF || fgB < 0xFF)) { // Increase by 10% until the ratio is hit fgR = Math.min(0xFF, fgR + Math.ceil((255 - fgR) * 0.1)); fgG = Math.min(0xFF, fgG + Math.ceil((255 - fgG) * 0.1)); fgB = Math.min(0xFF, fgB + Math.ceil((255 - fgB) * 0.1)); - cr = contrastRatio(rgb.relativeLuminance2(fgR, fgB, fgG), rgb.relativeLuminance2(bgR, bgG, bgB)); + cr = contrastRatio(rgb.relativeLuminance2(fgR, fgG, fgB), rgb.relativeLuminance2(bgR, bgG, bgB)); } return (fgR << 24 | fgG << 16 | fgB << 8 | 0xFF) >>> 0; } From 13abd2aa96169dbcfcbda0450372ffa4b85d5703 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 9 May 2022 15:39:02 -0700 Subject: [PATCH 213/245] Support min contrast ratio in decoration fg/bg --- .../renderer/dom/DomRendererRowFactory.ts | 27 ++++++++++++------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/src/browser/renderer/dom/DomRendererRowFactory.ts b/src/browser/renderer/dom/DomRendererRowFactory.ts index 300a8fb9..7a20db5a 100644 --- a/src/browser/renderer/dom/DomRendererRowFactory.ts +++ b/src/browser/renderer/dom/DomRendererRowFactory.ts @@ -175,6 +175,8 @@ export class DomRendererRowFactory { // Apply any decoration foreground/background overrides const decorations = this._decorationService.getDecorationsOnLine(row); + let bgOverride: IColor | undefined; + let fgOverride: IColor | undefined; for (const d of decorations) { const xmin = d.options.x ?? 0; const xmax = xmin + (d.options.width ?? 1); @@ -182,10 +184,12 @@ export class DomRendererRowFactory { if (d.backgroundColorRGB) { bgColorMode = Attributes.CM_RGB; bg = d.backgroundColorRGB.rgba >> 8; + bgOverride = d.backgroundColorRGB; } if (d.foregroundColorRGB) { fgColorMode = Attributes.CM_RGB; fg = d.foregroundColorRGB.rgba >> 8; + fgOverride = d.foregroundColorRGB; } } } @@ -198,7 +202,7 @@ export class DomRendererRowFactory { fg += 8; } // TODO: Pass in bg override - if (!this._applyMinimumContrast(charElement, this._colors.background, this._colors.ansi[fg], cell)) { + if (!this._applyMinimumContrast(charElement, this._colors.background, this._colors.ansi[fg], cell, undefined, undefined)) { charElement.classList.add(`xterm-fg-${fg}`); } break; @@ -208,13 +212,13 @@ export class DomRendererRowFactory { (fg >> 8) & 0xFF, (fg ) & 0xFF ); - if (!this._applyMinimumContrast(charElement, this._colors.background, color, cell)) { + if (!this._applyMinimumContrast(charElement, this._colors.background, color, cell, bgOverride, fgOverride)) { this._addStyle(charElement, `color:#${padStart(fg.toString(16), '0', 6)}`); } break; case Attributes.CM_DEFAULT: default: - if (!this._applyMinimumContrast(charElement, this._colors.background, this._colors.foreground, cell)) { + if (!this._applyMinimumContrast(charElement, this._colors.background, this._colors.foreground, cell, undefined, undefined)) { if (isInverse) { charElement.classList.add(`xterm-fg-${INVERTED_DEFAULT_COLOR}`); } @@ -228,7 +232,7 @@ export class DomRendererRowFactory { charElement.classList.add(`xterm-bg-${bg}`); break; case Attributes.CM_RGB: - this._addStyle(charElement, `background-color:#${padStart(bg.toString(16), '0', 6)}`); + this._addStyle(charElement, `background-color:#${padStart((bg >>> 0).toString(16), '0', 6)}`); break; case Attributes.CM_DEFAULT: default: @@ -244,18 +248,23 @@ export class DomRendererRowFactory { return fragment; } - private _applyMinimumContrast(element: HTMLElement, bg: IColor, fg: IColor, cell: ICellData): boolean { + private _applyMinimumContrast(element: HTMLElement, bg: IColor, fg: IColor, cell: ICellData, bgOverride: IColor | undefined, fgOverride: IColor | undefined): boolean { if (this._optionsService.rawOptions.minimumContrastRatio === 1 || isPowerlineGlyph(cell.getCode())) { return false; } - // Try get from cache first - let adjustedColor = this._colors.contrastCache.getColor(this._workCell.bg, this._workCell.fg); + // Try get from cache first, only use the cache when there are no decoration overrides + let adjustedColor: IColor | undefined | null = undefined; + if (!bgOverride || !fgOverride) { + adjustedColor = this._colors.contrastCache.getColor(this._workCell.bg, this._workCell.fg); + } // Calculate and store in cache if (adjustedColor === undefined) { - adjustedColor = color.ensureContrastRatio(bg, fg, this._optionsService.rawOptions.minimumContrastRatio); - this._colors.contrastCache.setColor(this._workCell.bg, this._workCell.fg, adjustedColor ?? null); + adjustedColor = color.ensureContrastRatio(bgOverride || bg, fgOverride || fg, this._optionsService.rawOptions.minimumContrastRatio); + if (!bgOverride || !fgOverride) { + this._colors.contrastCache.setColor(this._workCell.bg, this._workCell.fg, adjustedColor ?? null); + } } if (adjustedColor) { From 56c7c5c4eda4ef55005b0370d49087b69c8bb2c6 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Mon, 9 May 2022 15:55:33 -0700 Subject: [PATCH 214/245] check if undefined instead of index directly to fix search addon issue (#3767) --- addons/xterm-addon-search/src/SearchAddon.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/addons/xterm-addon-search/src/SearchAddon.ts b/addons/xterm-addon-search/src/SearchAddon.ts index a56b3ab3..c92c6c8a 100644 --- a/addons/xterm-addon-search/src/SearchAddon.ts +++ b/addons/xterm-addon-search/src/SearchAddon.ts @@ -128,7 +128,7 @@ export class SearchAddon implements ITerminalAddon { } this._lastSearchOptions = searchOptions; if (searchOptions?.decorations) { - if (this._resultIndex || this._cachedSearchTerm && term !== this._cachedSearchTerm) { + if (this._resultIndex !== undefined || this._cachedSearchTerm && term !== this._cachedSearchTerm) { this._highlightAllMatches(term, searchOptions); } } @@ -306,7 +306,7 @@ export class SearchAddon implements ITerminalAddon { throw new Error('Cannot use addon until it has been loaded'); } this._lastSearchOptions = searchOptions; - if (searchOptions?.decorations && (this._resultIndex || term !== this._cachedSearchTerm)) { + if (searchOptions?.decorations && (this._resultIndex !== undefined || term !== this._cachedSearchTerm)) { this._highlightAllMatches(term, searchOptions); } return this._fireResults(term, this._findPreviousAndSelect(term, searchOptions), searchOptions); From 9f1db12b1bd6f066812671434c2ac6005f7bdea1 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 9 May 2022 16:19:58 -0700 Subject: [PATCH 215/245] bg/fg decorations mostly working in canvas renderer --- demo/client.ts | 2 +- src/browser/renderer/BaseRenderLayer.ts | 56 ++++++++++++++----- src/browser/renderer/CursorRenderLayer.ts | 7 ++- src/browser/renderer/LinkRenderLayer.ts | 7 ++- src/browser/renderer/SelectionRenderLayer.ts | 7 ++- src/browser/renderer/TextRenderLayer.ts | 20 ++++++- .../renderer/atlas/DynamicCharAtlas.ts | 2 +- .../renderer/dom/DomRendererRowFactory.ts | 3 +- 8 files changed, 75 insertions(+), 29 deletions(-) diff --git a/demo/client.ts b/demo/client.ts index 2590d4b0..09359905 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -559,7 +559,7 @@ function addDecoration() { const decoration = term.registerDecoration({ marker, backgroundColor: '#00FF00', - foregroundColor: '#000000', + foregroundColor: '#00FE00', overviewRulerOptions: { color: '#ef292980', position: 'left' } }); decoration.onRender((e: HTMLElement) => { diff --git a/src/browser/renderer/BaseRenderLayer.ts b/src/browser/renderer/BaseRenderLayer.ts index 67edcb02..b815c672 100644 --- a/src/browser/renderer/BaseRenderLayer.ts +++ b/src/browser/renderer/BaseRenderLayer.ts @@ -13,7 +13,7 @@ import { acquireCharAtlas } from 'browser/renderer/atlas/CharAtlasCache'; import { AttributeData } from 'common/buffer/AttributeData'; import { IColorSet } from 'browser/Types'; import { CellData } from 'common/buffer/CellData'; -import { IBufferService, IOptionsService } from 'common/services/Services'; +import { IBufferService, IDecorationService, IOptionsService } from 'common/services/Services'; import { isPowerlineGlyph, throwIfFalsy } from 'browser/renderer/RendererUtils'; import { channels, color, rgba } from 'common/Color'; import { removeElementFromParent } from 'browser/Dom'; @@ -52,7 +52,8 @@ export abstract class BaseRenderLayer implements IRenderLayer { protected _colors: IColorSet, private _rendererId: number, protected readonly _bufferService: IBufferService, - protected readonly _optionsService: IOptionsService + protected readonly _optionsService: IOptionsService, + protected readonly _decorationService: IDecorationService ) { this._canvas = document.createElement('canvas'); this._canvas.classList.add(`xterm-${id}-layer`); @@ -294,7 +295,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { * @param bold Whether the text is bold. */ protected _drawChars(cell: ICellData, x: number, y: number): void { - const contrastColor = this._getContrastColor(cell); + const contrastColor = this._getContrastColor(cell, x, y); // skip cache right away if we draw in RGB // Note: to avoid bad runtime JoinedCellData will be skipped @@ -427,15 +428,35 @@ export abstract class BaseRenderLayer implements IRenderLayer { return `${fontStyle} ${fontWeight} ${this._optionsService.rawOptions.fontSize * window.devicePixelRatio}px ${this._optionsService.rawOptions.fontFamily}`; } - private _getContrastColor(cell: CellData): IColor | undefined { - if (this._optionsService.rawOptions.minimumContrastRatio === 1 || isPowerlineGlyph(cell.getCode())) { + private _getContrastColor(cell: CellData, x: number, y: number): IColor | undefined { + // Get any decoration foreground/background overrides, this must be fetched before the early + // exist but applied after inverse + const decorations = this._decorationService.getDecorationsOnLine(y); + let bgOverride: number | undefined; + let fgOverride: number | undefined; + for (const d of decorations) { + const xmin = d.options.x ?? 0; + const xmax = xmin + (d.options.width ?? 1); + if (x >= xmin && x < xmax) { + if (d.backgroundColorRGB) { + bgOverride = d.backgroundColorRGB.rgba; + } + if (d.foregroundColorRGB) { + fgOverride = d.foregroundColorRGB.rgba; + } + } + } + + if (!bgOverride && !fgOverride && (this._optionsService.rawOptions.minimumContrastRatio === 1 || isPowerlineGlyph(cell.getCode()))) { return undefined; } - // Try get from cache first - const adjustedColor = this._colors.contrastCache.getColor(cell.bg, cell.fg); - if (adjustedColor !== undefined) { - return adjustedColor || undefined; + if (!bgOverride && !fgOverride) { + // Try get from cache + const adjustedColor = this._colors.contrastCache.getColor(cell.bg, cell.fg); + if (adjustedColor !== undefined) { + return adjustedColor || undefined; + } } let fgColor = cell.getFgColor(); @@ -453,13 +474,18 @@ export abstract class BaseRenderLayer implements IRenderLayer { bgColorMode = temp2; } - const bgRgba = this._resolveBackgroundRgba(bgColorMode, bgColor, isInverse); + const bgRgba = this._resolveBackgroundRgba(bgOverride !== undefined ? Attributes.CM_RGB : bgColorMode, bgOverride ?? bgColor, isInverse); const fgRgba = this._resolveForegroundRgba(fgColorMode, fgColor, isInverse, isBold); - const result = rgba.ensureContrastRatio(bgRgba, fgRgba, this._optionsService.rawOptions.minimumContrastRatio); + let result = rgba.ensureContrastRatio(bgOverride ?? bgRgba, fgOverride ?? fgRgba, this._optionsService.rawOptions.minimumContrastRatio); if (!result) { - this._colors.contrastCache.setColor(cell.bg, cell.fg, null); - return undefined; + if (!bgOverride && !fgOverride) { + this._colors.contrastCache.setColor(cell.bg, cell.fg, null); + return undefined; + } + // If it was an override and there was no contrast change, set as the result + // TODO: This is white when it should be green + result = fgRgba; } const color: IColor = { @@ -470,7 +496,9 @@ export abstract class BaseRenderLayer implements IRenderLayer { ), rgba: result }; - this._colors.contrastCache.setColor(cell.bg, cell.fg, color); + if (!bgOverride && !fgOverride) { + this._colors.contrastCache.setColor(cell.bg, cell.fg, color); + } return color; } diff --git a/src/browser/renderer/CursorRenderLayer.ts b/src/browser/renderer/CursorRenderLayer.ts index ea419cb2..3fa576a9 100644 --- a/src/browser/renderer/CursorRenderLayer.ts +++ b/src/browser/renderer/CursorRenderLayer.ts @@ -8,7 +8,7 @@ import { BaseRenderLayer } from 'browser/renderer/BaseRenderLayer'; import { ICellData } from 'common/Types'; import { CellData } from 'common/buffer/CellData'; import { IColorSet } from 'browser/Types'; -import { IBufferService, IOptionsService, ICoreService } from 'common/services/Services'; +import { IBufferService, IOptionsService, ICoreService, IDecorationService } from 'common/services/Services'; import { IEventEmitter } from 'common/EventEmitter'; import { ICoreBrowserService } from 'browser/services/Services'; @@ -40,9 +40,10 @@ export class CursorRenderLayer extends BaseRenderLayer { @IBufferService bufferService: IBufferService, @IOptionsService optionsService: IOptionsService, @ICoreService private readonly _coreService: ICoreService, - @ICoreBrowserService private readonly _coreBrowserService: ICoreBrowserService + @ICoreBrowserService private readonly _coreBrowserService: ICoreBrowserService, + @IDecorationService decorationService: IDecorationService ) { - super(container, 'cursor', zIndex, true, colors, rendererId, bufferService, optionsService); + super(container, 'cursor', zIndex, true, colors, rendererId, bufferService, optionsService, decorationService); this._state = { x: 0, y: 0, diff --git a/src/browser/renderer/LinkRenderLayer.ts b/src/browser/renderer/LinkRenderLayer.ts index 2492f921..15086d9a 100644 --- a/src/browser/renderer/LinkRenderLayer.ts +++ b/src/browser/renderer/LinkRenderLayer.ts @@ -8,7 +8,7 @@ import { BaseRenderLayer } from './BaseRenderLayer'; import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants'; import { is256Color } from 'browser/renderer/atlas/CharAtlasUtils'; import { IColorSet, ILinkifierEvent, ILinkifier, ILinkifier2 } from 'browser/Types'; -import { IBufferService, IOptionsService } from 'common/services/Services'; +import { IBufferService, IDecorationService, IOptionsService } from 'common/services/Services'; export class LinkRenderLayer extends BaseRenderLayer { private _state: ILinkifierEvent | undefined; @@ -21,9 +21,10 @@ export class LinkRenderLayer extends BaseRenderLayer { linkifier: ILinkifier, linkifier2: ILinkifier2, @IBufferService bufferService: IBufferService, - @IOptionsService optionsService: IOptionsService + @IOptionsService optionsService: IOptionsService, + @IDecorationService decorationService: IDecorationService ) { - super(container, 'link', zIndex, true, colors, rendererId, bufferService, optionsService); + super(container, 'link', zIndex, true, colors, rendererId, bufferService, optionsService, decorationService); linkifier.onShowLinkUnderline(e => this._onShowLinkUnderline(e)); linkifier.onHideLinkUnderline(e => this._onHideLinkUnderline(e)); diff --git a/src/browser/renderer/SelectionRenderLayer.ts b/src/browser/renderer/SelectionRenderLayer.ts index 9054e3ca..be911eb9 100644 --- a/src/browser/renderer/SelectionRenderLayer.ts +++ b/src/browser/renderer/SelectionRenderLayer.ts @@ -6,7 +6,7 @@ import { IRenderDimensions } from 'browser/renderer/Types'; import { BaseRenderLayer } from 'browser/renderer/BaseRenderLayer'; import { IColorSet } from 'browser/Types'; -import { IBufferService, IOptionsService } from 'common/services/Services'; +import { IBufferService, IDecorationService, IOptionsService } from 'common/services/Services'; interface ISelectionState { start?: [number, number]; @@ -24,9 +24,10 @@ export class SelectionRenderLayer extends BaseRenderLayer { colors: IColorSet, rendererId: number, @IBufferService bufferService: IBufferService, - @IOptionsService optionsService: IOptionsService + @IOptionsService optionsService: IOptionsService, + @IDecorationService decorationService: IDecorationService ) { - super(container, 'selection', zIndex, true, colors, rendererId, bufferService, optionsService); + super(container, 'selection', zIndex, true, colors, rendererId, bufferService, optionsService, decorationService); this._clearState(); } diff --git a/src/browser/renderer/TextRenderLayer.ts b/src/browser/renderer/TextRenderLayer.ts index 33d942ff..e0f6d831 100644 --- a/src/browser/renderer/TextRenderLayer.ts +++ b/src/browser/renderer/TextRenderLayer.ts @@ -11,7 +11,7 @@ import { AttributeData } from 'common/buffer/AttributeData'; import { NULL_CELL_CODE, Content } from 'common/buffer/Constants'; import { IColorSet } from 'browser/Types'; import { CellData } from 'common/buffer/CellData'; -import { IOptionsService, IBufferService } from 'common/services/Services'; +import { IOptionsService, IBufferService, IDecorationService } from 'common/services/Services'; import { ICharacterJoinerService } from 'browser/services/Services'; import { JoinedCellData } from 'browser/services/CharacterJoinerService'; @@ -37,9 +37,10 @@ export class TextRenderLayer extends BaseRenderLayer { rendererId: number, @IBufferService bufferService: IBufferService, @IOptionsService optionsService: IOptionsService, - @ICharacterJoinerService private readonly _characterJoinerService: ICharacterJoinerService + @ICharacterJoinerService private readonly _characterJoinerService: ICharacterJoinerService, + @IDecorationService decorationService: IDecorationService ) { - super(container, 'text', zIndex, alpha, colors, rendererId, bufferService, optionsService); + super(container, 'text', zIndex, alpha, colors, rendererId, bufferService, optionsService, decorationService); this._state = new GridCache(); } @@ -176,6 +177,19 @@ export class TextRenderLayer extends BaseRenderLayer { nextFillStyle = this._colors.ansi[cell.getBgColor()].css; } + // Get any decoration foreground/background overrides, this must be fetched before the early + // exist but applied after inverse + const decorations = this._decorationService.getDecorationsOnLine(y); + for (const d of decorations) { + const xmin = d.options.x ?? 0; + const xmax = xmin + (d.options.width ?? 1); + if (x >= xmin && x < xmax) { + if (d.backgroundColorRGB) { + nextFillStyle = d.backgroundColorRGB.css; + } + } + } + if (prevFillStyle === null) { // This is either the first iteration, or the default background was set. Either way, we // don't need to draw anything. diff --git a/src/browser/renderer/atlas/DynamicCharAtlas.ts b/src/browser/renderer/atlas/DynamicCharAtlas.ts index 88194615..678f8b70 100644 --- a/src/browser/renderer/atlas/DynamicCharAtlas.ts +++ b/src/browser/renderer/atlas/DynamicCharAtlas.ts @@ -105,7 +105,7 @@ export class DynamicCharAtlas extends BaseCharAtlas { this._cacheMap.prealloc(capacity); // This is useful for debugging - // document.body.appendChild(this._cacheCanvas); + document.body.appendChild(this._cacheCanvas); } public dispose(): void { diff --git a/src/browser/renderer/dom/DomRendererRowFactory.ts b/src/browser/renderer/dom/DomRendererRowFactory.ts index 7a20db5a..3a8bf87c 100644 --- a/src/browser/renderer/dom/DomRendererRowFactory.ts +++ b/src/browser/renderer/dom/DomRendererRowFactory.ts @@ -173,7 +173,8 @@ export class DomRendererRowFactory { bgColorMode = temp2; } - // Apply any decoration foreground/background overrides + // Apply any decoration foreground/background overrides, this must happen after inverse has + // been applied const decorations = this._decorationService.getDecorationsOnLine(row); let bgOverride: IColor | undefined; let fgOverride: IColor | undefined; From 714d4b1cd625e3e21e6fd2e10d145b378c80d7c9 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 10 May 2022 04:05:35 -0700 Subject: [PATCH 216/245] Fix fg color when there is a decoration override in canvas renderer --- src/browser/renderer/BaseRenderLayer.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/browser/renderer/BaseRenderLayer.ts b/src/browser/renderer/BaseRenderLayer.ts index b815c672..a4f1e233 100644 --- a/src/browser/renderer/BaseRenderLayer.ts +++ b/src/browser/renderer/BaseRenderLayer.ts @@ -479,13 +479,12 @@ export abstract class BaseRenderLayer implements IRenderLayer { let result = rgba.ensureContrastRatio(bgOverride ?? bgRgba, fgOverride ?? fgRgba, this._optionsService.rawOptions.minimumContrastRatio); if (!result) { - if (!bgOverride && !fgOverride) { + if (!fgOverride) { this._colors.contrastCache.setColor(cell.bg, cell.fg, null); return undefined; } // If it was an override and there was no contrast change, set as the result - // TODO: This is white when it should be green - result = fgRgba; + result = fgOverride; } const color: IColor = { From d29388394c60cef40dc0aa4aece9aad94ac46945 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 10 May 2022 04:53:31 -0700 Subject: [PATCH 217/245] Get Webgl rendering fg/bg overrides --- addons/xterm-addon-webgl/src/GlyphRenderer.ts | 44 ++++++++++++++++++- .../src/RectangleRenderer.ts | 37 ++++++++++++++-- addons/xterm-addon-webgl/src/WebglAddon.ts | 4 +- addons/xterm-addon-webgl/src/WebglRenderer.ts | 6 ++- .../src/atlas/WebglCharAtlas.ts | 3 ++ addons/xterm-addon-webgl/src/tsconfig.json | 1 + .../renderer/atlas/DynamicCharAtlas.ts | 2 +- 7 files changed, 88 insertions(+), 9 deletions(-) diff --git a/addons/xterm-addon-webgl/src/GlyphRenderer.ts b/addons/xterm-addon-webgl/src/GlyphRenderer.ts index 6b9faf88..8977f0fa 100644 --- a/addons/xterm-addon-webgl/src/GlyphRenderer.ts +++ b/addons/xterm-addon-webgl/src/GlyphRenderer.ts @@ -15,6 +15,7 @@ import { IColor } from 'common/Types'; import { IColorSet } from 'browser/Types'; import { IRenderDimensions } from 'browser/renderer/Types'; import { AttributeData } from 'common/buffer/AttributeData'; +import { IDecorationService } from 'common/services/Services'; interface IVertices { attributes: Float32Array; @@ -100,7 +101,8 @@ export class GlyphRenderer { private _terminal: Terminal, private _colors: IColorSet, private _gl: IWebGL2RenderingContext, - private _dimensions: IRenderDimensions + private _dimensions: IRenderDimensions, + private readonly _decorationService: IDecorationService ) { const gl = this._gl; const program = throwIfFalsy(createProgram(gl, vertexShaderSource, fragmentShaderSource)); @@ -188,10 +190,48 @@ export class GlyphRenderer { if (!this._atlas) { return; } + + // Get any decoration foreground/background overrides + const decorations = this._decorationService.getDecorationsOnLine(y); + let bgOverride: number | undefined; + let fgOverride: number | undefined; + for (const d of decorations) { + const xmin = d.options.x ?? 0; + const xmax = xmin + (d.options.width ?? 1); + if (x >= xmin && x < xmax) { + if (d.backgroundColorRGB) { + bgOverride = d.backgroundColorRGB.rgba; + } + if (d.foregroundColorRGB) { + fgOverride = d.foregroundColorRGB.rgba; + } + } + } + + // Convert any overrides from rgba to the fg/bg packed format. This resolves the inverse flag + // ahead of time in order to use the correct cache key + if (bgOverride !== undefined) { + // Non-RGB attributes from model + override + force RGB color mode + if (fg & FgFlags.INVERSE) { + bgOverride = (bg & ~Attributes.RGB_MASK) | (fgOverride !== undefined ? fgOverride >> 8 : fg) | Attributes.CM_RGB; + } else { + bgOverride = (bg & ~Attributes.RGB_MASK) | bgOverride >> 8 | Attributes.CM_RGB; + } + } + if (fgOverride !== undefined) { + // Non-RGB attributes from model + force disable inverse + override + force RGB color mode + if (fg & FgFlags.INVERSE) { + fgOverride = (fg & ~Attributes.RGB_MASK & ~FgFlags.INVERSE) | (bgOverride !== undefined ? bgOverride >> 8 : bg) | Attributes.CM_RGB; + } else { + fgOverride = (fg & ~Attributes.RGB_MASK & ~FgFlags.INVERSE) | fgOverride >> 8 | Attributes.CM_RGB; + } + } + + // Get the glyph if (chars && chars.length > 1) { rasterizedGlyph = this._atlas.getRasterizedGlyphCombinedChar(chars, bg, fg); } else { - rasterizedGlyph = this._atlas.getRasterizedGlyph(code, bg, fg); + rasterizedGlyph = this._atlas.getRasterizedGlyph(code, bgOverride ?? bg, fgOverride ?? fg); } // Fill empty if no glyph was found diff --git a/addons/xterm-addon-webgl/src/RectangleRenderer.ts b/addons/xterm-addon-webgl/src/RectangleRenderer.ts index ab0b34e9..f25cd4c4 100644 --- a/addons/xterm-addon-webgl/src/RectangleRenderer.ts +++ b/addons/xterm-addon-webgl/src/RectangleRenderer.ts @@ -12,6 +12,7 @@ import { IColor } from 'common/Types'; import { IColorSet } from 'browser/Types'; import { IRenderDimensions } from 'browser/renderer/Types'; import { RENDER_MODEL_BG_OFFSET, RENDER_MODEL_FG_OFFSET, RENDER_MODEL_INDICIES_PER_CELL } from './RenderModel'; +import { IDecorationService } from 'common/services/Services'; const enum VertexAttribLocations { POSITION = 0, @@ -79,7 +80,8 @@ export class RectangleRenderer { private _terminal: Terminal, private _colors: IColorSet, private _gl: IWebGL2RenderingContext, - private _dimensions: IRenderDimensions + private _dimensions: IRenderDimensions, + private readonly _decorationService: IDecorationService ) { const gl = this._gl; @@ -252,8 +254,37 @@ export class RectangleRenderer { let currentInverse = false; for (let x = 0; x < terminal.cols; x++) { const modelIndex = ((y * terminal.cols) + x) * RENDER_MODEL_INDICIES_PER_CELL; - const bg = model.cells[modelIndex + RENDER_MODEL_BG_OFFSET]; - const fg = model.cells[modelIndex + RENDER_MODEL_FG_OFFSET]; + + // Get any decoration foreground/background overrides + const decorations = this._decorationService.getDecorationsOnLine(y); + let bgOverride: number | undefined; + let fgOverride: number | undefined; + for (const d of decorations) { + const xmin = d.options.x ?? 0; + const xmax = xmin + (d.options.width ?? 1); + if (x >= xmin && x < xmax) { + if (d.backgroundColorRGB) { + bgOverride = d.backgroundColorRGB.rgba; + } + if (d.foregroundColorRGB) { + fgOverride = d.foregroundColorRGB.rgba; + } + } + } + + // Convert any overrides from rgba to the fg/bg packed format: + // Non RGB attributes from model + RGB from override + force RGB color mode + if (bgOverride !== undefined) { + bgOverride = (model.cells[modelIndex + RENDER_MODEL_BG_OFFSET] & ~Attributes.RGB_MASK) | bgOverride >> 8 | Attributes.CM_RGB; + } + if (fgOverride !== undefined) { + fgOverride = (model.cells[modelIndex + RENDER_MODEL_FG_OFFSET] & ~Attributes.RGB_MASK) | fgOverride >> 8 | Attributes.CM_RGB; + } + + // TODO: This isn't handling invert correctly + const bg = bgOverride ?? model.cells[modelIndex + RENDER_MODEL_BG_OFFSET]; + const fg = fgOverride ?? model.cells[modelIndex + RENDER_MODEL_FG_OFFSET]; + const inverse = !!(fg & FgFlags.INVERSE); if (bg !== currentBg || (fg !== currentFg && (currentInverse || inverse))) { // A rectangle needs to be drawn if going from non-default to another color diff --git a/addons/xterm-addon-webgl/src/WebglAddon.ts b/addons/xterm-addon-webgl/src/WebglAddon.ts index b8bcf5b1..4db072e8 100644 --- a/addons/xterm-addon-webgl/src/WebglAddon.ts +++ b/addons/xterm-addon-webgl/src/WebglAddon.ts @@ -9,6 +9,7 @@ import { ICharacterJoinerService, IRenderService } from 'browser/services/Servic import { IColorSet } from 'browser/Types'; import { EventEmitter } from 'common/EventEmitter'; import { isSafari } from 'common/Platform'; +import { IDecorationService } from 'common/services/Services'; export class WebglAddon implements ITerminalAddon { private _terminal?: Terminal; @@ -30,8 +31,9 @@ export class WebglAddon implements ITerminalAddon { this._terminal = terminal; const renderService: IRenderService = (terminal as any)._core._renderService; const characterJoinerService: ICharacterJoinerService = (terminal as any)._core._characterJoinerService; + const decorationService: IDecorationService = (terminal as any)._core._decorationService; const colors: IColorSet = (terminal as any)._core._colorManager.colors; - this._renderer = new WebglRenderer(terminal, colors, characterJoinerService, this._preserveDrawingBuffer); + this._renderer = new WebglRenderer(terminal, colors, characterJoinerService, decorationService, this._preserveDrawingBuffer); this._renderer.onContextLoss(() => this._onContextLoss.fire()); renderService.setRenderer(this._renderer); } diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index a256b9da..faaa6d2c 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -23,6 +23,7 @@ import { addDisposableDomListener } from 'browser/Lifecycle'; import { ICharacterJoinerService } from 'browser/services/Services'; import { CharData, ICellData } from 'common/Types'; import { AttributeData } from 'common/buffer/AttributeData'; +import { IDecorationService } from 'common/services/Services'; export class WebglRenderer extends Disposable implements IRenderer { private _renderLayers: IRenderLayer[]; @@ -52,6 +53,7 @@ export class WebglRenderer extends Disposable implements IRenderer { private _terminal: Terminal, private _colors: IColorSet, private readonly _characterJoinerService: ICharacterJoinerService, + decorationService: IDecorationService, preserveDrawingBuffer?: boolean ) { super(); @@ -95,8 +97,8 @@ export class WebglRenderer extends Disposable implements IRenderer { this._core.screenElement!.appendChild(this._canvas); - this._rectangleRenderer = new RectangleRenderer(this._terminal, this._colors, this._gl, this.dimensions); - this._glyphRenderer = new GlyphRenderer(this._terminal, this._colors, this._gl, this.dimensions); + this._rectangleRenderer = new RectangleRenderer(this._terminal, this._colors, this._gl, this.dimensions, decorationService); + this._glyphRenderer = new GlyphRenderer(this._terminal, this._colors, this._gl, this.dimensions, decorationService); // Update dimensions and acquire char atlas this.onCharSizeChanged(); diff --git a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts index 34107fc5..9e14d5b8 100644 --- a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts +++ b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts @@ -88,6 +88,9 @@ export class WebglCharAtlas implements IDisposable { this._tmpCanvas.width = this._config.scaledCellWidth * 4 + TMP_CANVAS_GLYPH_PADDING * 2; this._tmpCanvas.height = this._config.scaledCellHeight + TMP_CANVAS_GLYPH_PADDING * 2; this._tmpCtx = throwIfFalsy(this._tmpCanvas.getContext('2d', { alpha: this._config.allowTransparency })); + + // This is useful for debugging + document.body.appendChild(this.cacheCanvas); } public dispose(): void { diff --git a/addons/xterm-addon-webgl/src/tsconfig.json b/addons/xterm-addon-webgl/src/tsconfig.json index 0b95491f..b0c9f6be 100644 --- a/addons/xterm-addon-webgl/src/tsconfig.json +++ b/addons/xterm-addon-webgl/src/tsconfig.json @@ -20,6 +20,7 @@ ] }, "strict": true, + "downlevelIteration": true, "types": [ "../../../node_modules/@types/mocha" ] diff --git a/src/browser/renderer/atlas/DynamicCharAtlas.ts b/src/browser/renderer/atlas/DynamicCharAtlas.ts index 678f8b70..88194615 100644 --- a/src/browser/renderer/atlas/DynamicCharAtlas.ts +++ b/src/browser/renderer/atlas/DynamicCharAtlas.ts @@ -105,7 +105,7 @@ export class DynamicCharAtlas extends BaseCharAtlas { this._cacheMap.prealloc(capacity); // This is useful for debugging - document.body.appendChild(this._cacheCanvas); + // document.body.appendChild(this._cacheCanvas); } public dispose(): void { From 40716b8ca5f5ca2119342c3bbd79fd3a5a41b87f Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 10 May 2022 05:00:52 -0700 Subject: [PATCH 218/245] Consolidate override logic into model/WebglRenderer --- addons/xterm-addon-webgl/src/GlyphRenderer.ts | 38 +---------- .../src/RectangleRenderer.ts | 31 +-------- addons/xterm-addon-webgl/src/WebglRenderer.ts | 66 +++++++++++++++---- 3 files changed, 58 insertions(+), 77 deletions(-) diff --git a/addons/xterm-addon-webgl/src/GlyphRenderer.ts b/addons/xterm-addon-webgl/src/GlyphRenderer.ts index 8977f0fa..7d35479a 100644 --- a/addons/xterm-addon-webgl/src/GlyphRenderer.ts +++ b/addons/xterm-addon-webgl/src/GlyphRenderer.ts @@ -191,47 +191,11 @@ export class GlyphRenderer { return; } - // Get any decoration foreground/background overrides - const decorations = this._decorationService.getDecorationsOnLine(y); - let bgOverride: number | undefined; - let fgOverride: number | undefined; - for (const d of decorations) { - const xmin = d.options.x ?? 0; - const xmax = xmin + (d.options.width ?? 1); - if (x >= xmin && x < xmax) { - if (d.backgroundColorRGB) { - bgOverride = d.backgroundColorRGB.rgba; - } - if (d.foregroundColorRGB) { - fgOverride = d.foregroundColorRGB.rgba; - } - } - } - - // Convert any overrides from rgba to the fg/bg packed format. This resolves the inverse flag - // ahead of time in order to use the correct cache key - if (bgOverride !== undefined) { - // Non-RGB attributes from model + override + force RGB color mode - if (fg & FgFlags.INVERSE) { - bgOverride = (bg & ~Attributes.RGB_MASK) | (fgOverride !== undefined ? fgOverride >> 8 : fg) | Attributes.CM_RGB; - } else { - bgOverride = (bg & ~Attributes.RGB_MASK) | bgOverride >> 8 | Attributes.CM_RGB; - } - } - if (fgOverride !== undefined) { - // Non-RGB attributes from model + force disable inverse + override + force RGB color mode - if (fg & FgFlags.INVERSE) { - fgOverride = (fg & ~Attributes.RGB_MASK & ~FgFlags.INVERSE) | (bgOverride !== undefined ? bgOverride >> 8 : bg) | Attributes.CM_RGB; - } else { - fgOverride = (fg & ~Attributes.RGB_MASK & ~FgFlags.INVERSE) | fgOverride >> 8 | Attributes.CM_RGB; - } - } - // Get the glyph if (chars && chars.length > 1) { rasterizedGlyph = this._atlas.getRasterizedGlyphCombinedChar(chars, bg, fg); } else { - rasterizedGlyph = this._atlas.getRasterizedGlyph(code, bgOverride ?? bg, fgOverride ?? fg); + rasterizedGlyph = this._atlas.getRasterizedGlyph(code, bg, fg); } // Fill empty if no glyph was found diff --git a/addons/xterm-addon-webgl/src/RectangleRenderer.ts b/addons/xterm-addon-webgl/src/RectangleRenderer.ts index f25cd4c4..ae6258f0 100644 --- a/addons/xterm-addon-webgl/src/RectangleRenderer.ts +++ b/addons/xterm-addon-webgl/src/RectangleRenderer.ts @@ -255,35 +255,8 @@ export class RectangleRenderer { for (let x = 0; x < terminal.cols; x++) { const modelIndex = ((y * terminal.cols) + x) * RENDER_MODEL_INDICIES_PER_CELL; - // Get any decoration foreground/background overrides - const decorations = this._decorationService.getDecorationsOnLine(y); - let bgOverride: number | undefined; - let fgOverride: number | undefined; - for (const d of decorations) { - const xmin = d.options.x ?? 0; - const xmax = xmin + (d.options.width ?? 1); - if (x >= xmin && x < xmax) { - if (d.backgroundColorRGB) { - bgOverride = d.backgroundColorRGB.rgba; - } - if (d.foregroundColorRGB) { - fgOverride = d.foregroundColorRGB.rgba; - } - } - } - - // Convert any overrides from rgba to the fg/bg packed format: - // Non RGB attributes from model + RGB from override + force RGB color mode - if (bgOverride !== undefined) { - bgOverride = (model.cells[modelIndex + RENDER_MODEL_BG_OFFSET] & ~Attributes.RGB_MASK) | bgOverride >> 8 | Attributes.CM_RGB; - } - if (fgOverride !== undefined) { - fgOverride = (model.cells[modelIndex + RENDER_MODEL_FG_OFFSET] & ~Attributes.RGB_MASK) | fgOverride >> 8 | Attributes.CM_RGB; - } - - // TODO: This isn't handling invert correctly - const bg = bgOverride ?? model.cells[modelIndex + RENDER_MODEL_BG_OFFSET]; - const fg = fgOverride ?? model.cells[modelIndex + RENDER_MODEL_FG_OFFSET]; + const bg = model.cells[modelIndex + RENDER_MODEL_BG_OFFSET]; + const fg = model.cells[modelIndex + RENDER_MODEL_FG_OFFSET]; const inverse = !!(fg & FgFlags.INVERSE); if (bg !== currentBg || (fg !== currentFg && (currentInverse || inverse))) { diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index faaa6d2c..20597401 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -12,7 +12,7 @@ import { RectangleRenderer } from './RectangleRenderer'; import { IWebGL2RenderingContext } from './Types'; import { RenderModel, COMBINED_CHAR_BIT_MASK, RENDER_MODEL_BG_OFFSET, RENDER_MODEL_FG_OFFSET, RENDER_MODEL_INDICIES_PER_CELL } from './RenderModel'; import { Disposable } from 'common/Lifecycle'; -import { Content, NULL_CELL_CHAR, NULL_CELL_CODE } from 'common/buffer/Constants'; +import { Attributes, Content, FgFlags, NULL_CELL_CHAR, NULL_CELL_CODE } from 'common/buffer/Constants'; import { Terminal, IEvent } from 'xterm'; import { IRenderLayer } from './renderLayer/Types'; import { IRenderDimensions, IRenderer, IRequestRedrawEvent } from 'browser/renderer/Types'; @@ -53,7 +53,7 @@ export class WebglRenderer extends Disposable implements IRenderer { private _terminal: Terminal, private _colors: IColorSet, private readonly _characterJoinerService: ICharacterJoinerService, - decorationService: IDecorationService, + private readonly _decorationService: IDecorationService, preserveDrawingBuffer?: boolean ) { super(); @@ -97,8 +97,8 @@ export class WebglRenderer extends Disposable implements IRenderer { this._core.screenElement!.appendChild(this._canvas); - this._rectangleRenderer = new RectangleRenderer(this._terminal, this._colors, this._gl, this.dimensions, decorationService); - this._glyphRenderer = new GlyphRenderer(this._terminal, this._colors, this._gl, this.dimensions, decorationService); + this._rectangleRenderer = new RectangleRenderer(this._terminal, this._colors, this._gl, this.dimensions, _decorationService); + this._glyphRenderer = new GlyphRenderer(this._terminal, this._colors, this._gl, this.dimensions, _decorationService); // Update dimensions and acquire char atlas this.onCharSizeChanged(); @@ -333,14 +333,58 @@ export class WebglRenderer extends Disposable implements IRenderer { let code = cell.getCode(); const i = ((y * terminal.cols) + x) * RENDER_MODEL_INDICIES_PER_CELL; + let bg = cell.bg; + let fg = cell.fg; + + // Get any decoration foreground/background overrides, this happens on the model to avoid + // spreading decoration override logic throughout the different sub-renderers + const decorations = this._decorationService.getDecorationsOnLine(y); + let bgOverride: number | undefined; + let fgOverride: number | undefined; + for (const d of decorations) { + const xmin = d.options.x ?? 0; + const xmax = xmin + (d.options.width ?? 1); + if (x >= xmin && x < xmax) { + if (d.backgroundColorRGB) { + bgOverride = d.backgroundColorRGB.rgba; + } + if (d.foregroundColorRGB) { + fgOverride = d.foregroundColorRGB.rgba; + } + } + } + + // Convert any overrides from rgba to the fg/bg packed format. This resolves the inverse flag + // ahead of time in order to use the correct cache key + if (bgOverride !== undefined) { + // Non-RGB attributes from model + override + force RGB color mode + if (fg & FgFlags.INVERSE) { + bgOverride = (bg & ~Attributes.RGB_MASK) | (fgOverride !== undefined ? fgOverride >> 8 : fg) | Attributes.CM_RGB; + } else { + bgOverride = (bg & ~Attributes.RGB_MASK) | bgOverride >> 8 | Attributes.CM_RGB; + } + } + if (fgOverride !== undefined) { + // Non-RGB attributes from model + force disable inverse + override + force RGB color mode + if (fg & FgFlags.INVERSE) { + fgOverride = (fg & ~Attributes.RGB_MASK & ~FgFlags.INVERSE) | (bgOverride !== undefined ? bgOverride >> 8 : bg) | Attributes.CM_RGB; + } else { + fgOverride = (fg & ~Attributes.RGB_MASK & ~FgFlags.INVERSE) | fgOverride >> 8 | Attributes.CM_RGB; + } + } + + // Use the override if it exists + bg = bgOverride ?? bg; + fg = fgOverride ?? fg; + if (code !== NULL_CELL_CODE) { this._model.lineLengths[y] = x + 1; } // Nothing has changed, no updates needed if (this._model.cells[i] === code && - this._model.cells[i + RENDER_MODEL_BG_OFFSET] === cell.bg && - this._model.cells[i + RENDER_MODEL_FG_OFFSET] === cell.fg) { + this._model.cells[i + RENDER_MODEL_BG_OFFSET] === bg && + this._model.cells[i + RENDER_MODEL_FG_OFFSET] === fg) { continue; } @@ -351,10 +395,10 @@ export class WebglRenderer extends Disposable implements IRenderer { // Cache the results in the model this._model.cells[i] = code; - this._model.cells[i + RENDER_MODEL_BG_OFFSET] = cell.bg; - this._model.cells[i + RENDER_MODEL_FG_OFFSET] = cell.fg; + this._model.cells[i + RENDER_MODEL_BG_OFFSET] = bg; + this._model.cells[i + RENDER_MODEL_FG_OFFSET] = fg; - this._glyphRenderer.updateCell(x, y, code, cell.bg, cell.fg, chars); + this._glyphRenderer.updateCell(x, y, code, bg, fg, chars); if (isJoined) { // Restore work cell @@ -365,8 +409,8 @@ export class WebglRenderer extends Disposable implements IRenderer { const j = ((y * terminal.cols) + x) * RENDER_MODEL_INDICIES_PER_CELL; this._glyphRenderer.updateCell(x, y, NULL_CELL_CODE, 0, 0, NULL_CELL_CHAR); this._model.cells[j] = NULL_CELL_CODE; - this._model.cells[j + RENDER_MODEL_BG_OFFSET] = this._workCell.bg; - this._model.cells[j + RENDER_MODEL_FG_OFFSET] = this._workCell.fg; + this._model.cells[j + RENDER_MODEL_BG_OFFSET] = bg; + this._model.cells[j + RENDER_MODEL_FG_OFFSET] = fg; } } } From 0a26cee08bff6c973bfaf18a9e7e4dc878127acc Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 10 May 2022 05:06:20 -0700 Subject: [PATCH 219/245] Pull override color logic into a function --- addons/xterm-addon-webgl/src/WebglRenderer.ts | 110 ++++++++++-------- 1 file changed, 60 insertions(+), 50 deletions(-) diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index 20597401..9cdcf9cd 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -32,6 +32,7 @@ export class WebglRenderer extends Disposable implements IRenderer { private _model: RenderModel = new RenderModel(); private _workCell: CellData = new CellData(); + private _workColors: { fg: number, bg: number } = { fg: 0, bg: 0 }; private _canvas: HTMLCanvasElement; private _gl: IWebGL2RenderingContext; @@ -333,49 +334,8 @@ export class WebglRenderer extends Disposable implements IRenderer { let code = cell.getCode(); const i = ((y * terminal.cols) + x) * RENDER_MODEL_INDICIES_PER_CELL; - let bg = cell.bg; - let fg = cell.fg; - - // Get any decoration foreground/background overrides, this happens on the model to avoid - // spreading decoration override logic throughout the different sub-renderers - const decorations = this._decorationService.getDecorationsOnLine(y); - let bgOverride: number | undefined; - let fgOverride: number | undefined; - for (const d of decorations) { - const xmin = d.options.x ?? 0; - const xmax = xmin + (d.options.width ?? 1); - if (x >= xmin && x < xmax) { - if (d.backgroundColorRGB) { - bgOverride = d.backgroundColorRGB.rgba; - } - if (d.foregroundColorRGB) { - fgOverride = d.foregroundColorRGB.rgba; - } - } - } - - // Convert any overrides from rgba to the fg/bg packed format. This resolves the inverse flag - // ahead of time in order to use the correct cache key - if (bgOverride !== undefined) { - // Non-RGB attributes from model + override + force RGB color mode - if (fg & FgFlags.INVERSE) { - bgOverride = (bg & ~Attributes.RGB_MASK) | (fgOverride !== undefined ? fgOverride >> 8 : fg) | Attributes.CM_RGB; - } else { - bgOverride = (bg & ~Attributes.RGB_MASK) | bgOverride >> 8 | Attributes.CM_RGB; - } - } - if (fgOverride !== undefined) { - // Non-RGB attributes from model + force disable inverse + override + force RGB color mode - if (fg & FgFlags.INVERSE) { - fgOverride = (fg & ~Attributes.RGB_MASK & ~FgFlags.INVERSE) | (bgOverride !== undefined ? bgOverride >> 8 : bg) | Attributes.CM_RGB; - } else { - fgOverride = (fg & ~Attributes.RGB_MASK & ~FgFlags.INVERSE) | fgOverride >> 8 | Attributes.CM_RGB; - } - } - - // Use the override if it exists - bg = bgOverride ?? bg; - fg = fgOverride ?? fg; + // Load colors/resolve overrides into work colors + this._loadColorsForCell(x, y); if (code !== NULL_CELL_CODE) { this._model.lineLengths[y] = x + 1; @@ -383,8 +343,8 @@ export class WebglRenderer extends Disposable implements IRenderer { // Nothing has changed, no updates needed if (this._model.cells[i] === code && - this._model.cells[i + RENDER_MODEL_BG_OFFSET] === bg && - this._model.cells[i + RENDER_MODEL_FG_OFFSET] === fg) { + this._model.cells[i + RENDER_MODEL_BG_OFFSET] === this._workColors.bg && + this._model.cells[i + RENDER_MODEL_FG_OFFSET] === this._workColors.fg) { continue; } @@ -395,10 +355,10 @@ export class WebglRenderer extends Disposable implements IRenderer { // Cache the results in the model this._model.cells[i] = code; - this._model.cells[i + RENDER_MODEL_BG_OFFSET] = bg; - this._model.cells[i + RENDER_MODEL_FG_OFFSET] = fg; + this._model.cells[i + RENDER_MODEL_BG_OFFSET] = this._workColors.bg; + this._model.cells[i + RENDER_MODEL_FG_OFFSET] = this._workColors.fg; - this._glyphRenderer.updateCell(x, y, code, bg, fg, chars); + this._glyphRenderer.updateCell(x, y, code, this._workColors.bg, this._workColors.fg, chars); if (isJoined) { // Restore work cell @@ -409,8 +369,8 @@ export class WebglRenderer extends Disposable implements IRenderer { const j = ((y * terminal.cols) + x) * RENDER_MODEL_INDICIES_PER_CELL; this._glyphRenderer.updateCell(x, y, NULL_CELL_CODE, 0, 0, NULL_CELL_CHAR); this._model.cells[j] = NULL_CELL_CODE; - this._model.cells[j + RENDER_MODEL_BG_OFFSET] = bg; - this._model.cells[j + RENDER_MODEL_FG_OFFSET] = fg; + this._model.cells[j + RENDER_MODEL_BG_OFFSET] = this._workColors.bg; + this._model.cells[j + RENDER_MODEL_FG_OFFSET] = this._workColors.fg; } } } @@ -422,6 +382,56 @@ export class WebglRenderer extends Disposable implements IRenderer { } } + /** + * Loads colors for the cell into the work colors object. This resolves overrides/inverse if + * necessary which is why the work cell object is not used. + */ + private _loadColorsForCell(x: number, y: number): void { + this._workColors.bg = this._workCell.bg; + this._workColors.fg = this._workCell.fg; + + // Get any decoration foreground/background overrides, this happens on the model to avoid + // spreading decoration override logic throughout the different sub-renderers + const decorations = this._decorationService.getDecorationsOnLine(y); + let bgOverride: number | undefined; + let fgOverride: number | undefined; + for (const d of decorations) { + const xmin = d.options.x ?? 0; + const xmax = xmin + (d.options.width ?? 1); + if (x >= xmin && x < xmax) { + if (d.backgroundColorRGB) { + bgOverride = d.backgroundColorRGB.rgba; + } + if (d.foregroundColorRGB) { + fgOverride = d.foregroundColorRGB.rgba; + } + } + } + + // Convert any overrides from rgba to the fg/bg packed format. This resolves the inverse flag + // ahead of time in order to use the correct cache key + if (bgOverride !== undefined) { + // Non-RGB attributes from model + override + force RGB color mode + if (this._workColors.fg & FgFlags.INVERSE) { + bgOverride = (this._workColors.bg & ~Attributes.RGB_MASK) | (fgOverride !== undefined ? fgOverride >> 8 : this._workColors.fg) | Attributes.CM_RGB; + } else { + bgOverride = (this._workColors.bg & ~Attributes.RGB_MASK) | bgOverride >> 8 | Attributes.CM_RGB; + } + } + if (fgOverride !== undefined) { + // Non-RGB attributes from model + force disable inverse + override + force RGB color mode + if (this._workColors.fg & FgFlags.INVERSE) { + fgOverride = (this._workColors.fg & ~Attributes.RGB_MASK & ~FgFlags.INVERSE) | (bgOverride !== undefined ? bgOverride >> 8 : this._workColors.bg) | Attributes.CM_RGB; + } else { + fgOverride = (this._workColors.fg & ~Attributes.RGB_MASK & ~FgFlags.INVERSE) | fgOverride >> 8 | Attributes.CM_RGB; + } + } + + // Use the override if it exists + this._workColors.bg = bgOverride ?? this._workColors.bg; + this._workColors.fg = fgOverride ?? this._workColors.fg; + } + private _updateSelectionModel(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean = false): void { const terminal = this._terminal; From 7c87f5b47a3589f1ba962ca522ebc49c46c6b99f Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 10 May 2022 05:31:49 -0700 Subject: [PATCH 220/245] Start to use new API in search addon --- addons/xterm-addon-search/src/SearchAddon.ts | 24 +++++++++---------- .../typings/xterm-addon-search.d.ts | 6 ++--- .../src/atlas/WebglCharAtlas.ts | 3 --- demo/client.ts | 4 ++-- .../renderer/dom/DomRendererRowFactory.ts | 4 ++-- 5 files changed, 19 insertions(+), 22 deletions(-) diff --git a/addons/xterm-addon-search/src/SearchAddon.ts b/addons/xterm-addon-search/src/SearchAddon.ts index 345b9153..0fb08056 100644 --- a/addons/xterm-addon-search/src/SearchAddon.ts +++ b/addons/xterm-addon-search/src/SearchAddon.ts @@ -653,7 +653,7 @@ export class SearchAddon implements ITerminalAddon { * @param result The result to select. * @return Whether a result was selected. */ - private _selectResult(result: ISearchResult | undefined, decorations?: ISearchDecorationOptions, noScroll?: boolean): boolean { + private _selectResult(result: ISearchResult | undefined, options?: ISearchDecorationOptions, noScroll?: boolean): boolean { const terminal = this._terminal!; this._selectedDecoration?.dispose(); if (!result) { @@ -661,18 +661,19 @@ export class SearchAddon implements ITerminalAddon { return false; } terminal.select(result.col, result.row, result.size); - if (decorations?.activeMatchColorOverviewRuler) { + if (options) { const marker = terminal.registerMarker(-terminal.buffer.active.baseY - terminal.buffer.active.cursorY + result.row); if (marker) { this._selectedDecoration = terminal.registerDecoration({ marker, x: result.col, width: result.size, + backgroundColor: options.activeMatchBackground, overviewRulerOptions: { - color: decorations.activeMatchColorOverviewRuler + color: options.activeMatchColorOverviewRuler } }); - this._selectedDecoration?.onRender((e) => this._applyStyles(e, decorations.activeMatchBackground, decorations.activeMatchBorder)); + this._selectedDecoration?.onRender((e) => this._applyStyles(e, options.activeMatchBorder)); this._selectedDecoration?.onDispose(() => marker.dispose()); } } @@ -696,15 +697,12 @@ export class SearchAddon implements ITerminalAddon { * @param result the search result associated with the decoration * @returns */ - private _applyStyles(element: HTMLElement, backgroundColor: string | undefined, borderColor: string | undefined): void { + private _applyStyles(element: HTMLElement, borderColor: string | undefined): void { if (element.clientWidth <= 0) { return; } if (!element.classList.contains('xterm-find-result-decoration')) { element.classList.add('xterm-find-result-decoration'); - if (backgroundColor) { - element.style.backgroundColor = backgroundColor; - } if (borderColor) { element.style.outline = `1px solid ${borderColor}`; } @@ -717,21 +715,23 @@ export class SearchAddon implements ITerminalAddon { * @param color the color to use for the decoration * @returns the {@link IDecoration} or undefined if the marker has already been disposed of */ - private _createResultDecoration(result: ISearchResult, decorations: ISearchDecorationOptions): IDecoration | undefined { + private _createResultDecoration(result: ISearchResult, options: ISearchDecorationOptions): IDecoration | undefined { const terminal = this._terminal!; const marker = terminal.registerMarker(-terminal.buffer.active.baseY - terminal.buffer.active.cursorY + result.row); - if (!marker || !decorations?.matchOverviewRuler) { + if (!marker) { return undefined; } const findResultDecoration = terminal.registerDecoration({ marker, x: result.col, width: result.size, + backgroundColor: options.matchBackground, overviewRulerOptions: this._resultDecorations?.get(marker.line) ? undefined : { - color: decorations.matchOverviewRuler, position: 'center' + color: options.matchOverviewRuler, + position: 'center' } }); - findResultDecoration?.onRender((e) => this._applyStyles(e, decorations.matchBackground, decorations.matchBorder)); + findResultDecoration?.onRender((e) => this._applyStyles(e, options.matchBorder)); findResultDecoration?.onDispose(() => marker.dispose()); return findResultDecoration; } diff --git a/addons/xterm-addon-search/typings/xterm-addon-search.d.ts b/addons/xterm-addon-search/typings/xterm-addon-search.d.ts index 4d683db0..9ed1da62 100644 --- a/addons/xterm-addon-search/typings/xterm-addon-search.d.ts +++ b/addons/xterm-addon-search/typings/xterm-addon-search.d.ts @@ -45,12 +45,12 @@ declare module 'xterm-addon-search' { */ interface ISearchDecorationOptions { /** - * The background color of a match. + * The background color of a match, this must use #RRGGBB format. */ matchBackground?: string; /** - * The border color of a match + * The border color of a match. */ matchBorder?: string; @@ -60,7 +60,7 @@ declare module 'xterm-addon-search' { matchOverviewRuler: string; /** - * The background color for the currently active match. + * The background color for the currently active match, this must use #RRGGBB format. */ activeMatchBackground?: string; diff --git a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts index 9e14d5b8..34107fc5 100644 --- a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts +++ b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts @@ -88,9 +88,6 @@ export class WebglCharAtlas implements IDisposable { this._tmpCanvas.width = this._config.scaledCellWidth * 4 + TMP_CANVAS_GLYPH_PADDING * 2; this._tmpCanvas.height = this._config.scaledCellHeight + TMP_CANVAS_GLYPH_PADDING * 2; this._tmpCtx = throwIfFalsy(this._tmpCanvas.getContext('2d', { alpha: this._config.allowTransparency })); - - // This is useful for debugging - document.body.appendChild(this.cacheCanvas); } public dispose(): void { diff --git a/demo/client.ts b/demo/client.ts index 09359905..3996652a 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -110,10 +110,10 @@ function getSearchOptions(e: KeyboardEvent): ISearchOptions { caseSensitive: (document.getElementById('case-sensitive') as HTMLInputElement).checked, incremental: e.key !== `Enter`, decorations: (document.getElementById('highlight-all-matches') as HTMLInputElement).checked ? { - matchBackground: '#55575380', + matchBackground: '#232422', matchBorder: '#555753', matchOverviewRuler: '#555753', - activeMatchBackground: '#ef292980', + activeMatchBackground: '#ef2929', activeMatchBorder: '#ef2929', activeMatchColorOverviewRuler: '#ef2929' } : undefined diff --git a/src/browser/renderer/dom/DomRendererRowFactory.ts b/src/browser/renderer/dom/DomRendererRowFactory.ts index 3a8bf87c..24a7b2c6 100644 --- a/src/browser/renderer/dom/DomRendererRowFactory.ts +++ b/src/browser/renderer/dom/DomRendererRowFactory.ts @@ -184,12 +184,12 @@ export class DomRendererRowFactory { if (x >= xmin && x < xmax) { if (d.backgroundColorRGB) { bgColorMode = Attributes.CM_RGB; - bg = d.backgroundColorRGB.rgba >> 8; + bg = d.backgroundColorRGB.rgba >> 8 & 0xFFFFFF; bgOverride = d.backgroundColorRGB; } if (d.foregroundColorRGB) { fgColorMode = Attributes.CM_RGB; - fg = d.foregroundColorRGB.rgba >> 8; + fg = d.foregroundColorRGB.rgba >> 8 & 0xFFFFFF; fgOverride = d.foregroundColorRGB; } } From de574a27fc71ec5544e15d767789d2a1e6dc0bc9 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 10 May 2022 05:44:00 -0700 Subject: [PATCH 221/245] Full re-render when there are decoration changes --- demo/client.ts | 2 +- src/browser/services/RenderService.ts | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/demo/client.ts b/demo/client.ts index 3996652a..a63864a2 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -114,7 +114,7 @@ function getSearchOptions(e: KeyboardEvent): ISearchOptions { matchBorder: '#555753', matchOverviewRuler: '#555753', activeMatchBackground: '#ef2929', - activeMatchBorder: '#ef2929', + activeMatchBorder: '#ffffff', activeMatchColorOverviewRuler: '#ef2929' } : undefined }; diff --git a/src/browser/services/RenderService.ts b/src/browser/services/RenderService.ts index 91b510a3..a789e025 100644 --- a/src/browser/services/RenderService.ts +++ b/src/browser/services/RenderService.ts @@ -10,7 +10,7 @@ import { Disposable } from 'common/Lifecycle'; import { ScreenDprMonitor } from 'browser/ScreenDprMonitor'; import { addDisposableDomListener } from 'browser/Lifecycle'; import { IColorSet, IRenderDebouncer } from 'browser/Types'; -import { IOptionsService, IBufferService } from 'common/services/Services'; +import { IOptionsService, IBufferService, IDecorationService } from 'common/services/Services'; import { ICharSizeService, IRenderService } from 'browser/services/Services'; interface ISelectionState { @@ -54,6 +54,7 @@ export class RenderService extends Disposable implements IRenderService { screenElement: HTMLElement, @IOptionsService optionsService: IOptionsService, @ICharSizeService private readonly _charSizeService: ICharSizeService, + @IDecorationService decorationService: IDecorationService, @IBufferService bufferService: IBufferService ) { super(); @@ -67,6 +68,9 @@ export class RenderService extends Disposable implements IRenderService { this._screenDprMonitor.setListener(() => this.onDevicePixelRatioChange()); this.register(this._screenDprMonitor); + // TODO: This will slow things down + this.register(decorationService.onDecorationRegistered(() => this._fullRefresh())); + this.register(decorationService.onDecorationRemoved(() => this._fullRefresh())); this.register(bufferService.onResize(() => this._fullRefresh())); this.register(bufferService.buffers.onBufferActivate(() => this._renderer?.clear())); this.register(optionsService.onOptionChange(() => this._renderer.onOptionsChanged())); From b85f5aacd62c753ca59d4a7bf2b105ae084acfd4 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 10 May 2022 06:14:53 -0700 Subject: [PATCH 222/245] Use correct row for override in canvas renderer --- src/browser/renderer/Renderer.ts | 1 - src/browser/renderer/TextRenderLayer.ts | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/browser/renderer/Renderer.ts b/src/browser/renderer/Renderer.ts index a58893b4..8dfe09c9 100644 --- a/src/browser/renderer/Renderer.ts +++ b/src/browser/renderer/Renderer.ts @@ -14,7 +14,6 @@ import { ICharSizeService } from 'browser/services/Services'; import { IBufferService, IOptionsService, IInstantiationService } from 'common/services/Services'; import { removeTerminalFromCache } from 'browser/renderer/atlas/CharAtlasCache'; import { EventEmitter, IEvent } from 'common/EventEmitter'; -import { IDecorationOptions, IDecoration } from 'xterm'; let nextRendererId = 1; diff --git a/src/browser/renderer/TextRenderLayer.ts b/src/browser/renderer/TextRenderLayer.ts index e0f6d831..e94b53e0 100644 --- a/src/browser/renderer/TextRenderLayer.ts +++ b/src/browser/renderer/TextRenderLayer.ts @@ -179,7 +179,7 @@ export class TextRenderLayer extends BaseRenderLayer { // Get any decoration foreground/background overrides, this must be fetched before the early // exist but applied after inverse - const decorations = this._decorationService.getDecorationsOnLine(y); + const decorations = this._decorationService.getDecorationsOnLine(this._bufferService.buffer.ydisp + y); for (const d of decorations) { const xmin = d.options.x ?? 0; const xmax = xmin + (d.options.width ?? 1); From a67e7286d3547aad8ff37a2e24ff47e4c32cada2 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 10 May 2022 06:16:32 -0700 Subject: [PATCH 223/245] Use correct row for override in webgl renderer --- addons/xterm-addon-webgl/src/WebglRenderer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index 9cdcf9cd..2a267ff8 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -335,7 +335,7 @@ export class WebglRenderer extends Disposable implements IRenderer { const i = ((y * terminal.cols) + x) * RENDER_MODEL_INDICIES_PER_CELL; // Load colors/resolve overrides into work colors - this._loadColorsForCell(x, y); + this._loadColorsForCell(x, row); if (code !== NULL_CELL_CODE) { this._model.lineLengths[y] = x + 1; From ba61509fdd3aacd16995190e37b5729ed516d397 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 10 May 2022 06:20:48 -0700 Subject: [PATCH 224/245] Remove react to changes idea --- src/common/services/DecorationService.ts | 2 -- typings/xterm.d.ts | 5 ++--- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/common/services/DecorationService.ts b/src/common/services/DecorationService.ts index d251dc35..fb7373bf 100644 --- a/src/common/services/DecorationService.ts +++ b/src/common/services/DecorationService.ts @@ -75,7 +75,6 @@ class Decoration extends Disposable implements IInternalDecoration { private _onDispose = this.register(new EventEmitter()); public readonly onDispose = this._onDispose.event; - // TODO: React to changes on options private _cachedBg: IColor | undefined | null = null; public get backgroundColorRGB(): IColor | undefined { if (this._cachedBg === null) { @@ -88,7 +87,6 @@ class Decoration extends Disposable implements IInternalDecoration { return this._cachedBg; } - // TODO: React to changes on options private _cachedFg: IColor | undefined | null = null; public get foregroundColorRGB(): IColor | undefined { if (this._cachedFg === null) { diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index fe1bb979..2de77989 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -445,7 +445,6 @@ declare module 'xterm' { * were provided initially. */ options: Pick; - // options: Pick; } @@ -493,13 +492,13 @@ declare module 'xterm' { * The background color of the cell(s). When 2 decorations both set the foreground color the * last registered decoration will be used. Only the `#RRGGBB` format is supported. */ - backgroundColor?: string; + readonly backgroundColor?: string; /** * The foreground color of the cell(s). When 2 decorations both set the foreground color the * last registered decoration will be used. Only the `#RRGGBB` format is supported. */ - foregroundColor?: string; + readonly foregroundColor?: string; /** * When defined, renders the decoration in the overview ruler to the right From e9643f593669211c8fa8277f23d0af9b3f6bbed6 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 10 May 2022 09:24:09 -0700 Subject: [PATCH 225/245] Ensure texture atlas isn't for overrides used in canvas renderer --- src/browser/renderer/BaseRenderLayer.ts | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/browser/renderer/BaseRenderLayer.ts b/src/browser/renderer/BaseRenderLayer.ts index a4f1e233..f30d95a7 100644 --- a/src/browser/renderer/BaseRenderLayer.ts +++ b/src/browser/renderer/BaseRenderLayer.ts @@ -326,7 +326,22 @@ export abstract class BaseRenderLayer implements IRenderLayer { this._currentGlyphIdentifier.bold = !!cell.isBold(); this._currentGlyphIdentifier.dim = !!cell.isDim(); this._currentGlyphIdentifier.italic = !!cell.isItalic(); - const atlasDidDraw = this._charAtlas?.draw(this._ctx, this._currentGlyphIdentifier, x * this._scaledCellWidth + this._scaledCharLeft, y * this._scaledCellHeight + this._scaledCharTop); + + // Don't try cache the glyph if it uses any decoration foreground/background override. + let hasOverrides = false; + const decorations = this._decorationService.getDecorationsOnLine(y); + for (const d of decorations) { + const xmin = d.options.x ?? 0; + const xmax = xmin + (d.options.width ?? 1); + if (x >= xmin && x < xmax) { + if (d.backgroundColorRGB || d.foregroundColorRGB) { + hasOverrides = true; + break; + } + } + } + + const atlasDidDraw = hasOverrides ? false : this._charAtlas?.draw(this._ctx, this._currentGlyphIdentifier, x * this._scaledCellWidth + this._scaledCharLeft, y * this._scaledCellHeight + this._scaledCharTop); if (!atlasDidDraw) { this._drawUncachedChars(cell, x, y); From 162879555751bb7dcb91d3708e409ba7398c9e04 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 10 May 2022 09:42:12 -0700 Subject: [PATCH 226/245] Fix fg/bg flags being set by negative ints --- addons/xterm-addon-webgl/src/WebglRenderer.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index 2a267ff8..495a9740 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -400,10 +400,10 @@ export class WebglRenderer extends Disposable implements IRenderer { const xmax = xmin + (d.options.width ?? 1); if (x >= xmin && x < xmax) { if (d.backgroundColorRGB) { - bgOverride = d.backgroundColorRGB.rgba; + bgOverride = (d.backgroundColorRGB.rgba >> 8) >>> 0 & 0xFFFFFF; } if (d.foregroundColorRGB) { - fgOverride = d.foregroundColorRGB.rgba; + fgOverride = (d.foregroundColorRGB.rgba >> 8) >>> 0 & 0xFFFFFF; } } } @@ -413,17 +413,17 @@ export class WebglRenderer extends Disposable implements IRenderer { if (bgOverride !== undefined) { // Non-RGB attributes from model + override + force RGB color mode if (this._workColors.fg & FgFlags.INVERSE) { - bgOverride = (this._workColors.bg & ~Attributes.RGB_MASK) | (fgOverride !== undefined ? fgOverride >> 8 : this._workColors.fg) | Attributes.CM_RGB; + bgOverride = (this._workColors.bg & ~Attributes.RGB_MASK) | (fgOverride !== undefined ? fgOverride : this._workColors.fg) | Attributes.CM_RGB; } else { - bgOverride = (this._workColors.bg & ~Attributes.RGB_MASK) | bgOverride >> 8 | Attributes.CM_RGB; + bgOverride = (this._workColors.bg & ~Attributes.RGB_MASK) | bgOverride | Attributes.CM_RGB; } } if (fgOverride !== undefined) { // Non-RGB attributes from model + force disable inverse + override + force RGB color mode if (this._workColors.fg & FgFlags.INVERSE) { - fgOverride = (this._workColors.fg & ~Attributes.RGB_MASK & ~FgFlags.INVERSE) | (bgOverride !== undefined ? bgOverride >> 8 : this._workColors.bg) | Attributes.CM_RGB; + fgOverride = (this._workColors.fg & ~Attributes.RGB_MASK & ~FgFlags.INVERSE) | (bgOverride !== undefined ? bgOverride : this._workColors.bg) | Attributes.CM_RGB; } else { - fgOverride = (this._workColors.fg & ~Attributes.RGB_MASK & ~FgFlags.INVERSE) | fgOverride >> 8 | Attributes.CM_RGB; + fgOverride = (this._workColors.fg & ~Attributes.RGB_MASK & ~FgFlags.INVERSE) | fgOverride | Attributes.CM_RGB; } } From afe1d7ed6fa733f897e100452c863be58ea1242d Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 10 May 2022 11:48:37 -0700 Subject: [PATCH 227/245] Fix webgl contrast tests since a bug in luminance was fixed --- .../test/WebglRenderer.api.ts | 38 +++++++++++-------- typings/xterm.d.ts | 2 +- 2 files changed, 24 insertions(+), 16 deletions(-) diff --git a/addons/xterm-addon-webgl/test/WebglRenderer.api.ts b/addons/xterm-addon-webgl/test/WebglRenderer.api.ts index 0b86b14b..0b3480ab 100644 --- a/addons/xterm-addon-webgl/test/WebglRenderer.api.ts +++ b/addons/xterm-addon-webgl/test/WebglRenderer.api.ts @@ -6,7 +6,7 @@ import { assert } from 'chai'; import { Browser, Page } from 'playwright'; import { ITheme } from 'xterm'; -import { getBrowserType, launchBrowser, openTerminal, pollFor, writeSync } from '../../../out-test/api/TestUtils'; +import { getBrowserType, launchBrowser, openTerminal, pollFor, timeout, writeSync } from '../../../out-test/api/TestUtils'; import { ITerminalOptions } from '../../../src/common/Types'; const APP = 'http://127.0.0.1:3001/test'; @@ -745,18 +745,18 @@ describe('WebGL Renderer Integration Tests', async () => { await page.evaluate(`window.term.options.minimumContrastRatio = 10;`); await pollFor(page, () => getCellColor(1, 1), [176, 180, 180, 255]); await pollFor(page, () => getCellColor(2, 1), [238, 158, 158, 255]); - await pollFor(page, () => getCellColor(3, 1), [197, 223, 171, 255]); - await pollFor(page, () => getCellColor(4, 1), [235, 221, 158, 255]); - await pollFor(page, () => getCellColor(5, 1), [124, 156, 198, 255]); - await pollFor(page, () => getCellColor(6, 1), [183, 165, 187, 255]); + await pollFor(page, () => getCellColor(3, 1), [152, 198, 110, 255]); + await pollFor(page, () => getCellColor(4, 1), [208, 179, 49, 255]); + await pollFor(page, () => getCellColor(5, 1), [161, 183, 215, 255]); + await pollFor(page, () => getCellColor(6, 1), [191, 174, 194, 255]); await pollFor(page, () => getCellColor(7, 1), [110, 197, 198, 255]); await pollFor(page, () => getCellColor(8, 1), [211, 215, 207, 255]); await pollFor(page, () => getCellColor(1, 2), [183, 185, 183, 255]); await pollFor(page, () => getCellColor(2, 2), [249, 156, 156, 255]); await pollFor(page, () => getCellColor(3, 2), [138, 226, 52, 255]); await pollFor(page, () => getCellColor(4, 2), [252, 233, 79, 255]); - await pollFor(page, () => getCellColor(5, 2), [114, 159, 207, 255]); - await pollFor(page, () => getCellColor(6, 2), [190, 152, 185, 255]); + await pollFor(page, () => getCellColor(5, 2), [154, 186, 221, 255]); + await pollFor(page, () => getCellColor(6, 2), [203, 173, 199, 255]); // Unchanged await pollFor(page, () => getCellColor(7, 2), [0x34, 0xe2, 0xe2, 255]); await pollFor(page, () => getCellColor(8, 2), [0xee, 0xee, 0xec, 255]); @@ -813,18 +813,18 @@ describe('WebGL Renderer Integration Tests', async () => { await page.evaluate(`window.term.options.minimumContrastRatio = 10;`); await pollFor(page, () => getCellColor(1, 1), [46, 52, 54, 255]); await pollFor(page, () => getCellColor(2, 1), [132, 0, 0, 255]); - await pollFor(page, () => getCellColor(3, 1), [78, 154, 6, 255]); - await pollFor(page, () => getCellColor(4, 1), [114, 93, 0, 255]); - await pollFor(page, () => getCellColor(5, 1), [19, 40, 68, 255]); - await pollFor(page, () => getCellColor(6, 1), [60, 40, 64, 255]); + await pollFor(page, () => getCellColor(3, 1), [36, 72, 0, 255]); + await pollFor(page, () => getCellColor(4, 1), [72, 59, 0, 255]); + await pollFor(page, () => getCellColor(5, 1), [32, 64, 106, 255]); + await pollFor(page, () => getCellColor(6, 1), [75, 51, 80, 255]); await pollFor(page, () => getCellColor(7, 1), [0, 71, 72, 255]); await pollFor(page, () => getCellColor(8, 1), [64, 64, 63, 255]); await pollFor(page, () => getCellColor(1, 2), [61, 63, 59, 255]); await pollFor(page, () => getCellColor(2, 2), [125, 19, 19, 255]); - await pollFor(page, () => getCellColor(3, 2), [89, 146, 32, 255]); - await pollFor(page, () => getCellColor(4, 2), [105, 98, 32, 255]); - await pollFor(page, () => getCellColor(5, 2), [36, 52, 70, 255]); - await pollFor(page, () => getCellColor(6, 2), [64, 45, 63, 255]); + await pollFor(page, () => getCellColor(3, 2), [40, 67, 13, 255]); + await pollFor(page, () => getCellColor(4, 2), [67, 63, 19, 255]); + await pollFor(page, () => getCellColor(5, 2), [45, 65, 87, 255]); + await pollFor(page, () => getCellColor(6, 2), [81, 57, 78, 255]); await pollFor(page, () => getCellColor(7, 2), [13, 67, 67, 255]); await pollFor(page, () => getCellColor(8, 2), [64, 64, 64, 255]); }); @@ -874,6 +874,14 @@ describe('WebGL Renderer Integration Tests', async () => { await pollFor(page, () => getCellColor(1, 1), [255, 0, 0, 255]); }); }); + + describe('decoration color overrides', async () => { + await page.evaluate(` + window.term.registerDecoration({ + x: + }); + `); + }); }); async function getCellColor(col: number, row: number): Promise { diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 2de77989..0c421dc0 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -498,7 +498,7 @@ declare module 'xterm' { * The foreground color of the cell(s). When 2 decorations both set the foreground color the * last registered decoration will be used. Only the `#RRGGBB` format is supported. */ - readonly foregroundColor?: string; + readonly foregroundColor?: string; /** * When defined, renders the decoration in the overview ruler to the right From 6b4df216d30b039dd9ff69381c39891292364ea3 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 10 May 2022 12:08:42 -0700 Subject: [PATCH 228/245] Remove unneeded code --- addons/xterm-addon-webgl/src/GlyphRenderer.ts | 6 +- .../src/RectangleRenderer.ts | 6 +- addons/xterm-addon-webgl/src/WebglRenderer.ts | 4 +- .../test/WebglRenderer.api.ts | 65 +++++++++++++++++-- 4 files changed, 64 insertions(+), 17 deletions(-) diff --git a/addons/xterm-addon-webgl/src/GlyphRenderer.ts b/addons/xterm-addon-webgl/src/GlyphRenderer.ts index 7d35479a..f3fd53a6 100644 --- a/addons/xterm-addon-webgl/src/GlyphRenderer.ts +++ b/addons/xterm-addon-webgl/src/GlyphRenderer.ts @@ -9,13 +9,12 @@ import { IWebGL2RenderingContext, IWebGLVertexArrayObject, IRenderModel, IRaster import { COMBINED_CHAR_BIT_MASK, RENDER_MODEL_INDICIES_PER_CELL, RENDER_MODEL_FG_OFFSET, RENDER_MODEL_BG_OFFSET } from './RenderModel'; import { fill } from 'common/TypedArrayUtils'; import { slice } from './TypedArray'; -import { NULL_CELL_CODE, WHITESPACE_CELL_CODE, Attributes, FgFlags } from 'common/buffer/Constants'; +import { NULL_CELL_CODE, Attributes, FgFlags } from 'common/buffer/Constants'; import { Terminal, IBufferLine } from 'xterm'; import { IColor } from 'common/Types'; import { IColorSet } from 'browser/Types'; import { IRenderDimensions } from 'browser/renderer/Types'; import { AttributeData } from 'common/buffer/AttributeData'; -import { IDecorationService } from 'common/services/Services'; interface IVertices { attributes: Float32Array; @@ -101,8 +100,7 @@ export class GlyphRenderer { private _terminal: Terminal, private _colors: IColorSet, private _gl: IWebGL2RenderingContext, - private _dimensions: IRenderDimensions, - private readonly _decorationService: IDecorationService + private _dimensions: IRenderDimensions ) { const gl = this._gl; const program = throwIfFalsy(createProgram(gl, vertexShaderSource, fragmentShaderSource)); diff --git a/addons/xterm-addon-webgl/src/RectangleRenderer.ts b/addons/xterm-addon-webgl/src/RectangleRenderer.ts index ae6258f0..ab0b34e9 100644 --- a/addons/xterm-addon-webgl/src/RectangleRenderer.ts +++ b/addons/xterm-addon-webgl/src/RectangleRenderer.ts @@ -12,7 +12,6 @@ import { IColor } from 'common/Types'; import { IColorSet } from 'browser/Types'; import { IRenderDimensions } from 'browser/renderer/Types'; import { RENDER_MODEL_BG_OFFSET, RENDER_MODEL_FG_OFFSET, RENDER_MODEL_INDICIES_PER_CELL } from './RenderModel'; -import { IDecorationService } from 'common/services/Services'; const enum VertexAttribLocations { POSITION = 0, @@ -80,8 +79,7 @@ export class RectangleRenderer { private _terminal: Terminal, private _colors: IColorSet, private _gl: IWebGL2RenderingContext, - private _dimensions: IRenderDimensions, - private readonly _decorationService: IDecorationService + private _dimensions: IRenderDimensions ) { const gl = this._gl; @@ -254,10 +252,8 @@ export class RectangleRenderer { let currentInverse = false; for (let x = 0; x < terminal.cols; x++) { const modelIndex = ((y * terminal.cols) + x) * RENDER_MODEL_INDICIES_PER_CELL; - const bg = model.cells[modelIndex + RENDER_MODEL_BG_OFFSET]; const fg = model.cells[modelIndex + RENDER_MODEL_FG_OFFSET]; - const inverse = !!(fg & FgFlags.INVERSE); if (bg !== currentBg || (fg !== currentFg && (currentInverse || inverse))) { // A rectangle needs to be drawn if going from non-default to another color diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index 495a9740..6d84bd26 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -98,8 +98,8 @@ export class WebglRenderer extends Disposable implements IRenderer { this._core.screenElement!.appendChild(this._canvas); - this._rectangleRenderer = new RectangleRenderer(this._terminal, this._colors, this._gl, this.dimensions, _decorationService); - this._glyphRenderer = new GlyphRenderer(this._terminal, this._colors, this._gl, this.dimensions, _decorationService); + this._rectangleRenderer = new RectangleRenderer(this._terminal, this._colors, this._gl, this.dimensions); + this._glyphRenderer = new GlyphRenderer(this._terminal, this._colors, this._gl, this.dimensions); // Update dimensions and acquire char atlas this.onCharSizeChanged(); diff --git a/addons/xterm-addon-webgl/test/WebglRenderer.api.ts b/addons/xterm-addon-webgl/test/WebglRenderer.api.ts index 0b3480ab..15359409 100644 --- a/addons/xterm-addon-webgl/test/WebglRenderer.api.ts +++ b/addons/xterm-addon-webgl/test/WebglRenderer.api.ts @@ -875,12 +875,65 @@ describe('WebGL Renderer Integration Tests', async () => { }); }); - describe('decoration color overrides', async () => { - await page.evaluate(` - window.term.registerDecoration({ - x: - }); - `); + describe.only('decoration color overrides', async () => { + if (areTestsEnabled) { + before(async () => setupBrowser({ rendererType: 'dom', allowTransparency: true })); + after(async () => browser.close()); + beforeEach(async () => page.evaluate(`window.term.reset()`)); + } + + itWebgl('foregroundColor', async () => { + await page.evaluate(` + const marker = window.term.registerMarker(-window.term.buffer.active.cursorY); + window.term.registerDecoration({ + marker, + foregroundColor: '#ff0000', + backgroundColor: '#0000ff' + }); + `); + const data = `█`; + await writeSync(page, data); + await pollFor(page, () => getCellColor(1, 1), [255, 0, 0, 255]); + }); + itWebgl('foregroundColor should ignore inverse', async () => { + await page.evaluate(` + const marker = window.term.registerMarker(-window.term.buffer.active.cursorY); + window.term.registerDecoration({ + marker, + foregroundColor: '#ff0000', + backgroundColor: '#0000ff' + }); + `); + const data = `\\x1b[7m█\\x1b0m`; + await writeSync(page, data); + await pollFor(page, () => getCellColor(1, 1), [255, 0, 0, 255]); + }); + itWebgl('backgroundColor', async () => { + await page.evaluate(` + const marker = window.term.registerMarker(-window.term.buffer.active.cursorY); + window.term.registerDecoration({ + marker, + foregroundColor: '#ff0000', + backgroundColor: '#0000ff' + }); + `); + const data = ` `; + await writeSync(page, data); + await pollFor(page, () => getCellColor(1, 1), [0, 0, 255, 255]); + }); + itWebgl('backgroundColor should ignore inverse', async () => { + await page.evaluate(` + const marker = window.term.registerMarker(-window.term.buffer.active.cursorY); + window.term.registerDecoration({ + marker, + foregroundColor: '#ff0000', + backgroundColor: '#0000ff' + }); + `); + const data = `\\x1b[7m \\x1b0m`; + await writeSync(page, data); + await pollFor(page, () => getCellColor(1, 1), [0, 0, 255, 255]); + }); }); }); From edba006045ea2a9acf459385bae442ed0d5ad5d7 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 10 May 2022 12:32:06 -0700 Subject: [PATCH 229/245] Clear all decorations on reset --- src/browser/Terminal.ts | 1 + src/common/TestUtils.test.ts | 1 + src/common/services/DecorationService.ts | 7 +++++++ src/common/services/Services.ts | 1 + 4 files changed, 10 insertions(+) diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index 491a209e..a8accd78 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -1358,6 +1358,7 @@ export class Terminal extends CoreTerminal implements ITerminal { this._setup(); super.reset(); this._selectionService?.reset(); + this._decorationService.reset(); // reattach this._customKeyEventHandler = customKeyEventHandler; diff --git a/src/common/TestUtils.test.ts b/src/common/TestUtils.test.ts index 1ec5a05a..10b6b5ef 100644 --- a/src/common/TestUtils.test.ts +++ b/src/common/TestUtils.test.ts @@ -166,6 +166,7 @@ export class MockDecorationService implements IDecorationService { public onDecorationRegistered = new EventEmitter().event; public onDecorationRemoved = new EventEmitter().event; public registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined { return undefined; } + public reset(): void { } public *getDecorationsOnLine(line: number): IterableIterator { } public dispose(): void { } } diff --git a/src/common/services/DecorationService.ts b/src/common/services/DecorationService.ts index 00ea3236..3c646501 100644 --- a/src/common/services/DecorationService.ts +++ b/src/common/services/DecorationService.ts @@ -49,6 +49,13 @@ export class DecorationService extends Disposable implements IDecorationService return decoration; } + public reset(): void { + for (let i = 0; i < this._decorations.length; i++) { + this._decorations[0].dispose(); + } + this._decorations.length = 0; + } + public *getDecorationsOnLine(line: number): IterableIterator { // TODO: This could be made much faster if _decorations was sorted by line (and col?) for (const d of this.decorations) { diff --git a/src/common/services/Services.ts b/src/common/services/Services.ts index e086ff56..c6d47816 100644 --- a/src/common/services/Services.ts +++ b/src/common/services/Services.ts @@ -308,6 +308,7 @@ export interface IDecorationService extends IDisposable { readonly onDecorationRegistered: IEvent; readonly onDecorationRemoved: IEvent; registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined; + reset(): void; /** Iterates over the decorations on a line (in no particular order). */ getDecorationsOnLine(line: number): IterableIterator; } From abdce874dba252238572a002ffb6d93bf3289874 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 11 May 2022 08:31:09 -0700 Subject: [PATCH 230/245] Fix cases where only one override was with tests --- addons/xterm-addon-webgl/src/WebglRenderer.ts | 32 ++++++++++++----- .../test/WebglRenderer.api.ts | 36 ++++++++++++++++--- 2 files changed, 55 insertions(+), 13 deletions(-) diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index 6d84bd26..c42c0067 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -412,18 +412,32 @@ export class WebglRenderer extends Disposable implements IRenderer { // ahead of time in order to use the correct cache key if (bgOverride !== undefined) { // Non-RGB attributes from model + override + force RGB color mode - if (this._workColors.fg & FgFlags.INVERSE) { - bgOverride = (this._workColors.bg & ~Attributes.RGB_MASK) | (fgOverride !== undefined ? fgOverride : this._workColors.fg) | Attributes.CM_RGB; - } else { - bgOverride = (this._workColors.bg & ~Attributes.RGB_MASK) | bgOverride | Attributes.CM_RGB; - } + bgOverride = (this._workCell.bg & ~Attributes.RGB_MASK) | bgOverride | Attributes.CM_RGB; } if (fgOverride !== undefined) { // Non-RGB attributes from model + force disable inverse + override + force RGB color mode - if (this._workColors.fg & FgFlags.INVERSE) { - fgOverride = (this._workColors.fg & ~Attributes.RGB_MASK & ~FgFlags.INVERSE) | (bgOverride !== undefined ? bgOverride : this._workColors.bg) | Attributes.CM_RGB; - } else { - fgOverride = (this._workColors.fg & ~Attributes.RGB_MASK & ~FgFlags.INVERSE) | fgOverride | Attributes.CM_RGB; + fgOverride = (this._workCell.fg & ~Attributes.RGB_MASK & ~FgFlags.INVERSE) | fgOverride | Attributes.CM_RGB; + } + + // Handle case where inverse was specified by only one of bgOverride or fgOverride was set, + // resolving the other inverse color and setting the inverse flag if needed. + if (this._workColors.fg & FgFlags.INVERSE) { + if (bgOverride !== undefined && fgOverride === undefined) { + // Resolve bg color type (default color has a different meaning in fg vs bg) + debugger; + if ((this._workColors.bg & Attributes.CM_MASK) === Attributes.CM_DEFAULT) { + fgOverride = (this._workColors.fg & ~(Attributes.RGB_MASK | FgFlags.INVERSE | Attributes.CM_MASK)) | ((this._colors.background.rgba >> 8 & 0xFFFFFF) & Attributes.RGB_MASK) | Attributes.CM_RGB; + } else { + fgOverride = (this._workColors.fg & ~(Attributes.RGB_MASK | FgFlags.INVERSE | Attributes.CM_MASK)) | this._workColors.bg & (Attributes.RGB_MASK | Attributes.CM_MASK); + } + } + if (bgOverride === undefined && fgOverride !== undefined) { + // Resolve bg color type (default color has a different meaning in fg vs bg) + if ((this._workColors.fg & Attributes.CM_MASK) === Attributes.CM_DEFAULT) { + bgOverride = (this._workColors.bg & ~(Attributes.RGB_MASK | Attributes.CM_MASK)) | ((this._colors.foreground.rgba >> 8 & 0xFFFFFF) & Attributes.RGB_MASK) | Attributes.CM_RGB; + } else { + bgOverride = (this._workColors.bg & ~(Attributes.RGB_MASK | Attributes.CM_MASK)) | this._workColors.fg & (Attributes.RGB_MASK | Attributes.CM_MASK); + } } } diff --git a/addons/xterm-addon-webgl/test/WebglRenderer.api.ts b/addons/xterm-addon-webgl/test/WebglRenderer.api.ts index 15359409..cec1e3b1 100644 --- a/addons/xterm-addon-webgl/test/WebglRenderer.api.ts +++ b/addons/xterm-addon-webgl/test/WebglRenderer.api.ts @@ -875,9 +875,9 @@ describe('WebGL Renderer Integration Tests', async () => { }); }); - describe.only('decoration color overrides', async () => { + describe('decoration color overrides', async () => { if (areTestsEnabled) { - before(async () => setupBrowser({ rendererType: 'dom', allowTransparency: true })); + before(async () => setupBrowser({ rendererType: 'dom' })); after(async () => browser.close()); beforeEach(async () => page.evaluate(`window.term.reset()`)); } @@ -904,10 +904,24 @@ describe('WebGL Renderer Integration Tests', async () => { backgroundColor: '#0000ff' }); `); - const data = `\\x1b[7m█\\x1b0m`; + const data = `\\x1b[7m█\\x1b[0m`; await writeSync(page, data); await pollFor(page, () => getCellColor(1, 1), [255, 0, 0, 255]); }); + itWebgl('foregroundColor should ignore inverse (only fg on decoration)', async () => { + await page.evaluate(` + const marker = window.term.registerMarker(-window.term.buffer.active.cursorY); + window.term.registerDecoration({ + marker, + width: 2, + foregroundColor: '#ff0000' + }); + `); + const data = `\\x1b[7m█ \\x1b[0m`; + await writeSync(page, data); + await pollFor(page, () => getCellColor(1, 1), [255, 0, 0, 255]); // inverse foreground of '█' should be decoration fg override + await pollFor(page, () => getCellColor(2, 1), [255, 255, 255, 255]); // inverse background of ' ' should be default foreground + }); itWebgl('backgroundColor', async () => { await page.evaluate(` const marker = window.term.registerMarker(-window.term.buffer.active.cursorY); @@ -930,10 +944,24 @@ describe('WebGL Renderer Integration Tests', async () => { backgroundColor: '#0000ff' }); `); - const data = `\\x1b[7m \\x1b0m`; + const data = `\\x1b[7m \\x1b[0m`; await writeSync(page, data); await pollFor(page, () => getCellColor(1, 1), [0, 0, 255, 255]); }); + itWebgl('backgroundColor should ignore inverse (only bg on decoration)', async () => { + const data = `\\x1b[7m█ \\x1b[0m`; + await writeSync(page, data); + await page.evaluate(` + const marker = window.term.registerMarker(-window.term.buffer.active.cursorY); + window.term.registerDecoration({ + marker, + width: 2, + backgroundColor: '#0000ff' + }); + `); + await pollFor(page, () => getCellColor(1, 1), [0, 0, 0, 255]); // inverse foreground of '█' should be default + await pollFor(page, () => getCellColor(2, 1), [0, 0, 255, 255]); // inverse background of ' ' should be decoration bg override + }); }); }); From c0b4edaf31c2eb1cd44d204757e6e9559a80a100 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 11 May 2022 08:37:42 -0700 Subject: [PATCH 231/245] Reduce duplicate with getDecorationsAtCell method --- addons/xterm-addon-webgl/src/WebglRenderer.ts | 17 ++++------- src/browser/renderer/BaseRenderLayer.ts | 30 +++++++------------ src/browser/renderer/TextRenderLayer.ts | 11 ++----- .../renderer/dom/DomRendererRowFactory.ts | 25 +++++++--------- src/common/TestUtils.test.ts | 3 +- src/common/services/DecorationService.ts | 16 +++++++++- src/common/services/Services.ts | 6 ++-- 7 files changed, 50 insertions(+), 58 deletions(-) diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index c42c0067..5d853f0f 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -392,19 +392,14 @@ export class WebglRenderer extends Disposable implements IRenderer { // Get any decoration foreground/background overrides, this happens on the model to avoid // spreading decoration override logic throughout the different sub-renderers - const decorations = this._decorationService.getDecorationsOnLine(y); let bgOverride: number | undefined; let fgOverride: number | undefined; - for (const d of decorations) { - const xmin = d.options.x ?? 0; - const xmax = xmin + (d.options.width ?? 1); - if (x >= xmin && x < xmax) { - if (d.backgroundColorRGB) { - bgOverride = (d.backgroundColorRGB.rgba >> 8) >>> 0 & 0xFFFFFF; - } - if (d.foregroundColorRGB) { - fgOverride = (d.foregroundColorRGB.rgba >> 8) >>> 0 & 0xFFFFFF; - } + for (const d of this._decorationService.getDecorationsAtCell(x, y)) { + if (d.backgroundColorRGB) { + bgOverride = (d.backgroundColorRGB.rgba >> 8) >>> 0 & 0xFFFFFF; + } + if (d.foregroundColorRGB) { + fgOverride = (d.foregroundColorRGB.rgba >> 8) >>> 0 & 0xFFFFFF; } } diff --git a/src/browser/renderer/BaseRenderLayer.ts b/src/browser/renderer/BaseRenderLayer.ts index f30d95a7..e0f3566c 100644 --- a/src/browser/renderer/BaseRenderLayer.ts +++ b/src/browser/renderer/BaseRenderLayer.ts @@ -329,15 +329,10 @@ export abstract class BaseRenderLayer implements IRenderLayer { // Don't try cache the glyph if it uses any decoration foreground/background override. let hasOverrides = false; - const decorations = this._decorationService.getDecorationsOnLine(y); - for (const d of decorations) { - const xmin = d.options.x ?? 0; - const xmax = xmin + (d.options.width ?? 1); - if (x >= xmin && x < xmax) { - if (d.backgroundColorRGB || d.foregroundColorRGB) { - hasOverrides = true; - break; - } + for (const d of this._decorationService.getDecorationsAtCell(x, y)) { + if (d.backgroundColorRGB || d.foregroundColorRGB) { + hasOverrides = true; + break; } } @@ -446,19 +441,14 @@ export abstract class BaseRenderLayer implements IRenderLayer { private _getContrastColor(cell: CellData, x: number, y: number): IColor | undefined { // Get any decoration foreground/background overrides, this must be fetched before the early // exist but applied after inverse - const decorations = this._decorationService.getDecorationsOnLine(y); let bgOverride: number | undefined; let fgOverride: number | undefined; - for (const d of decorations) { - const xmin = d.options.x ?? 0; - const xmax = xmin + (d.options.width ?? 1); - if (x >= xmin && x < xmax) { - if (d.backgroundColorRGB) { - bgOverride = d.backgroundColorRGB.rgba; - } - if (d.foregroundColorRGB) { - fgOverride = d.foregroundColorRGB.rgba; - } + for (const d of this._decorationService.getDecorationsAtCell(x, y)) { + if (d.backgroundColorRGB) { + bgOverride = d.backgroundColorRGB.rgba; + } + if (d.foregroundColorRGB) { + fgOverride = d.foregroundColorRGB.rgba; } } diff --git a/src/browser/renderer/TextRenderLayer.ts b/src/browser/renderer/TextRenderLayer.ts index e94b53e0..193d891d 100644 --- a/src/browser/renderer/TextRenderLayer.ts +++ b/src/browser/renderer/TextRenderLayer.ts @@ -179,14 +179,9 @@ export class TextRenderLayer extends BaseRenderLayer { // Get any decoration foreground/background overrides, this must be fetched before the early // exist but applied after inverse - const decorations = this._decorationService.getDecorationsOnLine(this._bufferService.buffer.ydisp + y); - for (const d of decorations) { - const xmin = d.options.x ?? 0; - const xmax = xmin + (d.options.width ?? 1); - if (x >= xmin && x < xmax) { - if (d.backgroundColorRGB) { - nextFillStyle = d.backgroundColorRGB.css; - } + for (const d of this._decorationService.getDecorationsAtCell(x, this._bufferService.buffer.ydisp + y)) { + if (d.backgroundColorRGB) { + nextFillStyle = d.backgroundColorRGB.css; } } diff --git a/src/browser/renderer/dom/DomRendererRowFactory.ts b/src/browser/renderer/dom/DomRendererRowFactory.ts index 24a7b2c6..c92f82c6 100644 --- a/src/browser/renderer/dom/DomRendererRowFactory.ts +++ b/src/browser/renderer/dom/DomRendererRowFactory.ts @@ -175,23 +175,18 @@ export class DomRendererRowFactory { // Apply any decoration foreground/background overrides, this must happen after inverse has // been applied - const decorations = this._decorationService.getDecorationsOnLine(row); let bgOverride: IColor | undefined; let fgOverride: IColor | undefined; - for (const d of decorations) { - const xmin = d.options.x ?? 0; - const xmax = xmin + (d.options.width ?? 1); - if (x >= xmin && x < xmax) { - if (d.backgroundColorRGB) { - bgColorMode = Attributes.CM_RGB; - bg = d.backgroundColorRGB.rgba >> 8 & 0xFFFFFF; - bgOverride = d.backgroundColorRGB; - } - if (d.foregroundColorRGB) { - fgColorMode = Attributes.CM_RGB; - fg = d.foregroundColorRGB.rgba >> 8 & 0xFFFFFF; - fgOverride = d.foregroundColorRGB; - } + for (const d of this._decorationService.getDecorationsAtCell(x, row)) { + if (d.backgroundColorRGB) { + bgColorMode = Attributes.CM_RGB; + bg = d.backgroundColorRGB.rgba >> 8 & 0xFFFFFF; + bgOverride = d.backgroundColorRGB; + } + if (d.foregroundColorRGB) { + fgColorMode = Attributes.CM_RGB; + fg = d.foregroundColorRGB.rgba >> 8 & 0xFFFFFF; + fgOverride = d.foregroundColorRGB; } } diff --git a/src/common/TestUtils.test.ts b/src/common/TestUtils.test.ts index 10b6b5ef..11d9a8c5 100644 --- a/src/common/TestUtils.test.ts +++ b/src/common/TestUtils.test.ts @@ -167,6 +167,7 @@ export class MockDecorationService implements IDecorationService { public onDecorationRemoved = new EventEmitter().event; public registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined { return undefined; } public reset(): void { } - public *getDecorationsOnLine(line: number): IterableIterator { } + public *getDecorationsAtLine(line: number): IterableIterator { } + public *getDecorationsAtCell(x: number, line: number): IterableIterator { } public dispose(): void { } } diff --git a/src/common/services/DecorationService.ts b/src/common/services/DecorationService.ts index 3c646501..ed58c813 100644 --- a/src/common/services/DecorationService.ts +++ b/src/common/services/DecorationService.ts @@ -56,7 +56,7 @@ export class DecorationService extends Disposable implements IDecorationService this._decorations.length = 0; } - public *getDecorationsOnLine(line: number): IterableIterator { + public *getDecorationsAtLine(line: number): IterableIterator { // TODO: This could be made much faster if _decorations was sorted by line (and col?) for (const d of this.decorations) { if (d.marker.line === line) { @@ -65,6 +65,20 @@ export class DecorationService extends Disposable implements IDecorationService } } + public *getDecorationsAtCell(x: number, line: number): IterableIterator { + let xmin = 0; + let xmax = 0; + for (const d of this.decorations) { + if (d.marker.line === line) { + xmin = d.options.x ?? 0; + xmax = xmin + (d.options.width ?? 1); + if (x >= xmin && x < xmax) { + yield d; + } + } + } + } + public dispose(): void { for (const decoration of this._decorations) { this._onDecorationRemoved.fire(decoration); diff --git a/src/common/services/Services.ts b/src/common/services/Services.ts index c6d47816..82492eb4 100644 --- a/src/common/services/Services.ts +++ b/src/common/services/Services.ts @@ -309,8 +309,10 @@ export interface IDecorationService extends IDisposable { readonly onDecorationRemoved: IEvent; registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined; reset(): void; - /** Iterates over the decorations on a line (in no particular order). */ - getDecorationsOnLine(line: number): IterableIterator; + /** Iterates over the decorations at a line (in no particular order). */ + getDecorationsAtLine(line: number): IterableIterator; + /** Iterates over the decorations at a cell (in no particular order). */ + getDecorationsAtCell(x: number, line: number): IterableIterator; } export interface IInternalDecoration extends IDecoration { readonly options: IDecorationOptions; From 931ee9e89ef776a10b9659ec2d20695ae1220dfc Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 11 May 2022 09:34:25 -0700 Subject: [PATCH 232/245] Maintain decorations sorted by line --- src/common/SortedList.test.ts | 105 +++++++++++++++++++++++ src/common/SortedList.ts | 80 +++++++++++++++++ src/common/services/DecorationService.ts | 47 +++++----- 3 files changed, 207 insertions(+), 25 deletions(-) create mode 100644 src/common/SortedList.test.ts create mode 100644 src/common/SortedList.ts diff --git a/src/common/SortedList.test.ts b/src/common/SortedList.test.ts new file mode 100644 index 00000000..5ecdbb77 --- /dev/null +++ b/src/common/SortedList.test.ts @@ -0,0 +1,105 @@ +/** + * Copyright (c) 2018 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { assert } from 'chai'; +import { SortedList } from 'common/SortedList'; + +const deepStrictEqual = assert.deepStrictEqual; + +describe('SortedList', () => { + let list: SortedList; + function assertList(expected: number[]): void { + deepStrictEqual(Array.from(list.values()), expected); + } + + beforeEach(() => { + list = new SortedList(e => e); + }); + + describe('insert', () => { + it('should maintain sorted values', () => { + list.insert(10); + assertList([10]); + list.insert(8); + assertList([8, 10]); + list.insert(15); + assertList([8, 10, 15]); + list.insert(2); + assertList([2, 8, 10, 15]); + list.insert(1); + assertList([1, 2, 8, 10, 15]); + list.insert(6); + assertList([1, 2, 6, 8, 10, 15]); + }); + it('should allow duplicates of the same key', () => { + list.insert(5); + assertList([5]); + list.insert(5); + assertList([5, 5]); + list.insert(8); + assertList([5, 5, 8]); + list.insert(5); + assertList([5, 5, 5, 8]); + list.insert(8); + assertList([5, 5, 5, 8, 8]); + list.insert(6); + assertList([5, 5, 5, 6, 8, 8]); + }); + }); + it('delete', () => { + list.insert(1); + list.insert(2); + list.insert(4); + list.insert(3); + list.insert(5); + assertList([1, 2, 3, 4, 5]); + list.delete(1); + assertList([2, 3, 4, 5]); + list.delete(3); + assertList([2, 4, 5]); + list.delete(4); + assertList([2, 5]); + list.delete(5); + assertList([2]); + list.delete(2); + assertList([]); + }); + it('getKeyIterator', () => { + list.insert(5); + list.insert(5); + list.insert(8); + list.insert(5); + list.insert(8); + list.insert(6); + assertList([5, 5, 5, 6, 8, 8]); + deepStrictEqual(Array.from(list.getKeyIterator(5)), [5, 5, 5]); + deepStrictEqual(Array.from(list.getKeyIterator(6)), [6]); + deepStrictEqual(Array.from(list.getKeyIterator(8)), [8, 8]); + }); + it('clear', () => { + list.insert(1); + list.insert(2); + list.insert(4); + list.insert(3); + list.insert(5); + list.clear(); + assertList([]); + }); + it('custom key', () => { + const customList = new SortedList<{ key: number }>(e => e.key); + customList.insert({ key: 5 }); + customList.insert({ key: 2 }); + customList.insert({ key: 10 }); + customList.insert({ key: 5 }); + customList.insert({ key: 6 }); + deepStrictEqual(Array.from(customList.values()), [ + { key: 2 }, + { key: 5 }, + { key: 5 }, + { key: 6 }, + { key: 10 } + ]); + }); +}); diff --git a/src/common/SortedList.ts b/src/common/SortedList.ts new file mode 100644 index 00000000..2112a73f --- /dev/null +++ b/src/common/SortedList.ts @@ -0,0 +1,80 @@ +/** + * Copyright (c) 2022 The xterm.js authors. All rights reserved. + * @license MIT + */ + +export class SortedList { + private readonly _array: T[] = []; + + constructor( + private readonly _getKey: (value: T) => number + ) { + } + + public clear(): void { + this._array.length = 0; + } + + public insert(value: T): void { + if (this._array.length === 0) { + this._array.push(value); + return; + } + const i = this._search(this._getKey(value), 0, this._array.length - 1); + this._array.splice(i, 0, value); + } + + public delete(value: T): boolean { + if (this._array.length === 0) { + return false; + } + const key = this._getKey(value); + let i = this._search(key, 0, this._array.length - 1); + if (this._getKey(this._array[i]) !== key) { + return false; + } + do { + if (this._array[i] === value) { + this._array.splice(i, 1); + return true; + } + } while (++i < this._array.length && this._getKey(this._array[i]) === key); + return false; + } + + public *getKeyIterator(key: number): IterableIterator { + if (this._array.length === 0) { + return; + } + let i = this._search(key, 0, this._array.length - 1); + if (this._getKey(this._array[i]) !== key) { + return; + } + do { + yield this._array[i]; + } while (++i < this._array.length && this._getKey(this._array[i]) === key); + } + + public values(): IterableIterator { + return this._array.values(); + } + + private _search(key: number, min: number, max: number): number { + if (max < min) { + return min; + } + let mid = Math.floor((min + max) / 2); + if (this._getKey(this._array[mid]) > key) { + return this._search(key, min, mid - 1); + } + if (this._getKey(this._array[mid]) < key) { + return this._search(key, mid + 1, max); + } + // Value found! Since keys can be duplicates, move the result index back to the lowest index + // that matches the key. + while (mid > 0 && this._getKey(this._array[mid - 1]) === key) { + mid--; + } + return mid; + } +} diff --git a/src/common/services/DecorationService.ts b/src/common/services/DecorationService.ts index ed58c813..58718333 100644 --- a/src/common/services/DecorationService.ts +++ b/src/common/services/DecorationService.ts @@ -7,13 +7,19 @@ import { css } from 'common/Color'; import { EventEmitter } from 'common/EventEmitter'; import { Disposable } from 'common/Lifecycle'; import { IDecorationService, IInternalDecoration } from 'common/services/Services'; +import { SortedList } from 'common/SortedList'; import { IColor } from 'common/Types'; import { IDecorationOptions, IDecoration, IMarker, IEvent } from 'xterm'; export class DecorationService extends Disposable implements IDecorationService { public serviceBrand: any; - private readonly _decorations: IInternalDecoration[] = []; + /** + * A list of all decorations, sorted by the marker's line value. This relies on the fact that + * while marker line values do change, they should all change by the same amount so this should + * never become out of order. + */ + private readonly _decorations: SortedList = new SortedList(e => e.marker.line); private _onDecorationRegistered = this.register(new EventEmitter()); public get onDecorationRegistered(): IEvent { return this._onDecorationRegistered.event; } @@ -35,56 +41,47 @@ export class DecorationService extends Disposable implements IDecorationService const markerDispose = decoration.marker.onDispose(() => decoration.dispose()); decoration.onDispose(() => { if (decoration) { - const index = this._decorations.indexOf(decoration); - if (index >= 0) { - this._decorations.splice(this._decorations.indexOf(decoration), 1); + if (this._decorations.delete(decoration)) { this._onDecorationRemoved.fire(decoration); } markerDispose.dispose(); } }); - this._decorations.push(decoration); + this._decorations.insert(decoration); this._onDecorationRegistered.fire(decoration); } return decoration; } public reset(): void { - for (let i = 0; i < this._decorations.length; i++) { - this._decorations[0].dispose(); + for (const d of this._decorations.values()) { + d.dispose(); } - this._decorations.length = 0; + this._decorations.clear(); } public *getDecorationsAtLine(line: number): IterableIterator { - // TODO: This could be made much faster if _decorations was sorted by line (and col?) - for (const d of this.decorations) { - if (d.marker.line === line) { - yield d; - } - } + return this._decorations.getKeyIterator(line); } public *getDecorationsAtCell(x: number, line: number): IterableIterator { let xmin = 0; let xmax = 0; - for (const d of this.decorations) { - if (d.marker.line === line) { - xmin = d.options.x ?? 0; - xmax = xmin + (d.options.width ?? 1); - if (x >= xmin && x < xmax) { - yield d; - } + for (const d of this._decorations.getKeyIterator(line)) { + console.log('d', d); + xmin = d.options.x ?? 0; + xmax = xmin + (d.options.width ?? 1); + if (x >= xmin && x < xmax) { + yield d; } } } public dispose(): void { - for (const decoration of this._decorations) { - this._onDecorationRemoved.fire(decoration); - decoration.dispose(); + for (const d of this._decorations.values()) { + this._onDecorationRemoved.fire(d); } - this._decorations.length = 0; + this.reset(); } } From 999f2558933cf5df9598615eab2ac588256867b8 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 11 May 2022 09:49:23 -0700 Subject: [PATCH 233/245] Fix sorted list edge case --- addons/xterm-addon-webgl/src/WebglRenderer.ts | 1 - src/common/SortedList.test.ts | 2 ++ src/common/SortedList.ts | 8 ++++++++ 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index 5d853f0f..e80464da 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -419,7 +419,6 @@ export class WebglRenderer extends Disposable implements IRenderer { if (this._workColors.fg & FgFlags.INVERSE) { if (bgOverride !== undefined && fgOverride === undefined) { // Resolve bg color type (default color has a different meaning in fg vs bg) - debugger; if ((this._workColors.bg & Attributes.CM_MASK) === Attributes.CM_DEFAULT) { fgOverride = (this._workColors.fg & ~(Attributes.RGB_MASK | FgFlags.INVERSE | Attributes.CM_MASK)) | ((this._colors.background.rgba >> 8 & 0xFFFFFF) & Attributes.RGB_MASK) | Attributes.CM_RGB; } else { diff --git a/src/common/SortedList.test.ts b/src/common/SortedList.test.ts index 5ecdbb77..ecafdb8f 100644 --- a/src/common/SortedList.test.ts +++ b/src/common/SortedList.test.ts @@ -74,9 +74,11 @@ describe('SortedList', () => { list.insert(8); list.insert(6); assertList([5, 5, 5, 6, 8, 8]); + deepStrictEqual(Array.from(list.getKeyIterator(1)), []); deepStrictEqual(Array.from(list.getKeyIterator(5)), [5, 5, 5]); deepStrictEqual(Array.from(list.getKeyIterator(6)), [6]); deepStrictEqual(Array.from(list.getKeyIterator(8)), [8, 8]); + deepStrictEqual(Array.from(list.getKeyIterator(9)), []); }); it('clear', () => { list.insert(1); diff --git a/src/common/SortedList.ts b/src/common/SortedList.ts index 2112a73f..051c6702 100644 --- a/src/common/SortedList.ts +++ b/src/common/SortedList.ts @@ -3,6 +3,11 @@ * @license MIT */ +/** + * A generic list that is maintained in sorted order and allows values with duplicate keys. This + * list is based on binary search and as such locating a key will take O(log n) amortized, this + * includes the by key iterator. + */ export class SortedList { private readonly _array: T[] = []; @@ -47,6 +52,9 @@ export class SortedList { return; } let i = this._search(key, 0, this._array.length - 1); + if (i < 0 || i >= this._array.length) { + return; + } if (this._getKey(this._array[i]) !== key) { return; } From 324ea2ecb7adf6ef145de73310e85c3cc692c0d2 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 11 May 2022 09:54:40 -0700 Subject: [PATCH 234/245] Clean up/resolve todos --- src/browser/renderer/dom/DomRendererRowFactory.ts | 1 - src/browser/services/RenderService.ts | 9 ++++++--- src/common/services/DecorationService.ts | 1 - 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/browser/renderer/dom/DomRendererRowFactory.ts b/src/browser/renderer/dom/DomRendererRowFactory.ts index c92f82c6..71dc782a 100644 --- a/src/browser/renderer/dom/DomRendererRowFactory.ts +++ b/src/browser/renderer/dom/DomRendererRowFactory.ts @@ -197,7 +197,6 @@ export class DomRendererRowFactory { if (cell.isBold() && fg < 8 && this._optionsService.rawOptions.drawBoldTextInBrightColors) { fg += 8; } - // TODO: Pass in bg override if (!this._applyMinimumContrast(charElement, this._colors.background, this._colors.ansi[fg], cell, undefined, undefined)) { charElement.classList.add(`xterm-fg-${fg}`); } diff --git a/src/browser/services/RenderService.ts b/src/browser/services/RenderService.ts index a789e025..b2e619fe 100644 --- a/src/browser/services/RenderService.ts +++ b/src/browser/services/RenderService.ts @@ -68,14 +68,17 @@ export class RenderService extends Disposable implements IRenderService { this._screenDprMonitor.setListener(() => this.onDevicePixelRatioChange()); this.register(this._screenDprMonitor); - // TODO: This will slow things down - this.register(decorationService.onDecorationRegistered(() => this._fullRefresh())); - this.register(decorationService.onDecorationRemoved(() => this._fullRefresh())); this.register(bufferService.onResize(() => this._fullRefresh())); this.register(bufferService.buffers.onBufferActivate(() => this._renderer?.clear())); this.register(optionsService.onOptionChange(() => this._renderer.onOptionsChanged())); this.register(this._charSizeService.onCharSizeChange(() => this.onCharSizeChanged())); + // Do a full refresh whenever any decoration is added or removed. This may not actually result + // in changes but since decorations should be used sparingly or added/removed all in the same + // frame this should have minimal performance impact. + this.register(decorationService.onDecorationRegistered(() => this._fullRefresh())); + this.register(decorationService.onDecorationRemoved(() => this._fullRefresh())); + // No need to register this as renderer is explicitly disposed in RenderService.dispose this._renderer.onRequestRedraw(e => this.refreshRows(e.start, e.end, true)); diff --git a/src/common/services/DecorationService.ts b/src/common/services/DecorationService.ts index 58718333..e32abdce 100644 --- a/src/common/services/DecorationService.ts +++ b/src/common/services/DecorationService.ts @@ -68,7 +68,6 @@ export class DecorationService extends Disposable implements IDecorationService let xmin = 0; let xmax = 0; for (const d of this._decorations.getKeyIterator(line)) { - console.log('d', d); xmin = d.options.x ?? 0; xmax = xmin + (d.options.width ?? 1); if (x >= xmin && x < xmax) { From 09aed4d339a89c2fef45f8c8d78fbdd9173ceaec Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 11 May 2022 10:27:35 -0700 Subject: [PATCH 235/245] Correct positioning of decoration elements Fixes #3774 --- src/browser/Decorations/BufferDecorationRenderer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/browser/Decorations/BufferDecorationRenderer.ts b/src/browser/Decorations/BufferDecorationRenderer.ts index 22dc73e9..61df134a 100644 --- a/src/browser/Decorations/BufferDecorationRenderer.ts +++ b/src/browser/Decorations/BufferDecorationRenderer.ts @@ -76,7 +76,7 @@ export class BufferDecorationRenderer extends Disposable { private _createElement(decoration: IInternalDecoration): HTMLElement { const element = document.createElement('div'); element.classList.add('xterm-decoration'); - element.style.width = `${(decoration.options.width || 1) * this._renderService.dimensions.actualCellWidth}px`; + element.style.width = `${Math.round((decoration.options.width || 1) * this._renderService.dimensions.actualCellWidth)}px`; element.style.height = `${(decoration.options.height || 1) * this._renderService.dimensions.actualCellHeight}px`; element.style.top = `${(decoration.marker.line - this._bufferService.buffers.active.ydisp) * this._renderService.dimensions.actualCellHeight}px`; element.style.lineHeight = `${this._renderService.dimensions.actualCellHeight}px`; From 181fb1cfe5dcfe81b409906fcc82c29fd61a2385 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 11 May 2022 11:46:44 -0700 Subject: [PATCH 236/245] Remove selection-specific render pass, use bg overrides instead Part of #3778 --- addons/xterm-addon-webgl/src/GlyphRenderer.ts | 99 +------------------ .../src/RectangleRenderer.ts | 81 +-------------- addons/xterm-addon-webgl/src/WebglRenderer.ts | 53 ++++++---- 3 files changed, 38 insertions(+), 195 deletions(-) diff --git a/addons/xterm-addon-webgl/src/GlyphRenderer.ts b/addons/xterm-addon-webgl/src/GlyphRenderer.ts index f3fd53a6..e9055a17 100644 --- a/addons/xterm-addon-webgl/src/GlyphRenderer.ts +++ b/addons/xterm-addon-webgl/src/GlyphRenderer.ts @@ -6,15 +6,11 @@ import { createProgram, PROJECTION_MATRIX, throwIfFalsy } from './WebglUtils'; import { WebglCharAtlas } from './atlas/WebglCharAtlas'; import { IWebGL2RenderingContext, IWebGLVertexArrayObject, IRenderModel, IRasterizedGlyph } from './Types'; -import { COMBINED_CHAR_BIT_MASK, RENDER_MODEL_INDICIES_PER_CELL, RENDER_MODEL_FG_OFFSET, RENDER_MODEL_BG_OFFSET } from './RenderModel'; import { fill } from 'common/TypedArrayUtils'; -import { slice } from './TypedArray'; -import { NULL_CELL_CODE, Attributes, FgFlags } from 'common/buffer/Constants'; +import { NULL_CELL_CODE } from 'common/buffer/Constants'; import { Terminal, IBufferLine } from 'xterm'; -import { IColor } from 'common/Types'; import { IColorSet } from 'browser/Types'; import { IRenderDimensions } from 'browser/renderer/Types'; -import { AttributeData } from 'common/buffer/AttributeData'; interface IVertices { attributes: Float32Array; @@ -25,7 +21,6 @@ interface IVertices { * working on the next frame. */ attributesBuffers: Float32Array[]; - selectionAttributes: Float32Array; count: number; } @@ -92,8 +87,7 @@ export class GlyphRenderer { attributesBuffers: [ new Float32Array(0), new Float32Array(0) - ], - selectionAttributes: new Float32Array(0) + ] }; constructor( @@ -217,91 +211,6 @@ export class GlyphRenderer { // a_cellpos only changes on resize } - public updateSelection(model: IRenderModel): void { - const terminal = this._terminal; - - this._vertices.selectionAttributes = slice(this._vertices.attributes, 0); - - const bg = (this._colors.selectionOpaque.rgba >>> 8) | Attributes.CM_RGB; - - if (model.selection.columnSelectMode) { - const startCol = model.selection.startCol; - const width = model.selection.endCol - startCol; - const height = model.selection.viewportCappedEndRow - model.selection.viewportCappedStartRow + 1; - for (let y = model.selection.viewportCappedStartRow; y < model.selection.viewportCappedStartRow + height; y++) { - this._updateSelectionRange(startCol, startCol + width, y, model, bg); - } - } else { - // Draw first row - const startCol = model.selection.viewportStartRow === model.selection.viewportCappedStartRow ? model.selection.startCol : 0; - const startRowEndCol = model.selection.viewportCappedStartRow === model.selection.viewportCappedEndRow ? model.selection.endCol : terminal.cols; - this._updateSelectionRange(startCol, startRowEndCol, model.selection.viewportCappedStartRow, model, bg); - - // Draw middle rows - const middleRowsCount = Math.max(model.selection.viewportCappedEndRow - model.selection.viewportCappedStartRow - 1, 0); - for (let y = model.selection.viewportCappedStartRow + 1; y <= model.selection.viewportCappedStartRow + middleRowsCount; y++) { - this._updateSelectionRange(0, startRowEndCol, y, model, bg); - } - - // Draw final row - if (model.selection.viewportCappedStartRow !== model.selection.viewportCappedEndRow) { - // Only draw viewportEndRow if it's not the same as viewportStartRow - const endCol = model.selection.viewportEndRow === model.selection.viewportCappedEndRow ? model.selection.endCol : terminal.cols; - this._updateSelectionRange(0, endCol, model.selection.viewportCappedEndRow, model, bg); - } - } - } - - private _updateSelectionRange(startCol: number, endCol: number, y: number, model: IRenderModel, bg: number): void { - const terminal = this._terminal; - const row = y + terminal.buffer.active.viewportY; - let line: IBufferLine | undefined; - for (let x = startCol; x < endCol; x++) { - const offset = (y * this._terminal.cols + x) * RENDER_MODEL_INDICIES_PER_CELL; - const code = model.cells[offset]; - let fg = model.cells[offset + RENDER_MODEL_FG_OFFSET]; - if (fg & FgFlags.INVERSE) { - const workCell = new AttributeData(); - workCell.fg = fg; - workCell.bg = model.cells[offset + RENDER_MODEL_BG_OFFSET]; - // Get attributes from fg (excluding inverse) and resolve inverse by pullibng rgb colors - // from bg. This is needed since the inverse fg color should be based on the original bg - // color, not on the selection color - fg &= ~(Attributes.CM_MASK | Attributes.RGB_MASK | FgFlags.INVERSE); - switch (workCell.getBgColorMode()) { - case Attributes.CM_P16: - case Attributes.CM_P256: - const c = this._getColorFromAnsiIndex(workCell.getBgColor()).rgba; - fg |= (c >> 8) & Attributes.RED_MASK | (c >> 8) & Attributes.GREEN_MASK | (c >> 8) & Attributes.BLUE_MASK; - case Attributes.CM_RGB: - const arr = AttributeData.toColorRGB(workCell.getBgColor()); - fg |= arr[0] << Attributes.RED_SHIFT | arr[1] << Attributes.GREEN_SHIFT | arr[2] << Attributes.BLUE_SHIFT; - case Attributes.CM_DEFAULT: - default: - const c2 = this._colors.background.rgba; - fg |= (c2 >> 8) & Attributes.RED_MASK | (c2 >> 8) & Attributes.GREEN_MASK | (c2 >> 8) & Attributes.BLUE_MASK; - } - fg |= Attributes.CM_RGB; - } - if (code & COMBINED_CHAR_BIT_MASK) { - if (!line) { - line = terminal.buffer.active.getLine(row); - } - const chars = line!.getCell(x)!.getChars(); - this._updateCell(this._vertices.selectionAttributes, x, y, model.cells[offset], bg, fg, chars); - } else { - this._updateCell(this._vertices.selectionAttributes, x, y, model.cells[offset], bg, fg); - } - } - } - - private _getColorFromAnsiIndex(idx: number): IColor { - if (idx >= this._colors.ansi.length) { - throw new Error('No color found for idx ' + idx); - } - return this._colors.ansi[idx]; - } - public clear(force?: boolean): void { const terminal = this._terminal; const newCount = terminal.cols * terminal.rows * INDICES_PER_CELL; @@ -336,7 +245,7 @@ export class GlyphRenderer { public setColors(): void { } - public render(renderModel: IRenderModel, isSelectionVisible: boolean): void { + public render(renderModel: IRenderModel): void { if (!this._atlas) { return; } @@ -360,7 +269,7 @@ export class GlyphRenderer { let bufferLength = 0; for (let y = 0; y < renderModel.lineLengths.length; y++) { const si = y * this._terminal.cols * INDICES_PER_CELL; - const sub = (isSelectionVisible ? this._vertices.selectionAttributes : this._vertices.attributes).subarray(si, si + renderModel.lineLengths[y] * INDICES_PER_CELL); + const sub = this._vertices.attributes.subarray(si, si + renderModel.lineLengths[y] * INDICES_PER_CELL); activeBuffer.set(sub, bufferLength); bufferLength += sub.length; } diff --git a/addons/xterm-addon-webgl/src/RectangleRenderer.ts b/addons/xterm-addon-webgl/src/RectangleRenderer.ts index ab0b34e9..420e58d4 100644 --- a/addons/xterm-addon-webgl/src/RectangleRenderer.ts +++ b/addons/xterm-addon-webgl/src/RectangleRenderer.ts @@ -4,8 +4,7 @@ */ import { createProgram, expandFloat32Array, PROJECTION_MATRIX, throwIfFalsy } from './WebglUtils'; -import { IRenderModel, IWebGLVertexArrayObject, IWebGL2RenderingContext, ISelectionRenderModel } from './Types'; -import { fill } from 'common/TypedArrayUtils'; +import { IRenderModel, IWebGLVertexArrayObject, IWebGL2RenderingContext } from './Types'; import { Attributes, FgFlags } from 'common/buffer/Constants'; import { Terminal } from 'xterm'; import { IColor } from 'common/Types'; @@ -50,7 +49,6 @@ void main() { interface IVertices { attributes: Float32Array; - selection: Float32Array; count: number; } @@ -67,12 +65,10 @@ export class RectangleRenderer { private _attributesBuffer: WebGLBuffer; private _projectionLocation: WebGLUniformLocation; private _bgFloat!: Float32Array; - private _selectionFloat!: Float32Array; private _vertices: IVertices = { count: 0, - attributes: new Float32Array(INITIAL_BUFFER_RECTANGLE_CAPACITY), - selection: new Float32Array(3 * INDICES_PER_RECTANGLE) + attributes: new Float32Array(INITIAL_BUFFER_RECTANGLE_CAPACITY) }; constructor( @@ -138,11 +134,6 @@ export class RectangleRenderer { gl.bindBuffer(gl.ARRAY_BUFFER, this._attributesBuffer); gl.bufferData(gl.ARRAY_BUFFER, this._vertices.attributes, gl.DYNAMIC_DRAW); gl.drawElementsInstanced(this._gl.TRIANGLES, 6, gl.UNSIGNED_BYTE, 0, this._vertices.count); - - // Bind selection buffer and draw - gl.bindBuffer(gl.ARRAY_BUFFER, this._attributesBuffer); - gl.bufferData(gl.ARRAY_BUFFER, this._vertices.selection, gl.DYNAMIC_DRAW); - gl.drawElementsInstanced(this._gl.TRIANGLES, 6, gl.UNSIGNED_BYTE, 0, 3); } public onResize(): void { @@ -156,7 +147,6 @@ export class RectangleRenderer { private _updateCachedColors(): void { this._bgFloat = this._colorToFloat32Array(this._colors.background); - this._selectionFloat = this._colorToFloat32Array(this._colors.selectionOpaque); } private _updateViewportRectangle(): void { @@ -172,73 +162,6 @@ export class RectangleRenderer { ); } - public updateSelection(model: ISelectionRenderModel): void { - const terminal = this._terminal; - - if (!model.hasSelection) { - fill(this._vertices.selection, 0, 0); - return; - } - - if (model.columnSelectMode) { - const startCol = model.startCol; - const width = model.endCol - startCol; - const height = model.viewportCappedEndRow - model.viewportCappedStartRow + 1; - this._addRectangleFloat( - this._vertices.selection, - 0, - startCol * this._dimensions.scaledCellWidth, - model.viewportCappedStartRow * this._dimensions.scaledCellHeight, - width * this._dimensions.scaledCellWidth, - height * this._dimensions.scaledCellHeight, - this._selectionFloat - ); - fill(this._vertices.selection, 0, INDICES_PER_RECTANGLE); - } else { - // Draw first row - const startCol = model.viewportStartRow === model.viewportCappedStartRow ? model.startCol : 0; - const startRowEndCol = model.viewportCappedStartRow === model.viewportEndRow ? model.endCol : terminal.cols; - this._addRectangleFloat( - this._vertices.selection, - 0, - startCol * this._dimensions.scaledCellWidth, - model.viewportCappedStartRow * this._dimensions.scaledCellHeight, - (startRowEndCol - startCol) * this._dimensions.scaledCellWidth, - this._dimensions.scaledCellHeight, - this._selectionFloat - ); - - // Draw middle rows - const middleRowsCount = Math.max(model.viewportCappedEndRow - model.viewportCappedStartRow - 1, 0); - this._addRectangleFloat( - this._vertices.selection, - INDICES_PER_RECTANGLE, - 0, - (model.viewportCappedStartRow + 1) * this._dimensions.scaledCellHeight, - terminal.cols * this._dimensions.scaledCellWidth, - middleRowsCount * this._dimensions.scaledCellHeight, - this._selectionFloat - ); - - // Draw final row - if (model.viewportCappedStartRow !== model.viewportCappedEndRow) { - // Only draw viewportEndRow if it's not the same as viewportStartRow - const endCol = model.viewportEndRow === model.viewportCappedEndRow ? model.endCol : terminal.cols; - this._addRectangleFloat( - this._vertices.selection, - INDICES_PER_RECTANGLE * 2, - 0, - model.viewportCappedEndRow * this._dimensions.scaledCellHeight, - endCol * this._dimensions.scaledCellWidth, - this._dimensions.scaledCellHeight, - this._selectionFloat - ); - } else { - fill(this._vertices.selection, 0, INDICES_PER_RECTANGLE * 2); - } - } - } - public updateBackgrounds(model: IRenderModel): void { const terminal = this._terminal; const vertices = this._vertices; diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index e80464da..1b45ae3a 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -167,10 +167,6 @@ export class WebglRenderer extends Disposable implements IRenderer { this._core.screenElement!.style.height = `${this.dimensions.canvasHeight}px`; this._rectangleRenderer.onResize(); - if (this._model.selection.hasSelection) { - // Update selection as dimensions have changed - this._rectangleRenderer.updateSelection(this._model.selection); - } this._glyphRenderer.setDimensions(this.dimensions); this._glyphRenderer.onResize(); @@ -201,10 +197,8 @@ export class WebglRenderer extends Disposable implements IRenderer { for (const l of this._renderLayers) { l.onSelectionChanged(this._terminal, start, end, columnSelectMode); } - this._updateSelectionModel(start, end, columnSelectMode); - - this._onRequestRedraw.fire({ start: 0, end: this._terminal.rows - 1 }); + this._requestRedrawViewport(); } public onCursorMove(): void { @@ -246,7 +240,7 @@ export class WebglRenderer extends Disposable implements IRenderer { this._charAtlas?.clearTexture(); this._model.clear(); this._updateModel(0, this._terminal.rows - 1); - this._onRequestRedraw.fire({ start: 0, end: this._terminal.rows - 1 }); + this._requestRedrawViewport(); } public clear(): void { @@ -292,7 +286,7 @@ export class WebglRenderer extends Disposable implements IRenderer { // Render this._rectangleRenderer.render(); - this._glyphRenderer.render(this._model, this._model.selection.hasSelection); + this._glyphRenderer.render(this._model); } private _updateModel(start: number, end: number): void { @@ -376,10 +370,6 @@ export class WebglRenderer extends Disposable implements IRenderer { } } this._rectangleRenderer.updateBackgrounds(this._model); - if (this._model.selection.hasSelection) { - // Model could be updated but the selection is unchanged - this._glyphRenderer.updateSelection(this._model); - } } /** @@ -390,16 +380,22 @@ export class WebglRenderer extends Disposable implements IRenderer { this._workColors.bg = this._workCell.bg; this._workColors.fg = this._workCell.fg; - // Get any decoration foreground/background overrides, this happens on the model to avoid - // spreading decoration override logic throughout the different sub-renderers let bgOverride: number | undefined; let fgOverride: number | undefined; + + // Apply the selection color if needed + if (this._isCellSelected(x, y)) { + bgOverride = this._colors.selectionOpaque.rgba >> 8 & 0xFFFFFF; + } + + // Get any decoration foreground/background overrides, this happens on the model to avoid + // spreading decoration override logic throughout the different sub-renderers for (const d of this._decorationService.getDecorationsAtCell(x, y)) { if (d.backgroundColorRGB) { - bgOverride = (d.backgroundColorRGB.rgba >> 8) >>> 0 & 0xFFFFFF; + bgOverride = d.backgroundColorRGB.rgba >> 8 & 0xFFFFFF; } if (d.foregroundColorRGB) { - fgOverride = (d.foregroundColorRGB.rgba >> 8) >>> 0 & 0xFFFFFF; + fgOverride = d.foregroundColorRGB.rgba >> 8 & 0xFFFFFF; } } @@ -440,13 +436,27 @@ export class WebglRenderer extends Disposable implements IRenderer { this._workColors.fg = fgOverride ?? this._workColors.fg; } + private _isCellSelected(x: number, y: number): boolean { + if (!this._model.selection.hasSelection) { + return false; + } + y -= this._terminal.buffer.active.viewportY; + if (this._model.selection.columnSelectMode) { + return x >= this._model.selection.startCol && y >= this._model.selection.viewportCappedStartRow && + x < this._model.selection.endCol && y < this._model.selection.viewportCappedEndRow; + } + return (y > this._model.selection.viewportStartRow && y < this._model.selection.viewportEndRow) || + (this._model.selection.viewportStartRow === this._model.selection.viewportEndRow && y === this._model.selection.viewportStartRow && x >= this._model.selection.startCol && x < this._model.selection.endCol) || + (this._model.selection.viewportStartRow < this._model.selection.viewportEndRow && y === this._model.selection.viewportEndRow && x < this._model.selection.endCol) || + (this._model.selection.viewportStartRow < this._model.selection.viewportEndRow && y === this._model.selection.viewportStartRow && x >= this._model.selection.startCol); + } + private _updateSelectionModel(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean = false): void { const terminal = this._terminal; // Selection does not exist if (!start || !end || (start[0] === end[0] && start[1] === end[1])) { this._model.clearSelection(); - this._rectangleRenderer.updateSelection(this._model.selection); return; } @@ -459,7 +469,6 @@ export class WebglRenderer extends Disposable implements IRenderer { // No need to draw the selection if (viewportCappedStartRow >= terminal.rows || viewportCappedEndRow < 0) { this._model.clearSelection(); - this._rectangleRenderer.updateSelection(this._model.selection); return; } @@ -471,8 +480,6 @@ export class WebglRenderer extends Disposable implements IRenderer { this._model.selection.viewportCappedEndRow = viewportCappedEndRow; this._model.selection.startCol = start[0]; this._model.selection.endCol = end[0]; - - this._rectangleRenderer.updateSelection(this._model.selection); } /** @@ -546,6 +553,10 @@ export class WebglRenderer extends Disposable implements IRenderer { this.dimensions.actualCellHeight = this.dimensions.scaledCellHeight / this._devicePixelRatio; this.dimensions.actualCellWidth = this.dimensions.scaledCellWidth / this._devicePixelRatio; } + + private _requestRedrawViewport(): void { + this._onRequestRedraw.fire({ start: 0, end: this._terminal.rows - 1 }); + } } // TODO: Share impl with core From 7b4423819ee4f5d87118b40445b185af4fc573f4 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 12 May 2022 10:51:11 -0700 Subject: [PATCH 237/245] Support setting the layer of a decoration Fixes #3778 --- addons/xterm-addon-search/src/SearchAddon.ts | 8 +++++++- .../typings/xterm-addon-search.d.ts | 7 +++++++ addons/xterm-addon-webgl/src/WebglRenderer.ts | 17 ++++++++++++++--- demo/client.ts | 7 ++++++- src/browser/renderer/BaseRenderLayer.ts | 5 +++++ src/browser/renderer/TextRenderLayer.ts | 5 +++++ .../renderer/dom/DomRendererRowFactory.ts | 5 +++++ src/common/services/DecorationService.ts | 4 ++-- src/common/services/Services.ts | 2 +- typings/xterm.d.ts | 10 ++++++++++ 10 files changed, 62 insertions(+), 8 deletions(-) diff --git a/addons/xterm-addon-search/src/SearchAddon.ts b/addons/xterm-addon-search/src/SearchAddon.ts index 7d1b145c..e7ece483 100644 --- a/addons/xterm-addon-search/src/SearchAddon.ts +++ b/addons/xterm-addon-search/src/SearchAddon.ts @@ -115,6 +115,11 @@ export class SearchAddon implements ITerminalAddon { } } + public clearActiveDecoration(): void { + this._selectedDecoration?.dispose(); + this._selectedDecoration = undefined; + } + /** * Find the next instance of the term, then scroll to and select it. If it * doesn't exist, do nothing. @@ -655,7 +660,7 @@ export class SearchAddon implements ITerminalAddon { */ private _selectResult(result: ISearchResult | undefined, options?: ISearchDecorationOptions, noScroll?: boolean): boolean { const terminal = this._terminal!; - this._selectedDecoration?.dispose(); + this.clearActiveDecoration(); if (!result) { terminal.clearSelection(); return false; @@ -669,6 +674,7 @@ export class SearchAddon implements ITerminalAddon { x: result.col, width: result.size, backgroundColor: options.activeMatchBackground, + layer: 'top', overviewRulerOptions: { color: options.activeMatchColorOverviewRuler } diff --git a/addons/xterm-addon-search/typings/xterm-addon-search.d.ts b/addons/xterm-addon-search/typings/xterm-addon-search.d.ts index 9ed1da62..e5a47190 100644 --- a/addons/xterm-addon-search/typings/xterm-addon-search.d.ts +++ b/addons/xterm-addon-search/typings/xterm-addon-search.d.ts @@ -111,6 +111,13 @@ declare module 'xterm-addon-search' { */ public clearDecorations(): void; + /** + * Clears the active result decoration, this decoration is applied on top of the selection so + * removing it will reveal the selection underneath. This is intended to be call on the search + * textarea's `blur` event. + */ + public clearActiveDecoration(): void; + /** * When decorations are enabled, fires when * the search results change. diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index 1b45ae3a..d060c4d1 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -380,17 +380,28 @@ export class WebglRenderer extends Disposable implements IRenderer { this._workColors.bg = this._workCell.bg; this._workColors.fg = this._workCell.fg; + // Get any foreground/background overrides, this happens on the model to avoid spreading + // override logic throughout the different sub-renderers let bgOverride: number | undefined; let fgOverride: number | undefined; + // Apply decorations on the bottom layer + for (const d of this._decorationService.getDecorationsAtCell(x, y, 'bottom')) { + if (d.backgroundColorRGB) { + bgOverride = d.backgroundColorRGB.rgba >> 8 & 0xFFFFFF; + } + if (d.foregroundColorRGB) { + fgOverride = d.foregroundColorRGB.rgba >> 8 & 0xFFFFFF; + } + } + // Apply the selection color if needed if (this._isCellSelected(x, y)) { bgOverride = this._colors.selectionOpaque.rgba >> 8 & 0xFFFFFF; } - // Get any decoration foreground/background overrides, this happens on the model to avoid - // spreading decoration override logic throughout the different sub-renderers - for (const d of this._decorationService.getDecorationsAtCell(x, y)) { + // Apply decorations on the top layer + for (const d of this._decorationService.getDecorationsAtCell(x, y, 'top')) { if (d.backgroundColorRGB) { bgOverride = d.backgroundColorRGB.rgba >> 8 & 0xFFFFFF; } diff --git a/demo/client.ts b/demo/client.ts index a63864a2..b9e52d7b 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -212,10 +212,15 @@ function createTerminal(): void { addDomListener(actionElements.findNext, 'keyup', (e) => { addons.search.instance.findNext(actionElements.findNext.value, getSearchOptions(e)); }); - addDomListener(actionElements.findPrevious, 'keyup', (e) => { addons.search.instance.findPrevious(actionElements.findPrevious.value, getSearchOptions(e)); }); + addDomListener(actionElements.findNext, 'blur', (e) => { + addons.search.instance.clearActiveDecoration(); + }); + addDomListener(actionElements.findPrevious, 'blur', (e) => { + addons.search.instance.clearActiveDecoration(); + }); // fit is called within a setTimeout, cols and rows need this. setTimeout(() => { diff --git a/src/browser/renderer/BaseRenderLayer.ts b/src/browser/renderer/BaseRenderLayer.ts index e0f3566c..696b793f 100644 --- a/src/browser/renderer/BaseRenderLayer.ts +++ b/src/browser/renderer/BaseRenderLayer.ts @@ -443,13 +443,18 @@ export abstract class BaseRenderLayer implements IRenderLayer { // exist but applied after inverse let bgOverride: number | undefined; let fgOverride: number | undefined; + let isTop = false; for (const d of this._decorationService.getDecorationsAtCell(x, y)) { + if (d.options.layer !== 'top' && isTop) { + continue; + } if (d.backgroundColorRGB) { bgOverride = d.backgroundColorRGB.rgba; } if (d.foregroundColorRGB) { fgOverride = d.foregroundColorRGB.rgba; } + isTop = d.options.layer === 'top'; } if (!bgOverride && !fgOverride && (this._optionsService.rawOptions.minimumContrastRatio === 1 || isPowerlineGlyph(cell.getCode()))) { diff --git a/src/browser/renderer/TextRenderLayer.ts b/src/browser/renderer/TextRenderLayer.ts index 193d891d..ef5a9b62 100644 --- a/src/browser/renderer/TextRenderLayer.ts +++ b/src/browser/renderer/TextRenderLayer.ts @@ -179,10 +179,15 @@ export class TextRenderLayer extends BaseRenderLayer { // Get any decoration foreground/background overrides, this must be fetched before the early // exist but applied after inverse + let isTop = false; for (const d of this._decorationService.getDecorationsAtCell(x, this._bufferService.buffer.ydisp + y)) { + if (d.options.layer !== 'top' && isTop) { + continue; + } if (d.backgroundColorRGB) { nextFillStyle = d.backgroundColorRGB.css; } + isTop = d.options.layer === 'top'; } if (prevFillStyle === null) { diff --git a/src/browser/renderer/dom/DomRendererRowFactory.ts b/src/browser/renderer/dom/DomRendererRowFactory.ts index 71dc782a..bf3939e8 100644 --- a/src/browser/renderer/dom/DomRendererRowFactory.ts +++ b/src/browser/renderer/dom/DomRendererRowFactory.ts @@ -177,7 +177,11 @@ export class DomRendererRowFactory { // been applied let bgOverride: IColor | undefined; let fgOverride: IColor | undefined; + let isTop = false; for (const d of this._decorationService.getDecorationsAtCell(x, row)) { + if (d.options.layer !== 'top' && isTop) { + continue; + } if (d.backgroundColorRGB) { bgColorMode = Attributes.CM_RGB; bg = d.backgroundColorRGB.rgba >> 8 & 0xFFFFFF; @@ -188,6 +192,7 @@ export class DomRendererRowFactory { fg = d.foregroundColorRGB.rgba >> 8 & 0xFFFFFF; fgOverride = d.foregroundColorRGB; } + isTop = d.options.layer === 'top'; } // Foreground diff --git a/src/common/services/DecorationService.ts b/src/common/services/DecorationService.ts index e32abdce..755f13b3 100644 --- a/src/common/services/DecorationService.ts +++ b/src/common/services/DecorationService.ts @@ -64,13 +64,13 @@ export class DecorationService extends Disposable implements IDecorationService return this._decorations.getKeyIterator(line); } - public *getDecorationsAtCell(x: number, line: number): IterableIterator { + public *getDecorationsAtCell(x: number, line: number, layer?: 'bottom' | 'top'): IterableIterator { let xmin = 0; let xmax = 0; for (const d of this._decorations.getKeyIterator(line)) { xmin = d.options.x ?? 0; xmax = xmin + (d.options.width ?? 1); - if (x >= xmin && x < xmax) { + if (x >= xmin && x < xmax && (!layer || (d.options.layer ?? 'bottom') === layer)) { yield d; } } diff --git a/src/common/services/Services.ts b/src/common/services/Services.ts index 82492eb4..c3190210 100644 --- a/src/common/services/Services.ts +++ b/src/common/services/Services.ts @@ -312,7 +312,7 @@ export interface IDecorationService extends IDisposable { /** Iterates over the decorations at a line (in no particular order). */ getDecorationsAtLine(line: number): IterableIterator; /** Iterates over the decorations at a cell (in no particular order). */ - getDecorationsAtCell(x: number, line: number): IterableIterator; + getDecorationsAtCell(x: number, line: number, layer?: 'bottom' | 'top'): IterableIterator; } export interface IInternalDecoration extends IDecoration { readonly options: IDecorationOptions; diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 0c421dc0..15ee4650 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -500,6 +500,16 @@ declare module 'xterm' { */ readonly foregroundColor?: string; + /** + * What layer to render the decoration at when {@link backgroundColor} or + * {@link foregroundColor} are used. `'bottom'` will render under the selection, `'top`' will + * render above the selection\*. + * + * *\* The selection will render on top regardless of layer on the canvas renderer due to how + * it renders selection separately.* + */ + readonly layer?: 'bottom' | 'top'; + /** * When defined, renders the decoration in the overview ruler to the right * of the terminal. {@link ITerminalOptions.overviewRulerWidth} must be set From f5223d30b85220c1c8e50022a692e33ab7dc05cc Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 12 May 2022 10:52:16 -0700 Subject: [PATCH 238/245] Fix typo --- addons/xterm-addon-search/typings/xterm-addon-search.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/xterm-addon-search/typings/xterm-addon-search.d.ts b/addons/xterm-addon-search/typings/xterm-addon-search.d.ts index e5a47190..300e5063 100644 --- a/addons/xterm-addon-search/typings/xterm-addon-search.d.ts +++ b/addons/xterm-addon-search/typings/xterm-addon-search.d.ts @@ -113,7 +113,7 @@ declare module 'xterm-addon-search' { /** * Clears the active result decoration, this decoration is applied on top of the selection so - * removing it will reveal the selection underneath. This is intended to be call on the search + * removing it will reveal the selection underneath. This is intended to be called on the search * textarea's `blur` event. */ public clearActiveDecoration(): void; From 24919e36f09de3da0cb57b2e3f997726aaf98bb2 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 12 May 2022 10:54:36 -0700 Subject: [PATCH 239/245] Highlight all matches in demo by default --- demo/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demo/index.html b/demo/index.html index 6bbcb4a2..f36fc629 100644 --- a/demo/index.html +++ b/demo/index.html @@ -43,7 +43,7 @@ - +

SerializeAddon

From ddde738326b7514c08c896d421403bf35c3a391f Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 12 May 2022 10:57:12 -0700 Subject: [PATCH 240/245] Merge .xterm css rules Fixes #3780 --- css/xterm.css | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/css/xterm.css b/css/xterm.css index 7432fbb1..3d7cbb9b 100644 --- a/css/xterm.css +++ b/css/xterm.css @@ -36,6 +36,7 @@ */ .xterm { + cursor: text; position: relative; user-select: none; -ms-user-select: none; @@ -124,10 +125,6 @@ line-height: normal; } -.xterm { - cursor: text; -} - .xterm.enable-mouse-events { /* When mouse events are enabled (eg. tmux), revert to the standard pointer cursor */ cursor: default; From d84b71d4c6f119c7498526b99c68f705fa71468a Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 12 May 2022 10:59:57 -0700 Subject: [PATCH 241/245] Disable pointer events on the overview ruler Fixes #3762 --- css/xterm.css | 1 + 1 file changed, 1 insertion(+) diff --git a/css/xterm.css b/css/xterm.css index 7432fbb1..0fd5af4e 100644 --- a/css/xterm.css +++ b/css/xterm.css @@ -184,4 +184,5 @@ position: absolute; top: 0; right: 0; + pointer-events: none; } From 29c1264b42b69162a17b9f00a457e1c14a5d331d Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 12 May 2022 11:04:48 -0700 Subject: [PATCH 242/245] Add warning in demo code about flow control Fixes #3760 --- demo/server.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/demo/server.js b/demo/server.js index c0d5e1f6..71a9d36a 100644 --- a/demo/server.js +++ b/demo/server.js @@ -114,6 +114,9 @@ function startServer() { } const send = USE_BINARY ? bufferUtf8(ws, 5) : buffer(ws, 5); + // 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 + // the problem and how to implement flow control at https://xtermjs.org/docs/guides/flowcontrol/ term.on('data', function(data) { try { send(data); From 4cc4b2cd427a298bea702c9efe1d4a9808a1c78f Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 12 May 2022 11:18:49 -0700 Subject: [PATCH 243/245] Rename Decorations -> decoration2 to fix casing --- .../{Decorations => decorations2}/BufferDecorationRenderer.ts | 0 src/browser/{Decorations => decorations2}/ColorZoneStore.test.ts | 0 src/browser/{Decorations => decorations2}/ColorZoneStore.ts | 0 .../{Decorations => decorations2}/OverviewRulerRenderer.ts | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename src/browser/{Decorations => decorations2}/BufferDecorationRenderer.ts (100%) rename src/browser/{Decorations => decorations2}/ColorZoneStore.test.ts (100%) rename src/browser/{Decorations => decorations2}/ColorZoneStore.ts (100%) rename src/browser/{Decorations => decorations2}/OverviewRulerRenderer.ts (100%) diff --git a/src/browser/Decorations/BufferDecorationRenderer.ts b/src/browser/decorations2/BufferDecorationRenderer.ts similarity index 100% rename from src/browser/Decorations/BufferDecorationRenderer.ts rename to src/browser/decorations2/BufferDecorationRenderer.ts diff --git a/src/browser/Decorations/ColorZoneStore.test.ts b/src/browser/decorations2/ColorZoneStore.test.ts similarity index 100% rename from src/browser/Decorations/ColorZoneStore.test.ts rename to src/browser/decorations2/ColorZoneStore.test.ts diff --git a/src/browser/Decorations/ColorZoneStore.ts b/src/browser/decorations2/ColorZoneStore.ts similarity index 100% rename from src/browser/Decorations/ColorZoneStore.ts rename to src/browser/decorations2/ColorZoneStore.ts diff --git a/src/browser/Decorations/OverviewRulerRenderer.ts b/src/browser/decorations2/OverviewRulerRenderer.ts similarity index 100% rename from src/browser/Decorations/OverviewRulerRenderer.ts rename to src/browser/decorations2/OverviewRulerRenderer.ts From cd01157e55883cceb8af26e060eef7f4e830935c Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 12 May 2022 11:19:07 -0700 Subject: [PATCH 244/245] Rename decorations2 -> decorations to fix casing --- .../{decorations2 => decorations}/BufferDecorationRenderer.ts | 0 src/browser/{decorations2 => decorations}/ColorZoneStore.test.ts | 0 src/browser/{decorations2 => decorations}/ColorZoneStore.ts | 0 .../{decorations2 => decorations}/OverviewRulerRenderer.ts | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename src/browser/{decorations2 => decorations}/BufferDecorationRenderer.ts (100%) rename src/browser/{decorations2 => decorations}/ColorZoneStore.test.ts (100%) rename src/browser/{decorations2 => decorations}/ColorZoneStore.ts (100%) rename src/browser/{decorations2 => decorations}/OverviewRulerRenderer.ts (100%) diff --git a/src/browser/decorations2/BufferDecorationRenderer.ts b/src/browser/decorations/BufferDecorationRenderer.ts similarity index 100% rename from src/browser/decorations2/BufferDecorationRenderer.ts rename to src/browser/decorations/BufferDecorationRenderer.ts diff --git a/src/browser/decorations2/ColorZoneStore.test.ts b/src/browser/decorations/ColorZoneStore.test.ts similarity index 100% rename from src/browser/decorations2/ColorZoneStore.test.ts rename to src/browser/decorations/ColorZoneStore.test.ts diff --git a/src/browser/decorations2/ColorZoneStore.ts b/src/browser/decorations/ColorZoneStore.ts similarity index 100% rename from src/browser/decorations2/ColorZoneStore.ts rename to src/browser/decorations/ColorZoneStore.ts diff --git a/src/browser/decorations2/OverviewRulerRenderer.ts b/src/browser/decorations/OverviewRulerRenderer.ts similarity index 100% rename from src/browser/decorations2/OverviewRulerRenderer.ts rename to src/browser/decorations/OverviewRulerRenderer.ts From 748d0cc74f0b0a6ce9903856b0db039b64c0af45 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 12 May 2022 11:49:57 -0700 Subject: [PATCH 245/245] Fix missed decorations casing --- src/browser/Terminal.ts | 4 ++-- src/browser/decorations/ColorZoneStore.test.ts | 2 +- src/browser/decorations/OverviewRulerRenderer.ts | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index a8accd78..de3fff90 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -55,8 +55,8 @@ import { CoreTerminal } from 'common/CoreTerminal'; import { color, rgba } from 'common/Color'; import { CharacterJoinerService } from 'browser/services/CharacterJoinerService'; import { toRgbString } from 'common/input/XParseColor'; -import { BufferDecorationRenderer } from 'browser/Decorations/BufferDecorationRenderer'; -import { OverviewRulerRenderer } from 'browser/Decorations/OverviewRulerRenderer'; +import { BufferDecorationRenderer } from 'browser/decorations/BufferDecorationRenderer'; +import { OverviewRulerRenderer } from 'browser/decorations/OverviewRulerRenderer'; import { DecorationService } from 'common/services/DecorationService'; import { IDecorationService } from 'common/services/Services'; diff --git a/src/browser/decorations/ColorZoneStore.test.ts b/src/browser/decorations/ColorZoneStore.test.ts index 73e3402f..719ef45b 100644 --- a/src/browser/decorations/ColorZoneStore.test.ts +++ b/src/browser/decorations/ColorZoneStore.test.ts @@ -4,7 +4,7 @@ */ import { assert } from 'chai'; -import { ColorZoneStore } from 'browser/Decorations/ColorZoneStore'; +import { ColorZoneStore } from 'browser/decorations/ColorZoneStore'; const optionsRedFull = { overviewRulerOptions: { diff --git a/src/browser/decorations/OverviewRulerRenderer.ts b/src/browser/decorations/OverviewRulerRenderer.ts index dc35b901..39480ca2 100644 --- a/src/browser/decorations/OverviewRulerRenderer.ts +++ b/src/browser/decorations/OverviewRulerRenderer.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ColorZoneStore, IColorZone, IColorZoneStore } from 'browser/Decorations/ColorZoneStore'; +import { ColorZoneStore, IColorZone, IColorZoneStore } from 'browser/decorations/ColorZoneStore'; import { addDisposableDomListener } from 'browser/Lifecycle'; import { IRenderService } from 'browser/services/Services'; import { Disposable } from 'common/Lifecycle';