From 0aeeebdd646b7ddd9b990565669778cf46b42dfa Mon Sep 17 00:00:00 2001 From: Simon Lamon <32477463+silamon@users.noreply.github.com> Date: Sat, 4 Nov 2023 13:05:42 +0000 Subject: [PATCH 01/46] Devcontainer update --- .devcontainer/devcontainer.json | 8 +++++--- .nvmrc | 2 +- .vscode/launch.json | 2 +- package.json | 2 +- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index c09a29bf..9c83cc4c 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,13 +1,15 @@ { "name": "xterm.js", - "image": "mcr.microsoft.com/devcontainers/typescript-node:0-18-buster", + "image": "mcr.microsoft.com/devcontainers/typescript-node:18-bookworm", "features": { - "ghcr.io/devcontainers/features/node:1": {} // yarn + "ghcr.io/devcontainers/features/node:1": { + "version": 18 + } // yarn }, "forwardPorts": [ 3000 ], - "postCreateCommand": "yarn install", + "postCreateCommand": "yarn install && yarn setup", "customizations": { "vscode": { "extensions": [ diff --git a/.nvmrc b/.nvmrc index b6a7d89c..3c032078 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -16 +18 diff --git a/.vscode/launch.json b/.vscode/launch.json index 5dbd01ce..eaa5e12e 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -61,7 +61,7 @@ "runtimeExecutable": "npm", "runtimeArgs": ["start"], "stopOnEntry": true, - "runtimeVersion": "16", + "runtimeVersion": "18", "serverReadyAction": { "action": "openExternally", "pattern": "App listening to (http://.*?:[0-9]+)" diff --git a/package.json b/package.json index d0510e5d..28e9952b 100644 --- a/package.json +++ b/package.json @@ -79,7 +79,7 @@ "cross-env": "^7.0.3", "deep-equal": "^2.0.5", "eslint": "^8.45.0", - "eslint-plugin-jsdoc": "^39.3.6", + "eslint-plugin-jsdoc": "^46.8.2", "express": "^4.17.1", "express-ws": "^5.0.2", "glob": "^7.2.0", From 5c1d9b28cd411ee1c1787ddc971b7bdc9c418b80 Mon Sep 17 00:00:00 2001 From: Simon Lamon <32477463+silamon@users.noreply.github.com> Date: Sat, 4 Nov 2023 13:10:36 +0000 Subject: [PATCH 02/46] Update yarn lock --- yarn.lock | 69 +++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 44 insertions(+), 25 deletions(-) diff --git a/yarn.lock b/yarn.lock index a0cb39da..5f25390b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -265,14 +265,14 @@ resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz#1d572bfbbe14b7704e0ba0f39b74815b84870d70" integrity sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw== -"@es-joy/jsdoccomment@~0.36.1": - version "0.36.1" - resolved "https://registry.yarnpkg.com/@es-joy/jsdoccomment/-/jsdoccomment-0.36.1.tgz#c37db40da36e4b848da5fd427a74bae3b004a30f" - integrity sha512-922xqFsTpHs6D0BUiG4toiyPOMc8/jafnWKxz1KWgS4XzKPy2qXf1Pe6UFuNSCQqt6tOuhAWXBNuuyUhJmw9Vg== +"@es-joy/jsdoccomment@~0.40.1": + version "0.40.1" + resolved "https://registry.yarnpkg.com/@es-joy/jsdoccomment/-/jsdoccomment-0.40.1.tgz#13acd77fb372ed1c83b7355edd865a3b370c9ec4" + integrity sha512-YORCdZSusAlBrFpZ77pJjc5r1bQs5caPWtAu+WWmiSo+8XaUzseapVrfAtiRFbQWnrBxxLLEwF6f6ZG/UgCQCg== dependencies: - comment-parser "1.3.1" - esquery "^1.4.0" - jsdoc-type-pratt-parser "~3.1.0" + comment-parser "1.4.0" + esquery "^1.5.0" + jsdoc-type-pratt-parser "~4.0.0" "@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0": version "4.4.0" @@ -1029,6 +1029,11 @@ archy@^1.0.0: resolved "https://registry.yarnpkg.com/archy/-/archy-1.0.0.tgz#f9c8c13757cc1dd7bc379ac77b2c62a5c2868c40" integrity sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw== +are-docs-informative@^0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/are-docs-informative/-/are-docs-informative-0.0.2.tgz#387f0e93f5d45280373d387a59d34c96db321963" + integrity sha512-ixiS0nLNNG5jNQzgZJNoUpBKdo9yTYZMGJ+QgT2jmjR7G7+QHRCc4v6LQ3NgE7EBJq+o0ams3waJwkrlBom8Ig== + argparse@^1.0.7: version "1.0.10" resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" @@ -1149,6 +1154,11 @@ buffer-from@^1.0.0: resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== +builtin-modules@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.3.0.tgz#cae62812b89801e9656336e46223e030386be7b6" + integrity sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw== + bytes@3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" @@ -1355,10 +1365,10 @@ commander@^7.0.0: resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7" integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw== -comment-parser@1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/comment-parser/-/comment-parser-1.3.1.tgz#3d7ea3adaf9345594aedee6563f422348f165c1b" - integrity sha512-B52sN2VNghyq5ofvUsqZjmk6YkihBX5vMSChmSK9v4ShjKf3Vk5Xcmgpw4o+iIgtrnM/u5FiMpz9VKb8lpBveA== +comment-parser@1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/comment-parser/-/comment-parser-1.4.0.tgz#0f8c560f59698193854f12884c20c0e39a26d32c" + integrity sha512-QLyTNiZ2KDOibvFPlZ6ZngVsZ/0gYnE6uTXi5aoDg8ed3AkJAz4sEje3Y8a29hQ1s6A99MZXe47fLAXQ1rTqaw== commondir@^1.0.1: version "1.0.1" @@ -1666,17 +1676,19 @@ escodegen@^2.0.0: optionalDependencies: source-map "~0.6.1" -eslint-plugin-jsdoc@^39.3.6: - version "39.9.1" - resolved "https://registry.yarnpkg.com/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-39.9.1.tgz#e9ce1723411fd7ea0933b3ef0dd02156ae3068e2" - integrity sha512-Rq2QY6BZP2meNIs48aZ3GlIlJgBqFCmR55+UBvaDkA3ZNQ0SvQXOs2QKkubakEijV8UbIVbVZKsOVN8G3MuqZw== +eslint-plugin-jsdoc@^46.8.2: + version "46.8.2" + resolved "https://registry.yarnpkg.com/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-46.8.2.tgz#3e6b1c93e91e38fe01874d45da121b56393c54a5" + integrity sha512-5TSnD018f3tUJNne4s4gDWQflbsgOycIKEUBoCLn6XtBMgNHxQFmV8vVxUtiPxAQq8lrX85OaSG/2gnctxw9uQ== dependencies: - "@es-joy/jsdoccomment" "~0.36.1" - comment-parser "1.3.1" + "@es-joy/jsdoccomment" "~0.40.1" + are-docs-informative "^0.0.2" + comment-parser "1.4.0" debug "^4.3.4" escape-string-regexp "^4.0.0" - esquery "^1.4.0" - semver "^7.3.8" + esquery "^1.5.0" + is-builtin-module "^3.2.1" + semver "^7.5.4" spdx-expression-parse "^3.0.1" eslint-scope@5.1.1: @@ -1757,7 +1769,7 @@ esprima@^4.0.0, esprima@^4.0.1: resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== -esquery@^1.4.0, esquery@^1.4.2: +esquery@^1.4.2, esquery@^1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.5.0.tgz#6ce17738de8577694edd7361c57182ac8cb0db0b" integrity sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg== @@ -2341,6 +2353,13 @@ is-boolean-object@^1.1.0: call-bind "^1.0.2" has-tostringtag "^1.0.0" +is-builtin-module@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/is-builtin-module/-/is-builtin-module-3.2.1.tgz#f03271717d8654cfcaf07ab0463faa3571581169" + integrity sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A== + dependencies: + builtin-modules "^3.3.0" + is-callable@^1.1.3: version "1.2.7" resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055" @@ -2599,10 +2618,10 @@ js-yaml@^3.13.1: argparse "^1.0.7" esprima "^4.0.0" -jsdoc-type-pratt-parser@~3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-3.1.0.tgz#a4a56bdc6e82e5865ffd9febc5b1a227ff28e67e" - integrity sha512-MgtD0ZiCDk9B+eI73BextfRrVQl0oyzRG8B2BjORts6jbunj4ScKPcyXGTbB6eXL4y9TzxCm6hyeLq/2ASzNdw== +jsdoc-type-pratt-parser@~4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-4.0.0.tgz#136f0571a99c184d84ec84662c45c29ceff71114" + integrity sha512-YtOli5Cmzy3q4dP26GraSOeAhqecewG04hoO8DY56CH4KJ9Fvv5qKWUCCo3HZob7esJQHCv6/+bnTy72xZZaVQ== jsdom@^18.0.1: version "18.1.1" @@ -3348,7 +3367,7 @@ semver@^6.0.0, semver@^6.3.0, semver@^6.3.1: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== -semver@^7.3.4, semver@^7.3.8, semver@^7.5.3, semver@^7.5.4: +semver@^7.3.4, semver@^7.5.3, semver@^7.5.4: version "7.5.4" resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== From b1d9b4751deffd1669b3cbf7b9384619ccd13dca Mon Sep 17 00:00:00 2001 From: tisilent Date: Mon, 1 Jan 2024 22:25:54 +0800 Subject: [PATCH 03/46] Check after updating the SelectionRenderModel --- src/browser/renderer/dom/DomRenderer.ts | 8 +++----- test/playwright/SharedRendererTests.ts | 14 ++++++++++++++ 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/browser/renderer/dom/DomRenderer.ts b/src/browser/renderer/dom/DomRenderer.ts index 1549b130..439f8457 100644 --- a/src/browser/renderer/dom/DomRenderer.ts +++ b/src/browser/renderer/dom/DomRenderer.ts @@ -324,6 +324,9 @@ export class DomRenderer extends Disposable implements IRenderer { } this._selectionRenderModel.update(this._terminal, start, end, columnSelectMode); + if (!this._selectionRenderModel.hasSelection) { + return; + } // Translate from buffer position to viewport position const viewportStartRow = this._selectionRenderModel.viewportStartRow; @@ -331,11 +334,6 @@ export class DomRenderer extends Disposable implements IRenderer { const viewportCappedStartRow = this._selectionRenderModel.viewportCappedStartRow; const viewportCappedEndRow = this._selectionRenderModel.viewportCappedEndRow; - // No need to draw the selection - if (viewportCappedStartRow >= this._bufferService.rows || viewportCappedEndRow < 0) { - return; - } - // Create the selections const documentFragment = this._document.createDocumentFragment(); diff --git a/test/playwright/SharedRendererTests.ts b/test/playwright/SharedRendererTests.ts index 657fba44..5871e8e4 100644 --- a/test/playwright/SharedRendererTests.ts +++ b/test/playwright/SharedRendererTests.ts @@ -1128,6 +1128,20 @@ export function injectSharedRendererTests(ctx: ISharedRendererTestContext): void await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [0, 0, 0, 255]); // inverse foreground of '■' should be default await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 1), [0, 0, 255, 255]); // inverse background of ' ' should be decoration bg override }); + test('#4911 The selection should not be displayed if it is not within the scope of the viewport.', async () => { + const theme: ITheme = { + selectionBackground: '#FF0000' + }; + await ctx.value.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`); + for (let index = 0; index < 160; index++) { + await ctx.value.proxy.writeln(``); + } + await ctx.value.proxy.scrollToBottom(); + const rows = await ctx.value.proxy.buffer.active.length; + await ctx.value.proxy.selectLines(rows - 1, rows - 1); + await ctx.value.proxy.scrollLines(-2); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [0, 0, 0, 255]); + }); }); test.describe('regression tests', () => { From 750f3179dec28a3a8620b6856ce2d898b70ece5e Mon Sep 17 00:00:00 2001 From: tisilent Date: Mon, 1 Jan 2024 22:48:39 +0800 Subject: [PATCH 04/46] move test --- test/playwright/SharedRendererTests.ts | 28 +++++++++++++------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/test/playwright/SharedRendererTests.ts b/test/playwright/SharedRendererTests.ts index 5871e8e4..322e03f4 100644 --- a/test/playwright/SharedRendererTests.ts +++ b/test/playwright/SharedRendererTests.ts @@ -1128,20 +1128,6 @@ export function injectSharedRendererTests(ctx: ISharedRendererTestContext): void await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [0, 0, 0, 255]); // inverse foreground of '■' should be default await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 1), [0, 0, 255, 255]); // inverse background of ' ' should be decoration bg override }); - test('#4911 The selection should not be displayed if it is not within the scope of the viewport.', async () => { - const theme: ITheme = { - selectionBackground: '#FF0000' - }; - await ctx.value.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`); - for (let index = 0; index < 160; index++) { - await ctx.value.proxy.writeln(``); - } - await ctx.value.proxy.scrollToBottom(); - const rows = await ctx.value.proxy.buffer.active.length; - await ctx.value.proxy.selectLines(rows - 1, rows - 1); - await ctx.value.proxy.scrollLines(-2); - await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [0, 0, 0, 255]); - }); }); test.describe('regression tests', () => { @@ -1246,6 +1232,20 @@ export function injectSharedRendererTests(ctx: ISharedRendererTestContext): void await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, rows), [0, 0, 0, 255]); await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, rows, CellColorPosition.FIRST), [0, 0, 255, 255]); }); + test('#4917 The selection should not be displayed if it is not within the scope of the viewport.', async () => { + const theme: ITheme = { + selectionBackground: '#FF0000' + }; + await ctx.value.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`); + for (let index = 0; index < 160; index++) { + await ctx.value.proxy.writeln(``); + } + await ctx.value.proxy.scrollToBottom(); + const rows = await ctx.value.proxy.buffer.active.length; + await ctx.value.proxy.selectLines(rows - 1, rows - 1); + await ctx.value.proxy.scrollLines(-2); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [0, 0, 0, 255]); + }); }); } From 547bda6e4fa03386448148664f05aaf5c672f91f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Thu, 22 Feb 2024 09:33:21 +0100 Subject: [PATCH 05/46] fix #4964 --- addons/addon-web-links/src/WebLinkProvider.ts | 30 ++++++++----------- addons/addon-web-links/src/WebLinksAddon.ts | 2 +- .../addon-web-links/test/WebLinksAddon.api.ts | 13 ++++++++ 3 files changed, 26 insertions(+), 19 deletions(-) diff --git a/addons/addon-web-links/src/WebLinkProvider.ts b/addons/addon-web-links/src/WebLinkProvider.ts index 713f9c23..63211dad 100644 --- a/addons/addon-web-links/src/WebLinkProvider.ts +++ b/addons/addon-web-links/src/WebLinkProvider.ts @@ -41,16 +41,18 @@ export class WebLinkProvider implements ILinkProvider { } } -function baseUrlString(url: URL): string { - if (url.password && url.username) { - return `${url.protocol}//${url.username}:${url.password}@${url.host}`; +function isUrl(urlString: string): boolean { + try { + const url = new URL(urlString); + const parsedBase = url.password && url.username + ? `${url.protocol}//${url.username}:${url.password}@${url.host}` + : url.username + ? `${url.protocol}//${url.username}@${url.host}` + : `${url.protocol}//${url.host}`; + return urlString.toLocaleLowerCase().startsWith(parsedBase); + } catch (e) { + return false; } - - if (url.username) { - return `${url.protocol}//${url.username}@${url.host}`; - } - - return `${url.protocol}//${url.host}`; } export class LinkComputer { @@ -67,15 +69,7 @@ export class LinkComputer { const text = match[0]; // check via URL if the matched text would form a proper url - // NOTE: This outsources the ugly url parsing to the browser. - // we check if the provided string resembles the URL-parsed one - // up to the end of the domain name (ignoring path and params) - try { - const url = new URL(text); - if (!text.startsWith(baseUrlString(url))) { - continue; - } - } catch (e) { + if (!isUrl(text)) { continue; } diff --git a/addons/addon-web-links/src/WebLinksAddon.ts b/addons/addon-web-links/src/WebLinksAddon.ts index 8902d8e0..b3f0548c 100644 --- a/addons/addon-web-links/src/WebLinksAddon.ts +++ b/addons/addon-web-links/src/WebLinksAddon.ts @@ -18,7 +18,7 @@ import { ILinkProviderOptions, WebLinkProvider } from './WebLinkProvider'; // - final interpunction like ,.!? // - any sort of brackets <>()[]{} (not spec conform, but often used to enclose urls) // - unsafe chars from rfc1738: {}|\^~[]` -const strictUrlRegex = /https?:[/]{2}[^\s"'!*(){}|\\\^<>`]*[^\s"':,.!?{}|\\\^~\[\]`()<>]/; +const strictUrlRegex = /(https?|HTTPS?):[/]{2}[^\s"'!*(){}|\\\^<>`]*[^\s"':,.!?{}|\\\^~\[\]`()<>]/; function handleLink(event: MouseEvent, uri: string): void { diff --git a/addons/addon-web-links/test/WebLinksAddon.api.ts b/addons/addon-web-links/test/WebLinksAddon.api.ts index 5b05ce2e..26a7421d 100644 --- a/addons/addon-web-links/test/WebLinksAddon.api.ts +++ b/addons/addon-web-links/test/WebLinksAddon.api.ts @@ -123,6 +123,19 @@ describe('WebLinksAddon', () => { await evalLinkStateData('http://test:password@example.com/some_path?param=1%202%3', { start: { x: 12, y: 1 }, end: { x: 27, y: 2 } }); }); }); + + // issue #4964 + it.only('uppercase in protocol and host, default ports', async () => { + const data = ` HTTP://EXAMPLE.COM \\r\\n` + + ` HTTPS://Example.com \\r\\n` + + ` HTTP://Example.com:80 \\r\\n` + + ` HTTP://Example.com:80/staysUpper \\r\\n`; + await writeSync(page, data); + await pollForLinkAtCell(3, 0, `HTTP://EXAMPLE.COM`); + await pollForLinkAtCell(3, 1, `HTTPS://Example.com`); + await pollForLinkAtCell(3, 2, `HTTP://Example.com:80`); + await pollForLinkAtCell(3, 3, `HTTP://Example.com:80/staysUpper`); + }); }); async function testHostName(hostname: string): Promise { From 768452a519e96bb53faa1eb7e306113295c6346b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Thu, 22 Feb 2024 09:38:36 +0100 Subject: [PATCH 06/46] make linter happy --- addons/addon-web-links/test/WebLinksAddon.api.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/addons/addon-web-links/test/WebLinksAddon.api.ts b/addons/addon-web-links/test/WebLinksAddon.api.ts index 26a7421d..e4e5ce94 100644 --- a/addons/addon-web-links/test/WebLinksAddon.api.ts +++ b/addons/addon-web-links/test/WebLinksAddon.api.ts @@ -126,15 +126,15 @@ describe('WebLinksAddon', () => { // issue #4964 it.only('uppercase in protocol and host, default ports', async () => { - const data = ` HTTP://EXAMPLE.COM \\r\\n` + + const data = ` HTTP://EXAMPLE.COM \\r\\n` + ` HTTPS://Example.com \\r\\n` + ` HTTP://Example.com:80 \\r\\n` + ` HTTP://Example.com:80/staysUpper \\r\\n`; - await writeSync(page, data); - await pollForLinkAtCell(3, 0, `HTTP://EXAMPLE.COM`); - await pollForLinkAtCell(3, 1, `HTTPS://Example.com`); - await pollForLinkAtCell(3, 2, `HTTP://Example.com:80`); - await pollForLinkAtCell(3, 3, `HTTP://Example.com:80/staysUpper`); + await writeSync(page, data); + await pollForLinkAtCell(3, 0, `HTTP://EXAMPLE.COM`); + await pollForLinkAtCell(3, 1, `HTTPS://Example.com`); + await pollForLinkAtCell(3, 2, `HTTP://Example.com:80`); + await pollForLinkAtCell(3, 3, `HTTP://Example.com:80/staysUpper`); }); }); From 5143f904ee7926740f194328356594ebf2c9a8b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Thu, 22 Feb 2024 09:40:38 +0100 Subject: [PATCH 07/46] remove leftover .only --- addons/addon-web-links/test/WebLinksAddon.api.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/addon-web-links/test/WebLinksAddon.api.ts b/addons/addon-web-links/test/WebLinksAddon.api.ts index e4e5ce94..6c3c8b1e 100644 --- a/addons/addon-web-links/test/WebLinksAddon.api.ts +++ b/addons/addon-web-links/test/WebLinksAddon.api.ts @@ -125,7 +125,7 @@ describe('WebLinksAddon', () => { }); // issue #4964 - it.only('uppercase in protocol and host, default ports', async () => { + it('uppercase in protocol and host, default ports', async () => { const data = ` HTTP://EXAMPLE.COM \\r\\n` + ` HTTPS://Example.com \\r\\n` + ` HTTP://Example.com:80 \\r\\n` + From 10fe2498777abe45691ec427826299c476a85e01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Thu, 22 Feb 2024 09:55:42 +0100 Subject: [PATCH 08/46] allow user+password to still match --- addons/addon-web-links/src/WebLinkProvider.ts | 2 +- addons/addon-web-links/test/WebLinksAddon.api.ts | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/addons/addon-web-links/src/WebLinkProvider.ts b/addons/addon-web-links/src/WebLinkProvider.ts index 63211dad..66691f44 100644 --- a/addons/addon-web-links/src/WebLinkProvider.ts +++ b/addons/addon-web-links/src/WebLinkProvider.ts @@ -49,7 +49,7 @@ function isUrl(urlString: string): boolean { : url.username ? `${url.protocol}//${url.username}@${url.host}` : `${url.protocol}//${url.host}`; - return urlString.toLocaleLowerCase().startsWith(parsedBase); + return urlString.toLocaleLowerCase().startsWith(parsedBase.toLocaleLowerCase()); } catch (e) { return false; } diff --git a/addons/addon-web-links/test/WebLinksAddon.api.ts b/addons/addon-web-links/test/WebLinksAddon.api.ts index 6c3c8b1e..99c11600 100644 --- a/addons/addon-web-links/test/WebLinksAddon.api.ts +++ b/addons/addon-web-links/test/WebLinksAddon.api.ts @@ -129,12 +129,14 @@ describe('WebLinksAddon', () => { const data = ` HTTP://EXAMPLE.COM \\r\\n` + ` HTTPS://Example.com \\r\\n` + ` HTTP://Example.com:80 \\r\\n` + - ` HTTP://Example.com:80/staysUpper \\r\\n`; + ` HTTP://Example.com:80/staysUpper \\r\\n` + + ` HTTP://Ab:xY@abc.com:80/staysUpper \\r\\n`; await writeSync(page, data); await pollForLinkAtCell(3, 0, `HTTP://EXAMPLE.COM`); await pollForLinkAtCell(3, 1, `HTTPS://Example.com`); await pollForLinkAtCell(3, 2, `HTTP://Example.com:80`); await pollForLinkAtCell(3, 3, `HTTP://Example.com:80/staysUpper`); + await pollForLinkAtCell(3, 4, `HTTP://Ab:xY@abc.com:80/staysUpper`); }); }); From 8ef3c1084417e9b5ee7813c1e50f14a37f83f6ea Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 29 Feb 2024 07:37:12 -0800 Subject: [PATCH 09/46] Fix spacing issue when measuring before element is attached See microsoft/vscode#204690 --- src/browser/renderer/dom/WidthCache.ts | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/browser/renderer/dom/WidthCache.ts b/src/browser/renderer/dom/WidthCache.ts index 1527bad0..03d6cb70 100644 --- a/src/browser/renderer/dom/WidthCache.ts +++ b/src/browser/renderer/dom/WidthCache.ts @@ -134,9 +134,14 @@ export class WidthCache implements IDisposable { public get(c: string, bold: boolean | number, italic: boolean | number): number { let cp = 0; if (!bold && !italic && c.length === 1 && (cp = c.charCodeAt(0)) < WidthCacheSettings.FLAT_SIZE) { - return this._flat[cp] !== WidthCacheSettings.FLAT_UNSET - ? this._flat[cp] - : (this._flat[cp] = this._measure(c, 0)); + if (this._flat[cp] !== WidthCacheSettings.FLAT_UNSET) { + return this._flat[cp]; + } + const width = this._measure(c, 0); + if (width > 0) { + this._flat[cp] = width; + } + return width; } let key = c; if (bold) key += 'B'; @@ -147,7 +152,9 @@ export class WidthCache implements IDisposable { if (bold) variant |= FontVariant.BOLD; if (italic) variant |= FontVariant.ITALIC; width = this._measure(c, variant); - this._holey!.set(key, width); + if (width > 0) { + this._holey!.set(key, width); + } } return width; } From 72dea6279ebb52e7425bb3f296809c28ad4b0c16 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 1 Mar 2024 08:39:19 -0800 Subject: [PATCH 10/46] v5.4.0 --- addons/addon-attach/package.json | 2 +- addons/addon-canvas/package.json | 2 +- addons/addon-fit/package.json | 2 +- addons/addon-image/package.json | 2 +- addons/addon-ligatures/package.json | 2 +- addons/addon-search/package.json | 2 +- addons/addon-serialize/package.json | 2 +- addons/addon-unicode-graphemes/package.json | 2 +- addons/addon-unicode11/package.json | 2 +- addons/addon-web-links/package.json | 2 +- addons/addon-webgl/package.json | 2 +- package.json | 2 +- 12 files changed, 12 insertions(+), 12 deletions(-) diff --git a/addons/addon-attach/package.json b/addons/addon-attach/package.json index 94b483dd..c2f42ce5 100644 --- a/addons/addon-attach/package.json +++ b/addons/addon-attach/package.json @@ -1,6 +1,6 @@ { "name": "@xterm/addon-attach", - "version": "0.9.0", + "version": "0.10.0", "author": { "name": "The xterm.js authors", "url": "https://xtermjs.org/" diff --git a/addons/addon-canvas/package.json b/addons/addon-canvas/package.json index 9aabb001..8fdec96b 100644 --- a/addons/addon-canvas/package.json +++ b/addons/addon-canvas/package.json @@ -1,6 +1,6 @@ { "name": "@xterm/addon-canvas", - "version": "0.5.0", + "version": "0.6.0", "author": { "name": "The xterm.js authors", "url": "https://xtermjs.org/" diff --git a/addons/addon-fit/package.json b/addons/addon-fit/package.json index 585f3621..be81b196 100644 --- a/addons/addon-fit/package.json +++ b/addons/addon-fit/package.json @@ -1,6 +1,6 @@ { "name": "@xterm/addon-fit", - "version": "0.8.0", + "version": "0.9.0", "author": { "name": "The xterm.js authors", "url": "https://xtermjs.org/" diff --git a/addons/addon-image/package.json b/addons/addon-image/package.json index 8572e330..2e28955d 100644 --- a/addons/addon-image/package.json +++ b/addons/addon-image/package.json @@ -1,6 +1,6 @@ { "name": "@xterm/addon-image", - "version": "0.6.0", + "version": "0.7.0", "author": { "name": "The xterm.js authors", "url": "https://xtermjs.org/" diff --git a/addons/addon-ligatures/package.json b/addons/addon-ligatures/package.json index e5c6b972..60166577 100644 --- a/addons/addon-ligatures/package.json +++ b/addons/addon-ligatures/package.json @@ -1,6 +1,6 @@ { "name": "@xterm/addon-ligatures", - "version": "0.7.0", + "version": "0.8.0", "description": "Add support for programming ligatures to xterm.js", "author": { "name": "The xterm.js authors", diff --git a/addons/addon-search/package.json b/addons/addon-search/package.json index 369d11c3..35a4015e 100644 --- a/addons/addon-search/package.json +++ b/addons/addon-search/package.json @@ -1,6 +1,6 @@ { "name": "@xterm/addon-search", - "version": "0.13.0", + "version": "0.14.0", "author": { "name": "The xterm.js authors", "url": "https://xtermjs.org/" diff --git a/addons/addon-serialize/package.json b/addons/addon-serialize/package.json index 763c52ca..9c50288d 100644 --- a/addons/addon-serialize/package.json +++ b/addons/addon-serialize/package.json @@ -1,6 +1,6 @@ { "name": "@xterm/addon-serialize", - "version": "0.11.0", + "version": "0.12.0", "author": { "name": "The xterm.js authors", "url": "https://xtermjs.org/" diff --git a/addons/addon-unicode-graphemes/package.json b/addons/addon-unicode-graphemes/package.json index d49eda87..610e14b4 100644 --- a/addons/addon-unicode-graphemes/package.json +++ b/addons/addon-unicode-graphemes/package.json @@ -1,6 +1,6 @@ { "name": "@xterm/addon-unicode-graphemes", - "version": "0.1.0", + "version": "0.2.0", "author": { "name": "The xterm.js authors", "url": "https://xtermjs.org/" diff --git a/addons/addon-unicode11/package.json b/addons/addon-unicode11/package.json index ad6a4892..e511c65a 100644 --- a/addons/addon-unicode11/package.json +++ b/addons/addon-unicode11/package.json @@ -1,6 +1,6 @@ { "name": "@xterm/addon-unicode11", - "version": "0.6.0", + "version": "0.7.0", "author": { "name": "The xterm.js authors", "url": "https://xtermjs.org/" diff --git a/addons/addon-web-links/package.json b/addons/addon-web-links/package.json index 6367907a..6a65f289 100644 --- a/addons/addon-web-links/package.json +++ b/addons/addon-web-links/package.json @@ -1,6 +1,6 @@ { "name": "@xterm/addon-web-links", - "version": "0.9.0", + "version": "0.10.0", "author": { "name": "The xterm.js authors", "url": "https://xtermjs.org/" diff --git a/addons/addon-webgl/package.json b/addons/addon-webgl/package.json index 9a31c306..f504bb7e 100644 --- a/addons/addon-webgl/package.json +++ b/addons/addon-webgl/package.json @@ -1,6 +1,6 @@ { "name": "@xterm/addon-webgl", - "version": "0.16.0", + "version": "0.17.0", "author": { "name": "The xterm.js authors", "url": "https://xtermjs.org/" diff --git a/package.json b/package.json index 050b8cad..dd425db6 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@xterm/xterm", "description": "Full xterm terminal, in your browser", - "version": "5.3.0", + "version": "5.4.0", "main": "lib/xterm.js", "style": "css/xterm.css", "types": "typings/xterm.d.ts", From 6ca8dc54ad942be16d84fd7e37453a3ff08c80b2 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 1 Mar 2024 09:00:33 -0800 Subject: [PATCH 11/46] Fix publishing of stable --- bin/publish.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/bin/publish.js b/bin/publish.js index 360e09fe..ad945534 100644 --- a/bin/publish.js +++ b/bin/publish.js @@ -57,9 +57,8 @@ function checkAndPublishPackage(packageDir) { const packageJson = require(path.join(packageDir, 'package.json')); // Determine if this is a stable or beta release - // TODO: Uncomment when publishing 5.4 - // const publishedVersions = getPublishedVersions(packageJson); - const isStableRelease = false; //!publishedVersions.includes(packageJson.version); + const publishedVersions = getPublishedVersions(packageJson); + const isStableRelease = !publishedVersions.includes(packageJson.version); // Get the next version let nextVersion = isStableRelease ? packageJson.version : getNextBetaVersion(packageJson); From da3aad4497e33c8757b4820c95bc8d7d70ade362 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 1 Mar 2024 09:18:18 -0800 Subject: [PATCH 12/46] Update script/css import in readme Part of #4859 --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7514e76c..c85ce317 100644 --- a/README.md +++ b/README.md @@ -30,8 +30,8 @@ To start using xterm.js on your browser, add the `xterm.js` and `xterm.css` to t - - + +
From 17fd7389c48e45e7d7bbb00dd3852570c30a1071 Mon Sep 17 00:00:00 2001 From: Daniel Steinberg Date: Sat, 2 Mar 2024 03:46:10 +0000 Subject: [PATCH 13/46] Add @xterm scope to packages --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c85ce317..b055ea7b 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ Xterm.js is a front-end component written in TypeScript that lets applications b First, you need to install the module, we ship exclusively through [npm](https://www.npmjs.com/), so you need that installed and then add xterm.js as a dependency by running: ```bash -npm install xterm +npm install @xterm/xterm ``` To start using xterm.js on your browser, add the `xterm.js` and `xterm.css` to the head of your HTML page. Then create a `
` onto which xterm can attach itself. Finally, instantiate the `Terminal` object and then call the `open` function with the DOM object of the `div`. @@ -113,7 +113,7 @@ All current and past releases are available on this repo's [Releases page](https Our CI releases beta builds to npm for every change that goes into master. Install the latest beta build with: ```bash -npm install -S xterm@beta +npm install -S @xterm/xterm@beta ``` These should generally be stable, but some bugs may slip in. We recommend using the beta build primarily to test out new features and to verify bug fixes. From fe22671a6edfefb38727e1c2d9346baea1e7ad9a Mon Sep 17 00:00:00 2001 From: tisilent Date: Wed, 6 Mar 2024 17:03:25 +0800 Subject: [PATCH 14/46] Clear timer when dispose --- src/browser/Viewport.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/browser/Viewport.ts b/src/browser/Viewport.ts index a8e1a498..cea2ca8f 100644 --- a/src/browser/Viewport.ts +++ b/src/browser/Viewport.ts @@ -49,6 +49,8 @@ export class Viewport extends Disposable implements IViewport { target: -1 }; + private _ensureTimeout: number; + private readonly _onRequestScrollLines = this.register(new EventEmitter<{ amount: number, suppressScrollEvent: boolean }>()); public readonly onRequestScrollLines = this._onRequestScrollLines.event; @@ -81,7 +83,7 @@ export class Viewport extends Disposable implements IViewport { this.register(this._optionsService.onSpecificOptionChange('scrollback', () => this.syncScrollArea())); // Perform this async to ensure the ICharSizeService is ready. - setTimeout(() => this.syncScrollArea()); + this._ensureTimeout = window.setTimeout(() => this.syncScrollArea()); } private _handleThemeChange(colors: ReadonlyColorSet): void { @@ -398,4 +400,8 @@ export class Viewport extends Disposable implements IViewport { this._viewportElement.scrollTop += deltaY; return this._bubbleScroll(ev, deltaY); } + + public dispose(): void { + clearTimeout(this._ensureTimeout); + } } From 553b9f261a2f231d4e16f27d6e745592432c0b94 Mon Sep 17 00:00:00 2001 From: Knox Lively Date: Wed, 6 Mar 2024 23:56:04 -0700 Subject: [PATCH 15/46] adding Wave Terminal to the README --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index b055ea7b..5f4a786a 100644 --- a/README.md +++ b/README.md @@ -222,6 +222,7 @@ Xterm.js is used in several world-class applications to provide great terminal e - [**Cloudtutor.io**](https://cloudtutor.io): innovative online learning platform that offers users access to an interactive lab. - [**Helix Editor Playground**](https://github.com/tomgroenwoldt/helix-editor-playground): Online playground for the terminal based helix editor. - [**Coder**](https://github.com/coder/coder): Self-Hosted Remote Development Environments +- [**Wave Terminal**](https://waveterm.dev): An open-source, ai-native, terminal built for seamless workflows. - [And much more...](https://github.com/xtermjs/xterm.js/network/dependents?package_id=UGFja2FnZS0xNjYzMjc4OQ%3D%3D) Do you use xterm.js in your application as well? Please [open a Pull Request](https://github.com/sourcelair/xterm.js/pulls) to include it here. We would love to have it on our list. Note: Please add any new contributions to the end of the list only. From c32313a471bf91fbcc21ff69fdae8018f9009037 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 7 Mar 2024 05:21:59 -0800 Subject: [PATCH 16/46] Add default to api d.ts Fixes #4992 --- typings/xterm-headless.d.ts | 2 +- typings/xterm.d.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/typings/xterm-headless.d.ts b/typings/xterm-headless.d.ts index b2ad5745..40034607 100644 --- a/typings/xterm-headless.d.ts +++ b/typings/xterm-headless.d.ts @@ -156,7 +156,7 @@ declare module '@xterm/headless' { /** * The amount of scrollback in the terminal. Scrollback is the amount of * rows that are retained when lines are scrolled beyond the initial - * viewport. + * viewport. Defaults to 1000. */ scrollback?: number; diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index d957d655..a46db4be 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -225,7 +225,7 @@ declare module '@xterm/xterm' { /** * The amount of scrollback in the terminal. Scrollback is the amount of * rows that are retained when lines are scrolled beyond the initial - * viewport. + * viewport. Defaults to 1000. */ scrollback?: number; From 33a7a9b6a2dc1e8781722bc9e6797e25f61ee87b Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 14 Mar 2024 07:14:13 -0700 Subject: [PATCH 17/46] Implement rescaleOverlappingGlyphs in webgl Part of #4969 --- addons/addon-webgl/src/GlyphRenderer.ts | 27 ++++++++++++++++---- addons/addon-webgl/src/WebglRenderer.ts | 11 +++++--- src/browser/renderer/shared/RendererUtils.ts | 13 ++++++++++ src/browser/services/RenderService.ts | 3 ++- src/common/services/OptionsService.ts | 1 + src/common/services/Services.ts | 1 + typings/xterm-headless.d.ts | 11 ++++++++ typings/xterm.d.ts | 11 ++++++++ 8 files changed, 68 insertions(+), 10 deletions(-) diff --git a/addons/addon-webgl/src/GlyphRenderer.ts b/addons/addon-webgl/src/GlyphRenderer.ts index 1fb0e18c..7e221fc6 100644 --- a/addons/addon-webgl/src/GlyphRenderer.ts +++ b/addons/addon-webgl/src/GlyphRenderer.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { throwIfFalsy } from 'browser/renderer/shared/RendererUtils'; +import { isEmoji, throwIfFalsy } from 'browser/renderer/shared/RendererUtils'; import { TextureAtlas } from 'browser/renderer/shared/TextureAtlas'; import { IRasterizedGlyph, IRenderDimensions, ITextureAtlas } from 'browser/renderer/shared/Types'; import { NULL_CELL_CODE } from 'common/buffer/Constants'; @@ -11,6 +11,7 @@ import { Disposable, toDisposable } from 'common/Lifecycle'; import { Terminal } from '@xterm/xterm'; import { IRenderModel, IWebGL2RenderingContext, IWebGLVertexArrayObject } from './Types'; import { createProgram, GLTexture, PROJECTION_MATRIX } from './WebglUtils'; +import type { IOptionsService } from 'common/services/Services'; interface IVertices { attributes: Float32Array; @@ -111,7 +112,8 @@ export class GlyphRenderer extends Disposable { constructor( private readonly _terminal: Terminal, private readonly _gl: IWebGL2RenderingContext, - private _dimensions: IRenderDimensions + private _dimensions: IRenderDimensions, + private readonly _optionsService: IOptionsService ) { super(); @@ -212,15 +214,15 @@ export class GlyphRenderer extends Disposable { return this._atlas ? this._atlas.beginFrame() : true; } - public updateCell(x: number, y: number, code: number, bg: number, fg: number, ext: number, chars: string, lastBg: number): void { + public updateCell(x: number, y: number, code: number, bg: number, fg: number, ext: number, chars: string, width: number, lastBg: number): void { // Since this function is called for every cell (`rows*cols`), it must be very optimized. It // should not instantiate any variables unless a new glyph is drawn to the cache where the // slight slowdown is acceptable for the developer ergonomics provided as it's a once of for // each glyph. - this._updateCell(this._vertices.attributes, x, y, code, bg, fg, ext, chars, lastBg); + this._updateCell(this._vertices.attributes, x, y, code, bg, fg, ext, chars, width, lastBg); } - private _updateCell(array: Float32Array, x: number, y: number, code: number | undefined, bg: number, fg: number, ext: number, chars: string, lastBg: number): void { + private _updateCell(array: Float32Array, x: number, y: number, code: number | undefined, bg: number, fg: number, ext: number, chars: string, width: number, lastBg: number): void { $i = (y * this._terminal.cols + x) * INDICES_PER_CELL; // Exit early if this is a null character, allow space character to continue as it may have @@ -275,6 +277,21 @@ export class GlyphRenderer extends Disposable { array[$i + 8] = $glyph.sizeClipSpace.y; } // a_cellpos only changes on resize + + // Reduce scale horizontally for wide glyphs printed in cells that would overlap with the + // following cell (ie. the width is not 2). + if (this._optionsService.rawOptions.rescaleOverlappingGlyphs) { + if ( + // Is single cell width + width === 1 && + // Glyph exceeds cell bounds, + 1 to avoid hurting readability + $glyph.size.x > this._dimensions.device.cell.width + 1 && + // Never rescale emoji + code && !isEmoji(code) + ) { + array[$i + 2] = (this._dimensions.device.cell.width - /* improve readability */1) / this._dimensions.device.canvas.width; // - 1 to improve readability + } + } } public clear(): void { diff --git a/addons/addon-webgl/src/WebglRenderer.ts b/addons/addon-webgl/src/WebglRenderer.ts index 3a01e244..fa178652 100644 --- a/addons/addon-webgl/src/WebglRenderer.ts +++ b/addons/addon-webgl/src/WebglRenderer.ts @@ -36,7 +36,8 @@ export class WebglRenderer extends Disposable implements IRenderer { private _observerDisposable = this.register(new MutableDisposable()); private _model: RenderModel = new RenderModel(); - private _workCell: CellData = new CellData(); + private _workCell: ICellData = new CellData(); + private _workCell2: ICellData = new CellData(); private _cellColorResolver: CellColorResolver; private _canvas: HTMLCanvasElement; @@ -245,7 +246,7 @@ export class WebglRenderer extends Disposable implements IRenderer { */ private _initializeWebGLState(): [RectangleRenderer, GlyphRenderer] { this._rectangleRenderer.value = new RectangleRenderer(this._terminal, this._gl, this.dimensions, this._themeService); - this._glyphRenderer.value = new GlyphRenderer(this._terminal, this._gl, this.dimensions); + this._glyphRenderer.value = new GlyphRenderer(this._terminal, this._gl, this.dimensions, this._optionsService); // Update dimensions and acquire char atlas this.handleCharSizeChanged(); @@ -388,6 +389,7 @@ export class WebglRenderer extends Disposable implements IRenderer { let range: [number, number]; let chars: string; let code: number; + let width: number; let i: number; let x: number; let j: number; @@ -500,7 +502,8 @@ export class WebglRenderer extends Disposable implements IRenderer { this._model.cells[i + RENDER_MODEL_FG_OFFSET] = this._cellColorResolver.result.fg; this._model.cells[i + RENDER_MODEL_EXT_OFFSET] = this._cellColorResolver.result.ext; - this._glyphRenderer.value!.updateCell(x, y, code, this._cellColorResolver.result.bg, this._cellColorResolver.result.fg, this._cellColorResolver.result.ext, chars, lastBg); + width = cell.getWidth(); + this._glyphRenderer.value!.updateCell(x, y, code, this._cellColorResolver.result.bg, this._cellColorResolver.result.fg, this._cellColorResolver.result.ext, chars, width, lastBg); if (isJoined) { // Restore work cell @@ -509,7 +512,7 @@ export class WebglRenderer extends Disposable implements IRenderer { // Null out non-first cells for (x++; x < lastCharX; x++) { j = ((y * terminal.cols) + x) * RENDER_MODEL_INDICIES_PER_CELL; - this._glyphRenderer.value!.updateCell(x, y, NULL_CELL_CODE, 0, 0, 0, NULL_CELL_CHAR, 0); + this._glyphRenderer.value!.updateCell(x, y, NULL_CELL_CODE, 0, 0, 0, NULL_CELL_CHAR, 0, 0); this._model.cells[j] = NULL_CELL_CODE; this._model.cells[j + RENDER_MODEL_BG_OFFSET] = this._cellColorResolver.result.bg; this._model.cells[j + RENDER_MODEL_FG_OFFSET] = this._cellColorResolver.result.fg; diff --git a/src/browser/renderer/shared/RendererUtils.ts b/src/browser/renderer/shared/RendererUtils.ts index 9a4bffe0..b9fc7312 100644 --- a/src/browser/renderer/shared/RendererUtils.ts +++ b/src/browser/renderer/shared/RendererUtils.ts @@ -27,6 +27,19 @@ function isBoxOrBlockGlyph(codepoint: number): boolean { return 0x2500 <= codepoint && codepoint <= 0x259F; } +export function isEmoji(codepoint: number): boolean { + return ( + codepoint >= 0x1F600 && codepoint <= 0x1F64F || // Emoticons + codepoint >= 0x1F300 && codepoint <= 0x1F5FF || // Misc Symbols and Pictographs + codepoint >= 0x1F680 && codepoint <= 0x1F6FF || // Transport and Map + codepoint >= 0x2600 && codepoint <= 0x26FF || // Misc symbols + codepoint >= 0x2700 && codepoint <= 0x27BF || // Dingbats + codepoint >= 0xFE00 && codepoint <= 0xFE0F || // Variation Selectors + codepoint >= 0x1F900 && codepoint <= 0x1F9FF || // Supplemental Symbols and Pictographs + codepoint >= 0x1F1E6 && codepoint <= 0x1F1FF + ); +} + export function treatGlyphAsBackgroundColor(codepoint: number): boolean { return isPowerlineGlyph(codepoint) || isBoxOrBlockGlyph(codepoint); } diff --git a/src/browser/services/RenderService.ts b/src/browser/services/RenderService.ts index a184641b..d4f2be46 100644 --- a/src/browser/services/RenderService.ts +++ b/src/browser/services/RenderService.ts @@ -87,7 +87,8 @@ export class RenderService extends Disposable implements IRenderService { 'fontSize', 'fontWeight', 'fontWeightBold', - 'minimumContrastRatio' + 'minimumContrastRatio', + 'rescaleOverlappingGlyphs' ], () => { this.clear(); this.handleResize(bufferService.cols, bufferService.rows); diff --git a/src/common/services/OptionsService.ts b/src/common/services/OptionsService.ts index ba92992e..0375f6ad 100644 --- a/src/common/services/OptionsService.ts +++ b/src/common/services/OptionsService.ts @@ -44,6 +44,7 @@ export const DEFAULT_OPTIONS: Readonly> = { allowTransparency: false, tabStopWidth: 8, theme: {}, + rescaleOverlappingGlyphs: false, rightClickSelectsWord: isMac, windowOptions: {}, windowsMode: false, diff --git a/src/common/services/Services.ts b/src/common/services/Services.ts index 304e8cbb..210a0afb 100644 --- a/src/common/services/Services.ts +++ b/src/common/services/Services.ts @@ -234,6 +234,7 @@ export interface ITerminalOptions { macOptionIsMeta?: boolean; macOptionClickForcesSelection?: boolean; minimumContrastRatio?: number; + rescaleOverlappingGlyphs?: boolean; rightClickSelectsWord?: boolean; rows?: number; screenReaderMode?: boolean; diff --git a/typings/xterm-headless.d.ts b/typings/xterm-headless.d.ts index b2ad5745..d27c39e0 100644 --- a/typings/xterm-headless.d.ts +++ b/typings/xterm-headless.d.ts @@ -140,6 +140,17 @@ declare module '@xterm/headless' { */ minimumContrastRatio?: number; + /** + * Whether to rescale glyphs horizontally that are a single cell wide but + * have glyphs that would overlap following cell(s). This typically happens + * for ambiguous width characters (eg. the roman numeral characters U+2160+) + * which aren't featured in monospace fonts. Emoji glyphs are never + * rescaled. This is an important feature for achieving GB18030 compliance. + * + * Note that this doesn't work with the DOM renderer. The default is false. + */ + rescaleOverlappingGlyphs?: boolean; + /** * Whether to select the word under the cursor on right click, this is * standard behavior in a lot of macOS applications. diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index d957d655..00d0e497 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -209,6 +209,17 @@ declare module '@xterm/xterm' { */ minimumContrastRatio?: number; + /** + * Whether to rescale glyphs horizontally that are a single cell wide but + * have glyphs that would overlap following cell(s). This typically happens + * for ambiguous width characters (eg. the roman numeral characters U+2160+) + * which aren't featured in monospace fonts. Emoji glyphs are never + * rescaled. This is an important feature for achieving GB18030 compliance. + * + * Note that this doesn't work with the DOM renderer. The default is false. + */ + rescaleOverlappingGlyphs?: boolean; + /** * Whether to select the word under the cursor on right click, this is * standard behavior in a lot of macOS applications. From df559e3444950e76e9f2b2cfddac1e66f8131dfa Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 14 Mar 2024 07:26:25 -0700 Subject: [PATCH 18/46] Rescale overlapping in canvas --- addons/addon-canvas/src/BaseRenderLayer.ts | 23 ++++++++++++++++++++-- addons/addon-webgl/src/GlyphRenderer.ts | 2 +- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/addons/addon-canvas/src/BaseRenderLayer.ts b/addons/addon-canvas/src/BaseRenderLayer.ts index e7e23400..0868ba8e 100644 --- a/addons/addon-canvas/src/BaseRenderLayer.ts +++ b/addons/addon-canvas/src/BaseRenderLayer.ts @@ -8,7 +8,7 @@ import { CellColorResolver } from 'browser/renderer/shared/CellColorResolver'; import { acquireTextureAtlas } from 'browser/renderer/shared/CharAtlasCache'; import { TEXT_BASELINE } from 'browser/renderer/shared/Constants'; import { tryDrawCustomChar } from 'browser/renderer/shared/CustomGlyphs'; -import { throwIfFalsy } from 'browser/renderer/shared/RendererUtils'; +import { isEmoji, throwIfFalsy } from 'browser/renderer/shared/RendererUtils'; import { createSelectionRenderModel } from 'browser/renderer/shared/SelectionRenderModel'; import { IRasterizedGlyph, IRenderDimensions, ISelectionRenderModel, ITextureAtlas } from 'browser/renderer/shared/Types'; import { ICoreBrowserService, IThemeService } from 'browser/services/Services'; @@ -365,6 +365,8 @@ export abstract class BaseRenderLayer extends Disposable implements IRenderLayer */ protected _drawChars(cell: ICellData, x: number, y: number): void { const chars = cell.getChars(); + const code = cell.getCode(); + const width = cell.getWidth(); this._cellColorResolver.resolve(cell, x, this._bufferService.buffer.ydisp + y, this._deviceCellWidth); if (!this._charAtlas) { @@ -400,6 +402,23 @@ export abstract class BaseRenderLayer extends Disposable implements IRenderLayer this._bitmapGenerator[glyph.texturePage]!.refresh(); this._bitmapGenerator[glyph.texturePage]!.version = this._charAtlas.pages[glyph.texturePage].version; } + + // Reduce scale horizontally for wide glyphs printed in cells that would overlap with the + // following cell (ie. the width is not 2). + let renderWidth = glyph.size.x; + if (this._optionsService.rawOptions.rescaleOverlappingGlyphs) { + if ( + // Is single cell width + width === 1 && + // Glyph exceeds cell bounds, + 1 to avoid hurting readability + glyph.size.x > this._deviceCellWidth + 1 && + // Never rescale emoji + code && !isEmoji(code) + ) { + renderWidth = this._deviceCellWidth - 1; // - 1 to improve readability + } + } + this._ctx.drawImage( this._bitmapGenerator[glyph.texturePage]?.bitmap || this._charAtlas!.pages[glyph.texturePage].canvas, glyph.texturePosition.x, @@ -408,7 +427,7 @@ export abstract class BaseRenderLayer extends Disposable implements IRenderLayer glyph.size.y, x * this._deviceCellWidth + this._deviceCharLeft - glyph.offset.x, y * this._deviceCellHeight + this._deviceCharTop - glyph.offset.y, - glyph.size.x, + renderWidth, glyph.size.y ); this._ctx.restore(); diff --git a/addons/addon-webgl/src/GlyphRenderer.ts b/addons/addon-webgl/src/GlyphRenderer.ts index 7e221fc6..7ac89084 100644 --- a/addons/addon-webgl/src/GlyphRenderer.ts +++ b/addons/addon-webgl/src/GlyphRenderer.ts @@ -289,7 +289,7 @@ export class GlyphRenderer extends Disposable { // Never rescale emoji code && !isEmoji(code) ) { - array[$i + 2] = (this._dimensions.device.cell.width - /* improve readability */1) / this._dimensions.device.canvas.width; // - 1 to improve readability + array[$i + 2] = (this._dimensions.device.cell.width - 1) / this._dimensions.device.canvas.width; // - 1 to improve readability } } } From 48c6e96a78d4a4ba76f12786676b4e904ad8124d Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 15 Mar 2024 08:18:26 -0700 Subject: [PATCH 19/46] Don't rescale powerline or nerd fonts Part of #4969 --- src/browser/renderer/shared/RendererUtils.ts | 29 ++++++++++++++++---- 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/src/browser/renderer/shared/RendererUtils.ts b/src/browser/renderer/shared/RendererUtils.ts index b9fc7312..f99c23a1 100644 --- a/src/browser/renderer/shared/RendererUtils.ts +++ b/src/browser/renderer/shared/RendererUtils.ts @@ -23,6 +23,10 @@ export function isRestrictedPowerlineGlyph(codepoint: number): boolean { return 0xE0B0 <= codepoint && codepoint <= 0xE0B7; } +function isNerdFontGlyph(codepoint: number): boolean { + return 0xE000 <= codepoint && codepoint <= 0xF8FF; +} + function isBoxOrBlockGlyph(codepoint: number): boolean { return 0x2500 <= codepoint && codepoint <= 0x259F; } @@ -30,16 +34,29 @@ function isBoxOrBlockGlyph(codepoint: number): boolean { export function isEmoji(codepoint: number): boolean { return ( codepoint >= 0x1F600 && codepoint <= 0x1F64F || // Emoticons - codepoint >= 0x1F300 && codepoint <= 0x1F5FF || // Misc Symbols and Pictographs - codepoint >= 0x1F680 && codepoint <= 0x1F6FF || // Transport and Map - codepoint >= 0x2600 && codepoint <= 0x26FF || // Misc symbols - codepoint >= 0x2700 && codepoint <= 0x27BF || // Dingbats - codepoint >= 0xFE00 && codepoint <= 0xFE0F || // Variation Selectors - codepoint >= 0x1F900 && codepoint <= 0x1F9FF || // Supplemental Symbols and Pictographs + codepoint >= 0x1F300 && codepoint <= 0x1F5FF || // Misc Symbols and Pictographs + codepoint >= 0x1F680 && codepoint <= 0x1F6FF || // Transport and Map + codepoint >= 0x2600 && codepoint <= 0x26FF || // Misc symbols + codepoint >= 0x2700 && codepoint <= 0x27BF || // Dingbats + codepoint >= 0xFE00 && codepoint <= 0xFE0F || // Variation Selectors + codepoint >= 0x1F900 && codepoint <= 0x1F9FF || // Supplemental Symbols and Pictographs codepoint >= 0x1F1E6 && codepoint <= 0x1F1FF ); } +export function allowRescaling(codepoint: number | undefined, width: number, glyphSizeX: number, deviceCellWidth: number): boolean { + return ( + // Is single cell width + width === 1 && + // Glyph exceeds cell bounds, + 1 to avoid hurting readability + glyphSizeX > deviceCellWidth + 1 && + // Never rescale emoji + codepoint !== undefined && !isEmoji(codepoint) && + // Never rescale powerline or nerd fonts + !isPowerlineGlyph(codepoint) && !isNerdFontGlyph(codepoint) + ); +} + export function treatGlyphAsBackgroundColor(codepoint: number): boolean { return isPowerlineGlyph(codepoint) || isBoxOrBlockGlyph(codepoint); } From 6b3d485d774d96041afbfee13189f1b34950f1f7 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 15 Mar 2024 08:30:55 -0700 Subject: [PATCH 20/46] Note new excluded glyphs in API --- typings/xterm-headless.d.ts | 10 ++++++++-- typings/xterm.d.ts | 10 ++++++++-- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/typings/xterm-headless.d.ts b/typings/xterm-headless.d.ts index 9c1feaea..5e84f266 100644 --- a/typings/xterm-headless.d.ts +++ b/typings/xterm-headless.d.ts @@ -144,8 +144,14 @@ declare module '@xterm/headless' { * Whether to rescale glyphs horizontally that are a single cell wide but * have glyphs that would overlap following cell(s). This typically happens * for ambiguous width characters (eg. the roman numeral characters U+2160+) - * which aren't featured in monospace fonts. Emoji glyphs are never - * rescaled. This is an important feature for achieving GB18030 compliance. + * which aren't featured in monospace fonts. This is an important feature + * for achieving GB18030 compliance. + * + * The following glyphs will never be rescaled: + * + * - Emoji glyphs + * - Powerline glyphs + * - Nerd font glyphs * * Note that this doesn't work with the DOM renderer. The default is false. */ diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index b4e01d84..33008289 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -213,8 +213,14 @@ declare module '@xterm/xterm' { * Whether to rescale glyphs horizontally that are a single cell wide but * have glyphs that would overlap following cell(s). This typically happens * for ambiguous width characters (eg. the roman numeral characters U+2160+) - * which aren't featured in monospace fonts. Emoji glyphs are never - * rescaled. This is an important feature for achieving GB18030 compliance. + * which aren't featured in monospace fonts. This is an important feature + * for achieving GB18030 compliance. + * + * The following glyphs will never be rescaled: + * + * - Emoji glyphs + * - Powerline glyphs + * - Nerd font glyphs * * Note that this doesn't work with the DOM renderer. The default is false. */ From 47409f39f684c417717d885b2cba56dd918d591a Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 16 Mar 2024 09:05:45 -0700 Subject: [PATCH 21/46] Set up shared context on browser even if process exists Fixes #4995 --- src/common/Color.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/common/Color.ts b/src/common/Color.ts index 5ec2d87d..b7b3ff47 100644 --- a/src/common/Color.ts +++ b/src/common/Color.ts @@ -3,7 +3,6 @@ * @license MIT */ -import { isNode } from 'common/Platform'; import { IColor, IColorRGB } from 'common/Types'; let $r = 0; @@ -117,9 +116,10 @@ export namespace color { * '#rrggbbaa'). */ export namespace css { + // Attempt to set get the shared canvas context let $ctx: CanvasRenderingContext2D | undefined; let $litmusColor: CanvasGradient | undefined; - if (!isNode) { + try { // This is guaranteed to run in the first window, so document should be correct const canvas = document.createElement('canvas'); canvas.width = 1; @@ -133,6 +133,9 @@ export namespace css { $litmusColor = $ctx.createLinearGradient(0, 0, 1, 1); } } + catch { + // noop + } /** * Converts a css string to an IColor, this should handle all valid CSS color strings and will From 37efeeb044c55ab5debe60f9ae92cdd280285a06 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 16 Mar 2024 09:08:54 -0700 Subject: [PATCH 22/46] Make process node check stricter Fixes #4995 --- src/common/Platform.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/Platform.ts b/src/common/Platform.ts index 1007fc0a..4102f20c 100644 --- a/src/common/Platform.ts +++ b/src/common/Platform.ts @@ -14,7 +14,7 @@ interface INavigator { declare const navigator: INavigator; declare const process: unknown; -export const isNode = (typeof process !== 'undefined') ? true : false; +export const isNode = (typeof process !== 'undefined' && 'title' in (process as any)) ? true : false; const userAgent = (isNode) ? 'node' : navigator.userAgent; const platform = (isNode) ? 'node' : navigator.platform; From 4071f41f525016ff9f02e11b281f39a47258b909 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 16 Mar 2024 09:16:31 -0700 Subject: [PATCH 23/46] Don't rescale unless it exceeds 25% of following cell Fixes #5005 --- addons/addon-canvas/src/BaseRenderLayer.ts | 11 ++--------- addons/addon-webgl/src/GlyphRenderer.ts | 11 ++--------- src/browser/renderer/shared/RendererUtils.ts | 5 +++-- 3 files changed, 7 insertions(+), 20 deletions(-) diff --git a/addons/addon-canvas/src/BaseRenderLayer.ts b/addons/addon-canvas/src/BaseRenderLayer.ts index 0868ba8e..cd3cfa1f 100644 --- a/addons/addon-canvas/src/BaseRenderLayer.ts +++ b/addons/addon-canvas/src/BaseRenderLayer.ts @@ -8,7 +8,7 @@ import { CellColorResolver } from 'browser/renderer/shared/CellColorResolver'; import { acquireTextureAtlas } from 'browser/renderer/shared/CharAtlasCache'; import { TEXT_BASELINE } from 'browser/renderer/shared/Constants'; import { tryDrawCustomChar } from 'browser/renderer/shared/CustomGlyphs'; -import { isEmoji, throwIfFalsy } from 'browser/renderer/shared/RendererUtils'; +import { allowRescaling, throwIfFalsy } from 'browser/renderer/shared/RendererUtils'; import { createSelectionRenderModel } from 'browser/renderer/shared/SelectionRenderModel'; import { IRasterizedGlyph, IRenderDimensions, ISelectionRenderModel, ITextureAtlas } from 'browser/renderer/shared/Types'; import { ICoreBrowserService, IThemeService } from 'browser/services/Services'; @@ -407,14 +407,7 @@ export abstract class BaseRenderLayer extends Disposable implements IRenderLayer // following cell (ie. the width is not 2). let renderWidth = glyph.size.x; if (this._optionsService.rawOptions.rescaleOverlappingGlyphs) { - if ( - // Is single cell width - width === 1 && - // Glyph exceeds cell bounds, + 1 to avoid hurting readability - glyph.size.x > this._deviceCellWidth + 1 && - // Never rescale emoji - code && !isEmoji(code) - ) { + if (allowRescaling(code, width, glyph.size.x, this._deviceCellWidth)) { renderWidth = this._deviceCellWidth - 1; // - 1 to improve readability } } diff --git a/addons/addon-webgl/src/GlyphRenderer.ts b/addons/addon-webgl/src/GlyphRenderer.ts index 7ac89084..35b56eef 100644 --- a/addons/addon-webgl/src/GlyphRenderer.ts +++ b/addons/addon-webgl/src/GlyphRenderer.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { isEmoji, throwIfFalsy } from 'browser/renderer/shared/RendererUtils'; +import { allowRescaling, throwIfFalsy } from 'browser/renderer/shared/RendererUtils'; import { TextureAtlas } from 'browser/renderer/shared/TextureAtlas'; import { IRasterizedGlyph, IRenderDimensions, ITextureAtlas } from 'browser/renderer/shared/Types'; import { NULL_CELL_CODE } from 'common/buffer/Constants'; @@ -281,14 +281,7 @@ export class GlyphRenderer extends Disposable { // Reduce scale horizontally for wide glyphs printed in cells that would overlap with the // following cell (ie. the width is not 2). if (this._optionsService.rawOptions.rescaleOverlappingGlyphs) { - if ( - // Is single cell width - width === 1 && - // Glyph exceeds cell bounds, + 1 to avoid hurting readability - $glyph.size.x > this._dimensions.device.cell.width + 1 && - // Never rescale emoji - code && !isEmoji(code) - ) { + if (allowRescaling(code, width, $glyph.size.x, this._dimensions.device.cell.width)) { array[$i + 2] = (this._dimensions.device.cell.width - 1) / this._dimensions.device.canvas.width; // - 1 to improve readability } } diff --git a/src/browser/renderer/shared/RendererUtils.ts b/src/browser/renderer/shared/RendererUtils.ts index f99c23a1..792d5b1d 100644 --- a/src/browser/renderer/shared/RendererUtils.ts +++ b/src/browser/renderer/shared/RendererUtils.ts @@ -48,8 +48,9 @@ export function allowRescaling(codepoint: number | undefined, width: number, gly return ( // Is single cell width width === 1 && - // Glyph exceeds cell bounds, + 1 to avoid hurting readability - glyphSizeX > deviceCellWidth + 1 && + // Glyph exceeds cell bounds, add 25% to avoid hurting readability by rescaling glyphs that + // barely overlap + glyphSizeX > deviceCellWidth * 1.25 && // Never rescale emoji codepoint !== undefined && !isEmoji(codepoint) && // Never rescale powerline or nerd fonts From 3e5d0015afa09633aabb9a5e2634f4d866c4f8f6 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 16 Mar 2024 09:30:25 -0700 Subject: [PATCH 24/46] Fix search not destroying cache on linefeed Fixes #4994 --- addons/addon-search/src/SearchAddon.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/addons/addon-search/src/SearchAddon.ts b/addons/addon-search/src/SearchAddon.ts index 3fae7373..17850f60 100644 --- a/addons/addon-search/src/SearchAddon.ts +++ b/addons/addon-search/src/SearchAddon.ts @@ -78,6 +78,7 @@ export class SearchAddon extends Disposable implements ITerminalAddon , ISearchA */ private _linesCache: LineCacheEntry[] | undefined; private _linesCacheTimeoutId = 0; + private _lineFeedListener: IDisposable | undefined; private _cursorMoveListener: IDisposable | undefined; private _resizeListener: IDisposable | undefined; @@ -427,6 +428,7 @@ export class SearchAddon extends Disposable implements ITerminalAddon , ISearchA const terminal = this._terminal!; if (!this._linesCache) { this._linesCache = new Array(terminal.buffer.active.length); + this._lineFeedListener = terminal.onLineFeed(() => this._destroyLinesCache()); this._cursorMoveListener = terminal.onCursorMove(() => this._destroyLinesCache()); this._resizeListener = terminal.onResize(() => this._destroyLinesCache()); } @@ -445,6 +447,10 @@ export class SearchAddon extends Disposable implements ITerminalAddon , ISearchA this._resizeListener.dispose(); this._resizeListener = undefined; } + if (this._lineFeedListener) { + this._lineFeedListener.dispose(); + this._lineFeedListener = undefined; + } if (this._linesCacheTimeoutId) { window.clearTimeout(this._linesCacheTimeoutId); this._linesCacheTimeoutId = 0; From 14fbdd3e9cc00aed02fbd24963a1d601e0db83c8 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 16 Mar 2024 09:32:49 -0700 Subject: [PATCH 25/46] Adopt MutableDisposable in SearchAddon --- addons/addon-search/src/SearchAddon.ts | 27 ++++++++------------------ 1 file changed, 8 insertions(+), 19 deletions(-) diff --git a/addons/addon-search/src/SearchAddon.ts b/addons/addon-search/src/SearchAddon.ts index 17850f60..d42f4d27 100644 --- a/addons/addon-search/src/SearchAddon.ts +++ b/addons/addon-search/src/SearchAddon.ts @@ -6,7 +6,7 @@ import type { Terminal, IDisposable, ITerminalAddon, IDecoration } from '@xterm/xterm'; import type { SearchAddon as ISearchApi } from '@xterm/addon-search'; import { EventEmitter } from 'common/EventEmitter'; -import { Disposable, toDisposable, disposeArray, MutableDisposable } from 'common/Lifecycle'; +import { Disposable, toDisposable, disposeArray, MutableDisposable, getDisposeArrayDisposable } from 'common/Lifecycle'; export interface ISearchOptions { regex?: boolean; @@ -78,9 +78,7 @@ export class SearchAddon extends Disposable implements ITerminalAddon , ISearchA */ private _linesCache: LineCacheEntry[] | undefined; private _linesCacheTimeoutId = 0; - private _lineFeedListener: IDisposable | undefined; - private _cursorMoveListener: IDisposable | undefined; - private _resizeListener: IDisposable | undefined; + private _linesCacheDisposables = new MutableDisposable(); private readonly _onDidChangeResults = this.register(new EventEmitter<{ resultIndex: number, resultCount: number }>()); public readonly onDidChangeResults = this._onDidChangeResults.event; @@ -428,9 +426,11 @@ export class SearchAddon extends Disposable implements ITerminalAddon , ISearchA const terminal = this._terminal!; if (!this._linesCache) { this._linesCache = new Array(terminal.buffer.active.length); - this._lineFeedListener = terminal.onLineFeed(() => this._destroyLinesCache()); - this._cursorMoveListener = terminal.onCursorMove(() => this._destroyLinesCache()); - this._resizeListener = terminal.onResize(() => this._destroyLinesCache()); + this._linesCacheDisposables.value = getDisposeArrayDisposable([ + terminal.onLineFeed(() => this._destroyLinesCache()), + terminal.onCursorMove(() => this._destroyLinesCache()), + terminal.onResize(() => this._destroyLinesCache()) + ]); } window.clearTimeout(this._linesCacheTimeoutId); @@ -439,18 +439,7 @@ export class SearchAddon extends Disposable implements ITerminalAddon , ISearchA private _destroyLinesCache(): void { this._linesCache = undefined; - if (this._cursorMoveListener) { - this._cursorMoveListener.dispose(); - this._cursorMoveListener = undefined; - } - if (this._resizeListener) { - this._resizeListener.dispose(); - this._resizeListener = undefined; - } - if (this._lineFeedListener) { - this._lineFeedListener.dispose(); - this._lineFeedListener = undefined; - } + this._linesCacheDisposables.clear(); if (this._linesCacheTimeoutId) { window.clearTimeout(this._linesCacheTimeoutId); this._linesCacheTimeoutId = 0; From c78af2b448b9fa34359750a639f07a8049cf1af9 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 16 Mar 2024 09:49:36 -0700 Subject: [PATCH 26/46] Fix dom cursor blink animation Fixes #4987 Doesn't seem to regress #4773 which caused this issue --- src/browser/renderer/dom/DomRenderer.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/browser/renderer/dom/DomRenderer.ts b/src/browser/renderer/dom/DomRenderer.ts index 1549b130..3e8f2111 100644 --- a/src/browser/renderer/dom/DomRenderer.ts +++ b/src/browser/renderer/dom/DomRenderer.ts @@ -209,8 +209,8 @@ export class DomRenderer extends Disposable implements IRenderer { ` animation: blink_block` + `_` + this._terminalClass + ` 1s step-end infinite;` + `}` + `${this._terminalSelector} .${ROW_CONTAINER_CLASS} .${RowCss.CURSOR_CLASS}.${RowCss.CURSOR_STYLE_BLOCK_CLASS} {` + - ` background-color: ${colors.cursor.css} !important;` + - ` color: ${colors.cursorAccent.css} !important;` + + ` background-color: ${colors.cursor.css};` + + ` color: ${colors.cursorAccent.css};` + `}` + `${this._terminalSelector} .${ROW_CONTAINER_CLASS} .${RowCss.CURSOR_CLASS}.${RowCss.CURSOR_STYLE_OUTLINE_CLASS} {` + ` outline: 1px solid ${colors.cursor.css};` + From 18147b2134b1c73a3aae57f012bd46a690309b13 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 16 Mar 2024 09:55:20 -0700 Subject: [PATCH 27/46] Fix bar blink animation --- src/browser/renderer/dom/DomRenderer.ts | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/browser/renderer/dom/DomRenderer.ts b/src/browser/renderer/dom/DomRenderer.ts index 3e8f2111..68004ff5 100644 --- a/src/browser/renderer/dom/DomRenderer.ts +++ b/src/browser/renderer/dom/DomRenderer.ts @@ -184,11 +184,17 @@ export class DomRenderer extends Disposable implements IRenderer { `}`; // Blink animation styles += - `@keyframes blink_box_shadow` + `_` + this._terminalClass + ` {` + + `@keyframes blink_underline` + `_` + this._terminalClass + ` {` + ` 50% {` + ` border-bottom-style: hidden;` + ` }` + `}`; + styles += + `@keyframes blink_bar` + `_` + this._terminalClass + ` {` + + ` 50% {` + + ` box-shadow: none;` + + ` }` + + `}`; styles += `@keyframes blink_block` + `_` + this._terminalClass + ` {` + ` 0% {` + @@ -202,8 +208,11 @@ export class DomRenderer extends Disposable implements IRenderer { `}`; // Cursor styles += - `${this._terminalSelector} .${ROW_CONTAINER_CLASS}.${FOCUS_CLASS} .${RowCss.CURSOR_CLASS}.${RowCss.CURSOR_BLINK_CLASS}:not(.${RowCss.CURSOR_STYLE_BLOCK_CLASS}) {` + - ` animation: blink_box_shadow` + `_` + this._terminalClass + ` 1s step-end infinite;` + + `${this._terminalSelector} .${ROW_CONTAINER_CLASS}.${FOCUS_CLASS} .${RowCss.CURSOR_CLASS}.${RowCss.CURSOR_BLINK_CLASS}.${RowCss.CURSOR_STYLE_UNDERLINE_CLASS} {` + + ` animation: blink_underline` + `_` + this._terminalClass + ` 1s step-end infinite;` + + `}` + + `${this._terminalSelector} .${ROW_CONTAINER_CLASS}.${FOCUS_CLASS} .${RowCss.CURSOR_CLASS}.${RowCss.CURSOR_BLINK_CLASS}.${RowCss.CURSOR_STYLE_BAR_CLASS} {` + + ` animation: blink_bar` + `_` + this._terminalClass + ` 1s step-end infinite;` + `}` + `${this._terminalSelector} .${ROW_CONTAINER_CLASS}.${FOCUS_CLASS} .${RowCss.CURSOR_CLASS}.${RowCss.CURSOR_BLINK_CLASS}.${RowCss.CURSOR_STYLE_BLOCK_CLASS} {` + ` animation: blink_block` + `_` + this._terminalClass + ` 1s step-end infinite;` + From 7b76ca7e49276d4388f750106576302982fcb23e Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 16 Mar 2024 09:57:23 -0700 Subject: [PATCH 28/46] Make dom rendere animation classes more readable --- src/browser/renderer/dom/DomRenderer.ts | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/browser/renderer/dom/DomRenderer.ts b/src/browser/renderer/dom/DomRenderer.ts index 68004ff5..e6db73e8 100644 --- a/src/browser/renderer/dom/DomRenderer.ts +++ b/src/browser/renderer/dom/DomRenderer.ts @@ -183,20 +183,23 @@ export class DomRenderer extends Disposable implements IRenderer { ` font-style: italic;` + `}`; // Blink animation + const blinkAnimationUnderlineId = `blink_underline_${this._terminalClass}`; + const blinkAnimationBarId = `blink_bar_${this._terminalClass}`; + const blinkAnimationBlockId = `blink_block_${this._terminalClass}`; styles += - `@keyframes blink_underline` + `_` + this._terminalClass + ` {` + + `@keyframes ${blinkAnimationUnderlineId} {` + ` 50% {` + ` border-bottom-style: hidden;` + ` }` + `}`; styles += - `@keyframes blink_bar` + `_` + this._terminalClass + ` {` + + `@keyframes ${blinkAnimationBarId} {` + ` 50% {` + ` box-shadow: none;` + ` }` + `}`; styles += - `@keyframes blink_block` + `_` + this._terminalClass + ` {` + + `@keyframes ${blinkAnimationBlockId} {` + ` 0% {` + ` background-color: ${colors.cursor.css};` + ` color: ${colors.cursorAccent.css};` + @@ -209,13 +212,13 @@ export class DomRenderer extends Disposable implements IRenderer { // Cursor styles += `${this._terminalSelector} .${ROW_CONTAINER_CLASS}.${FOCUS_CLASS} .${RowCss.CURSOR_CLASS}.${RowCss.CURSOR_BLINK_CLASS}.${RowCss.CURSOR_STYLE_UNDERLINE_CLASS} {` + - ` animation: blink_underline` + `_` + this._terminalClass + ` 1s step-end infinite;` + + ` animation: ${blinkAnimationUnderlineId} 1s step-end infinite;` + `}` + `${this._terminalSelector} .${ROW_CONTAINER_CLASS}.${FOCUS_CLASS} .${RowCss.CURSOR_CLASS}.${RowCss.CURSOR_BLINK_CLASS}.${RowCss.CURSOR_STYLE_BAR_CLASS} {` + - ` animation: blink_bar` + `_` + this._terminalClass + ` 1s step-end infinite;` + + ` animation: ${blinkAnimationBarId} 1s step-end infinite;` + `}` + `${this._terminalSelector} .${ROW_CONTAINER_CLASS}.${FOCUS_CLASS} .${RowCss.CURSOR_CLASS}.${RowCss.CURSOR_BLINK_CLASS}.${RowCss.CURSOR_STYLE_BLOCK_CLASS} {` + - ` animation: blink_block` + `_` + this._terminalClass + ` 1s step-end infinite;` + + ` animation: ${blinkAnimationBlockId} 1s step-end infinite;` + `}` + `${this._terminalSelector} .${ROW_CONTAINER_CLASS} .${RowCss.CURSOR_CLASS}.${RowCss.CURSOR_STYLE_BLOCK_CLASS} {` + ` background-color: ${colors.cursor.css};` + From a4f1299f42beaa1c7298dee68600ba15de51bdc5 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 16 Mar 2024 11:05:01 -0700 Subject: [PATCH 29/46] Special case cursor block blink to not use important Fixes #4987 --- src/browser/renderer/dom/DomRenderer.ts | 7 +++++++ test/playwright/SharedRendererTests.ts | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/browser/renderer/dom/DomRenderer.ts b/src/browser/renderer/dom/DomRenderer.ts index e6db73e8..c89cee62 100644 --- a/src/browser/renderer/dom/DomRenderer.ts +++ b/src/browser/renderer/dom/DomRenderer.ts @@ -220,10 +220,17 @@ export class DomRenderer extends Disposable implements IRenderer { `${this._terminalSelector} .${ROW_CONTAINER_CLASS}.${FOCUS_CLASS} .${RowCss.CURSOR_CLASS}.${RowCss.CURSOR_BLINK_CLASS}.${RowCss.CURSOR_STYLE_BLOCK_CLASS} {` + ` animation: ${blinkAnimationBlockId} 1s step-end infinite;` + `}` + + // !important helps fix an issue where the cursor will not render on top of the selection, + // however it's very hard to fix this issue and retain the blink animation without the use of + // !important. So this edge case fails when cursor blink is on. `${this._terminalSelector} .${ROW_CONTAINER_CLASS} .${RowCss.CURSOR_CLASS}.${RowCss.CURSOR_STYLE_BLOCK_CLASS} {` + ` background-color: ${colors.cursor.css};` + ` color: ${colors.cursorAccent.css};` + `}` + + `${this._terminalSelector} .${ROW_CONTAINER_CLASS} .${RowCss.CURSOR_CLASS}.${RowCss.CURSOR_STYLE_BLOCK_CLASS}:not(.${RowCss.CURSOR_BLINK_CLASS}) {` + + ` background-color: ${colors.cursor.css} !important;` + + ` color: ${colors.cursorAccent.css} !important;` + + `}` + `${this._terminalSelector} .${ROW_CONTAINER_CLASS} .${RowCss.CURSOR_CLASS}.${RowCss.CURSOR_STYLE_OUTLINE_CLASS} {` + ` outline: 1px solid ${colors.cursor.css};` + ` outline-offset: -1px;` + diff --git a/test/playwright/SharedRendererTests.ts b/test/playwright/SharedRendererTests.ts index 29dc51c3..c4b66536 100644 --- a/test/playwright/SharedRendererTests.ts +++ b/test/playwright/SharedRendererTests.ts @@ -6,7 +6,7 @@ import { IImage32, decodePng } from '@lunapaint/png-codec'; import { LocatorScreenshotOptions, test } from '@playwright/test'; import { ITheme } from '@xterm/xterm'; -import { ITestContext, MaybeAsync, openTerminal, pollFor, pollForApproximate, timeout } from './TestUtils'; +import { ITestContext, MaybeAsync, openTerminal, pollFor, pollForApproximate } from './TestUtils'; export interface ISharedRendererTestContext { value: ITestContext; From f8ae7edaa0d6f188101df9b3a9d26a835e2c223f Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 19 Mar 2024 08:56:13 -0700 Subject: [PATCH 30/46] Only scale when over 50% of following cell See microsoft/vscode#208102 --- src/browser/renderer/shared/RendererUtils.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/browser/renderer/shared/RendererUtils.ts b/src/browser/renderer/shared/RendererUtils.ts index 792d5b1d..13c3bdd0 100644 --- a/src/browser/renderer/shared/RendererUtils.ts +++ b/src/browser/renderer/shared/RendererUtils.ts @@ -48,9 +48,9 @@ export function allowRescaling(codepoint: number | undefined, width: number, gly return ( // Is single cell width width === 1 && - // Glyph exceeds cell bounds, add 25% to avoid hurting readability by rescaling glyphs that + // Glyph exceeds cell bounds, add 50% to avoid hurting readability by rescaling glyphs that // barely overlap - glyphSizeX > deviceCellWidth * 1.25 && + glyphSizeX > Math.ceil(deviceCellWidth * 1.5) && // Never rescale emoji codepoint !== undefined && !isEmoji(codepoint) && // Never rescale powerline or nerd fonts From 4c90009a0e7678dcb751d4632f5354dded9c3ed1 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 20 Mar 2024 10:02:46 -0700 Subject: [PATCH 31/46] Don't rescale ascii See microsoft/vscode#208102 --- src/browser/renderer/shared/RendererUtils.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/browser/renderer/shared/RendererUtils.ts b/src/browser/renderer/shared/RendererUtils.ts index 13c3bdd0..01064364 100644 --- a/src/browser/renderer/shared/RendererUtils.ts +++ b/src/browser/renderer/shared/RendererUtils.ts @@ -51,8 +51,10 @@ export function allowRescaling(codepoint: number | undefined, width: number, gly // Glyph exceeds cell bounds, add 50% to avoid hurting readability by rescaling glyphs that // barely overlap glyphSizeX > Math.ceil(deviceCellWidth * 1.5) && + // Never rescale ascii + codepoint !== undefined && codepoint > 0xFF && // Never rescale emoji - codepoint !== undefined && !isEmoji(codepoint) && + !isEmoji(codepoint) && // Never rescale powerline or nerd fonts !isPowerlineGlyph(codepoint) && !isNerdFontGlyph(codepoint) ); From 78297e52aaf12f67f1d1abf885869adc176fceb3 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 5 Apr 2024 06:50:30 -0700 Subject: [PATCH 32/46] v5.5 --- addons/addon-attach/package.json | 2 +- addons/addon-canvas/package.json | 2 +- addons/addon-fit/package.json | 2 +- addons/addon-image/package.json | 2 +- addons/addon-ligatures/package.json | 2 +- addons/addon-search/package.json | 2 +- addons/addon-serialize/package.json | 2 +- addons/addon-unicode-graphemes/package.json | 2 +- addons/addon-unicode11/package.json | 2 +- addons/addon-web-links/package.json | 2 +- addons/addon-webgl/package.json | 2 +- package.json | 2 +- 12 files changed, 12 insertions(+), 12 deletions(-) diff --git a/addons/addon-attach/package.json b/addons/addon-attach/package.json index c2f42ce5..71b1188d 100644 --- a/addons/addon-attach/package.json +++ b/addons/addon-attach/package.json @@ -1,6 +1,6 @@ { "name": "@xterm/addon-attach", - "version": "0.10.0", + "version": "0.11.0", "author": { "name": "The xterm.js authors", "url": "https://xtermjs.org/" diff --git a/addons/addon-canvas/package.json b/addons/addon-canvas/package.json index 8fdec96b..2ca5d163 100644 --- a/addons/addon-canvas/package.json +++ b/addons/addon-canvas/package.json @@ -1,6 +1,6 @@ { "name": "@xterm/addon-canvas", - "version": "0.6.0", + "version": "0.7.0", "author": { "name": "The xterm.js authors", "url": "https://xtermjs.org/" diff --git a/addons/addon-fit/package.json b/addons/addon-fit/package.json index be81b196..cacf31dd 100644 --- a/addons/addon-fit/package.json +++ b/addons/addon-fit/package.json @@ -1,6 +1,6 @@ { "name": "@xterm/addon-fit", - "version": "0.9.0", + "version": "0.10.0", "author": { "name": "The xterm.js authors", "url": "https://xtermjs.org/" diff --git a/addons/addon-image/package.json b/addons/addon-image/package.json index 2e28955d..ac4dc018 100644 --- a/addons/addon-image/package.json +++ b/addons/addon-image/package.json @@ -1,6 +1,6 @@ { "name": "@xterm/addon-image", - "version": "0.7.0", + "version": "0.8.0", "author": { "name": "The xterm.js authors", "url": "https://xtermjs.org/" diff --git a/addons/addon-ligatures/package.json b/addons/addon-ligatures/package.json index 60166577..80608ee3 100644 --- a/addons/addon-ligatures/package.json +++ b/addons/addon-ligatures/package.json @@ -1,6 +1,6 @@ { "name": "@xterm/addon-ligatures", - "version": "0.8.0", + "version": "0.9.0", "description": "Add support for programming ligatures to xterm.js", "author": { "name": "The xterm.js authors", diff --git a/addons/addon-search/package.json b/addons/addon-search/package.json index 35a4015e..9292a0c4 100644 --- a/addons/addon-search/package.json +++ b/addons/addon-search/package.json @@ -1,6 +1,6 @@ { "name": "@xterm/addon-search", - "version": "0.14.0", + "version": "0.15.0", "author": { "name": "The xterm.js authors", "url": "https://xtermjs.org/" diff --git a/addons/addon-serialize/package.json b/addons/addon-serialize/package.json index 9c50288d..30dadbb6 100644 --- a/addons/addon-serialize/package.json +++ b/addons/addon-serialize/package.json @@ -1,6 +1,6 @@ { "name": "@xterm/addon-serialize", - "version": "0.12.0", + "version": "0.13.0", "author": { "name": "The xterm.js authors", "url": "https://xtermjs.org/" diff --git a/addons/addon-unicode-graphemes/package.json b/addons/addon-unicode-graphemes/package.json index 610e14b4..3d846286 100644 --- a/addons/addon-unicode-graphemes/package.json +++ b/addons/addon-unicode-graphemes/package.json @@ -1,6 +1,6 @@ { "name": "@xterm/addon-unicode-graphemes", - "version": "0.2.0", + "version": "0.3.0", "author": { "name": "The xterm.js authors", "url": "https://xtermjs.org/" diff --git a/addons/addon-unicode11/package.json b/addons/addon-unicode11/package.json index e511c65a..9f2c21b8 100644 --- a/addons/addon-unicode11/package.json +++ b/addons/addon-unicode11/package.json @@ -1,6 +1,6 @@ { "name": "@xterm/addon-unicode11", - "version": "0.7.0", + "version": "0.8.0", "author": { "name": "The xterm.js authors", "url": "https://xtermjs.org/" diff --git a/addons/addon-web-links/package.json b/addons/addon-web-links/package.json index 6a65f289..da888716 100644 --- a/addons/addon-web-links/package.json +++ b/addons/addon-web-links/package.json @@ -1,6 +1,6 @@ { "name": "@xterm/addon-web-links", - "version": "0.10.0", + "version": "0.11.0", "author": { "name": "The xterm.js authors", "url": "https://xtermjs.org/" diff --git a/addons/addon-webgl/package.json b/addons/addon-webgl/package.json index f504bb7e..da37eb26 100644 --- a/addons/addon-webgl/package.json +++ b/addons/addon-webgl/package.json @@ -1,6 +1,6 @@ { "name": "@xterm/addon-webgl", - "version": "0.17.0", + "version": "0.18.0", "author": { "name": "The xterm.js authors", "url": "https://xtermjs.org/" diff --git a/package.json b/package.json index dd425db6..c79b1196 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@xterm/xterm", "description": "Full xterm terminal, in your browser", - "version": "5.4.0", + "version": "5.5.0", "main": "lib/xterm.js", "style": "css/xterm.css", "types": "typings/xterm.d.ts", From 52e8a75e9f3b0f12cdba71d8c3cfe3a5f4958885 Mon Sep 17 00:00:00 2001 From: ksqsf Date: Mon, 8 Apr 2024 13:37:55 +0800 Subject: [PATCH 33/46] Fix duplicate input for some IMEs fixes #5023 --- src/browser/input/CompositionHelper.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/browser/input/CompositionHelper.ts b/src/browser/input/CompositionHelper.ts index 7542969a..9891709f 100644 --- a/src/browser/input/CompositionHelper.ts +++ b/src/browser/input/CompositionHelper.ts @@ -159,8 +159,9 @@ export class CompositionHelper { // otherwise input characters can be duplicated. (Issue #3191) currentCompositionPosition.start += this._dataAlreadySent.length; if (this._isComposing) { - // Use the end position to get the string if a new composition has started. - input = this._textarea.value.substring(currentCompositionPosition.start, currentCompositionPosition.end); + // Use the start position of the new composition to get the string + // if a new composition has started. + input = this._textarea.value.substring(currentCompositionPosition.start, this._compositionPosition.start); } else { // Don't use the end position here in order to pick up any characters after the // composition has finished, for example when typing a non-composition character From e845abc784dba466f504d9da629ce15e4ec1875d Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 12 Apr 2024 16:47:59 -0700 Subject: [PATCH 34/46] Finish OSC hyperlinks when the second param is only whitespace Fixes #4916 --- src/common/InputHandler.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/InputHandler.ts b/src/common/InputHandler.ts index a4b8c64b..9b300993 100644 --- a/src/common/InputHandler.ts +++ b/src/common/InputHandler.ts @@ -2979,7 +2979,7 @@ export class InputHandler extends Disposable implements IInputHandler { if (args[1]) { return this._createHyperlink(args[0], args[1]); } - if (args[0]) { + if (args[0].trim()) { return false; } return this._finishHyperlink(); From 8458bb4c4f4b8b3cf1d4a7207e0d47b13d17260c Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 18 Apr 2024 16:27:48 -0700 Subject: [PATCH 35/46] Expose onWriteParsed on API Inconsistency with headless API --- src/headless/public/Terminal.ts | 1 + typings/xterm-headless.d.ts | 11 +++++++++++ 2 files changed, 12 insertions(+) diff --git a/src/headless/public/Terminal.ts b/src/headless/public/Terminal.ts index 0d73f9d3..1b39c184 100644 --- a/src/headless/public/Terminal.ts +++ b/src/headless/public/Terminal.ts @@ -80,6 +80,7 @@ export class Terminal extends Disposable implements ITerminalApi { public get onResize(): IEvent<{ cols: number, rows: number }> { return this._core.onResize; } public get onScroll(): IEvent { return this._core.onScroll; } public get onTitleChange(): IEvent { return this._core.onTitleChange; } + public get onWriteParsed(): IEvent { return this._core.onWriteParsed; } public get parser(): IParser { this._checkProposedApi(); diff --git a/typings/xterm-headless.d.ts b/typings/xterm-headless.d.ts index 5e84f266..2d3329ed 100644 --- a/typings/xterm-headless.d.ts +++ b/typings/xterm-headless.d.ts @@ -714,6 +714,17 @@ declare module '@xterm/headless' { */ onLineFeed: IEvent; + /** + * Adds an event listener for when data has been parsed by the terminal, + * after {@link write} is called. This event is useful to listen for any + * changes in the buffer. + * + * This fires at most once per frame, after data parsing completes. Note + * that this can fire when there are still writes pending if there is a lot + * of data. + */ + onWriteParsed: IEvent; + /** * Adds an event listener for when the terminal is resized. The event value * contains the new size. From 16db22711946e7843b360ba583eb05e4a73694f6 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 20 Apr 2024 08:11:59 -0700 Subject: [PATCH 36/46] Prevent smooth scroll from running more than once per frame Fixes #5036 --- src/browser/Viewport.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/browser/Viewport.ts b/src/browser/Viewport.ts index a8e1a498..1b2f7b5b 100644 --- a/src/browser/Viewport.ts +++ b/src/browser/Viewport.ts @@ -36,6 +36,8 @@ export class Viewport extends Disposable implements IViewport { private _activeBuffer: IBuffer; private _renderDimensions: IRenderDimensions; + private _smoothScrollAnimationFrame: number = 0; + // Stores a partial line amount when scrolling, this is used to keep track of how much of a line // is scrolled so we can "scroll" over partial lines and feel natural on touchpads. This is a // quick fix and could have a more robust solution in place that reset the value when needed. @@ -211,7 +213,12 @@ export class Viewport extends Disposable implements IViewport { // Continue or finish smooth scroll if (percent < 1) { - this._coreBrowserService.window.requestAnimationFrame(() => this._smoothScroll()); + if (!this._smoothScrollAnimationFrame) { + this._smoothScrollAnimationFrame = this._coreBrowserService.window.requestAnimationFrame(() => { + this._smoothScrollAnimationFrame = 0; + this._smoothScroll(); + }); + } } else { this._clearSmoothScrollState(); } From 5c8a9084e3e52c528ac14ce5035e4c7d9d060dd1 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 20 Apr 2024 08:41:22 -0700 Subject: [PATCH 37/46] Add mass decoration test button Part of #4911 --- demo/client.ts | 30 +++++++++++++++++++++++++++++- demo/index.html | 1 + 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/demo/client.ts b/demo/client.ts index 7e0830a5..a3e58051 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -44,7 +44,7 @@ if ('WebAssembly' in window) { // Pulling in the module's types relies on the above, it's looks a // little weird here as we're importing "this" module -import { Terminal as TerminalType, ITerminalOptions } from '@xterm/xterm'; +import { Terminal as TerminalType, ITerminalOptions, type IDisposable } from '@xterm/xterm'; export interface IWindowWithTerminal extends Window { term: TerminalType; @@ -255,6 +255,7 @@ if (document.location.pathname === '/test') { document.getElementById('add-grapheme-clusters').addEventListener('click', addGraphemeClusters); document.getElementById('add-decoration').addEventListener('click', addDecoration); document.getElementById('add-overview-ruler').addEventListener('click', addOverviewRuler); + document.getElementById('decoration-stress-test').addEventListener('click', decorationStressTest); document.getElementById('weblinks-test').addEventListener('click', testWeblinks); document.getElementById('bce').addEventListener('click', coloredErase); addVtButtons(); @@ -1170,6 +1171,33 @@ function addOverviewRuler(): void { term.registerDecoration({ marker: term.registerMarker(10), overviewRulerOptions: { color: '#ffffff80', position: 'full' } }); } +let decorationStressTestDecorations: IDisposable[] | undefined; +function decorationStressTest(): void { + if (decorationStressTestDecorations) { + for (const d of decorationStressTestDecorations) { + d.dispose(); + } + decorationStressTestDecorations = undefined; + } else { + const t = term as Terminal; + const buffer = t.buffer.active; + const cursorY = buffer.baseY + buffer.cursorY; + decorationStressTestDecorations = []; + for (const x of [0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95]) { + for (let y = 0; y < t.buffer.active.length; y++) { + const cursorOffsetY = y - cursorY; + decorationStressTestDecorations.push(t.registerDecoration({ + marker: t.registerMarker(cursorOffsetY), + x, + width: 4, + backgroundColor: '#FF0000', + overviewRulerOptions: { color: '#FF0000' } + })); + } + } + } +} + (console as any).image = (source: ImageData | HTMLCanvasElement, scale: number = 1) => { function getBox(width: number, height: number): any { return { diff --git a/demo/index.html b/demo/index.html index caff2ca2..238c9886 100644 --- a/demo/index.html +++ b/demo/index.html @@ -102,6 +102,7 @@
Decorations
+
Weblinks Addon
From dc541d549543f4bff48993074a7222d923d1aa72 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 20 Apr 2024 10:32:30 -0700 Subject: [PATCH 38/46] Optimize SortedList.delete by batching to idle task --- src/common/SortedList.ts | 43 ++++++++++++++++++++++-- src/common/services/DecorationService.ts | 3 +- 2 files changed, 43 insertions(+), 3 deletions(-) diff --git a/src/common/SortedList.ts b/src/common/SortedList.ts index c3250091..0fb71e4e 100644 --- a/src/common/SortedList.ts +++ b/src/common/SortedList.ts @@ -3,6 +3,8 @@ * @license MIT */ +import { IdleTaskQueue } from 'common/TaskQueue'; + // Work variables to avoid garbage collection. let i = 0; @@ -12,7 +14,10 @@ let i = 0; * includes the by key iterator. */ export class SortedList { - private readonly _array: T[] = []; + private _array: T[] = []; + private readonly _deletedIndices: Set = new Set(); + private readonly _cleanupDeletedTask = new IdleTaskQueue(); + private _isCleaningUp = false; constructor( private readonly _getKey: (value: T) => number @@ -21,9 +26,13 @@ export class SortedList { public clear(): void { this._array.length = 0; + this._deletedIndices.clear(); + this._cleanupDeletedTask.clear(); + this._isCleaningUp = false; } public insert(value: T): void { + this._flushCleanupDeleted(); if (this._array.length === 0) { this._array.push(value); return; @@ -49,14 +58,42 @@ export class SortedList { } do { if (this._array[i] === value) { - this._array.splice(i, 1); + if (this._deletedIndices.size === 0) { + this._cleanupDeletedTask.enqueue(() => this._cleanupDeleted()); + } + this._deletedIndices.add(i); return true; } } while (++i < this._array.length && this._getKey(this._array[i]) === key); return false; } + private _cleanupDeleted(): void { + this._isCleaningUp = true; + const sortedDeletedIndices = Array.from(this._deletedIndices).sort((a, b) => a - b); + let sortedDeletedIndicesIndex = 0; + const newArray = new Array(this._array.length - sortedDeletedIndices.length); + let newArrayIndex = 0; + for (let i = 0; i < this._array.length; i++) { + if (sortedDeletedIndices[sortedDeletedIndicesIndex] === i) { + sortedDeletedIndicesIndex++; + } else { + newArray[newArrayIndex++] = this._array[i]; + } + } + this._array = newArray; + this._deletedIndices.clear(); + this._isCleaningUp = false; + } + + private _flushCleanupDeleted(): void { + if (!this._isCleaningUp) { + this._cleanupDeletedTask.flush(); + } + } + public *getKeyIterator(key: number): IterableIterator { + this._flushCleanupDeleted(); if (this._array.length === 0) { return; } @@ -73,6 +110,7 @@ export class SortedList { } public forEachByKey(key: number, callback: (value: T) => void): void { + this._flushCleanupDeleted(); if (this._array.length === 0) { return; } @@ -89,6 +127,7 @@ export class SortedList { } public values(): IterableIterator { + this._flushCleanupDeleted(); // Duplicate the array to avoid issues when _array changes while iterating return [...this._array].values(); } diff --git a/src/common/services/DecorationService.ts b/src/common/services/DecorationService.ts index da759152..c9be78af 100644 --- a/src/common/services/DecorationService.ts +++ b/src/common/services/DecorationService.ts @@ -45,7 +45,8 @@ export class DecorationService extends Disposable implements IDecorationService const decoration = new Decoration(options); if (decoration) { const markerDispose = decoration.marker.onDispose(() => decoration.dispose()); - decoration.onDispose(() => { + const listener = decoration.onDispose(() => { + listener.dispose(); if (decoration) { if (this._decorations.delete(decoration)) { this._onDecorationRemoved.fire(decoration); From 3af11c2b1f8c79cf61b040231e851f6d523c4e19 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 20 Apr 2024 10:47:40 -0700 Subject: [PATCH 39/46] Optimize SortedList.insert by batching to idle task --- src/common/SortedList.ts | 58 +++++++++++++++++++++++++++++++++------- 1 file changed, 49 insertions(+), 9 deletions(-) diff --git a/src/common/SortedList.ts b/src/common/SortedList.ts index 0fb71e4e..0aba3668 100644 --- a/src/common/SortedList.ts +++ b/src/common/SortedList.ts @@ -15,8 +15,14 @@ let i = 0; */ export class SortedList { private _array: T[] = []; + + private readonly _addedValues: Set = new Set(); + private readonly _cleanupAddedTask = new IdleTaskQueue(); + private _isCleaningUpAdded = false; + private readonly _deletedIndices: Set = new Set(); - private readonly _cleanupDeletedTask = new IdleTaskQueue(); + + private readonly _cleanupTask = new IdleTaskQueue(); private _isCleaningUp = false; constructor( @@ -27,21 +33,52 @@ export class SortedList { public clear(): void { this._array.length = 0; this._deletedIndices.clear(); - this._cleanupDeletedTask.clear(); + this._cleanupTask.clear(); this._isCleaningUp = false; } public insert(value: T): void { this._flushCleanupDeleted(); - if (this._array.length === 0) { - this._array.push(value); - return; + if (this._addedValues.size === 0) { + this._cleanupAddedTask.enqueue(() => this._cleanupAdded()); + } + this._addedValues.add(value); + // if (this._array.length === 0) { + // this._array.push(value); + // return; + // } + // i = this._search(this._getKey(value)); + // this._array.splice(i, 0, value); + } + + private _cleanupAdded(): void { + const sortedAddedValues = Array.from(this._addedValues).sort((a, b) => this._getKey(a) - this._getKey(b)); + let sortedAddedValuesIndex = 0; + let arrayIndex = 0; + + const newArray = new Array(this._array.length + this._addedValues.size); + + for (let newArrayIndex = 0; newArrayIndex < newArray.length; newArrayIndex++) { + if (arrayIndex >= this._array.length || this._getKey(sortedAddedValues[sortedAddedValuesIndex]) === this._getKey(this._array[arrayIndex])) { + newArray[newArrayIndex] = sortedAddedValues[sortedAddedValuesIndex]; + sortedAddedValuesIndex++; + } else { + newArray[newArrayIndex] = this._array[arrayIndex++]; + } + } + + this._array = newArray; + this._addedValues.clear(); + } + + private _flushCleanupAdded(): void { + if (!this._isCleaningUpAdded) { + this._cleanupAddedTask.flush(); } - i = this._search(this._getKey(value)); - this._array.splice(i, 0, value); } public delete(value: T): boolean { + this._flushCleanupAdded(); if (this._array.length === 0) { return false; } @@ -59,7 +96,7 @@ export class SortedList { do { if (this._array[i] === value) { if (this._deletedIndices.size === 0) { - this._cleanupDeletedTask.enqueue(() => this._cleanupDeleted()); + this._cleanupTask.enqueue(() => this._cleanupDeleted()); } this._deletedIndices.add(i); return true; @@ -88,11 +125,12 @@ export class SortedList { private _flushCleanupDeleted(): void { if (!this._isCleaningUp) { - this._cleanupDeletedTask.flush(); + this._cleanupTask.flush(); } } public *getKeyIterator(key: number): IterableIterator { + this._flushCleanupAdded(); this._flushCleanupDeleted(); if (this._array.length === 0) { return; @@ -110,6 +148,7 @@ export class SortedList { } public forEachByKey(key: number, callback: (value: T) => void): void { + this._flushCleanupAdded(); this._flushCleanupDeleted(); if (this._array.length === 0) { return; @@ -127,6 +166,7 @@ export class SortedList { } public values(): IterableIterator { + this._flushCleanupAdded(); this._flushCleanupDeleted(); // Duplicate the array to avoid issues when _array changes while iterating return [...this._array].values(); From 69c4966804d6fc33ebe27bf3f38901a6e5875aff Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 20 Apr 2024 10:48:55 -0700 Subject: [PATCH 40/46] Consistent naming --- src/common/SortedList.ts | 61 ++++++++++++++++++---------------------- 1 file changed, 27 insertions(+), 34 deletions(-) diff --git a/src/common/SortedList.ts b/src/common/SortedList.ts index 0aba3668..42dc7ef3 100644 --- a/src/common/SortedList.ts +++ b/src/common/SortedList.ts @@ -16,14 +16,13 @@ let i = 0; export class SortedList { private _array: T[] = []; - private readonly _addedValues: Set = new Set(); - private readonly _cleanupAddedTask = new IdleTaskQueue(); - private _isCleaningUpAdded = false; + private readonly _insertedValues: Set = new Set(); + private readonly _flushInsertedTask = new IdleTaskQueue(); + private _isFlushingInserted = false; private readonly _deletedIndices: Set = new Set(); - - private readonly _cleanupTask = new IdleTaskQueue(); - private _isCleaningUp = false; + private readonly _flushDeletedTask = new IdleTaskQueue(); + private _isflushingDeleted = false; constructor( private readonly _getKey: (value: T) => number @@ -33,30 +32,24 @@ export class SortedList { public clear(): void { this._array.length = 0; this._deletedIndices.clear(); - this._cleanupTask.clear(); - this._isCleaningUp = false; + this._flushDeletedTask.clear(); + this._isflushingDeleted = false; } public insert(value: T): void { this._flushCleanupDeleted(); - if (this._addedValues.size === 0) { - this._cleanupAddedTask.enqueue(() => this._cleanupAdded()); + if (this._insertedValues.size === 0) { + this._flushInsertedTask.enqueue(() => this._flushInserted()); } - this._addedValues.add(value); - // if (this._array.length === 0) { - // this._array.push(value); - // return; - // } - // i = this._search(this._getKey(value)); - // this._array.splice(i, 0, value); + this._insertedValues.add(value); } - private _cleanupAdded(): void { - const sortedAddedValues = Array.from(this._addedValues).sort((a, b) => this._getKey(a) - this._getKey(b)); + private _flushInserted(): void { + const sortedAddedValues = Array.from(this._insertedValues).sort((a, b) => this._getKey(a) - this._getKey(b)); let sortedAddedValuesIndex = 0; let arrayIndex = 0; - const newArray = new Array(this._array.length + this._addedValues.size); + const newArray = new Array(this._array.length + this._insertedValues.size); for (let newArrayIndex = 0; newArrayIndex < newArray.length; newArrayIndex++) { if (arrayIndex >= this._array.length || this._getKey(sortedAddedValues[sortedAddedValuesIndex]) === this._getKey(this._array[arrayIndex])) { @@ -68,17 +61,17 @@ export class SortedList { } this._array = newArray; - this._addedValues.clear(); + this._insertedValues.clear(); } - private _flushCleanupAdded(): void { - if (!this._isCleaningUpAdded) { - this._cleanupAddedTask.flush(); + private _flushCleanupInserted(): void { + if (!this._isFlushingInserted) { + this._flushInsertedTask.flush(); } } public delete(value: T): boolean { - this._flushCleanupAdded(); + this._flushCleanupInserted(); if (this._array.length === 0) { return false; } @@ -96,7 +89,7 @@ export class SortedList { do { if (this._array[i] === value) { if (this._deletedIndices.size === 0) { - this._cleanupTask.enqueue(() => this._cleanupDeleted()); + this._flushDeletedTask.enqueue(() => this._flushDeleted()); } this._deletedIndices.add(i); return true; @@ -105,8 +98,8 @@ export class SortedList { return false; } - private _cleanupDeleted(): void { - this._isCleaningUp = true; + private _flushDeleted(): void { + this._isflushingDeleted = true; const sortedDeletedIndices = Array.from(this._deletedIndices).sort((a, b) => a - b); let sortedDeletedIndicesIndex = 0; const newArray = new Array(this._array.length - sortedDeletedIndices.length); @@ -120,17 +113,17 @@ export class SortedList { } this._array = newArray; this._deletedIndices.clear(); - this._isCleaningUp = false; + this._isflushingDeleted = false; } private _flushCleanupDeleted(): void { - if (!this._isCleaningUp) { - this._cleanupTask.flush(); + if (!this._isflushingDeleted) { + this._flushDeletedTask.flush(); } } public *getKeyIterator(key: number): IterableIterator { - this._flushCleanupAdded(); + this._flushCleanupInserted(); this._flushCleanupDeleted(); if (this._array.length === 0) { return; @@ -148,7 +141,7 @@ export class SortedList { } public forEachByKey(key: number, callback: (value: T) => void): void { - this._flushCleanupAdded(); + this._flushCleanupInserted(); this._flushCleanupDeleted(); if (this._array.length === 0) { return; @@ -166,7 +159,7 @@ export class SortedList { } public values(): IterableIterator { - this._flushCleanupAdded(); + this._flushCleanupInserted(); this._flushCleanupDeleted(); // Duplicate the array to avoid issues when _array changes while iterating return [...this._array].values(); From 45fd3c900f2cfe448a129ba5b7525bacb24c9b9f Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 20 Apr 2024 10:52:43 -0700 Subject: [PATCH 41/46] Only flush when needed --- src/common/SortedList.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/common/SortedList.ts b/src/common/SortedList.ts index 42dc7ef3..8a2ad9cc 100644 --- a/src/common/SortedList.ts +++ b/src/common/SortedList.ts @@ -22,7 +22,7 @@ export class SortedList { private readonly _deletedIndices: Set = new Set(); private readonly _flushDeletedTask = new IdleTaskQueue(); - private _isflushingDeleted = false; + private _isFlushingDeleted = false; constructor( private readonly _getKey: (value: T) => number @@ -33,7 +33,7 @@ export class SortedList { this._array.length = 0; this._deletedIndices.clear(); this._flushDeletedTask.clear(); - this._isflushingDeleted = false; + this._isFlushingDeleted = false; } public insert(value: T): void { @@ -65,7 +65,7 @@ export class SortedList { } private _flushCleanupInserted(): void { - if (!this._isFlushingInserted) { + if (!this._isFlushingInserted && this._insertedValues.size > 0) { this._flushInsertedTask.flush(); } } @@ -99,7 +99,7 @@ export class SortedList { } private _flushDeleted(): void { - this._isflushingDeleted = true; + this._isFlushingDeleted = true; const sortedDeletedIndices = Array.from(this._deletedIndices).sort((a, b) => a - b); let sortedDeletedIndicesIndex = 0; const newArray = new Array(this._array.length - sortedDeletedIndices.length); @@ -113,11 +113,11 @@ export class SortedList { } this._array = newArray; this._deletedIndices.clear(); - this._isflushingDeleted = false; + this._isFlushingDeleted = false; } private _flushCleanupDeleted(): void { - if (!this._isflushingDeleted) { + if (!this._isFlushingDeleted && this._deletedIndices.size > 0) { this._flushDeletedTask.flush(); } } From 71dddbc8b93f141413c32c20443639681b5ad914 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 20 Apr 2024 10:57:39 -0700 Subject: [PATCH 42/46] Move from set to array --- src/common/SortedList.ts | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/src/common/SortedList.ts b/src/common/SortedList.ts index 8a2ad9cc..eb88f368 100644 --- a/src/common/SortedList.ts +++ b/src/common/SortedList.ts @@ -16,11 +16,11 @@ let i = 0; export class SortedList { private _array: T[] = []; - private readonly _insertedValues: Set = new Set(); + private readonly _insertedValues: T[] = []; private readonly _flushInsertedTask = new IdleTaskQueue(); private _isFlushingInserted = false; - private readonly _deletedIndices: Set = new Set(); + private readonly _deletedIndices: number[] = []; private readonly _flushDeletedTask = new IdleTaskQueue(); private _isFlushingDeleted = false; @@ -31,17 +31,20 @@ export class SortedList { public clear(): void { this._array.length = 0; - this._deletedIndices.clear(); + this._insertedValues.length = 0; + this._flushInsertedTask.clear(); + this._isFlushingInserted = false; + this._deletedIndices.length = 0; this._flushDeletedTask.clear(); this._isFlushingDeleted = false; } public insert(value: T): void { this._flushCleanupDeleted(); - if (this._insertedValues.size === 0) { + if (this._insertedValues.length === 0) { this._flushInsertedTask.enqueue(() => this._flushInserted()); } - this._insertedValues.add(value); + this._insertedValues.push(value); } private _flushInserted(): void { @@ -49,7 +52,7 @@ export class SortedList { let sortedAddedValuesIndex = 0; let arrayIndex = 0; - const newArray = new Array(this._array.length + this._insertedValues.size); + const newArray = new Array(this._array.length + this._insertedValues.length); for (let newArrayIndex = 0; newArrayIndex < newArray.length; newArrayIndex++) { if (arrayIndex >= this._array.length || this._getKey(sortedAddedValues[sortedAddedValuesIndex]) === this._getKey(this._array[arrayIndex])) { @@ -61,11 +64,11 @@ export class SortedList { } this._array = newArray; - this._insertedValues.clear(); + this._insertedValues.length = 0; } private _flushCleanupInserted(): void { - if (!this._isFlushingInserted && this._insertedValues.size > 0) { + if (!this._isFlushingInserted && this._insertedValues.length > 0) { this._flushInsertedTask.flush(); } } @@ -88,10 +91,10 @@ export class SortedList { } do { if (this._array[i] === value) { - if (this._deletedIndices.size === 0) { + if (this._deletedIndices.length === 0) { this._flushDeletedTask.enqueue(() => this._flushDeleted()); } - this._deletedIndices.add(i); + this._deletedIndices.push(i); return true; } } while (++i < this._array.length && this._getKey(this._array[i]) === key); @@ -112,12 +115,12 @@ export class SortedList { } } this._array = newArray; - this._deletedIndices.clear(); + this._deletedIndices.length = 0; this._isFlushingDeleted = false; } private _flushCleanupDeleted(): void { - if (!this._isFlushingDeleted && this._deletedIndices.size > 0) { + if (!this._isFlushingDeleted && this._deletedIndices.length > 0) { this._flushDeletedTask.flush(); } } From ef1152a8fb0c5e7bdf5bda2bb433b7fe148c93b7 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 20 Apr 2024 10:59:17 -0700 Subject: [PATCH 43/46] Doc --- src/common/SortedList.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/common/SortedList.ts b/src/common/SortedList.ts index eb88f368..67e1047e 100644 --- a/src/common/SortedList.ts +++ b/src/common/SortedList.ts @@ -9,9 +9,10 @@ import { IdleTaskQueue } from 'common/TaskQueue'; let i = 0; /** - * A generic list that is maintained in sorted order and allows values with duplicate keys. This - * list is based on binary search and as such locating a key will take O(log n) amortized, this - * includes the by key iterator. + * A generic list that is maintained in sorted order and allows values with duplicate keys. Deferred + * batch insertion and deletion is used to significantly reduce the time it takes to insert and + * delete a large amount of items in succession. This list is based on binary search and as such + * locating a key will take O(log n) amortized, this includes the by key iterator. */ export class SortedList { private _array: T[] = []; From 73fe7015b7776b80c78927ae28809a73f5b8ffcc Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 20 Apr 2024 11:00:41 -0700 Subject: [PATCH 44/46] Use sort on object --- src/common/SortedList.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/common/SortedList.ts b/src/common/SortedList.ts index 67e1047e..1fb4f962 100644 --- a/src/common/SortedList.ts +++ b/src/common/SortedList.ts @@ -49,7 +49,7 @@ export class SortedList { } private _flushInserted(): void { - const sortedAddedValues = Array.from(this._insertedValues).sort((a, b) => this._getKey(a) - this._getKey(b)); + const sortedAddedValues = this._insertedValues.sort((a, b) => this._getKey(a) - this._getKey(b)); let sortedAddedValuesIndex = 0; let arrayIndex = 0; @@ -104,7 +104,7 @@ export class SortedList { private _flushDeleted(): void { this._isFlushingDeleted = true; - const sortedDeletedIndices = Array.from(this._deletedIndices).sort((a, b) => a - b); + const sortedDeletedIndices = this._deletedIndices.sort((a, b) => a - b); let sortedDeletedIndicesIndex = 0; const newArray = new Array(this._array.length - sortedDeletedIndices.length); let newArrayIndex = 0; From a4a7ce843ce1555c9ab6159ddba39100536db222 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 20 Apr 2024 11:08:28 -0700 Subject: [PATCH 45/46] Reduce gc pressure in api verify integers --- src/browser/public/Terminal.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/browser/public/Terminal.ts b/src/browser/public/Terminal.ts index a6349225..56edbc50 100644 --- a/src/browser/public/Terminal.ts +++ b/src/browser/public/Terminal.ts @@ -20,6 +20,8 @@ import { IBufferNamespace as IBufferNamespaceApi, IDecoration, IDecorationOption */ const CONSTRUCTOR_ONLY_OPTIONS = ['cols', 'rows']; +let $value = 0; + export class Terminal extends Disposable implements ITerminalApi { private _core: ITerminal; private _addonManager: AddonManager; @@ -249,16 +251,16 @@ export class Terminal extends Disposable implements ITerminalApi { } private _verifyIntegers(...values: number[]): void { - for (const value of values) { - if (value === Infinity || isNaN(value) || value % 1 !== 0) { + for ($value of values) { + if ($value === Infinity || isNaN($value) || $value % 1 !== 0) { throw new Error('This API only accepts integers'); } } } private _verifyPositiveIntegers(...values: number[]): void { - for (const value of values) { - if (value && (value === Infinity || isNaN(value) || value % 1 !== 0 || value < 0)) { + for ($value of values) { + if ($value && ($value === Infinity || isNaN($value) || $value % 1 !== 0 || $value < 0)) { throw new Error('This API only accepts positive integers'); } } From 6b4bf428de661f7269b777de68454dd08175050d Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 20 Apr 2024 11:15:14 -0700 Subject: [PATCH 46/46] Fix tests --- src/common/SortedList.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/SortedList.ts b/src/common/SortedList.ts index 1fb4f962..82b6dfa6 100644 --- a/src/common/SortedList.ts +++ b/src/common/SortedList.ts @@ -56,7 +56,7 @@ export class SortedList { const newArray = new Array(this._array.length + this._insertedValues.length); for (let newArrayIndex = 0; newArrayIndex < newArray.length; newArrayIndex++) { - if (arrayIndex >= this._array.length || this._getKey(sortedAddedValues[sortedAddedValuesIndex]) === this._getKey(this._array[arrayIndex])) { + if (arrayIndex >= this._array.length || this._getKey(sortedAddedValues[sortedAddedValuesIndex]) <= this._getKey(this._array[arrayIndex])) { newArray[newArrayIndex] = sortedAddedValues[sortedAddedValuesIndex]; sortedAddedValuesIndex++; } else {