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

Options

-

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

+

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

diff --git a/test/api/InputHandler.api.ts b/test/api/InputHandler.api.ts index 4d3a15dd..6e0bf88b 100644 --- a/test/api/InputHandler.api.ts +++ b/test/api/InputHandler.api.ts @@ -362,7 +362,7 @@ describe('InputHandler Integration Tests', function(): void { await pollFor(page, async () => await page.evaluate(`(() => _stack)()`), []); }); it('14 - GetWinSizePixels', async function(): Promise { - await page.evaluate(`window.term.setOption('windowOptions', { getWinSizePixels: true }); `); + await page.evaluate(`window.term.options.windowOptions = { getWinSizePixels: true }; `); await page.evaluate(`(() => { window._stack = []; const _h = window.term.onData(data => window._stack.push(data)); @@ -373,7 +373,7 @@ describe('InputHandler Integration Tests', function(): void { await pollFor(page, async () => await page.evaluate(`(() => _stack)()`), [`\x1b[4;${d.height};${d.width}t`]); }); it('16 - GetCellSizePixels', async function(): Promise { - await page.evaluate(`window.term.setOption('windowOptions', { getCellSizePixels: true }); `); + await page.evaluate(`window.term.options.windowOptions = { getCellSizePixels: true }; `); await page.evaluate(`(() => { window._stack = []; const _h = window.term.onData(data => window._stack.push(data)); diff --git a/test/api/MouseTracking.api.ts b/test/api/MouseTracking.api.ts index ebeb680f..16a9b32c 100644 --- a/test/api/MouseTracking.api.ts +++ b/test/api/MouseTracking.api.ts @@ -229,7 +229,7 @@ describe('Mouse Tracking Tests', async () => { window.calls = []; window.term.onData(e => calls.push( Array.from(e).map(el => el.charCodeAt(0)) )); window.term.onBinary(e => calls.push( Array.from(e).map(el => el.charCodeAt(0)) )); - window.term.setOption('fontSize', ${fontSize}); + window.term.options.fontSize = ${fontSize}; window.term.resize(${cols}, ${rows}); `); }); diff --git a/test/api/Terminal.api.ts b/test/api/Terminal.api.ts index ee6a0cfa..cc3b78be 100644 --- a/test/api/Terminal.api.ts +++ b/test/api/Terminal.api.ts @@ -154,13 +154,6 @@ describe('API Integration Tests', function(): void { } }); - it('getOption, setOption', async () => { - await openTerminal(page); - assert.equal(await page.evaluate(`window.term.getOption('rendererType')`), 'canvas'); - await page.evaluate(`window.term.setOption('rendererType', 'dom')`); - assert.equal(await page.evaluate(`window.term.getOption('rendererType')`), 'dom'); - }); - describe('options', () => { it('getter', async () => { await openTerminal(page); From 78c2f06107030f6abfe165b385318c46c5784df5 Mon Sep 17 00:00:00 2001 From: Simon Lamon Date: Wed, 2 Mar 2022 13:19:53 +0000 Subject: [PATCH 2/5] Fix triple select edge case --- src/browser/services/SelectionService.test.ts | 6 ++++++ src/browser/services/SelectionService.ts | 10 +++++++--- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/browser/services/SelectionService.test.ts b/src/browser/services/SelectionService.test.ts index 514d5803..d829e394 100644 --- a/src/browser/services/SelectionService.test.ts +++ b/src/browser/services/SelectionService.test.ts @@ -340,6 +340,9 @@ describe('SelectionService', () => { buffer.lines.set(0, stringToRow('foo bar')); selectionService.selectLineAt(0); assert.equal(selectionService.selectionText, 'foo bar', 'The selected text is correct'); + assert.deepEqual(selectionService.model.selectionStart, [0, 0]); + assert.deepEqual(selectionService.model.selectionEnd, undefined); + assert.deepEqual(selectionService.model.selectionStartLength, 20); assert.deepEqual(selectionService.model.finalSelectionStart, [0, 0]); assert.deepEqual(selectionService.model.finalSelectionEnd, [bufferService.cols, 0], 'The actual selection spans the entire column'); }); @@ -350,6 +353,9 @@ describe('SelectionService', () => { buffer.lines.set(1, line2); selectionService.selectLineAt(0); assert.equal(selectionService.selectionText, 'foobar', 'The selected text is correct'); + assert.deepEqual(selectionService.model.selectionStart, [0, 0]); + assert.deepEqual(selectionService.model.selectionEnd, undefined); + assert.deepEqual(selectionService.model.selectionStartLength, 40); assert.deepEqual(selectionService.model.finalSelectionStart, [0, 0]); assert.deepEqual(selectionService.model.finalSelectionEnd, [bufferService.cols, 1], 'The actual selection spans the entire column'); }); diff --git a/src/browser/services/SelectionService.ts b/src/browser/services/SelectionService.ts index 1ea2395d..53020b53 100644 --- a/src/browser/services/SelectionService.ts +++ b/src/browser/services/SelectionService.ts @@ -11,7 +11,7 @@ import { SelectionModel } from 'browser/selection/SelectionModel'; import { CellData } from 'common/buffer/CellData'; import { EventEmitter, IEvent } from 'common/EventEmitter'; import { IMouseService, ISelectionService, IRenderService } from 'browser/services/Services'; -import { ILinkifier2 } from 'browser/Types'; +import { IBufferRange, ILinkifier2 } from 'browser/Types'; import { IBufferService, IOptionsService, ICoreService } from 'common/services/Services'; import { getCoordsRelativeToElement } from 'browser/input/Mouse'; import { moveToCellSequence } from 'browser/input/MoveToCell'; @@ -1002,8 +1002,12 @@ export class SelectionService extends Disposable implements ISelectionService { */ protected _selectLineAt(line: number): void { const wrappedRange = this._bufferService.buffer.getWrappedRangeForLine(line); + const range: IBufferRange = { + start: { x: 0, y: wrappedRange.first }, + end: { x: this._bufferService.cols - 1, y: wrappedRange.last } + }; this._model.selectionStart = [0, wrappedRange.first]; - this._model.selectionEnd = [this._bufferService.cols, wrappedRange.last]; - this._model.selectionStartLength = 0; + this._model.selectionEnd = undefined; + this._model.selectionStartLength = getRangeLength(range, this._bufferService.cols); } } From c10a451381ca0ae2fe8695a12f2c7de293d9d76f Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 2 Mar 2022 06:59:57 -0800 Subject: [PATCH 3/5] Bring back getOption/setOption api test --- test/api/Terminal.api.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test/api/Terminal.api.ts b/test/api/Terminal.api.ts index cc3b78be..ee6a0cfa 100644 --- a/test/api/Terminal.api.ts +++ b/test/api/Terminal.api.ts @@ -154,6 +154,13 @@ describe('API Integration Tests', function(): void { } }); + it('getOption, setOption', async () => { + await openTerminal(page); + assert.equal(await page.evaluate(`window.term.getOption('rendererType')`), 'canvas'); + await page.evaluate(`window.term.setOption('rendererType', 'dom')`); + assert.equal(await page.evaluate(`window.term.getOption('rendererType')`), 'dom'); + }); + describe('options', () => { it('getter', async () => { await openTerminal(page); From 4799832a33521e2378ba4a56268679626e5e95fc Mon Sep 17 00:00:00 2001 From: Simon Lamon Date: Wed, 2 Mar 2022 16:17:31 +0000 Subject: [PATCH 4/5] Keep the initial wrapped line upon dragging --- src/browser/selection/SelectionModel.test.ts | 6 ++++++ src/browser/selection/SelectionModel.ts | 7 ++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/browser/selection/SelectionModel.test.ts b/src/browser/selection/SelectionModel.test.ts index 410902d7..5ce3e316 100644 --- a/src/browser/selection/SelectionModel.test.ts +++ b/src/browser/selection/SelectionModel.test.ts @@ -116,6 +116,12 @@ describe('SelectionModel', () => { model.selectionStartLength = 4; assert.deepEqual(model.finalSelectionEnd, [2, 3]); }); + it('should return the end on a different row when start + length overflows onto a following row with selectionEnd inbetween', () => { + model.selectionStart = [78, 2]; + model.selectionEnd = [79, 2]; + model.selectionStartLength = 4; + assert.deepEqual(model.finalSelectionEnd, [2, 3]); + }); it('should return selection end if selection end is after selection start + length', () => { model.selectionStart = [2, 2]; model.selectionStartLength = 2; diff --git a/src/browser/selection/SelectionModel.ts b/src/browser/selection/SelectionModel.ts index 1d84446a..6c8abbfd 100644 --- a/src/browser/selection/SelectionModel.ts +++ b/src/browser/selection/SelectionModel.ts @@ -92,7 +92,12 @@ export class SelectionModel { if (this.selectionStartLength) { // Select the larger of the two when start and end are on the same line if (this.selectionEnd[1] === this.selectionStart[1]) { - return [Math.max(this.selectionStart[0] + this.selectionStartLength, this.selectionEnd[0]), this.selectionEnd[1]]; + // Keep the whole wrapped word/line selected if the content wraps multiple lines + const startPlusLength = this.selectionStart[0] + this.selectionStartLength; + if (startPlusLength > this._bufferService.cols) { + return [startPlusLength % this._bufferService.cols, this.selectionStart[1] + Math.floor(startPlusLength / this._bufferService.cols)]; + } + return [Math.max(startPlusLength, this.selectionEnd[0]), this.selectionEnd[1]]; } } return this.selectionEnd; From 842f4c20888b308d18aa976b34904567d953ad36 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Wed, 30 Mar 2022 20:08:55 -0400 Subject: [PATCH 5/5] 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'),