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/44] 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 c09a29bfd..9c83cc4cb 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 b6a7d89c6..3c032078a 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -16 +18 diff --git a/.vscode/launch.json b/.vscode/launch.json index 5dbd01ce2..eaa5e12ec 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 d0510e5d1..28e9952b5 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/44] Update yarn lock --- yarn.lock | 69 +++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 44 insertions(+), 25 deletions(-) diff --git a/yarn.lock b/yarn.lock index a0cb39da5..5f25390b0 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 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 03/44] 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 713f9c238..63211dade 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 8902d8e0a..b3f0548cc 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 5b05ce2e7..26a7421d0 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 04/44] 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 26a7421d0..e4e5ce944 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 05/44] 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 e4e5ce944..6c3c8b1e4 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 06/44] 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 63211dade..66691f44c 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 6c3c8b1e4..99c11600d 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 07/44] 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 1527bad06..03d6cb703 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 08/44] 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 94b483dd8..c2f42ce5f 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 9aabb001f..8fdec96b7 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 585f3621c..be81b1966 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 8572e3300..2e28955d0 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 e5c6b9725..601665773 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 369d11c34..35a4015ea 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 763c52ca9..9c50288dd 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 d49eda873..610e14b4b 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 ad6a48924..e511c65a6 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 6367907aa..6a65f2891 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 9a31c306c..f504bb7e2 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 050b8cadf..dd425db61 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 09/44] 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 360e09fe6..ad9455345 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 10/44] 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 7514e76c4..c85ce3170 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 11/44] Add @xterm scope to packages --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c85ce3170..b055ea7b7 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 12/44] 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 a8e1a4987..cea2ca8fb 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 13/44] adding Wave Terminal to the README --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index b055ea7b7..5f4a786ad 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 14/44] 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 b2ad5745b..400346072 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 d957d6551..a46db4be5 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 15/44] 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 1fb0e18c5..7e221fc62 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 3a01e244b..fa1786522 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 9a4bffe00..b9fc73124 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 a184641bb..d4f2be465 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 ba92992e9..0375f6add 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 304e8cbb4..210a0afb0 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 b2ad5745b..d27c39e0c 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 d957d6551..00d0e497d 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 16/44] 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 e7e234003..0868ba8ea 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 7e221fc62..7ac89084f 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 17/44] 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 b9fc73124..f99c23a13 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 18/44] 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 9c1feaea7..5e84f266f 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 b4e01d84e..330082890 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 19/44] 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 5ec2d87db..b7b3ff47f 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 20/44] 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 1007fc0a3..4102f20c0 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 21/44] 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 0868ba8ea..cd3cfa1f9 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 7ac89084f..35b56eefe 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 f99c23a13..792d5b1d7 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 22/44] 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 3fae73739..17850f609 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 23/44] 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 17850f609..d42f4d27f 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 24/44] 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 1549b1307..3e8f21112 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 25/44] 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 3e8f21112..68004ff59 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 26/44] 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 68004ff59..e6db73e8f 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 27/44] 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 e6db73e8f..c89cee625 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 29dc51c33..c4b665365 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 28/44] 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 792d5b1d7..13c3bdd09 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 29/44] 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 13c3bdd09..010643648 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 30/44] 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 c2f42ce5f..71b1188db 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 8fdec96b7..2ca5d163c 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 be81b1966..cacf31ddc 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 2e28955d0..ac4dc0184 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 601665773..80608ee3c 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 35a4015ea..9292a0c4f 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 9c50288dd..30dadbb61 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 610e14b4b..3d846286c 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 e511c65a6..9f2c21b87 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 6a65f2891..da888716b 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 f504bb7e2..da37eb26c 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 dd425db61..c79b1196e 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 31/44] 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 7542969a6..9891709ff 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 32/44] 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 a4b8c64b0..9b300993d 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 33/44] 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 0d73f9d35..1b39c1844 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 5e84f266f..2d3329edf 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 34/44] 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 a8e1a4987..1b2f7b5bc 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 35/44] 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 7e0830a58..a3e58051c 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 caff2ca21..238c98866 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 36/44] 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 c32500911..0fb71e4ed 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 da7591527..c9be78af2 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 37/44] 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 0fb71e4ed..0aba3668f 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 38/44] 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 0aba3668f..42dc7ef35 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 39/44] 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 42dc7ef35..8a2ad9cc1 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 40/44] 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 8a2ad9cc1..eb88f3688 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 41/44] 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 eb88f3688..67e1047e0 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 42/44] 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 67e1047e0..1fb4f962d 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 43/44] 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 a6349225d..56edbc507 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 44/44] 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 1fb4f962d..82b6dfa6a 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 {