From ff6fa739c43165ad187683f6bdca0481ca4b7c93 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Wed, 2 Mar 2022 17:58:27 +0000 Subject: [PATCH 001/178] 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 002/178] 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 003/178] 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 004/178] 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 005/178] 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 006/178] 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 007/178] 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 008/178] =?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 009/178] 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 010/178] 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 011/178] 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 012/178] 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 013/178] 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 014/178] 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 015/178] 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 016/178] 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 017/178] 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 018/178] 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 019/178] 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 020/178] 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 021/178] 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 022/178] 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 023/178] 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 024/178] 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 025/178] 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 026/178] 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 027/178] 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 028/178] 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 029/178] 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 030/178] 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 031/178] 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 032/178] 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 033/178] 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 034/178] 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 035/178] 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 036/178] 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 037/178] 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 038/178] 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 039/178] 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 040/178] 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 041/178] 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 042/178] 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 043/178] 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 044/178] 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 045/178] 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 046/178] 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 047/178] 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 048/178] 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 049/178] 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 050/178] 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 051/178] 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 052/178] 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 053/178] 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 054/178] 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 055/178] 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 056/178] 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 057/178] 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 058/178] 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 059/178] 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 060/178] 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 061/178] 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 062/178] 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 063/178] 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 064/178] 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 065/178] 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 066/178] 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 067/178] 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 068/178] 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 069/178] 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 070/178] 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 071/178] 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 072/178] 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 073/178] 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 074/178] 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 075/178] 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 076/178] 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 077/178] 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 078/178] 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 079/178] 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 080/178] 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 081/178] 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 082/178] 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 083/178] 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 084/178] 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 085/178] 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 086/178] 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 087/178] 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 088/178] 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 089/178] 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 090/178] 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 091/178] 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 092/178] 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 093/178] 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 094/178] 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 095/178] 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 096/178] 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 097/178] 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 098/178] 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 099/178] 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 100/178] 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 101/178] 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 102/178] 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 103/178] 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 104/178] 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 105/178] 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 106/178] 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 107/178] 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 108/178] 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 109/178] 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 110/178] 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 111/178] 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 112/178] 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 113/178] 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 114/178] 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 115/178] 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 116/178] 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 117/178] 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 118/178] 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 119/178] 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 120/178] 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 121/178] 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 122/178] 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 123/178] 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 124/178] 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 125/178] 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 126/178] 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 127/178] 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 128/178] 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 129/178] 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 130/178] 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 131/178] 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 132/178] 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 133/178] 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 134/178] 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 135/178] 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 136/178] 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 137/178] 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 138/178] 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 139/178] 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 140/178] 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 141/178] 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 142/178] 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 143/178] 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 144/178] 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 145/178] 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 146/178] 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 147/178] 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 148/178] 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 149/178] 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 150/178] 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 151/178] 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 152/178] 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 153/178] 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 154/178] 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 155/178] 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 156/178] 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 157/178] 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 158/178] 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 159/178] 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 160/178] 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 161/178] 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 162/178] 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 163/178] 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 164/178] 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 165/178] 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 166/178] 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 167/178] 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 168/178] 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 169/178] 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 170/178] 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 171/178] 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 172/178] 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 173/178] 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 174/178] 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 175/178] 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 7f87b4632857c6a1d2a40582800e5ef3d0260e53 Mon Sep 17 00:00:00 2001 From: Pavel Sychev Date: Wed, 30 Mar 2022 14:30:56 +0200 Subject: [PATCH 176/178] 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 177/178] 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 178/178] 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'),