From f26264ac23c52349556f355d0b398c441a08ce36 Mon Sep 17 00:00:00 2001 From: uellenberg Date: Mon, 18 Jul 2022 15:45:53 -0700 Subject: [PATCH 01/42] Add Cratecode to Real-world uses --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 7226d8b4..2d1cba47 100644 --- a/README.md +++ b/README.md @@ -194,6 +194,7 @@ Xterm.js is used in several world-class applications to provide great terminal e - [**CloudTTY**](https://github.com/cloudtty/cloudtty): A Friendly Kubernetes CloudShell (Web Terminal). - [**Go SSH Web Client**](https://github.com/wuchihsu/go-ssh-web-client): A simple SSH web client using Go, WebSocket and Xterm.js. - [**web3os**](https://web3os.sh): A decentralized operating system for the next web +- [**Cratecode**](https://cratecode.com): Learn to program for free through interactive online lessons. Cratecode uses xterm.js to give users access to their own Linux environment. - [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 8b3f9ad8e1ac77e6585c6ee48c540ea4e3a27fa6 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 19 Jul 2022 05:10:31 -0700 Subject: [PATCH 02/42] Be more defensive when setting extended ansi colors Part of #3601 --- src/browser/ColorManager.test.ts | 4 ++-- src/browser/ColorManager.ts | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/browser/ColorManager.test.ts b/src/browser/ColorManager.test.ts index dfcc9ec8..3fbd92f1 100644 --- a/src/browser/ColorManager.test.ts +++ b/src/browser/ColorManager.test.ts @@ -315,14 +315,14 @@ describe('ColorManager', () => { extendedAnsi: DEFAULT_ANSI_COLORS.map(a => a.css).slice().reverse() }); - for (let ansiColor = 16; ansiColor <= 255; ansiColor++){ + for (let ansiColor = 16; ansiColor <= 255; ansiColor++) { assert.equal(cm.colors.ansi[ansiColor].css, DEFAULT_ANSI_COLORS[255 + 16 - ansiColor].css); } }); it('should set one extended ansi colors and keep the other default', () => { cm.setTheme({ - extendedAnsi: [ '#ffffff' ] + extendedAnsi: ['#ffffff'] }); assert.equal(cm.colors.ansi[16].css, '#ffffff'); diff --git a/src/browser/ColorManager.ts b/src/browser/ColorManager.ts index 1d1b5672..9eac2f5c 100644 --- a/src/browser/ColorManager.ts +++ b/src/browser/ColorManager.ts @@ -168,9 +168,9 @@ export class ColorManager implements IColorManager { this.colors.ansi[14] = this._parseColor(theme.brightCyan, DEFAULT_ANSI_COLORS[14]); this.colors.ansi[15] = this._parseColor(theme.brightWhite, DEFAULT_ANSI_COLORS[15]); if (theme.extendedAnsi) { - const colorCount = Math.max(theme.extendedAnsi.length + 16, 256); - for (let i = 16; i < colorCount; i++) { - this.colors.ansi[i] = this._parseColor(theme.extendedAnsi[i - 16], DEFAULT_ANSI_COLORS[i]); + const colorCount = Math.min(240/* 256 - base 16 */, this.colors.ansi.length - 16); + for (let i = 0; i < colorCount; i++) { + this.colors.ansi[i + 16] = this._parseColor(theme.extendedAnsi[i], DEFAULT_ANSI_COLORS[i + 16]); } } // Clear our the cache From 67f83d51c95ba5eb4cd73a8d1168fbdfce32f633 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 19 Jul 2022 05:58:20 -0700 Subject: [PATCH 03/42] Fix min color count --- src/browser/ColorManager.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/browser/ColorManager.ts b/src/browser/ColorManager.ts index 9eac2f5c..84e5d58d 100644 --- a/src/browser/ColorManager.ts +++ b/src/browser/ColorManager.ts @@ -168,7 +168,7 @@ export class ColorManager implements IColorManager { this.colors.ansi[14] = this._parseColor(theme.brightCyan, DEFAULT_ANSI_COLORS[14]); this.colors.ansi[15] = this._parseColor(theme.brightWhite, DEFAULT_ANSI_COLORS[15]); if (theme.extendedAnsi) { - const colorCount = Math.min(240/* 256 - base 16 */, this.colors.ansi.length - 16); + const colorCount = Math.min(this.colors.ansi.length - 16, theme.extendedAnsi.length); for (let i = 0; i < colorCount; i++) { this.colors.ansi[i + 16] = this._parseColor(theme.extendedAnsi[i], DEFAULT_ANSI_COLORS[i + 16]); } From 06627cb0bb2f20ed3be555585152356241acc3ab Mon Sep 17 00:00:00 2001 From: Simon Lamon Date: Tue, 19 Jul 2022 17:22:54 +0000 Subject: [PATCH 04/42] Allow unsetting extending ansicolors --- src/browser/ColorManager.test.ts | 22 +++++++++++++++++++++- src/browser/ColorManager.ts | 1 + 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/browser/ColorManager.test.ts b/src/browser/ColorManager.test.ts index 3fbd92f1..2a1c3fba 100644 --- a/src/browser/ColorManager.test.ts +++ b/src/browser/ColorManager.test.ts @@ -320,7 +320,7 @@ describe('ColorManager', () => { } }); - it('should set one extended ansi colors and keep the other default', () => { + it('should set one extended ansi color and keep the other default', () => { cm.setTheme({ extendedAnsi: ['#ffffff'] }); @@ -328,5 +328,25 @@ describe('ColorManager', () => { assert.equal(cm.colors.ansi[16].css, '#ffffff'); assert.equal(cm.colors.ansi[17].css, DEFAULT_ANSI_COLORS[17].css); }); + + it('should set extended ansi colors to the default after setting to something', () => { + cm.setTheme({ + extendedAnsi: ['#ffffff'] + }); + assert.equal(cm.colors.ansi[16].css, '#ffffff'); + + cm.setTheme({ + extendedAnsi: [] + }); + assert.equal(cm.colors.ansi[16].css, DEFAULT_ANSI_COLORS[16].css); + + cm.setTheme({ + extendedAnsi: ['#ffffff'] + }); + assert.equal(cm.colors.ansi[16].css, '#ffffff'); + + cm.setTheme({}); + assert.equal(cm.colors.ansi[16].css, DEFAULT_ANSI_COLORS[16].css); + }); }); }); diff --git a/src/browser/ColorManager.ts b/src/browser/ColorManager.ts index 84e5d58d..9e9891c3 100644 --- a/src/browser/ColorManager.ts +++ b/src/browser/ColorManager.ts @@ -151,6 +151,7 @@ export class ColorManager implements IColorManager { const opacity = 0.3; this.colors.selectionTransparent = color.opacity(this.colors.selectionTransparent, opacity); } + this.colors.ansi = DEFAULT_ANSI_COLORS.slice(); this.colors.ansi[0] = this._parseColor(theme.black, DEFAULT_ANSI_COLORS[0]); this.colors.ansi[1] = this._parseColor(theme.red, DEFAULT_ANSI_COLORS[1]); this.colors.ansi[2] = this._parseColor(theme.green, DEFAULT_ANSI_COLORS[2]); From 1fdbd504c74c6f34191daacdd0cf45eec649a517 Mon Sep 17 00:00:00 2001 From: Simon Lamon Date: Tue, 19 Jul 2022 17:37:02 +0000 Subject: [PATCH 05/42] Add another unit test to test partial unset --- src/browser/ColorManager.test.ts | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/browser/ColorManager.test.ts b/src/browser/ColorManager.test.ts index 2a1c3fba..019bf42a 100644 --- a/src/browser/ColorManager.test.ts +++ b/src/browser/ColorManager.test.ts @@ -329,7 +329,7 @@ describe('ColorManager', () => { assert.equal(cm.colors.ansi[17].css, DEFAULT_ANSI_COLORS[17].css); }); - it('should set extended ansi colors to the default after setting to something', () => { + it('should set extended ansi colors to the default when they are unset', () => { cm.setTheme({ extendedAnsi: ['#ffffff'] }); @@ -348,5 +348,19 @@ describe('ColorManager', () => { cm.setTheme({}); assert.equal(cm.colors.ansi[16].css, DEFAULT_ANSI_COLORS[16].css); }); + + it('should set extended ansi colors to the default when they are partially unset', () => { + cm.setTheme({ + extendedAnsi: ['#ffffff', '#000000'] + }); + assert.equal(cm.colors.ansi[16].css, '#ffffff'); + assert.equal(cm.colors.ansi[17].css, '#000000'); + + cm.setTheme({ + extendedAnsi: ['#ffffff'] + }); + assert.equal(cm.colors.ansi[16].css, '#ffffff'); + assert.equal(cm.colors.ansi[17].css, DEFAULT_ANSI_COLORS[17].css); + }); }); }); From 3ffbcbf20176f0dcb338ff15882556398ccf8fc1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 20 Jul 2022 01:30:11 +0000 Subject: [PATCH 06/42] Bump terser from 5.7.0 to 5.14.2 Bumps [terser](https://github.com/terser/terser) from 5.7.0 to 5.14.2. - [Release notes](https://github.com/terser/terser/releases) - [Changelog](https://github.com/terser/terser/blob/master/CHANGELOG.md) - [Commits](https://github.com/terser/terser/commits) --- updated-dependencies: - dependency-name: terser dependency-type: indirect ... Signed-off-by: dependabot[bot] --- yarn.lock | 98 +++++++++++++++++++++++++++++++------------------------ 1 file changed, 56 insertions(+), 42 deletions(-) diff --git a/yarn.lock b/yarn.lock index 7cb970bb..01fb6061 100644 --- a/yarn.lock +++ b/yarn.lock @@ -233,6 +233,46 @@ resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.2.tgz#26520bf09abe4a5644cd5414e37125a8954241dd" integrity sha512-tsAQNx32a8CoFhjhijUIhI4kccIAgmGhy8LZMZgGfmXcpMbPRUqn5LWmgRttILi6yeGmBJd2xsPkFMs0PzgPCw== +"@jridgewell/gen-mapping@^0.3.0": + version "0.3.2" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz#c1aedc61e853f2bb9f5dfe6d4442d3b565b253b9" + integrity sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A== + dependencies: + "@jridgewell/set-array" "^1.0.1" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/trace-mapping" "^0.3.9" + +"@jridgewell/resolve-uri@^3.0.3": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78" + integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w== + +"@jridgewell/set-array@^1.0.1": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72" + integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw== + +"@jridgewell/source-map@^0.3.2": + version "0.3.2" + resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.2.tgz#f45351aaed4527a298512ec72f81040c998580fb" + integrity sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw== + dependencies: + "@jridgewell/gen-mapping" "^0.3.0" + "@jridgewell/trace-mapping" "^0.3.9" + +"@jridgewell/sourcemap-codec@^1.4.10": + version "1.4.14" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24" + integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw== + +"@jridgewell/trace-mapping@^0.3.9": + version "0.3.14" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.14.tgz#b231a081d8f66796e475ad588a1ef473112701ed" + integrity sha512-bJWEfQ9lPTvm3SneWwRFVLzrh6nhjwqw7TUFFBEMzwvg7t7PCDenf2lDwqo4NQXzdpgBXyFgDWnQA+2vkruksQ== + dependencies: + "@jridgewell/resolve-uri" "^3.0.3" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@nodelib/fs.scandir@2.1.5": version "2.1.5" resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" @@ -814,15 +854,10 @@ acorn@^7.1.1: resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== -acorn@^8.2.1: - version "8.2.4" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.2.4.tgz#caba24b08185c3b56e3168e97d15ed17f4d31fd0" - integrity sha512-Ibt84YwBDDA890eDiDCEqcbwvHlBvzzDkU2cGBBDDI1QWT12jTiXIOn2CIw5KK4i6N5Z2HUxwYjzriDyqaqqZg== - -acorn@^8.4.1, acorn@^8.5.0: - version "8.5.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.5.0.tgz#4512ccb99b3698c752591e9bb4472e38ad43cee2" - integrity sha512-yXbYeFy+jUuYd3/CDcg2NkIYE991XYX/bje7LmjJigUciaeO1JR4XxXgCIV1/Zc/dRuFEyw1L0pbA+qynJkW5Q== +acorn@^8.2.1, acorn@^8.4.1, acorn@^8.5.0: + version "8.7.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.1.tgz#0197122c843d1bf6d0a5e83220a788f278f63c30" + integrity sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A== agent-base@6: version "6.0.2" @@ -1021,9 +1056,9 @@ browserslist@^4.14.5: node-releases "^1.1.71" buffer-from@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" - integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== + version "1.1.2" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" + integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== bytes@3.1.0: version "3.1.0" @@ -3515,17 +3550,9 @@ source-map-loader@^3.0.0: source-map-js "^0.6.2" source-map-support@^0.5.20, source-map-support@~0.5.20: - version "0.5.20" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.20.tgz#12166089f8f5e5e8c56926b377633392dd2cb6c9" - integrity sha512-n1lZZ8Ve4ksRqizaBQgxXDgKwttHDhyfQjA6YZZn8+AroHbsIz+JjwxQDxbp+7y5OYCI8t1Yk7etjD9CRd2hIw== - dependencies: - buffer-from "^1.0.0" - source-map "^0.6.0" - -source-map-support@~0.5.19: - version "0.5.19" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61" - integrity sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw== + version "0.5.21" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" + integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== dependencies: buffer-from "^1.0.0" source-map "^0.6.0" @@ -3540,11 +3567,6 @@ source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1: resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== -source-map@~0.7.2: - version "0.7.3" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383" - integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ== - spawn-wrap@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/spawn-wrap/-/spawn-wrap-2.0.0.tgz#103685b8b8f9b79771318827aa78650a610d457e" @@ -3676,22 +3698,14 @@ terser-webpack-plugin@^5.1.3: source-map "^0.6.1" terser "^5.7.2" -terser@^5.5.1: - version "5.7.0" - resolved "https://registry.yarnpkg.com/terser/-/terser-5.7.0.tgz#a761eeec206bc87b605ab13029876ead938ae693" - integrity sha512-HP5/9hp2UaZt5fYkuhNBR8YyRcT8juw8+uFbAme53iN9hblvKnLUTKkmwJG6ocWpIKf8UK4DoeWG4ty0J6S6/g== +terser@^5.5.1, terser@^5.7.2: + version "5.14.2" + resolved "https://registry.yarnpkg.com/terser/-/terser-5.14.2.tgz#9ac9f22b06994d736174f4091aa368db896f1c10" + integrity sha512-oL0rGeM/WFQCUd0y2QrWxYnq7tfSuKBiqTjRPWrRgB46WD/kiwHwF8T23z78H6Q6kGCuuHcPB+KULHRdxvVGQA== dependencies: + "@jridgewell/source-map" "^0.3.2" + acorn "^8.5.0" commander "^2.20.0" - source-map "~0.7.2" - source-map-support "~0.5.19" - -terser@^5.7.2: - version "5.9.0" - resolved "https://registry.yarnpkg.com/terser/-/terser-5.9.0.tgz#47d6e629a522963240f2b55fcaa3c99083d2c351" - integrity sha512-h5hxa23sCdpzcye/7b8YqbE5OwKca/ni0RQz1uRX3tGh8haaGHqcuSqbGRybuAKNdntZ0mDgFNXPJ48xQ2RXKQ== - dependencies: - commander "^2.20.0" - source-map "~0.7.2" source-map-support "~0.5.20" test-exclude@^6.0.0: From b5b7a5d7d0e1aa2fd84188b227180caa739a6b12 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 21 Jul 2022 15:24:44 -0700 Subject: [PATCH 07/42] Remove note about IE11 which is EOL --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index 2d1cba47..77a73cfe 100644 --- a/README.md +++ b/README.md @@ -87,8 +87,6 @@ The xterm.js team maintains the following addons, but anyone can build them: Since xterm.js is typically implemented as a developer tool, only modern browsers are supported officially. Specifically the latest versions of *Chrome*, *Edge*, *Firefox*, and *Safari*. -We also partially support *Internet Explorer 11*, meaning xterm.js should work for the most part, but we reserve the right to not provide workarounds specifically for it unless it's absolutely necessary to get the basic input/output flow working. - Xterm.js works seamlessly in [Electron](https://electronjs.org/) apps and may even work on earlier versions of the browsers. These are the versions we strive to keep working. ### Node.js Support From 92855ba399eb121ef0771efd0220174c41f1218d Mon Sep 17 00:00:00 2001 From: Svante Boberg Date: Fri, 22 Jul 2022 13:03:07 +0200 Subject: [PATCH 08/42] Remove terminal from cache on disposal --- addons/xterm-addon-webgl/src/WebglRenderer.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index 1e385b33..d235c4db 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -6,7 +6,7 @@ import { GlyphRenderer } from './GlyphRenderer'; import { LinkRenderLayer } from './renderLayer/LinkRenderLayer'; import { CursorRenderLayer } from './renderLayer/CursorRenderLayer'; -import { acquireCharAtlas } from './atlas/CharAtlasCache'; +import { acquireCharAtlas, removeTerminalFromCache } from './atlas/CharAtlasCache'; import { WebglCharAtlas } from './atlas/WebglCharAtlas'; import { RectangleRenderer } from './RectangleRenderer'; import { IWebGL2RenderingContext } from './Types'; @@ -112,6 +112,7 @@ export class WebglRenderer extends Disposable implements IRenderer { l.dispose(); } this._canvas.parentElement?.removeChild(this._canvas); + removeTerminalFromCache(this._terminal); super.dispose(); } From c29cd83213be521fb68cafcc6581adef5944fe94 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 23 Jul 2022 06:39:41 -0700 Subject: [PATCH 09/42] Allow start task to be restarted without killing watch --- .vscode/tasks.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.vscode/tasks.json b/.vscode/tasks.json index be833820..89a811b7 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -32,7 +32,6 @@ "label": "start", "type": "npm", "script": "start", - "dependsOn": "watch", "group": "build", "isBackground": true, "problemMatcher": [], @@ -42,7 +41,7 @@ }, { "label": "Start demo", - "dependsOn": "start", + "dependsOn": ["start", "watch"], "group": { "kind": "build", "isDefault": true From 91a362be7bc902d1eed0b11fbca512e3ddf7339e Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 23 Jul 2022 13:58:31 -0700 Subject: [PATCH 10/42] Add 3 sample themes to demo --- demo/client.ts | 59 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 58 insertions(+), 1 deletion(-) diff --git a/demo/client.ts b/demo/client.ts index 03a9044c..8bf783eb 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -311,6 +311,7 @@ function initOptions(term: TerminalType): void { fontWeightBold: ['normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900'], logLevel: ['debug', 'info', 'warn', 'error', 'off'], rendererType: ['dom', 'canvas'], + theme: ['default', 'sapphire', 'light'], wordSeparator: null }; const options = Object.getOwnPropertyNames(term.options); @@ -385,7 +386,63 @@ function initOptions(term: TerminalType): void { const input = document.getElementById(`opt-${o}`); addDomListener(input, 'change', () => { console.log('change', o, input.value); - term.options[o] = input.value; + let value: any = input.value; + if (o === 'theme') { + switch (input.value) { + case 'default': + value = undefined; + break; + case 'sapphire': + // Color source: https://github.com/Tyriar/vscode-theme-sapphire + value = { + background: '#1c2431', + foreground: '#cccccc', + selectionBackground: '#399ef440', + black: '#666666', + blue: '#399ef4', + brightBlack: '#666666', + brightBlue: '#399ef4', + brightCyan: '#21c5c7', + brightGreen: '#4eb071', + brightMagenta: '#b168df', + brightRed: '#da6771', + brightWhite: '#efefef', + brightYellow: '#fff099', + cyan: '#21c5c7', + green: '#4eb071', + magenta: '#b168df', + red: '#da6771', + white: '#efefef', + yellow: '#fff099', + }; + break; + case 'light': + // Color source: https://github.com/microsoft/vscode/blob/main/extensions/theme-defaults/themes/light_plus.json + value = { + background: '#ffffff', + foreground: '#333333', + selectionBackground: '#add6ff', + black: '#000000', + blue: '#0451a5', + brightBlack: '#666666', + brightBlue: '#0451a5', + brightCyan: '#0598bc', + brightGreen: '#14ce14', + brightMagenta: '#bc05bc', + brightRed: '#cd3131', + brightWhite: '#a5a5a5', + brightYellow: '#b5ba00', + cyan: '#0598bc', + green: '#00bc00', + magenta: '#bc05bc', + red: '#cd3131', + white: '#555555', + yellow: '#949800', + }; + break; + } + } + term.options[o] = value; }); }); } From f34a30b24fa0baa15018ec6661533adde8e35128 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 23 Jul 2022 14:08:15 -0700 Subject: [PATCH 11/42] Add theme from xtermjs.org and make it the demo default --- demo/client.ts | 32 +++++++++++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/demo/client.ts b/demo/client.ts index 8bf783eb..d0d193e8 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -99,6 +99,27 @@ const actionElements = { }; const paddingElement = document.getElementById('padding'); +const xtermjsTheme = { + foreground: '#F8F8F8', + background: '#2D2E2C', + selection: '#5DA5D533', + black: '#1E1E1D', + brightBlack: '#262625', + red: '#CE5C5C', + brightRed: '#FF7272', + green: '#5BCC5B', + brightGreen: '#72FF72', + yellow: '#CCCC5B', + brightYellow: '#FFFF72', + blue: '#5D5DD3', + brightBlue: '#7279FF', + magenta: '#BC5ED1', + brightMagenta: '#E572FF', + cyan: '#5DA5D5', + brightCyan: '#72F0FF', + white: '#F8F8F8', + brightWhite: '#FFFFFF' +}; function setPadding(): void { term.element.style.padding = parseInt(paddingElement.value, 10).toString() + 'px'; addons.fit.instance.fit(); @@ -175,7 +196,8 @@ function createTerminal(): void { term = new Terminal({ allowTransparency: true, windowsMode: isWindows, - fontFamily: 'Fira Code, courier-new, courier, monospace' + fontFamily: 'Fira Code, courier-new, courier, monospace', + theme: xtermjsTheme } as ITerminalOptions); // Load addons @@ -311,7 +333,7 @@ function initOptions(term: TerminalType): void { fontWeightBold: ['normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900'], logLevel: ['debug', 'info', 'warn', 'error', 'off'], rendererType: ['dom', 'canvas'], - theme: ['default', 'sapphire', 'light'], + theme: ['default', 'xtermjs', 'sapphire', 'light'], wordSeparator: null }; const options = Object.getOwnPropertyNames(term.options); @@ -344,7 +366,8 @@ function initOptions(term: TerminalType): void { html += '
'; Object.keys(stringOptions).forEach(o => { if (stringOptions[o]) { - html += `
`; + const selectedOption = o === 'theme' ? 'xtermjs' : term.options[o]; + html += `
`; } else { html += `
`; } @@ -392,6 +415,9 @@ function initOptions(term: TerminalType): void { case 'default': value = undefined; break; + case 'xtermjs': + // Custom theme to match style of xterm.js logo + value = xtermjsTheme; case 'sapphire': // Color source: https://github.com/Tyriar/vscode-theme-sapphire value = { From dbe2b98bb4e1cfc4c44c1b2221720addbd3c6dda Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 23 Jul 2022 18:57:16 -0700 Subject: [PATCH 12/42] Don't overlap the atlas ontop of options This would prevent mouse input in some options in narrow screens --- demo/style.css | 1 - 1 file changed, 1 deletion(-) diff --git a/demo/style.css b/demo/style.css index cebd08e0..04e8a570 100644 --- a/demo/style.css +++ b/demo/style.css @@ -45,7 +45,6 @@ pre { #container { display: flex; - height: 75vh; } #grid { flex: 1; From 90b6f058c9e2f3965912d8b3fa67cb49bebac80b Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sun, 24 Jul 2022 07:38:48 -0700 Subject: [PATCH 13/42] Support overviewRulerWidth in demo Fixes #3771 --- demo/client.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/demo/client.ts b/demo/client.ts index 03a9044c..c291dd6f 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -315,7 +315,9 @@ function initOptions(term: TerminalType): void { }; const options = Object.getOwnPropertyNames(term.options); const booleanOptions = []; - const numberOptions = []; + const numberOptions = [ + 'overviewRulerWidth' + ]; options.filter(o => blacklistedOptions.indexOf(o) === -1).forEach(o => { switch (typeof term.options[o]) { case 'boolean': @@ -325,7 +327,7 @@ function initOptions(term: TerminalType): void { numberOptions.push(o); break; default: - if (Object.keys(stringOptions).indexOf(o) === -1) { + if (Object.keys(stringOptions).indexOf(o) === -1 && numberOptions.indexOf(o) === -1 && booleanOptions.indexOf(o) === -1) { console.warn(`Unrecognized option: "${o}"`); } } @@ -338,7 +340,7 @@ function initOptions(term: TerminalType): void { }); html += '
'; numberOptions.forEach(o => { - html += `
`; + html += `
`; }); html += '
'; Object.keys(stringOptions).forEach(o => { @@ -373,7 +375,7 @@ function initOptions(term: TerminalType): void { } else if (o === 'scrollSensitivity') { term.options.scrollSensitivity = parseFloat(input.value); updateTerminalSize(); - } else if(o === 'scrollback') { + } else if (o === 'scrollback') { term.options.scrollback = parseInt(input.value); setTimeout(() => updateTerminalSize(), 5); } else { From 8c55c83e4a66bab525a064043f8082db8fbf89b6 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sun, 24 Jul 2022 17:12:23 -0700 Subject: [PATCH 14/42] Fix test-api running unit tests as well This file was added to support the mocha test explorer in vscode but it turns out it causes all calls to mocha to run the unit tests. I couldn't figure out how to ignore through args. Fixes #3777 --- .mocharc.yml | 11 ----------- 1 file changed, 11 deletions(-) delete mode 100644 .mocharc.yml diff --git a/.mocharc.yml b/.mocharc.yml deleted file mode 100644 index c19fe15a..00000000 --- a/.mocharc.yml +++ /dev/null @@ -1,11 +0,0 @@ -require: - - source-map-support/register -spec: - - out/**/*.test.js - - addons/**/out/*.test.js -watch-files: - - out/**/*.js - - addons/**/out/*.js -reporter: spec -color: true -check-leaks: true From 09ae3a9b443133e043091525dd6d35e00b18f4c6 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 27 Jul 2022 05:55:00 -0700 Subject: [PATCH 15/42] Remove sound service and simplify bell --- src/browser/Terminal.ts | 28 +------------ src/browser/services/Services.ts | 8 ---- src/browser/services/SoundService.ts | 63 ---------------------------- 3 files changed, 2 insertions(+), 97 deletions(-) delete mode 100644 src/browser/services/SoundService.ts diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index bd82325d..4c65b1b6 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -34,7 +34,6 @@ import { SelectionService } from 'browser/services/SelectionService'; import * as Browser from 'common/Platform'; import { addDisposableDomListener } from 'browser/Lifecycle'; import * as Strings from 'browser/LocalizableStrings'; -import { SoundService } from 'browser/services/SoundService'; import { MouseZoneManager } from 'browser/MouseZoneManager'; import { AccessibilityManager } from './AccessibilityManager'; import { ITheme, IMarker, IDisposable, ISelectionPosition, ILinkProvider, IDecorationOptions, IDecoration } from 'xterm'; @@ -45,7 +44,7 @@ import { EventEmitter, IEvent, forwardEvent } from 'common/EventEmitter'; import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine'; import { ColorManager } from 'browser/ColorManager'; import { RenderService } from 'browser/services/RenderService'; -import { ICharSizeService, IRenderService, IMouseService, ISelectionService, ISoundService, ICoreBrowserService, ICharacterJoinerService } from 'browser/services/Services'; +import { ICharSizeService, IRenderService, IMouseService, ISelectionService, ICoreBrowserService, ICharacterJoinerService } from 'browser/services/Services'; import { CharSizeService } from 'browser/services/CharSizeService'; import { IBuffer } from 'common/buffer/Types'; import { MouseService } from 'browser/services/MouseService'; @@ -89,7 +88,6 @@ export class Terminal extends CoreTerminal implements ITerminal { private _renderService: IRenderService | undefined; private _characterJoinerService: ICharacterJoinerService | undefined; private _selectionService: ISelectionService | undefined; - private _soundService: ISoundService | undefined; /** * Records whether the keydown event has already been handled and triggered a data event, if so @@ -174,7 +172,7 @@ export class Terminal extends CoreTerminal implements ITerminal { this._instantiationService.setService(IDecorationService, this._decorationService); // Setup InputHandler listeners - this.register(this._inputHandler.onRequestBell(() => this.bell())); + this.register(this._inputHandler.onRequestBell(() => this._onBell.fire())); this.register(this._inputHandler.onRequestRefreshRows((start, end) => this.refresh(start, end))); this.register(this._inputHandler.onRequestSendFocus(() => this._reportFocus())); this.register(this._inputHandler.onRequestReset(() => this.reset())); @@ -537,8 +535,6 @@ export class Terminal extends CoreTerminal implements ITerminal { // Performance: Add viewport and helper elements from the fragment this.element.appendChild(fragment); - this._soundService = this._instantiationService.createInstance(SoundService); - this._instantiationService.setService(ISoundService, this._soundService); this._mouseService = this._instantiationService.createInstance(MouseService); this._instantiationService.setService(IMouseService, this._mouseService); @@ -1285,26 +1281,6 @@ export class Terminal extends CoreTerminal implements ITerminal { return false; } - /** - * Ring the bell. - * Note: We could do sweet things with webaudio here - */ - public bell(): void { - if (this._soundBell()) { - this._soundService?.playBellSound(); - } - - this._onBell.fire(); - - // if (this._visualBell()) { - // this.element.classList.add('visual-bell-active'); - // clearTimeout(this._visualBellTimer); - // this._visualBellTimer = window.setTimeout(() => { - // this.element.classList.remove('visual-bell-active'); - // }, 200); - // } - } - /** * Resizes the terminal. * diff --git a/src/browser/services/Services.ts b/src/browser/services/Services.ts index e1fb5dbd..9f226338 100644 --- a/src/browser/services/Services.ts +++ b/src/browser/services/Services.ts @@ -105,14 +105,6 @@ export interface ISelectionService { isCellInSelection(x: number, y: number): boolean; } -export const ISoundService = createDecorator('SoundService'); -export interface ISoundService { - serviceBrand: undefined; - - playBellSound(): void; -} - - export const ICharacterJoinerService = createDecorator('CharacterJoinerService'); export interface ICharacterJoinerService { serviceBrand: undefined; diff --git a/src/browser/services/SoundService.ts b/src/browser/services/SoundService.ts deleted file mode 100644 index a3b6800d..00000000 --- a/src/browser/services/SoundService.ts +++ /dev/null @@ -1,63 +0,0 @@ -/** - * Copyright (c) 2018 The xterm.js authors. All rights reserved. - * @license MIT - */ - -import { IOptionsService } from 'common/services/Services'; -import { ISoundService } from 'browser/services/Services'; - -export class SoundService implements ISoundService { - public serviceBrand: undefined; - - private static _audioContext: AudioContext; - - public static get audioContext(): AudioContext | null { - if (!SoundService._audioContext) { - const audioContextCtor: typeof AudioContext = (window as any).AudioContext || (window as any).webkitAudioContext; - if (!audioContextCtor) { - console.warn('Web Audio API is not supported by this browser. Consider upgrading to the latest version'); - return null; - } - SoundService._audioContext = new audioContextCtor(); - } - return SoundService._audioContext; - } - - constructor( - @IOptionsService private _optionsService: IOptionsService - ) { - } - - public playBellSound(): void { - const ctx = SoundService.audioContext; - if (!ctx) { - return; - } - const bellAudioSource = ctx.createBufferSource(); - ctx.decodeAudioData(this._base64ToArrayBuffer(this._removeMimeType(this._optionsService.rawOptions.bellSound)), (buffer) => { - bellAudioSource.buffer = buffer; - bellAudioSource.connect(ctx.destination); - bellAudioSource.start(0); - }); - } - - private _base64ToArrayBuffer(base64: string): ArrayBuffer { - const binaryString = window.atob(base64); - const len = binaryString.length; - const bytes = new Uint8Array(len); - - for (let i = 0; i < len; i++) { - bytes[i] = binaryString.charCodeAt(i); - } - - return bytes.buffer; - } - - private _removeMimeType(dataURI: string): string { - // Split the input to get the mime-type and the data itself - const splitUri = dataURI.split(','); - - // Return only the data - return splitUri[1]; - } -} From 66d6ed65ef2db9ee23df7bcbf17cb8327e02ff81 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 27 Jul 2022 05:58:26 -0700 Subject: [PATCH 16/42] Remove all bellSound/bellStyle/etc. references Fixes #3316 --- demo/client.ts | 2 -- src/browser/Terminal.ts | 12 ------------ src/browser/public/Terminal.ts | 9 ++++----- src/common/services/OptionsService.ts | 9 --------- src/common/services/Services.ts | 2 -- src/headless/public/Terminal.ts | 9 ++++----- typings/xterm-headless.d.ts | 24 ++++-------------------- typings/xterm.d.ts | 25 ++++--------------------- 8 files changed, 16 insertions(+), 76 deletions(-) diff --git a/demo/client.ts b/demo/client.ts index cfab9aa8..66ba7bd7 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -324,8 +324,6 @@ function initOptions(term: TerminalType): void { 'windowOptions' ]; const stringOptions = { - bellSound: null, - bellStyle: ['none', 'sound'], cursorStyle: ['block', 'underline', 'bar'], fastScrollModifier: ['alt', 'ctrl', 'shift', undefined], fontFamily: null, diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index 4c65b1b6..76d5461f 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -1398,18 +1398,6 @@ export class Terminal extends CoreTerminal implements ITerminal { ev.stopPropagation(); return false; } - - private _visualBell(): boolean { - return false; - // return this.options.bellStyle === 'visual' || - // this.options.bellStyle === 'both'; - } - - private _soundBell(): boolean { - return this.options.bellStyle === 'sound'; - // return this.options.bellStyle === 'sound' || - // this.options.bellStyle === 'both'; - } } /** diff --git a/src/browser/public/Terminal.ts b/src/browser/public/Terminal.ts index 187bd3b5..4b7f2725 100644 --- a/src/browser/public/Terminal.ts +++ b/src/browser/public/Terminal.ts @@ -241,20 +241,19 @@ export class Terminal implements ITerminalApi { public paste(data: string): void { this._core.paste(data); } - public getOption(key: 'bellSound' | 'bellStyle' | 'cursorStyle' | 'fontFamily' | 'logLevel' | 'rendererType' | 'termName' | 'wordSeparator'): string; - public getOption(key: 'allowTransparency' | 'altClickMovesCursor' | 'cancelEvents' | 'convertEol' | 'cursorBlink' | 'disableStdin' | 'macOptionIsMeta' | 'rightClickSelectsWord' | 'popOnBell' | 'visualBell'): boolean; + public getOption(key: 'cursorStyle' | 'fontFamily' | 'logLevel' | 'rendererType' | 'termName' | 'wordSeparator'): string; + public getOption(key: 'allowTransparency' | 'altClickMovesCursor' | 'cancelEvents' | 'convertEol' | 'cursorBlink' | 'disableStdin' | 'macOptionIsMeta' | 'rightClickSelectsWord'): boolean; public getOption(key: 'cols' | 'fontSize' | 'letterSpacing' | 'lineHeight' | 'rows' | 'tabStopWidth' | 'scrollback'): number; public getOption(key: 'fontWeight' | 'fontWeightBold'): FontWeight; public getOption(key: string): any; public getOption(key: any): any { return this._core.optionsService.getOption(key); } - public setOption(key: 'bellSound' | 'fontFamily' | 'termName' | 'wordSeparator', value: string): void; + public setOption(key: 'fontFamily' | 'termName' | 'wordSeparator', value: string): void; public setOption(key: 'fontWeight' | 'fontWeightBold', value: 'normal' | 'bold' | '100' | '200' | '300' | '400' | '500' | '600' | '700' | '800' | '900' | number): void; public setOption(key: 'logLevel', value: 'debug' | 'info' | 'warn' | 'error' | 'off'): void; - public setOption(key: 'bellStyle', value: 'none' | 'visual' | 'sound' | 'both'): void; public setOption(key: 'cursorStyle', value: 'block' | 'underline' | 'bar'): void; - public setOption(key: 'allowTransparency' | 'altClickMovesCursor' | 'cancelEvents' | 'convertEol' | 'cursorBlink' | 'disableStdin' | 'macOptionIsMeta' | 'rightClickSelectsWord' | 'popOnBell' | 'visualBell', value: boolean): void; + public setOption(key: 'allowTransparency' | 'altClickMovesCursor' | 'cancelEvents' | 'convertEol' | 'cursorBlink' | 'disableStdin' | 'macOptionIsMeta' | 'rightClickSelectsWord', value: boolean): void; public setOption(key: 'fontSize' | 'letterSpacing' | 'lineHeight' | 'tabStopWidth' | 'scrollback', value: number): void; public setOption(key: 'theme', value: ITheme): void; public setOption(key: 'cols' | 'rows', value: number): void; diff --git a/src/common/services/OptionsService.ts b/src/common/services/OptionsService.ts index 4f9600a4..a8c4ff75 100644 --- a/src/common/services/OptionsService.ts +++ b/src/common/services/OptionsService.ts @@ -8,12 +8,6 @@ import { EventEmitter, IEvent } from 'common/EventEmitter'; import { isMac } from 'common/Platform'; import { CursorStyle } from 'common/Types'; -// Source: https://freesound.org/people/altemark/sounds/45759/ -// This sound is released under the Creative Commons Attribution 3.0 Unported -// (CC BY 3.0) license. It was created by 'altemark'. No modifications have been -// made, apart from the conversion to base64. -export const DEFAULT_BELL_SOUND = 'data:audio/mp3;base64,SUQzBAAAAAAAI1RTU0UAAAAPAAADTGF2ZjU4LjMyLjEwNAAAAAAAAAAAAAAA//tQxAADB8AhSmxhIIEVCSiJrDCQBTcu3UrAIwUdkRgQbFAZC1CQEwTJ9mjRvBA4UOLD8nKVOWfh+UlK3z/177OXrfOdKl7pyn3Xf//WreyTRUoAWgBgkOAGbZHBgG1OF6zM82DWbZaUmMBptgQhGjsyYqc9ae9XFz280948NMBWInljyzsNRFLPWdnZGWrddDsjK1unuSrVN9jJsK8KuQtQCtMBjCEtImISdNKJOopIpBFpNSMbIHCSRpRR5iakjTiyzLhchUUBwCgyKiweBv/7UsQbg8isVNoMPMjAAAA0gAAABEVFGmgqK////9bP/6XCykxBTUUzLjEwMKqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq'; - export const DEFAULT_OPTIONS: Readonly = { cols: 80, rows: 24, @@ -21,8 +15,6 @@ export const DEFAULT_OPTIONS: Readonly = { cursorStyle: 'block', cursorWidth: 1, customGlyphs: true, - bellSound: DEFAULT_BELL_SOUND, - bellStyle: 'none', drawBoldTextInBrightColors: true, fastScrollModifier: 'alt', fastScrollSensitivity: 5, @@ -132,7 +124,6 @@ export class OptionsService implements IOptionsService { throw new Error(`"${value}" is not a valid value for ${key}`); } break; - case 'bellStyle': case 'cursorStyle': case 'rendererType': case 'wordSeparator': diff --git a/src/common/services/Services.ts b/src/common/services/Services.ts index fab8435a..c2fdd8e5 100644 --- a/src/common/services/Services.ts +++ b/src/common/services/Services.ts @@ -212,8 +212,6 @@ export interface ITerminalOptions { allowProposedApi: boolean; allowTransparency: boolean; altClickMovesCursor: boolean; - bellSound: string; - bellStyle: 'none' | 'sound' /* | 'visual' | 'both' */; cols: number; convertEol: boolean; cursorBlink: boolean; diff --git a/src/headless/public/Terminal.ts b/src/headless/public/Terminal.ts index 5a35fae2..01c0eab0 100644 --- a/src/headless/public/Terminal.ts +++ b/src/headless/public/Terminal.ts @@ -179,19 +179,18 @@ export class Terminal implements ITerminalApi { this._core.write(data); this._core.write('\r\n', callback); } - public getOption(key: 'bellSound' | 'bellStyle' | 'cursorStyle' | 'fontFamily' | 'logLevel' | 'rendererType' | 'termName' | 'wordSeparator'): string; - public getOption(key: 'allowTransparency' | 'altClickMovesCursor' | 'cancelEvents' | 'convertEol' | 'cursorBlink' | 'disableStdin' | 'macOptionIsMeta' | 'rightClickSelectsWord' | 'popOnBell' | 'visualBell'): boolean; + public getOption(key: 'cursorStyle' | 'fontFamily' | 'logLevel' | 'rendererType' | 'termName' | 'wordSeparator'): string; + public getOption(key: 'allowTransparency' | 'altClickMovesCursor' | 'cancelEvents' | 'convertEol' | 'cursorBlink' | 'disableStdin' | 'macOptionIsMeta' | 'rightClickSelectsWord'): boolean; public getOption(key: 'cols' | 'fontSize' | 'letterSpacing' | 'lineHeight' | 'rows' | 'tabStopWidth' | 'scrollback'): number; public getOption(key: string): any; public getOption(key: any): any { return this._core.optionsService.getOption(key); } - public setOption(key: 'bellSound' | 'fontFamily' | 'termName' | 'wordSeparator', value: string): void; + public setOption(key: 'fontFamily' | 'termName' | 'wordSeparator', value: string): void; public setOption(key: 'fontWeight' | 'fontWeightBold', value: 'normal' | 'bold' | '100' | '200' | '300' | '400' | '500' | '600' | '700' | '800' | '900' | number): void; public setOption(key: 'logLevel', value: 'debug' | 'info' | 'warn' | 'error' | 'off'): void; - public setOption(key: 'bellStyle', value: 'none' | 'visual' | 'sound' | 'both'): void; public setOption(key: 'cursorStyle', value: 'block' | 'underline' | 'bar'): void; - public setOption(key: 'allowTransparency' | 'altClickMovesCursor' | 'cancelEvents' | 'convertEol' | 'cursorBlink' | 'disableStdin' | 'macOptionIsMeta' | 'rightClickSelectsWord' | 'popOnBell' | 'visualBell', value: boolean): void; + public setOption(key: 'allowTransparency' | 'altClickMovesCursor' | 'cancelEvents' | 'convertEol' | 'cursorBlink' | 'disableStdin' | 'macOptionIsMeta' | 'rightClickSelectsWord', value: boolean): void; public setOption(key: 'fontSize' | 'letterSpacing' | 'lineHeight' | 'tabStopWidth' | 'scrollback', value: number): void; public setOption(key: 'cols' | 'rows', value: number): void; public setOption(key: string, value: any): void; diff --git a/typings/xterm-headless.d.ts b/typings/xterm-headless.d.ts index 26a01e4c..c840ed09 100644 --- a/typings/xterm-headless.d.ts +++ b/typings/xterm-headless.d.ts @@ -38,16 +38,6 @@ declare module 'xterm-headless' { */ altClickMovesCursor?: boolean; - /** - * A data uri of the sound to use for the bell when `bellStyle = 'sound'`. - */ - bellSound?: string; - - /** - * The type of the bell notification the terminal will use. - */ - bellStyle?: 'none' | 'sound'; - /** * When enabled the cursor will be set to the beginning of the next line * with every new line. This is equivalent to sending '\r\n' for each '\n'. @@ -723,12 +713,12 @@ declare module 'xterm-headless' { * Retrieves an option's value from the terminal. * @param key The option key. */ - getOption(key: 'bellSound' | 'bellStyle' | 'cursorStyle' | 'fontFamily' | 'logLevel' | 'rendererType' | 'termName' | 'wordSeparator'): string; + getOption(key: 'cursorStyle' | 'fontFamily' | 'logLevel' | 'rendererType' | 'termName' | 'wordSeparator'): string; /** * Retrieves an option's value from the terminal. * @param key The option key. */ - getOption(key: 'allowTransparency' | 'cancelEvents' | 'convertEol' | 'cursorBlink' | 'disableStdin' | 'macOptionIsMeta' | 'rightClickSelectsWord' | 'popOnBell' | 'visualBell' | 'windowsMode'): boolean; + getOption(key: 'allowTransparency' | 'cancelEvents' | 'convertEol' | 'cursorBlink' | 'disableStdin' | 'macOptionIsMeta' | 'rightClickSelectsWord' | 'windowsMode'): boolean; /** * Retrieves an option's value from the terminal. * @param key The option key. @@ -745,7 +735,7 @@ declare module 'xterm-headless' { * @param key The option key. * @param value The option value. */ - setOption(key: 'fontFamily' | 'termName' | 'bellSound' | 'wordSeparator', value: string): void; + setOption(key: 'fontFamily' | 'termName' | 'wordSeparator', value: string): void; /** * Sets an option on the terminal. * @param key The option key. @@ -758,12 +748,6 @@ declare module 'xterm-headless' { * @param value The option value. */ setOption(key: 'logLevel', value: LogLevel): void; - /** - * Sets an option on the terminal. - * @param key The option key. - * @param value The option value. - */ - setOption(key: 'bellStyle', value: null | 'none' | 'visual' | 'sound' | 'both'): void; /** * Sets an option on the terminal. * @param key The option key. @@ -775,7 +759,7 @@ declare module 'xterm-headless' { * @param key The option key. * @param value The option value. */ - setOption(key: 'allowTransparency' | 'cancelEvents' | 'convertEol' | 'cursorBlink' | 'disableStdin' | 'macOptionIsMeta' | 'popOnBell' | 'rightClickSelectsWord' | 'visualBell' | 'windowsMode', value: boolean): void; + setOption(key: 'allowTransparency' | 'cancelEvents' | 'convertEol' | 'cursorBlink' | 'disableStdin' | 'macOptionIsMeta' | 'rightClickSelectsWord' | 'windowsMode', value: boolean): void; /** * Sets an option on the terminal. * @param key The option key. diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index a3f47300..e8a2f8f4 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -50,16 +50,6 @@ declare module 'xterm' { */ altClickMovesCursor?: boolean; - /** - * A data uri of the sound to use for the bell when `bellStyle = 'sound'`. - */ - bellSound?: string; - - /** - * The type of the bell notification the terminal will use. - */ - bellStyle?: 'none' | 'sound'; - /** * When enabled the cursor will be set to the beginning of the next line * with every new line. This is equivalent to sending '\r\n' for each '\n'. @@ -1128,13 +1118,13 @@ declare module 'xterm' { * @param key The option key. * @deprecated Use `options` instead. */ - getOption(key: 'bellSound' | 'bellStyle' | 'cursorStyle' | 'fontFamily' | 'logLevel' | 'rendererType' | 'termName' | 'wordSeparator'): string; + getOption(key: 'cursorStyle' | 'fontFamily' | 'logLevel' | 'rendererType' | 'termName' | 'wordSeparator'): string; /** * Retrieves an option's value from the terminal. * @param key The option key. * @deprecated Use `options` instead. */ - getOption(key: 'allowTransparency' | 'cancelEvents' | 'convertEol' | 'cursorBlink' | 'disableStdin' | 'macOptionIsMeta' | 'rightClickSelectsWord' | 'popOnBell' | 'visualBell' | 'windowsMode'): boolean; + getOption(key: 'allowTransparency' | 'cancelEvents' | 'convertEol' | 'cursorBlink' | 'disableStdin' | 'macOptionIsMeta' | 'rightClickSelectsWord' | 'windowsMode'): boolean; /** * Retrieves an option's value from the terminal. * @param key The option key. @@ -1160,7 +1150,7 @@ declare module 'xterm' { * @param value The option value. * @deprecated Use `options` instead. */ - setOption(key: 'fontFamily' | 'termName' | 'bellSound' | 'wordSeparator', value: string): void; + setOption(key: 'fontFamily' | 'termName' | 'wordSeparator', value: string): void; /** * Sets an option on the terminal. * @param key The option key. @@ -1175,13 +1165,6 @@ declare module 'xterm' { * @deprecated Use `options` instead. */ setOption(key: 'logLevel', value: LogLevel): void; - /** - * Sets an option on the terminal. - * @param key The option key. - * @param value The option value. - * @deprecated Use `options` instead. - */ - setOption(key: 'bellStyle', value: null | 'none' | 'visual' | 'sound' | 'both'): void; /** * Sets an option on the terminal. * @param key The option key. @@ -1195,7 +1178,7 @@ declare module 'xterm' { * @param value The option value. * @deprecated Use `options` instead. */ - setOption(key: 'allowTransparency' | 'cancelEvents' | 'convertEol' | 'cursorBlink' | 'disableStdin' | 'macOptionIsMeta' | 'popOnBell' | 'rightClickSelectsWord' | 'visualBell' | 'windowsMode', value: boolean): void; + setOption(key: 'allowTransparency' | 'cancelEvents' | 'convertEol' | 'cursorBlink' | 'disableStdin' | 'macOptionIsMeta' | 'rightClickSelectsWord' | 'windowsMode', value: boolean): void; /** * Sets an option on the terminal. * @param key The option key. From 8a19f8c1cc5af98801dc7474737b27b6cd6a7afd Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 27 Jul 2022 06:04:53 -0700 Subject: [PATCH 17/42] Enable CI on v5 branch --- azure-pipelines.yml | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 34a17ca9..fa3f1b54 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -1,7 +1,10 @@ -# Node.js -# Build a general Node.js application with npm. -# Add steps that analyze code, save build artifacts, deploy, and more: -# https://docs.microsoft.com/vsts/pipelines/languages/javascript +pr: + -master + -v5 + +trigger: + -master + -v5 jobs: - job: Linux From dc4940e26edf13e29c60be59db5a73dd3e801f23 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 27 Jul 2022 06:13:28 -0700 Subject: [PATCH 18/42] Try different syntax --- azure-pipelines.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index fa3f1b54..1710922e 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -1,10 +1,10 @@ pr: - -master - -v5 + branches: + include: ["main", "v5"] trigger: - -master - -v5 + branches: + include: ["main", "v5"] jobs: - job: Linux From 7a17a26e18c9640821508cb987ddf43bb505c6dc Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 27 Jul 2022 06:49:35 -0700 Subject: [PATCH 19/42] Remove link matchers Fixes #2703 --- .../typings/xterm-addon-attach.d.ts | 2 +- .../src/WebLinksAddon.ts | 24 +- .../typings/xterm-addon-web-links.d.ts | 12 +- .../src/renderLayer/LinkRenderLayer.ts | 2 - src/browser/Linkifier.test.ts | 244 ------------ src/browser/Linkifier.ts | 356 ------------------ src/browser/Terminal.test.ts | 117 +----- src/browser/Terminal.ts | 46 +-- src/browser/TestUtils.test.ts | 9 +- src/browser/Types.d.ts | 78 ---- src/browser/public/Terminal.ts | 10 +- src/browser/renderer/LinkRenderLayer.ts | 5 +- src/browser/renderer/Renderer.ts | 5 +- src/browser/renderer/dom/DomRenderer.ts | 6 +- typings/xterm.d.ts | 68 +--- 15 files changed, 23 insertions(+), 961 deletions(-) delete mode 100644 src/browser/Linkifier.test.ts delete mode 100644 src/browser/Linkifier.ts diff --git a/addons/xterm-addon-attach/typings/xterm-addon-attach.d.ts b/addons/xterm-addon-attach/typings/xterm-addon-attach.d.ts index 1aa21357..2956b9d9 100644 --- a/addons/xterm-addon-attach/typings/xterm-addon-attach.d.ts +++ b/addons/xterm-addon-attach/typings/xterm-addon-attach.d.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { Terminal, ILinkMatcherOptions, ITerminalAddon } from 'xterm'; +import { Terminal, ITerminalAddon } from 'xterm'; declare module 'xterm-addon-attach' { export interface IAttachOptions { diff --git a/addons/xterm-addon-web-links/src/WebLinksAddon.ts b/addons/xterm-addon-web-links/src/WebLinksAddon.ts index 285ef5dc..1e3c877d 100644 --- a/addons/xterm-addon-web-links/src/WebLinksAddon.ts +++ b/addons/xterm-addon-web-links/src/WebLinksAddon.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { Terminal, ILinkMatcherOptions, ITerminalAddon, IDisposable } from 'xterm'; +import { Terminal, ITerminalAddon, IDisposable } from 'xterm'; import { ILinkProviderOptions, WebLinkProvider } from './WebLinkProvider'; const protocolClause = '(https?:\\/\\/)'; @@ -41,37 +41,23 @@ function handleLink(event: MouseEvent, uri: string): void { } export class WebLinksAddon implements ITerminalAddon { - private _linkMatcherId: number | undefined; private _terminal: Terminal | undefined; private _linkProvider: IDisposable | undefined; constructor( private _handler: (event: MouseEvent, uri: string) => void = handleLink, - private _options: ILinkMatcherOptions | ILinkProviderOptions = {}, - private _useLinkProvider: boolean = false + private _options: ILinkProviderOptions = {} ) { } public activate(terminal: Terminal): void { this._terminal = terminal; - - if (this._useLinkProvider && 'registerLinkProvider' in this._terminal) { - const options = this._options as ILinkProviderOptions; - const regex = options.urlRegex || strictUrlRegex; - this._linkProvider = this._terminal.registerLinkProvider(new WebLinkProvider(this._terminal, regex, this._handler, options)); - } else { - // TODO: This should be removed eventually - const options = this._options as ILinkMatcherOptions; - options.matchIndex = 1; - this._linkMatcherId = (this._terminal as Terminal).registerLinkMatcher(strictUrlRegex, this._handler, options); - } + const options = this._options as ILinkProviderOptions; + const regex = options.urlRegex || strictUrlRegex; + this._linkProvider = this._terminal.registerLinkProvider(new WebLinkProvider(this._terminal, regex, this._handler, options)); } public dispose(): void { - if (this._linkMatcherId !== undefined && this._terminal !== undefined) { - this._terminal.deregisterLinkMatcher(this._linkMatcherId); - } - this._linkProvider?.dispose(); } } diff --git a/addons/xterm-addon-web-links/typings/xterm-addon-web-links.d.ts b/addons/xterm-addon-web-links/typings/xterm-addon-web-links.d.ts index 5e6c266d..4e1767b9 100644 --- a/addons/xterm-addon-web-links/typings/xterm-addon-web-links.d.ts +++ b/addons/xterm-addon-web-links/typings/xterm-addon-web-links.d.ts @@ -4,7 +4,7 @@ */ -import { Terminal, ILinkMatcherOptions, ITerminalAddon, IViewportRange } from 'xterm'; +import { Terminal, ITerminalAddon, IViewportRange } from 'xterm'; declare module 'xterm-addon-web-links' { /** @@ -14,13 +14,9 @@ declare module 'xterm-addon-web-links' { /** * Creates a new web links addon. * @param handler The callback when the link is called. - * @param options Options for the link matcher. - * @param useLinkProvider Whether to use the new link provider API to create - * the links. This is an option because use of both link matcher (old) and - * link provider (new) may cause issues. Link provider will eventually be - * the default and only option. + * @param options Options for the link provider. */ - constructor(handler?: (event: MouseEvent, uri: string) => void, options?: ILinkMatcherOptions | ILinkProviderOptions, useLinkProvider?: boolean); + constructor(handler?: (event: MouseEvent, uri: string) => void, options?: ILinkProviderOptions); /** * Activates the addon @@ -50,7 +46,7 @@ declare module 'xterm-addon-web-links' { */ leave?(event: MouseEvent, text: string): void; - /** + /** * A callback to use instead of the default one. */ urlRegex?: RegExp; diff --git a/addons/xterm-addon-webgl/src/renderLayer/LinkRenderLayer.ts b/addons/xterm-addon-webgl/src/renderLayer/LinkRenderLayer.ts index 8f6b3e6d..b2d004f9 100644 --- a/addons/xterm-addon-webgl/src/renderLayer/LinkRenderLayer.ts +++ b/addons/xterm-addon-webgl/src/renderLayer/LinkRenderLayer.ts @@ -15,8 +15,6 @@ export class LinkRenderLayer extends BaseRenderLayer { constructor(container: HTMLElement, zIndex: number, colors: IColorSet, terminal: ITerminal) { super(container, 'link', zIndex, true, colors); - terminal.linkifier.onShowLinkUnderline(e => this._onShowLinkUnderline(e)); - terminal.linkifier.onHideLinkUnderline(e => this._onHideLinkUnderline(e)); terminal.linkifier2.onShowLinkUnderline(e => this._onShowLinkUnderline(e)); terminal.linkifier2.onHideLinkUnderline(e => this._onHideLinkUnderline(e)); diff --git a/src/browser/Linkifier.test.ts b/src/browser/Linkifier.test.ts deleted file mode 100644 index a07f69ed..00000000 --- a/src/browser/Linkifier.test.ts +++ /dev/null @@ -1,244 +0,0 @@ -/** - * Copyright (c) 2017 The xterm.js authors. All rights reserved. - * @license MIT - */ - -import { assert } from 'chai'; -import { IMouseZoneManager, IMouseZone, IRegisteredLinkMatcher } from 'browser/Types'; -import { IBufferLine } from 'common/Types'; -import { Linkifier } from 'browser/Linkifier'; -import { BufferLine } from 'common/buffer/BufferLine'; -import { CellData } from 'common/buffer/CellData'; -import { MockLogService, MockBufferService } from 'common/TestUtils.test'; -import { IBufferService } from 'common/services/Services'; -import { UnicodeService } from 'common/services/UnicodeService'; - -class TestLinkifier extends Linkifier { - constructor(bufferService: IBufferService) { - super(bufferService, new MockLogService(), new UnicodeService()); - Linkifier._timeBeforeLatency = 0; - } - - public get linkMatchers(): IRegisteredLinkMatcher[] { return this._linkMatchers; } - public linkifyRows(): void { super.linkifyRows(0, this._bufferService.buffer.lines.length - 1); } -} - -class TestMouseZoneManager implements IMouseZoneManager { - public dispose(): void { - } - public clears: number = 0; - public zones: IMouseZone[] = []; - public add(zone: IMouseZone): void { - this.zones.push(zone); - } - public clearAll(): void { - this.clears++; - } -} - -describe('Linkifier', () => { - let bufferService: IBufferService; - let linkifier: TestLinkifier; - let mouseZoneManager: TestMouseZoneManager; - - beforeEach(() => { - bufferService = new MockBufferService(100, 10); - linkifier = new TestLinkifier(bufferService); - mouseZoneManager = new TestMouseZoneManager(); - }); - - function stringToRow(text: string): IBufferLine { - const result = new BufferLine(text.length); - for (let i = 0; i < text.length; i++) { - result.setCell(i, CellData.fromCharData([0, text.charAt(i), 1, text.charCodeAt(i)])); - } - return result; - } - - function addRow(text: string): void { - bufferService.buffer.lines.push(stringToRow(text)); - } - - function assertLinkifiesRow(rowText: string, linkMatcherRegex: RegExp, links: {x: number, length: number}[], done: Mocha.Done): void { - addRow(rowText); - linkifier.registerLinkMatcher(linkMatcherRegex, () => {}); - linkifier.linkifyRows(); - // Allow linkify to happen - setTimeout(() => { - assert.equal(mouseZoneManager.zones.length, links.length); - links.forEach((l, i) => { - assert.equal(mouseZoneManager.zones[i].x1, l.x + 1); - assert.equal(mouseZoneManager.zones[i].x2, l.x + l.length + 1); - assert.equal(mouseZoneManager.zones[i].y1, bufferService.buffer.lines.length); - assert.equal(mouseZoneManager.zones[i].y2, bufferService.buffer.lines.length); - }); - done(); - }, 0); - } - - function assertLinkifiesMultiLineLink(rowText: string, linkMatcherRegex: RegExp, links: {x1: number, y1: number, x2: number, y2: number}[], done: Mocha.Done): void { - addRow(rowText); - linkifier.registerLinkMatcher(linkMatcherRegex, () => {}); - linkifier.linkifyRows(); - // Allow linkify to happen - setTimeout(() => { - assert.equal(mouseZoneManager.zones.length, links.length); - links.forEach((l, i) => { - assert.equal(mouseZoneManager.zones[i].x1, l.x1 + 1); - assert.equal(mouseZoneManager.zones[i].x2, l.x2 + 1); - assert.equal(mouseZoneManager.zones[i].y1, l.y1 + 1); - assert.equal(mouseZoneManager.zones[i].y2, l.y2 + 1); - }); - done(); - }, 0); - } - - describe('before attachToDom', () => { - it('should allow link matcher registration', done => { - assert.doesNotThrow(() => { - const linkMatcherId = linkifier.registerLinkMatcher(/foo/, () => {}); - assert.isTrue(linkifier.deregisterLinkMatcher(linkMatcherId)); - done(); - }); - }); - }); - - describe('after attachToDom', () => { - beforeEach(() => { - linkifier.attachToDom({} as any, mouseZoneManager); - }); - - describe('link matcher', () => { - it('should match a single link', done => { - assertLinkifiesRow('foo', /foo/, [{x: 0, length: 3}], done); - }); - it('should match a single link at the start of a text node', done => { - assertLinkifiesRow('foo bar', /foo/, [{x: 0, length: 3}], done); - }); - it('should match a single link in the middle of a text node', done => { - assertLinkifiesRow('foo bar baz', /bar/, [{x: 4, length: 3}], done); - }); - it('should match a single link at the end of a text node', done => { - assertLinkifiesRow('foo bar', /bar/, [{x: 4, length: 3}], done); - }); - it('should match a link after a link at the start of a text node', done => { - assertLinkifiesRow('foo bar', /foo|bar/, [{x: 0, length: 3}, {x: 4, length: 3}], done); - }); - it('should match a link after a link in the middle of a text node', done => { - assertLinkifiesRow('foo bar baz', /bar|baz/, [{x: 4, length: 3}, {x: 8, length: 3}], done); - }); - it('should match a link immediately after a link at the end of a text node', done => { - assertLinkifiesRow('foo barbaz', /bar|baz/, [{x: 4, length: 3}, {x: 7, length: 3}], done); - }); - it('should not duplicate text after a unicode character (wrapped in a span)', done => { - // This is a regression test for an issue that came about when using - // an oh-my-zsh theme that added the large blue diamond unicode - // character (U+1F537) which caused the path to be duplicated. See #642. - assertLinkifiesRow('echo \'🔷foo\'', /foo/, [{x: 8, length: 3}], done); - }); - describe('multi-line links', () => { - it('should match links that start on line 1/2 of a wrapped line and end on the last character of line 1/2', done => { - bufferService.resize(4, bufferService.rows); - bufferService.buffer.lines.length = 0; - assertLinkifiesMultiLineLink('12345', /1234/, [{x1: 0, x2: 4, y1: 0, y2: 0}], done); - }); - it('should match links that start on line 1/2 of a wrapped line and wrap to line 2/2', done => { - bufferService.resize(4, bufferService.rows); - bufferService.buffer.lines.length = 0; - assertLinkifiesMultiLineLink('12345', /12345/, [{x1: 0, x2: 1, y1: 0, y2: 1}], done); - }); - it('should match links that start and end on line 2/2 of a wrapped line', done => { - bufferService.resize(4, bufferService.rows); - bufferService.buffer.lines.length = 0; - assertLinkifiesMultiLineLink('12345678', /5678/, [{x1: 0, x2: 4, y1: 1, y2: 1}], done); - }); - it('should match links that start on line 2/3 of a wrapped line and wrap to line 3/3', done => { - bufferService.resize(4, bufferService.rows); - bufferService.buffer.lines.length = 0; - assertLinkifiesMultiLineLink('123456789', /56789/, [{x1: 0, x2: 1, y1: 1, y2: 2}], done); - }); - }); - }); - - describe('validationCallback', () => { - it('should enable link if true', done => { - bufferService.buffer.lines.length = 0; - addRow('test'); - linkifier.registerLinkMatcher(/test/, () => done(), { - validationCallback: (url, cb) => { - assert.equal(mouseZoneManager.zones.length, 0); - cb(true); - assert.equal(mouseZoneManager.zones.length, 1); - assert.equal(mouseZoneManager.zones[0].x1, 1); - assert.equal(mouseZoneManager.zones[0].x2, 5); - assert.equal(mouseZoneManager.zones[0].y1, 1); - assert.equal(mouseZoneManager.zones[0].y2, 1); - // Fires done() - mouseZoneManager.zones[0].clickCallback({} as any); - } - }); - linkifier.linkifyRows(); - }); - - it('should validate the uri, not the row', done => { - addRow('abc test abc'); - linkifier.registerLinkMatcher(/test/, () => done(), { - validationCallback: (uri, cb) => { - assert.equal(uri, 'test'); - done(); - } - }); - linkifier.linkifyRows(); - }); - - it('should disable link if false', done => { - addRow('test'); - linkifier.registerLinkMatcher(/test/, () => assert.fail(), { - validationCallback: (url, cb) => { - assert.equal(mouseZoneManager.zones.length, 0); - cb(false); - assert.equal(mouseZoneManager.zones.length, 0); - } - }); - linkifier.linkifyRows(); - // Allow time for the validation callback to be performed - setTimeout(() => done(), 10); - }); - - it('should trigger for multiple link matches on one row', done => { - addRow('test test'); - let count = 0; - linkifier.registerLinkMatcher(/test/, () => assert.fail(), { - validationCallback: (url, cb) => { - count++; - if (count === 2) { - done(); - } - cb(false); - } - }); - linkifier.linkifyRows(); - }); - }); - - describe('priority', () => { - it('should order the list from highest priority to lowest #1', () => { - const aId = linkifier.registerLinkMatcher(/a/, () => {}, { priority: 1 }); - const bId = linkifier.registerLinkMatcher(/b/, () => {}, { priority: -1 }); - assert.deepEqual(linkifier.linkMatchers.map(lm => lm.id), [aId, bId]); - }); - - it('should order the list from highest priority to lowest #2', () => { - const aId = linkifier.registerLinkMatcher(/a/, () => {}, { priority: -1 }); - const bId = linkifier.registerLinkMatcher(/b/, () => {}, { priority: 1 }); - assert.deepEqual(linkifier.linkMatchers.map(lm => lm.id), [bId, aId]); - }); - - it('should order items of equal priority in the order they are added', () => { - const aId = linkifier.registerLinkMatcher(/a/, () => {}, { priority: 0 }); - const bId = linkifier.registerLinkMatcher(/b/, () => {}, { priority: 0 }); - assert.deepEqual(linkifier.linkMatchers.map(lm => lm.id), [aId, bId]); - }); - }); - }); -}); diff --git a/src/browser/Linkifier.ts b/src/browser/Linkifier.ts deleted file mode 100644 index b17d66a8..00000000 --- a/src/browser/Linkifier.ts +++ /dev/null @@ -1,356 +0,0 @@ -/** - * Copyright (c) 2017 The xterm.js authors. All rights reserved. - * @license MIT - */ - -import { ILinkifierEvent, ILinkMatcher, LinkMatcherHandler, ILinkMatcherOptions, ILinkifier, IMouseZoneManager, IMouseZone, IRegisteredLinkMatcher } from 'browser/Types'; -import { IBufferStringIteratorResult } from 'common/buffer/Types'; -import { EventEmitter, IEvent } from 'common/EventEmitter'; -import { ILogService, IBufferService, IOptionsService, IUnicodeService } from 'common/services/Services'; - -/** - * Limit of the unwrapping line expansion (overscan) at the top and bottom - * of the actual viewport in ASCII characters. - * A limit of 2000 should match most sane urls. - */ -const OVERSCAN_CHAR_LIMIT = 2000; - -/** - * The Linkifier applies links to rows shortly after they have been refreshed. - */ -export class Linkifier implements ILinkifier { - /** - * The time to wait after a row is changed before it is linkified. This prevents - * the costly operation of searching every row multiple times, potentially a - * huge amount of times. - */ - protected static _timeBeforeLatency = 200; - - protected _linkMatchers: IRegisteredLinkMatcher[] = []; - - private _mouseZoneManager: IMouseZoneManager | undefined; - private _element: HTMLElement | undefined; - - private _rowsTimeoutId: number | undefined; - private _nextLinkMatcherId = 0; - private _rowsToLinkify: { start: number | undefined, end: number | undefined }; - - private _onShowLinkUnderline = new EventEmitter(); - public get onShowLinkUnderline(): IEvent { return this._onShowLinkUnderline.event; } - private _onHideLinkUnderline = new EventEmitter(); - public get onHideLinkUnderline(): IEvent { return this._onHideLinkUnderline.event; } - private _onLinkTooltip = new EventEmitter(); - public get onLinkTooltip(): IEvent { return this._onLinkTooltip.event; } - - constructor( - @IBufferService protected readonly _bufferService: IBufferService, - @ILogService private readonly _logService: ILogService, - @IUnicodeService private readonly _unicodeService: IUnicodeService - ) { - this._rowsToLinkify = { - start: undefined, - end: undefined - }; - } - - /** - * Attaches the linkifier to the DOM, enabling linkification. - * @param mouseZoneManager The mouse zone manager to register link zones with. - */ - public attachToDom(element: HTMLElement, mouseZoneManager: IMouseZoneManager): void { - this._element = element; - this._mouseZoneManager = mouseZoneManager; - } - - /** - * Queue linkification on a set of rows. - * @param start The row to linkify from (inclusive). - * @param end The row to linkify to (inclusive). - */ - public linkifyRows(start: number, end: number): void { - // Don't attempt linkify if not yet attached to DOM - if (!this._mouseZoneManager) { - return; - } - - // Increase range to linkify - if (this._rowsToLinkify.start === undefined || this._rowsToLinkify.end === undefined) { - this._rowsToLinkify.start = start; - this._rowsToLinkify.end = end; - } else { - this._rowsToLinkify.start = Math.min(this._rowsToLinkify.start, start); - this._rowsToLinkify.end = Math.max(this._rowsToLinkify.end, end); - } - - // Clear out any existing links on this row range - this._mouseZoneManager.clearAll(start, end); - - // Restart timer - if (this._rowsTimeoutId) { - clearTimeout(this._rowsTimeoutId); - } - - // Cannot use window.setTimeout since tests need to run in node - this._rowsTimeoutId = setTimeout(() => this._linkifyRows(), Linkifier._timeBeforeLatency) as any as number; - } - - /** - * Linkifies the rows requested. - */ - private _linkifyRows(): void { - this._rowsTimeoutId = undefined; - const buffer = this._bufferService.buffer; - - if (this._rowsToLinkify.start === undefined || this._rowsToLinkify.end === undefined) { - this._logService.debug('_rowToLinkify was unset before _linkifyRows was called'); - return; - } - - // Ensure the start row exists - const absoluteRowIndexStart = buffer.ydisp + this._rowsToLinkify.start; - if (absoluteRowIndexStart >= buffer.lines.length) { - return; - } - - // Invalidate bad end row values (if a resize happened) - const absoluteRowIndexEnd = buffer.ydisp + Math.min(this._rowsToLinkify.end, this._bufferService.rows) + 1; - - // Iterate over the range of unwrapped content strings within start..end - // (excluding). - // _doLinkifyRow gets full unwrapped lines with the start row as buffer offset - // for every matcher. - // The unwrapping is needed to also match content that got wrapped across - // several buffer lines. To avoid a worst case scenario where the whole buffer - // contains just a single unwrapped string we limit this line expansion beyond - // the viewport to +OVERSCAN_CHAR_LIMIT chars (overscan) at top and bottom. - // This comes with the tradeoff that matches longer than OVERSCAN_CHAR_LIMIT - // chars will not match anymore at the viewport borders. - const overscanLineLimit = Math.ceil(OVERSCAN_CHAR_LIMIT / this._bufferService.cols); - const iterator = this._bufferService.buffer.iterator( - false, absoluteRowIndexStart, absoluteRowIndexEnd, overscanLineLimit, overscanLineLimit); - while (iterator.hasNext()) { - const lineData: IBufferStringIteratorResult = iterator.next(); - for (let i = 0; i < this._linkMatchers.length; i++) { - this._doLinkifyRow(lineData.range.first, lineData.content, this._linkMatchers[i]); - } - } - - this._rowsToLinkify.start = undefined; - this._rowsToLinkify.end = undefined; - } - - /** - * Registers a link matcher, allowing custom link patterns to be matched and - * handled. - * @param regex The regular expression to search for. Specifically, this - * searches the textContent of the rows. You will want to use \s to match a - * space ' ' character for example. - * @param handler The callback when the link is called. - * @param options Options for the link matcher. - * @return The ID of the new matcher, this can be used to deregister. - */ - public registerLinkMatcher(regex: RegExp, handler: LinkMatcherHandler, options: ILinkMatcherOptions = {}): number { - if (!handler) { - throw new Error('handler must be defined'); - } - const matcher: IRegisteredLinkMatcher = { - id: this._nextLinkMatcherId++, - regex, - handler, - matchIndex: options.matchIndex, - validationCallback: options.validationCallback, - hoverTooltipCallback: options.tooltipCallback, - hoverLeaveCallback: options.leaveCallback, - willLinkActivate: options.willLinkActivate, - priority: options.priority || 0 - }; - this._addLinkMatcherToList(matcher); - return matcher.id; - } - - /** - * Inserts a link matcher to the list in the correct position based on the - * priority of each link matcher. New link matchers of equal priority are - * considered after older link matchers. - * @param matcher The link matcher to be added. - */ - private _addLinkMatcherToList(matcher: IRegisteredLinkMatcher): void { - if (this._linkMatchers.length === 0) { - this._linkMatchers.push(matcher); - return; - } - - for (let i = this._linkMatchers.length - 1; i >= 0; i--) { - if (matcher.priority <= this._linkMatchers[i].priority) { - this._linkMatchers.splice(i + 1, 0, matcher); - return; - } - } - - this._linkMatchers.splice(0, 0, matcher); - } - - /** - * Deregisters a link matcher if it has been registered. - * @param matcherId The link matcher's ID (returned after register) - * @return Whether a link matcher was found and deregistered. - */ - public deregisterLinkMatcher(matcherId: number): boolean { - for (let i = 0; i < this._linkMatchers.length; i++) { - if (this._linkMatchers[i].id === matcherId) { - this._linkMatchers.splice(i, 1); - return true; - } - } - return false; - } - - /** - * Linkifies a row given a specific handler. - * @param rowIndex The row index to linkify (absolute index). - * @param text string content of the unwrapped row. - * @param matcher The link matcher for this line. - */ - private _doLinkifyRow(rowIndex: number, text: string, matcher: ILinkMatcher): void { - // clone regex to do a global search on text - const rex = new RegExp(matcher.regex.source, (matcher.regex.flags || '') + 'g'); - let match; - let stringIndex = -1; - while ((match = rex.exec(text)) !== null) { - const uri = match[typeof matcher.matchIndex !== 'number' ? 0 : matcher.matchIndex]; - if (!uri) { - // something matched but does not comply with the given matchIndex - // since this is most likely a bug the regex itself we simply do nothing here - this._logService.debug('match found without corresponding matchIndex', match, matcher); - break; - } - - // Get index, match.index is for the outer match which includes negated chars - // therefore we cannot use match.index directly, instead we search the position - // of the match group in text again - // also correct regex and string search offsets for the next loop run - stringIndex = text.indexOf(uri, stringIndex + 1); - rex.lastIndex = stringIndex + uri.length; - if (stringIndex < 0) { - // invalid stringIndex (should not have happened) - break; - } - - // get the buffer index as [absolute row, col] for the match - const bufferIndex = this._bufferService.buffer.stringIndexToBufferIndex(rowIndex, stringIndex); - if (bufferIndex[0] < 0) { - // invalid bufferIndex (should not have happened) - break; - } - - const line = this._bufferService.buffer.lines.get(bufferIndex[0]); - if (!line) { - break; - } - - const attr = line.getFg(bufferIndex[1]); - const fg = attr ? (attr >> 9) & 0x1ff : undefined; - - if (matcher.validationCallback) { - matcher.validationCallback(uri, isValid => { - // Discard link if the line has already changed - if (this._rowsTimeoutId) { - return; - } - if (isValid) { - this._addLink(bufferIndex[1], bufferIndex[0] - this._bufferService.buffer.ydisp, uri, matcher, fg); - } - }); - } else { - this._addLink(bufferIndex[1], bufferIndex[0] - this._bufferService.buffer.ydisp, uri, matcher, fg); - } - } - } - - /** - * Registers a link to the mouse zone manager. - * @param x The column the link starts. - * @param y The row the link is on. - * @param uri The URI of the link. - * @param matcher The link matcher for the link. - * @param fg The link color for hover event. - */ - private _addLink(x: number, y: number, uri: string, matcher: ILinkMatcher, fg: number | undefined): void { - if (!this._mouseZoneManager || !this._element) { - return; - } - // FIXME: get cell length from buffer to avoid mismatch after Unicode version change - const width = this._unicodeService.getStringCellWidth(uri); - const x1 = x % this._bufferService.cols; - const y1 = y + Math.floor(x / this._bufferService.cols); - let x2 = (x1 + width) % this._bufferService.cols; - let y2 = y1 + Math.floor((x1 + width) / this._bufferService.cols); - if (x2 === 0) { - x2 = this._bufferService.cols; - y2--; - } - - this._mouseZoneManager.add(new MouseZone( - x1 + 1, - y1 + 1, - x2 + 1, - y2 + 1, - e => { - if (matcher.handler) { - return matcher.handler(e, uri); - } - const newWindow = window.open(); - if (newWindow) { - newWindow.opener = null; - newWindow.location.href = uri; - } else { - console.warn('Opening link blocked as opener could not be cleared'); - } - }, - () => { - this._onShowLinkUnderline.fire(this._createLinkHoverEvent(x1, y1, x2, y2, fg)); - this._element!.classList.add('xterm-cursor-pointer'); - }, - e => { - this._onLinkTooltip.fire(this._createLinkHoverEvent(x1, y1, x2, y2, fg)); - if (matcher.hoverTooltipCallback) { - // Note that IViewportRange use 1-based coordinates to align with escape sequences such - // as CUP which use 1,1 as the default for row/col - matcher.hoverTooltipCallback(e, uri, { start: { x: x1, y: y1 }, end: { x: x2, y: y2 } }); - } - }, - () => { - this._onHideLinkUnderline.fire(this._createLinkHoverEvent(x1, y1, x2, y2, fg)); - this._element!.classList.remove('xterm-cursor-pointer'); - if (matcher.hoverLeaveCallback) { - matcher.hoverLeaveCallback(); - } - }, - e => { - if (matcher.willLinkActivate) { - return matcher.willLinkActivate(e, uri); - } - return true; - } - )); - } - - private _createLinkHoverEvent(x1: number, y1: number, x2: number, y2: number, fg: number | undefined): ILinkifierEvent { - return { x1, y1, x2, y2, cols: this._bufferService.cols, fg }; - } -} - -export class MouseZone implements IMouseZone { - constructor( - public x1: number, - public y1: number, - public x2: number, - public y2: number, - public clickCallback: (e: MouseEvent) => any, - public hoverCallback: (e: MouseEvent) => any, - public tooltipCallback: (e: MouseEvent) => any, - public leaveCallback: () => void, - public willLinkActivate: (e: MouseEvent) => boolean - ) { - } -} diff --git a/src/browser/Terminal.test.ts b/src/browser/Terminal.test.ts index 3eafb261..8542ddca 100644 --- a/src/browser/Terminal.test.ts +++ b/src/browser/Terminal.test.ts @@ -7,11 +7,9 @@ import { assert } from 'chai'; import { MockViewport, MockCompositionHelper, MockRenderer, TestTerminal } from 'browser/TestUtils.test'; import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine'; import { CellData } from 'common/buffer/CellData'; -import { IBufferService, IUnicodeService } from 'common/services/Services'; -import { Linkifier } from 'browser/Linkifier'; -import { MockLogService, MockUnicodeService } from 'common/TestUtils.test'; -import { IRegisteredLinkMatcher, IMouseZoneManager, IMouseZone } from 'browser/Types'; -import { IMarker, ITerminalOptions } from 'common/Types'; +import { MockUnicodeService } from 'common/TestUtils.test'; +import { IMouseZoneManager, IMouseZone } from 'browser/Types'; +import { IMarker } from 'common/Types'; const INIT_COLS = 80; const INIT_ROWS = 24; @@ -1044,105 +1042,6 @@ describe('Terminal', () => { }); }); - describe('Linkifier unicode handling', () => { - let terminal: TestTerminal; - let linkifier: TestLinkifier; - let mouseZoneManager: TestMouseZoneManager; - - // other than the tests above unicode testing needs the full terminal instance - // to get the special handling of fullwidth, surrogate and combining chars in the input handler - beforeEach(() => { - terminal = new TestTerminal({ cols: 10, rows: 5 }); - linkifier = new TestLinkifier((terminal as any)._bufferService, terminal.unicodeService); - mouseZoneManager = new TestMouseZoneManager(); - linkifier.attachToDom({} as any, mouseZoneManager); - }); - - function assertLinkifiesInTerminal(rowText: string, linkMatcherRegex: RegExp, links: { x1: number, y1: number, x2: number, y2: number }[]): Promise { - return new Promise(async r => { - await terminal.writeP(rowText); - linkifier.registerLinkMatcher(linkMatcherRegex, () => { }); - linkifier.linkifyRows(); - // Allow linkify to happen - setTimeout(() => { - assert.equal(mouseZoneManager.zones.length, links.length); - links.forEach((l, i) => { - assert.equal(mouseZoneManager.zones[i].x1, l.x1 + 1); - assert.equal(mouseZoneManager.zones[i].x2, l.x2 + 1); - assert.equal(mouseZoneManager.zones[i].y1, l.y1 + 1); - assert.equal(mouseZoneManager.zones[i].y2, l.y2 + 1); - }); - r(); - }, 0); - }); - } - - describe('unicode before the match', () => { - it('combining - match within one line', () => { - return assertLinkifiesInTerminal('e\u0301e\u0301e\u0301 foo', /foo/, [{ x1: 4, x2: 7, y1: 0, y2: 0 }]); - }); - it('combining - match over two lines', () => { - return assertLinkifiesInTerminal('e\u0301e\u0301e\u0301 foo', /foo/, [{ x1: 8, x2: 1, y1: 0, y2: 1 }]); - }); - it('surrogate - match within one line', () => { - return assertLinkifiesInTerminal('𝄞𝄞𝄞 foo', /foo/, [{ x1: 4, x2: 7, y1: 0, y2: 0 }]); - }); - it('surrogate - match over two lines', () => { - return assertLinkifiesInTerminal('𝄞𝄞𝄞 foo', /foo/, [{ x1: 8, x2: 1, y1: 0, y2: 1 }]); - }); - it('combining surrogate - match within one line', () => { - return assertLinkifiesInTerminal('𓂀\u0301𓂀\u0301𓂀\u0301 foo', /foo/, [{ x1: 4, x2: 7, y1: 0, y2: 0 }]); - }); - it('combining surrogate - match over two lines', () => { - return assertLinkifiesInTerminal('𓂀\u0301𓂀\u0301𓂀\u0301 foo', /foo/, [{ x1: 8, x2: 1, y1: 0, y2: 1 }]); - }); - it('fullwidth - match within one line', () => { - return assertLinkifiesInTerminal('12 foo', /foo/, [{ x1: 5, x2: 8, y1: 0, y2: 0 }]); - }); - it('fullwidth - match over two lines', () => { - return assertLinkifiesInTerminal('12 foo', /foo/, [{ x1: 8, x2: 1, y1: 0, y2: 1 }]); - }); - it('combining fullwidth - match within one line', () => { - return assertLinkifiesInTerminal('¥\u0301¥\u0301 foo', /foo/, [{ x1: 5, x2: 8, y1: 0, y2: 0 }]); - }); - it('combining fullwidth - match over two lines', () => { - return assertLinkifiesInTerminal('¥\u0301¥\u0301 foo', /foo/, [{ x1: 8, x2: 1, y1: 0, y2: 1 }]); - }); - }); - describe('unicode within the match', () => { - it('combining - match within one line', () => { - return assertLinkifiesInTerminal('test cafe\u0301', /cafe\u0301/, [{ x1: 5, x2: 9, y1: 0, y2: 0 }]); - }); - it('combining - match over two lines', () => { - return assertLinkifiesInTerminal('testtest cafe\u0301', /cafe\u0301/, [{ x1: 9, x2: 3, y1: 0, y2: 1 }]); - }); - it('surrogate - match within one line', () => { - return assertLinkifiesInTerminal('test a𝄞b', /a𝄞b/, [{ x1: 5, x2: 8, y1: 0, y2: 0 }]); - }); - it('surrogate - match over two lines', () => { - return assertLinkifiesInTerminal('testtest a𝄞b', /a𝄞b/, [{ x1: 9, x2: 2, y1: 0, y2: 1 }]); - }); - it('combining surrogate - match within one line', () => { - return assertLinkifiesInTerminal('test a𓂀\u0301b', /a𓂀\u0301b/, [{ x1: 5, x2: 8, y1: 0, y2: 0 }]); - }); - it('combining surrogate - match over two lines', () => { - return assertLinkifiesInTerminal('testtest a𓂀\u0301b', /a𓂀\u0301b/, [{ x1: 9, x2: 2, y1: 0, y2: 1 }]); - }); - it('fullwidth - match within one line', () => { - return assertLinkifiesInTerminal('test a1b', /a1b/, [{ x1: 5, x2: 9, y1: 0, y2: 0 }]); - }); - it('fullwidth - match over two lines', () => { - return assertLinkifiesInTerminal('testtest a1b', /a1b/, [{ x1: 9, x2: 3, y1: 0, y2: 1 }]); - }); - it('combining fullwidth - match within one line', () => { - return assertLinkifiesInTerminal('test a¥\u0301b', /a¥\u0301b/, [{ x1: 5, x2: 9, y1: 0, y2: 0 }]); - }); - it('combining fullwidth - match over two lines', () => { - return assertLinkifiesInTerminal('testtest a¥\u0301b', /a¥\u0301b/, [{ x1: 9, x2: 3, y1: 0, y2: 1 }]); - }); - }); - }); - describe('Buffer.stringIndexToBufferIndex', () => { let terminal: TestTerminal; @@ -1519,16 +1418,6 @@ describe('Terminal', () => { }); }); -class TestLinkifier extends Linkifier { - constructor(bufferService: IBufferService, unicodeService: IUnicodeService) { - super(bufferService, new MockLogService(), unicodeService); - Linkifier._timeBeforeLatency = 0; - } - - public get linkMatchers(): IRegisteredLinkMatcher[] { return this._linkMatchers; } - public linkifyRows(): void { super.linkifyRows(0, this._bufferService.buffer.lines.length - 1); } -} - class TestMouseZoneManager implements IMouseZoneManager { public dispose(): void { } diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index 76d5461f..202e4a80 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -21,7 +21,7 @@ * http://linux.die.net/man/7/urxvt */ -import { ICompositionHelper, ITerminal, IBrowser, CustomKeyEventHandler, ILinkifier, IMouseZoneManager, LinkMatcherHandler, ILinkMatcherOptions, IViewport, ILinkifier2, CharacterJoinerHandler } from 'browser/Types'; +import { ICompositionHelper, ITerminal, IBrowser, CustomKeyEventHandler, IMouseZoneManager, IViewport, ILinkifier2, CharacterJoinerHandler } from 'browser/Types'; import { IRenderer } from 'browser/renderer/Types'; import { CompositionHelper } from 'browser/input/CompositionHelper'; import { Viewport } from 'browser/Viewport'; @@ -29,7 +29,6 @@ import { rightClickHandler, moveTextAreaUnderMouseCursor, handlePasteEvent, copy import { C0, C1_ESCAPED } from 'common/data/EscapeSequences'; import { WindowsOptionsReportType } from '../common/InputHandler'; import { Renderer } from 'browser/renderer/Renderer'; -import { Linkifier } from 'browser/Linkifier'; import { SelectionService } from 'browser/services/SelectionService'; import * as Browser from 'common/Platform'; import { addDisposableDomListener } from 'browser/Lifecycle'; @@ -116,7 +115,6 @@ export class Terminal extends CoreTerminal implements ITerminal { */ private _unprocessedDeadKey: boolean = false; - public linkifier: ILinkifier; public linkifier2: ILinkifier2; public viewport: IViewport | undefined; private _compositionHelper: ICompositionHelper | undefined; @@ -166,7 +164,6 @@ export class Terminal extends CoreTerminal implements ITerminal { this._setup(); - this.linkifier = this._instantiationService.createInstance(Linkifier); this.linkifier2 = this.register(this._instantiationService.createInstance(Linkifier2)); this._decorationService = this._instantiationService.createInstance(DecorationService); this._instantiationService.setService(IDecorationService, this._decorationService); @@ -448,7 +445,6 @@ export class Terminal extends CoreTerminal implements ITerminal { this.register(addDisposableDomListener(this.textarea!, 'compositionend', () => this._compositionHelper!.compositionend())); this.register(addDisposableDomListener(this.textarea!, 'input', (ev: InputEvent) => this._inputEvent(ev), true)); this.register(this.onRender(() => this._compositionHelper!.updateCompositionElements())); - this.register(this.onRender(e => this._queueLinkification(e.start, e.end))); } /** @@ -583,7 +579,6 @@ export class Terminal extends CoreTerminal implements ITerminal { this._mouseZoneManager = this._instantiationService.createInstance(MouseZoneManager, this.element, this.screenElement); this.register(this._mouseZoneManager); this.register(this.onScroll(() => this._mouseZoneManager!.clearAll())); - this.linkifier.attachToDom(this.element, this._mouseZoneManager); this.linkifier2.attachToDom(this.screenElement, this._mouseService, this._renderService); this.register(this._instantiationService.createInstance(BufferDecorationRenderer, this.screenElement)); // This event listener must be registered aftre MouseZoneManager is created @@ -627,8 +622,8 @@ export class Terminal extends CoreTerminal implements ITerminal { private _createRenderer(): IRenderer { switch (this.options.rendererType) { - case 'canvas': return this._instantiationService.createInstance(Renderer, this._colorManager!.colors, this.screenElement!, this.linkifier, this.linkifier2); - case 'dom': return this._instantiationService.createInstance(DomRenderer, this._colorManager!.colors, this.element!, this.screenElement!, this._viewportElement!, this.linkifier, this.linkifier2); + case 'canvas': return this._instantiationService.createInstance(Renderer, this._colorManager!.colors, this.screenElement!, this.linkifier2); + case 'dom': return this._instantiationService.createInstance(DomRenderer, this._colorManager!.colors, this.element!, this.screenElement!, this._viewportElement!, this.linkifier2); default: throw new Error(`Unrecognized rendererType "${this.options.rendererType}"`); } } @@ -908,15 +903,6 @@ export class Terminal extends CoreTerminal implements ITerminal { this._renderService?.refreshRows(start, end); } - /** - * Queues linkification for the specified rows. - * @param start The row to start from (between 0 and this.rows - 1). - * @param end The row to end at (between start and this.rows - 1). - */ - private _queueLinkification(start: number, end: number): void { - this.linkifier?.linkifyRows(start, end); - } - /** * Change the cursor style for different selection modes */ @@ -960,32 +946,6 @@ export class Terminal extends CoreTerminal implements ITerminal { this._customKeyEventHandler = customKeyEventHandler; } - /** - * Registers a link matcher, allowing custom link patterns to be matched and - * handled. - * @param regex The regular expression to search for, specifically - * this searches the textContent of the rows. You will want to use \s to match - * a space ' ' character for example. - * @param handler The callback when the link is called. - * @param options Options for the link matcher. - * @return The ID of the new matcher, this can be used to deregister. - */ - public registerLinkMatcher(regex: RegExp, handler: LinkMatcherHandler, options?: ILinkMatcherOptions): number { - const matcherId = this.linkifier.registerLinkMatcher(regex, handler, options); - this.refresh(0, this.rows - 1); - return matcherId; - } - - /** - * Deregisters a link matcher if it has been registered. - * @param matcherId The link matcher's ID (returned after register) - */ - public deregisterLinkMatcher(matcherId: number): void { - if (this.linkifier.deregisterLinkMatcher(matcherId)) { - this.refresh(0, this.rows - 1); - } - } - public registerLinkProvider(linkProvider: ILinkProvider): IDisposable { return this.linkifier2.registerLinkProvider(linkProvider); } diff --git a/src/browser/TestUtils.test.ts b/src/browser/TestUtils.test.ts index 92f90a1d..62dc611e 100644 --- a/src/browser/TestUtils.test.ts +++ b/src/browser/TestUtils.test.ts @@ -7,7 +7,7 @@ import { IDisposable, IMarker, ISelectionPosition, ILinkProvider, IDecorationOpt import { IEvent, EventEmitter } from 'common/EventEmitter'; import { ICharacterJoinerService, ICharSizeService, IMouseService, IRenderService, ISelectionService } from 'browser/services/Services'; import { IRenderDimensions, IRenderer, IRequestRedrawEvent } from 'browser/renderer/Types'; -import { IColorSet, ILinkMatcherOptions, ITerminal, ILinkifier, ILinkifier2, IBrowser, IViewport, IColorManager, ICompositionHelper, CharacterJoinerHandler, IRenderDebouncer } from 'browser/Types'; +import { IColorSet, ITerminal, ILinkifier2, IBrowser, IViewport, IColorManager, ICompositionHelper, CharacterJoinerHandler, IRenderDebouncer } from 'browser/Types'; import { IBuffer, IBufferStringIterator, IBufferSet } from 'common/buffer/Types'; import { IBufferLine, ICellData, IAttributeData, ICircularList, XtermListener, ICharset, ITerminalOptions } from 'common/Types'; import { Buffer } from 'common/buffer/Buffer'; @@ -95,12 +95,6 @@ export class MockTerminal implements ITerminal { public registerOscHandler(ident: number, callback: (data: string) => boolean | Promise): IDisposable { throw new Error('Method not implemented.'); } - public registerLinkMatcher(regex: RegExp, handler: (event: MouseEvent, uri: string) => boolean | void, options?: ILinkMatcherOptions): number { - throw new Error('Method not implemented.'); - } - public deregisterLinkMatcher(matcherId: number): void { - throw new Error('Method not implemented.'); - } public registerLinkProvider(linkProvider: ILinkProvider): IDisposable { throw new Error('Method not implemented.'); } @@ -148,7 +142,6 @@ export class MockTerminal implements ITerminal { } public bracketedPasteMode!: boolean; public renderer!: IRenderer; - public linkifier!: ILinkifier; public linkifier2!: ILinkifier2; public isFocused!: boolean; public options: ITerminalOptions = {}; diff --git a/src/browser/Types.d.ts b/src/browser/Types.d.ts index 41992a8b..e328924a 100644 --- a/src/browser/Types.d.ts +++ b/src/browser/Types.d.ts @@ -17,7 +17,6 @@ export interface ITerminal extends IPublicTerminal, ICoreTerminal { buffer: IBuffer; viewport: IViewport | undefined; options: ITerminalOptions; - linkifier: ILinkifier; linkifier2: ILinkifier2; onBlur: IEvent; @@ -56,8 +55,6 @@ export interface IPublicTerminal extends IDisposable { registerDcsHandler(id: IFunctionIdentifier, callback: (data: string, param: IParams) => boolean | Promise): IDisposable; registerEscHandler(id: IFunctionIdentifier, callback: () => boolean | Promise): IDisposable; registerOscHandler(ident: number, callback: (data: string) => boolean | Promise): IDisposable; - registerLinkMatcher(regex: RegExp, handler: (event: MouseEvent, uri: string) => void, options?: ILinkMatcherOptions): number; - deregisterLinkMatcher(matcherId: number): void; registerLinkProvider(linkProvider: ILinkProvider): IDisposable; registerCharacterJoiner(handler: (text: string) => [number, number][]): number; deregisterCharacterJoiner(joinerId: number): void; @@ -153,36 +150,6 @@ export interface IViewport extends IDisposable { onThemeChange(colors: IColorSet): void; } -export interface IViewportRange { - start: IViewportRangePosition; - end: IViewportRangePosition; -} - -export interface IViewportRangePosition { - x: number; - y: number; -} - -export type LinkMatcherHandler = (event: MouseEvent, uri: string) => void; -export type LinkMatcherHoverTooltipCallback = (event: MouseEvent, uri: string, position: IViewportRange) => void; -export type LinkMatcherValidationCallback = (uri: string, callback: (isValid: boolean) => void) => void; - -export interface ILinkMatcher { - id: number; - regex: RegExp; - handler: LinkMatcherHandler; - hoverTooltipCallback?: LinkMatcherHoverTooltipCallback; - hoverLeaveCallback?: () => void; - matchIndex?: number; - validationCallback?: LinkMatcherValidationCallback; - priority?: number; - willLinkActivate?: (event: MouseEvent, uri: string) => boolean; -} - -export interface IRegisteredLinkMatcher extends ILinkMatcher { - priority: number; -} - export interface ILinkifierEvent { x1: number; y1: number; @@ -192,17 +159,6 @@ export interface ILinkifierEvent { fg: number | undefined; } -export interface ILinkifier { - onShowLinkUnderline: IEvent; - onHideLinkUnderline: IEvent; - onLinkTooltip: IEvent; - - attachToDom(element: HTMLElement, mouseZoneManager: IMouseZoneManager): void; - linkifyRows(start: number, end: number): void; - registerLinkMatcher(regex: RegExp, handler: LinkMatcherHandler, options?: ILinkMatcherOptions): number; - deregisterLinkMatcher(matcherId: number): boolean; -} - interface ILinkState { decorations: ILinkDecorations; isHovered: boolean; @@ -221,40 +177,6 @@ export interface ILinkifier2 { registerLinkProvider(linkProvider: ILinkProvider): IDisposable; } -export interface ILinkMatcherOptions { - /** - * The index of the link from the regex.match(text) call. This defaults to 0 - * (for regular expressions without capture groups). - */ - matchIndex?: number; - /** - * A callback that validates an individual link, returning true if valid and - * false if invalid. - */ - validationCallback?: LinkMatcherValidationCallback; - /** - * A callback that fires when the mouse hovers over a link. - */ - tooltipCallback?: LinkMatcherHoverTooltipCallback; - /** - * A callback that fires when the mouse leaves a link that was hovered. - */ - leaveCallback?: () => void; - /** - * The priority of the link matcher, this defines the order in which the link - * matcher is evaluated relative to others, from highest to lowest. The - * default value is 0. - */ - priority?: number; - /** - * A callback that fires when the mousedown and click events occur that - * determines whether a link will be activated upon click. This enables - * only activating a link when a certain modifier is held down, if not the - * mouse event will continue propagation (eg. double click to select word). - */ - willLinkActivate?: (event: MouseEvent, uri: string) => boolean; -} - export interface IMouseZoneManager extends IDisposable { add(zone: IMouseZone): void; clearAll(start?: number, end?: number): void; diff --git a/src/browser/public/Terminal.ts b/src/browser/public/Terminal.ts index 4b7f2725..6898af36 100644 --- a/src/browser/public/Terminal.ts +++ b/src/browser/public/Terminal.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { Terminal as ITerminalApi, IMarker, IDisposable, ILinkMatcherOptions, ITheme, ILocalizableStrings, ITerminalAddon, ISelectionPosition, IBufferNamespace as IBufferNamespaceApi, IParser, ILinkProvider, IUnicodeHandling, FontWeight, IModes, IDecorationOptions, IDecoration } from 'xterm'; +import { Terminal as ITerminalApi, IMarker, IDisposable, ITheme, ILocalizableStrings, ITerminalAddon, ISelectionPosition, IBufferNamespace as IBufferNamespaceApi, IParser, ILinkProvider, IUnicodeHandling, FontWeight, IModes, IDecorationOptions, IDecoration } from 'xterm'; import { ITerminal } from 'browser/Types'; import { Terminal as TerminalCore } from 'browser/Terminal'; import * as Strings from 'browser/LocalizableStrings'; @@ -147,14 +147,6 @@ export class Terminal implements ITerminalApi { public attachCustomKeyEventHandler(customKeyEventHandler: (event: KeyboardEvent) => boolean): void { this._core.attachCustomKeyEventHandler(customKeyEventHandler); } - public registerLinkMatcher(regex: RegExp, handler: (event: MouseEvent, uri: string) => void, options?: ILinkMatcherOptions): number { - this._checkProposedApi(); - return this._core.registerLinkMatcher(regex, handler, options); - } - public deregisterLinkMatcher(matcherId: number): void { - this._checkProposedApi(); - this._core.deregisterLinkMatcher(matcherId); - } public registerLinkProvider(linkProvider: ILinkProvider): IDisposable { this._checkProposedApi(); return this._core.registerLinkProvider(linkProvider); diff --git a/src/browser/renderer/LinkRenderLayer.ts b/src/browser/renderer/LinkRenderLayer.ts index 15086d9a..f1a9b13e 100644 --- a/src/browser/renderer/LinkRenderLayer.ts +++ b/src/browser/renderer/LinkRenderLayer.ts @@ -7,7 +7,7 @@ import { IRenderDimensions } from 'browser/renderer/Types'; import { BaseRenderLayer } from './BaseRenderLayer'; import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants'; import { is256Color } from 'browser/renderer/atlas/CharAtlasUtils'; -import { IColorSet, ILinkifierEvent, ILinkifier, ILinkifier2 } from 'browser/Types'; +import { IColorSet, ILinkifierEvent, ILinkifier2 } from 'browser/Types'; import { IBufferService, IDecorationService, IOptionsService } from 'common/services/Services'; export class LinkRenderLayer extends BaseRenderLayer { @@ -18,15 +18,12 @@ export class LinkRenderLayer extends BaseRenderLayer { zIndex: number, colors: IColorSet, rendererId: number, - linkifier: ILinkifier, linkifier2: ILinkifier2, @IBufferService bufferService: IBufferService, @IOptionsService optionsService: IOptionsService, @IDecorationService decorationService: IDecorationService ) { super(container, 'link', zIndex, true, colors, rendererId, bufferService, optionsService, decorationService); - linkifier.onShowLinkUnderline(e => this._onShowLinkUnderline(e)); - linkifier.onHideLinkUnderline(e => this._onHideLinkUnderline(e)); linkifier2.onShowLinkUnderline(e => this._onShowLinkUnderline(e)); linkifier2.onHideLinkUnderline(e => this._onHideLinkUnderline(e)); diff --git a/src/browser/renderer/Renderer.ts b/src/browser/renderer/Renderer.ts index 8bc32278..66597931 100644 --- a/src/browser/renderer/Renderer.ts +++ b/src/browser/renderer/Renderer.ts @@ -9,7 +9,7 @@ import { CursorRenderLayer } from 'browser/renderer/CursorRenderLayer'; import { IRenderLayer, IRenderer, IRenderDimensions, IRequestRedrawEvent } from 'browser/renderer/Types'; import { LinkRenderLayer } from 'browser/renderer/LinkRenderLayer'; import { Disposable } from 'common/Lifecycle'; -import { IColorSet, ILinkifier, ILinkifier2 } from 'browser/Types'; +import { IColorSet, ILinkifier2 } from 'browser/Types'; import { ICharSizeService } from 'browser/services/Services'; import { IBufferService, IOptionsService, IInstantiationService } from 'common/services/Services'; import { removeTerminalFromCache } from 'browser/renderer/atlas/CharAtlasCache'; @@ -31,7 +31,6 @@ export class Renderer extends Disposable implements IRenderer { constructor( private _colors: IColorSet, private readonly _screenElement: HTMLElement, - linkifier: ILinkifier, linkifier2: ILinkifier2, @IInstantiationService instantiationService: IInstantiationService, @IBufferService private readonly _bufferService: IBufferService, @@ -43,7 +42,7 @@ export class Renderer extends Disposable implements IRenderer { this._renderLayers = [ instantiationService.createInstance(TextRenderLayer, this._screenElement, 0, this._colors, allowTransparency, this._id), instantiationService.createInstance(SelectionRenderLayer, this._screenElement, 1, this._colors, this._id), - instantiationService.createInstance(LinkRenderLayer, this._screenElement, 2, this._colors, this._id, linkifier, linkifier2), + instantiationService.createInstance(LinkRenderLayer, this._screenElement, 2, this._colors, this._id, linkifier2), instantiationService.createInstance(CursorRenderLayer, this._screenElement, 3, this._colors, this._id, this._onRequestRedraw) ]; this.dimensions = { diff --git a/src/browser/renderer/dom/DomRenderer.ts b/src/browser/renderer/dom/DomRenderer.ts index 248f37ac..a4500e39 100644 --- a/src/browser/renderer/dom/DomRenderer.ts +++ b/src/browser/renderer/dom/DomRenderer.ts @@ -7,7 +7,7 @@ import { IRenderer, IRenderDimensions, IRequestRedrawEvent } from 'browser/rende import { BOLD_CLASS, ITALIC_CLASS, CURSOR_CLASS, CURSOR_STYLE_BLOCK_CLASS, CURSOR_BLINK_CLASS, CURSOR_STYLE_BAR_CLASS, CURSOR_STYLE_UNDERLINE_CLASS, DomRendererRowFactory } from 'browser/renderer/dom/DomRendererRowFactory'; import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants'; import { Disposable } from 'common/Lifecycle'; -import { IColorSet, ILinkifierEvent, ILinkifier, ILinkifier2 } from 'browser/Types'; +import { IColorSet, ILinkifierEvent, ILinkifier2 } from 'browser/Types'; import { ICharSizeService } from 'browser/services/Services'; import { IOptionsService, IBufferService, IInstantiationService, IDecorationService } from 'common/services/Services'; import { EventEmitter, IEvent } from 'common/EventEmitter'; @@ -47,7 +47,6 @@ export class DomRenderer extends Disposable implements IRenderer { private readonly _element: HTMLElement, private readonly _screenElement: HTMLElement, private readonly _viewportElement: HTMLElement, - private readonly _linkifier: ILinkifier, private readonly _linkifier2: ILinkifier2, @IInstantiationService instantiationService: IInstantiationService, @ICharSizeService private readonly _charSizeService: ICharSizeService, @@ -87,9 +86,6 @@ export class DomRenderer extends Disposable implements IRenderer { this._screenElement.appendChild(this._rowContainer); this._screenElement.appendChild(this._selectionContainer); - this.register(this._linkifier.onShowLinkUnderline(e => this._onLinkHover(e))); - this.register(this._linkifier.onHideLinkUnderline(e => this._onLinkLeave(e))); - this.register(this._linkifier2.onShowLinkUnderline(e => this._onLinkHover(e))); this.register(this._linkifier2.onHideLinkUnderline(e => this._onLinkLeave(e))); } diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index e8a2f8f4..b6bb0fda 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -316,50 +316,6 @@ declare module 'xterm' { extendedAnsi?: string[]; } - /** - * An object containing options for a link matcher. - */ - export interface ILinkMatcherOptions { - /** - * The index of the link from the regex.match(text) call. This defaults to 0 - * (for regular expressions without capture groups). - */ - matchIndex?: number; - - /** - * A callback that validates whether to create an individual link, pass - * whether the link is valid to the callback. - */ - validationCallback?: (uri: string, callback: (isValid: boolean) => void) => void; - - /** - * A callback that fires when the mouse hovers over a link for a period of - * time (defined by {@link ITerminalOptions.linkTooltipHoverDuration}). - */ - tooltipCallback?: (event: MouseEvent, uri: string, location: IViewportRange) => boolean | void; - - /** - * A callback that fires when the mouse leaves a link. Note that this can - * happen even when tooltipCallback hasn't fired for the link yet. - */ - leaveCallback?: () => void; - - /** - * The priority of the link matcher, this defines the order in which the - * link matcher is evaluated relative to others, from highest to lowest. The - * default value is 0. - */ - priority?: number; - - /** - * A callback that fires when the mousedown and click events occur that - * determines whether a link will be activated upon click. This enables - * only activating a link when a certain modifier is held down, if not the - * mouse event will continue propagation (eg. double click to select word). - */ - willLinkActivate?: (event: MouseEvent, uri: string) => boolean; - } - /** * An object that can be disposed via a dispose function. */ @@ -909,28 +865,6 @@ declare module 'xterm' { */ attachCustomKeyEventHandler(customKeyEventHandler: (event: KeyboardEvent) => boolean): void; - /** - * (EXPERIMENTAL) Registers a link matcher, allowing custom link patterns to - * be matched and handled. - * @deprecated The link matcher API is now deprecated in favor of the link - * provider API, see `registerLinkProvider`. - * @param regex The regular expression to search for, specifically this - * searches the textContent of the rows. You will want to use \s to match a - * space ' ' character for example. - * @param handler The callback when the link is called. - * @param options Options for the link matcher. - * @return The ID of the new matcher, this can be used to deregister. - */ - registerLinkMatcher(regex: RegExp, handler: (event: MouseEvent, uri: string) => void, options?: ILinkMatcherOptions): number; - - /** - * (EXPERIMENTAL) Deregisters a link matcher if it has been registered. - * @deprecated The link matcher API is now deprecated in favor of the link - * provider API, see `registerLinkProvider`. - * @param matcherId The link matcher's ID (returned after register) - */ - deregisterLinkMatcher(matcherId: number): void; - /** * Registers a link provider, allowing a custom parser to be used to match * and handle links. Multiple link providers can be used, they will be asked @@ -1274,7 +1208,7 @@ declare module 'xterm' { /** * An object representing a range within the viewport of the terminal. */ - export interface IViewportRange { + export interface IViewportRange { /** * The start of the range. */ From 238a2bafb14f3f259dc02b3dfaf37e3529c907bd Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 27 Jul 2022 08:20:54 -0700 Subject: [PATCH 20/42] Set allowProposedApi default to false Fixes #2826 --- .../src/SerializeAddon.test.ts | 2 +- demo/client.ts | 1 + src/common/services/OptionsService.ts | 2 +- src/headless/public/Terminal.test.ts | 22 +++++++++---------- test/api/Terminal.api.ts | 6 ++--- test/api/TestUtils.ts | 2 +- 6 files changed, 18 insertions(+), 17 deletions(-) diff --git a/addons/xterm-addon-serialize/src/SerializeAddon.test.ts b/addons/xterm-addon-serialize/src/SerializeAddon.test.ts index 67b83884..05f2c61c 100644 --- a/addons/xterm-addon-serialize/src/SerializeAddon.test.ts +++ b/addons/xterm-addon-serialize/src/SerializeAddon.test.ts @@ -74,7 +74,7 @@ describe('xterm-addon-serialize', () => { } }); - terminal = new Terminal({ cols: 10, rows: 2 }); + terminal = new Terminal({ cols: 10, rows: 2, allowProposedApi: true }); terminal.loadAddon(serializeAddon); selectionService = new TestSelectionService((terminal as any)._core._bufferService); diff --git a/demo/client.ts b/demo/client.ts index 66ba7bd7..adbbd85a 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -194,6 +194,7 @@ function createTerminal(): void { const isWindows = ['Windows', 'Win16', 'Win32', 'WinCE'].indexOf(navigator.platform) >= 0; term = new Terminal({ + allowProposedApi: true, allowTransparency: true, windowsMode: isWindows, fontFamily: 'Fira Code, courier-new, courier, monospace', diff --git a/src/common/services/OptionsService.ts b/src/common/services/OptionsService.ts index a8c4ff75..4889f120 100644 --- a/src/common/services/OptionsService.ts +++ b/src/common/services/OptionsService.ts @@ -33,7 +33,7 @@ export const DEFAULT_OPTIONS: Readonly = { macOptionClickForcesSelection: false, minimumContrastRatio: 1, disableStdin: false, - allowProposedApi: true, + allowProposedApi: false, allowTransparency: false, tabStopWidth: 8, theme: {}, diff --git a/src/headless/public/Terminal.test.ts b/src/headless/public/Terminal.test.ts index a65d8775..403cf35a 100644 --- a/src/headless/public/Terminal.test.ts +++ b/src/headless/public/Terminal.test.ts @@ -12,7 +12,7 @@ let term: Terminal; describe('Headless API Tests', function (): void { beforeEach(() => { // Create default terminal to be used by most tests - term = new Terminal(); + term = new Terminal({ allowProposedApi: true }); }); it('Default options', async () => { @@ -102,7 +102,7 @@ describe('Headless API Tests', function (): void { }); it('clear', async () => { - term = new Terminal({ rows: 5 }); + term = new Terminal({ rows: 5, allowProposedApi: true }); for (let i = 0; i < 10; i++) { await writeSync('\n\rtest' + i); } @@ -254,7 +254,7 @@ describe('Headless API Tests', function (): void { describe('buffer', () => { it('cursorX, cursorY', async () => { - term = new Terminal({ rows: 5, cols: 5 }); + term = new Terminal({ rows: 5, cols: 5, allowProposedApi: true }); strictEqual(term.buffer.active.cursorX, 0); strictEqual(term.buffer.active.cursorY, 0); await writeSync('foo'); @@ -275,7 +275,7 @@ describe('Headless API Tests', function (): void { }); it('viewportY', async () => { - term = new Terminal({ rows: 5 }); + term = new Terminal({ rows: 5, allowProposedApi: true }); strictEqual(term.buffer.active.viewportY, 0); await writeSync('\n\n\n\n'); strictEqual(term.buffer.active.viewportY, 0); @@ -290,7 +290,7 @@ describe('Headless API Tests', function (): void { }); it('baseY', async () => { - term = new Terminal({ rows: 5 }); + term = new Terminal({ rows: 5, allowProposedApi: true }); strictEqual(term.buffer.active.baseY, 0); await writeSync('\n\n\n\n'); strictEqual(term.buffer.active.baseY, 0); @@ -305,7 +305,7 @@ describe('Headless API Tests', function (): void { }); it('length', async () => { - term = new Terminal({ rows: 5 }); + term = new Terminal({ rows: 5, allowProposedApi: true }); strictEqual(term.buffer.active.length, 5); await writeSync('\n\n\n\n'); strictEqual(term.buffer.active.length, 5); @@ -317,13 +317,13 @@ describe('Headless API Tests', function (): void { describe('getLine', () => { it('invalid index', async () => { - term = new Terminal({ rows: 5 }); + term = new Terminal({ rows: 5, allowProposedApi: true }); strictEqual(term.buffer.active.getLine(-1), undefined); strictEqual(term.buffer.active.getLine(5), undefined); }); it('isWrapped', async () => { - term = new Terminal({ cols: 5 }); + term = new Terminal({ cols: 5, allowProposedApi: true }); strictEqual(term.buffer.active.getLine(0)!.isWrapped, false); strictEqual(term.buffer.active.getLine(1)!.isWrapped, false); await writeSync('abcde'); @@ -335,7 +335,7 @@ describe('Headless API Tests', function (): void { }); it('translateToString', async () => { - term = new Terminal({ cols: 5 }); + term = new Terminal({ cols: 5, allowProposedApi: true }); strictEqual(term.buffer.active.getLine(0)!.translateToString(), ' '); strictEqual(term.buffer.active.getLine(0)!.translateToString(true), ''); await writeSync('foo'); @@ -350,7 +350,7 @@ describe('Headless API Tests', function (): void { }); it('getCell', async () => { - term = new Terminal({ cols: 5 }); + term = new Terminal({ cols: 5, allowProposedApi: true }); strictEqual(term.buffer.active.getLine(0)!.getCell(-1), undefined); strictEqual(term.buffer.active.getLine(0)!.getCell(5), undefined); strictEqual(term.buffer.active.getLine(0)!.getCell(0)!.getChars(), ''); @@ -366,7 +366,7 @@ describe('Headless API Tests', function (): void { }); it('active, normal, alternate', async () => { - term = new Terminal({ cols: 5 }); + term = new Terminal({ cols: 5, allowProposedApi: true }); strictEqual(term.buffer.active.type, 'normal'); strictEqual(term.buffer.normal.type, 'normal'); strictEqual(term.buffer.alternate.type, 'alternate'); diff --git a/test/api/Terminal.api.ts b/test/api/Terminal.api.ts index 3a023d4a..0e1f7764 100644 --- a/test/api/Terminal.api.ts +++ b/test/api/Terminal.api.ts @@ -734,7 +734,7 @@ describe('API Integration Tests', function(): void { describe('registerDecoration', () => { describe('bufferDecorations', () => { it('should register decorations and render them when terminal open is called', async () => { - await page.evaluate(`window.term = new Terminal({})`); + await page.evaluate(`window.term = new Terminal({ allowProposedApi: true })`); await page.evaluate(`window.term.open(document.querySelector('#terminal-container'))`); await page.waitForSelector('.xterm-text-layer'); await page.evaluate(`window.marker1 = window.term.addMarker(1)`); @@ -765,7 +765,7 @@ describe('API Integration Tests', function(): void { }); describe('overviewRulerDecorations', () => { it('should not add an overview ruler when width is not set', async () => { - await page.evaluate(`window.term = new Terminal({})`); + await page.evaluate(`window.term = new Terminal({ allowProposedApi: true })`); await page.evaluate(`window.term.open(document.querySelector('#terminal-container'))`); await page.waitForSelector('.xterm-text-layer'); await page.evaluate(`window.marker1 = window.term.addMarker(1)`); @@ -776,7 +776,7 @@ describe('API Integration Tests', function(): void { await pollFor(page, `document.querySelectorAll('.xterm-decoration-overview-ruler').length`, 0); }); it('should add an overview ruler when width is set', async () => { - await page.evaluate(`window.term = new Terminal({ overviewRulerWidth: 15 })`); + await page.evaluate(`window.term = new Terminal({ allowProposedApi: true, overviewRulerWidth: 15 })`); await page.evaluate(`window.term.open(document.querySelector('#terminal-container'))`); await page.waitForSelector('.xterm-text-layer'); await page.evaluate(`window.marker1 = window.term.addMarker(1)`); diff --git a/test/api/TestUtils.ts b/test/api/TestUtils.ts index 2acc4d09..e059c92c 100644 --- a/test/api/TestUtils.ts +++ b/test/api/TestUtils.ts @@ -44,7 +44,7 @@ export async function timeout(ms: number): Promise { } export async function openTerminal(page: playwright.Page, options: ITerminalOptions = {}): Promise { - await page.evaluate(`window.term = new Terminal(${JSON.stringify(options)})`); + await page.evaluate(`window.term = new Terminal(${JSON.stringify({ allowProposedApi: true, ...options })})`); await page.evaluate(`window.term.open(document.querySelector('#terminal-container'))`); if (options.rendererType === 'dom') { await page.waitForSelector('.xterm-rows'); From 8cfa7a0ee22c29cade0064ee28286e61aefaf331 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 27 Jul 2022 08:35:40 -0700 Subject: [PATCH 21/42] Remove deprecated add* APIs Fixes #2652 --- demo/client.ts | 18 +++++++++--------- src/browser/public/Terminal.ts | 4 ---- typings/xterm-headless.d.ts | 9 ++------- typings/xterm.d.ts | 9 ++------- 4 files changed, 13 insertions(+), 27 deletions(-) diff --git a/demo/client.ts b/demo/client.ts index adbbd85a..b2468ec3 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -736,7 +736,7 @@ function powerlineSymbolTest() { function addDecoration() { term.options['overviewRulerWidth'] = 15; - const marker = term.addMarker(1); + const marker = term.registerMarker(1); const decoration = term.registerDecoration({ marker, backgroundColor: '#00FF00', @@ -751,13 +751,13 @@ function addDecoration() { function addOverviewRuler() { term.options['overviewRulerWidth'] = 15; - term.registerDecoration({marker: term.addMarker(1), overviewRulerOptions: { color: '#ef2929' }}); - term.registerDecoration({marker: term.addMarker(3), overviewRulerOptions: { color: '#8ae234' }}); - term.registerDecoration({marker: term.addMarker(5), overviewRulerOptions: { color: '#729fcf' }}); - term.registerDecoration({marker: term.addMarker(7), overviewRulerOptions: { color: '#ef2929', position: 'left' }}); - term.registerDecoration({marker: term.addMarker(7), overviewRulerOptions: { color: '#8ae234', position: 'center' }}); - term.registerDecoration({marker: term.addMarker(7), overviewRulerOptions: { color: '#729fcf', position: 'right' }}); - term.registerDecoration({marker: term.addMarker(10), overviewRulerOptions: { color: '#8ae234', position: 'center' }}); - term.registerDecoration({marker: term.addMarker(10), overviewRulerOptions: { color: '#ffffff80', position: 'full' }}); + term.registerDecoration({marker: term.registerMarker(1), overviewRulerOptions: { color: '#ef2929' }}); + term.registerDecoration({marker: term.registerMarker(3), overviewRulerOptions: { color: '#8ae234' }}); + term.registerDecoration({marker: term.registerMarker(5), overviewRulerOptions: { color: '#729fcf' }}); + term.registerDecoration({marker: term.registerMarker(7), overviewRulerOptions: { color: '#ef2929', position: 'left' }}); + term.registerDecoration({marker: term.registerMarker(7), overviewRulerOptions: { color: '#8ae234', position: 'center' }}); + term.registerDecoration({marker: term.registerMarker(7), overviewRulerOptions: { color: '#729fcf', position: 'right' }}); + term.registerDecoration({marker: term.registerMarker(10), overviewRulerOptions: { color: '#8ae234', position: 'center' }}); + term.registerDecoration({marker: term.registerMarker(10), overviewRulerOptions: { color: '#ffffff80', position: 'full' }}); } diff --git a/src/browser/public/Terminal.ts b/src/browser/public/Terminal.ts index 6898af36..53904cf9 100644 --- a/src/browser/public/Terminal.ts +++ b/src/browser/public/Terminal.ts @@ -160,7 +160,6 @@ export class Terminal implements ITerminalApi { this._core.deregisterCharacterJoiner(joinerId); } public registerMarker(cursorYOffset: number = 0): IMarker | undefined { - this._checkProposedApi(); this._verifyIntegers(cursorYOffset); return this._core.addMarker(cursorYOffset); } @@ -169,9 +168,6 @@ export class Terminal implements ITerminalApi { this._verifyPositiveIntegers(decorationOptions.x ?? 0, decorationOptions.width ?? 0, decorationOptions.height ?? 0); return this._core.registerDecoration(decorationOptions); } - public addMarker(cursorYOffset: number): IMarker | undefined { - return this.registerMarker(cursorYOffset); - } public hasSelection(): boolean { return this._core.hasSelection(); } diff --git a/typings/xterm-headless.d.ts b/typings/xterm-headless.d.ts index c840ed09..93029c6f 100644 --- a/typings/xterm-headless.d.ts +++ b/typings/xterm-headless.d.ts @@ -630,18 +630,13 @@ declare module 'xterm-headless' { resize(columns: number, rows: number): void; /** - * (EXPERIMENTAL) Adds a marker to the normal buffer and returns it. If the - * alt buffer is active, undefined is returned. + * Adds a marker to the normal buffer and returns it. If the alt buffer is + * active, undefined is returned. * @param cursorYOffset The y position offset of the marker from the cursor. * @returns The new marker or undefined. */ registerMarker(cursorYOffset?: number): IMarker | undefined; - /** - * @deprecated use `registerMarker` instead. - */ - addMarker(cursorYOffset: number): IMarker | undefined; - /* * Disposes of the terminal, detaching it from the DOM and removing any * active listeners. diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index b6bb0fda..adfc3f54 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -912,18 +912,13 @@ declare module 'xterm' { deregisterCharacterJoiner(joinerId: number): void; /** - * (EXPERIMENTAL) Adds a marker to the normal buffer and returns it. If the - * alt buffer is active, undefined is returned. + * Adds a marker to the normal buffer and returns it. If the alt buffer is + * active, undefined is returned. * @param cursorYOffset The y position offset of the marker from the cursor. * @returns The new marker or undefined. */ registerMarker(cursorYOffset?: number): IMarker | undefined; - /** - * @deprecated use `registerMarker` instead. - */ - addMarker(cursorYOffset: number): IMarker | undefined; - /** * (EXPERIMENTAL) Adds a decoration to the terminal using * @param decorationOptions, which takes a marker and an optional anchor, From 7bd3801f582a26206389f5d8d640ec95441a687c Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 27 Jul 2022 08:41:27 -0700 Subject: [PATCH 22/42] Remove mouse zone manager Part of #2703 --- src/browser/MouseZoneManager.ts | 236 -------------------------- src/browser/Terminal.test.ts | 14 -- src/browser/Terminal.ts | 8 +- src/browser/Types.d.ts | 17 -- src/common/services/OptionsService.ts | 1 - src/common/services/Services.ts | 1 - typings/xterm.d.ts | 7 - 7 files changed, 1 insertion(+), 283 deletions(-) delete mode 100644 src/browser/MouseZoneManager.ts diff --git a/src/browser/MouseZoneManager.ts b/src/browser/MouseZoneManager.ts deleted file mode 100644 index 71ffe7c5..00000000 --- a/src/browser/MouseZoneManager.ts +++ /dev/null @@ -1,236 +0,0 @@ -/** - * Copyright (c) 2017 The xterm.js authors. All rights reserved. - * @license MIT - */ - -import { Disposable } from 'common/Lifecycle'; -import { addDisposableDomListener } from 'browser/Lifecycle'; -import { IMouseService, ISelectionService } from 'browser/services/Services'; -import { IMouseZoneManager, IMouseZone } from 'browser/Types'; -import { IBufferService, IOptionsService } from 'common/services/Services'; - -/** - * The MouseZoneManager allows components to register zones within the terminal - * that trigger hover and click callbacks. - * - * This class was intentionally made not so robust initially as the only case it - * needed to support was single-line links which never overlap. Improvements can - * be made in the future. - */ -export class MouseZoneManager extends Disposable implements IMouseZoneManager { - private _zones: IMouseZone[] = []; - - private _areZonesActive: boolean = false; - private _mouseMoveListener: (e: MouseEvent) => any; - private _mouseLeaveListener: (e: MouseEvent) => any; - private _clickListener: (e: MouseEvent) => any; - - private _tooltipTimeout: number | undefined; - private _currentZone: IMouseZone | undefined; - private _lastHoverCoords: [number | undefined, number | undefined] = [undefined, undefined]; - private _initialSelectionLength: number = 0; - - constructor( - private readonly _element: HTMLElement, - private readonly _screenElement: HTMLElement, - @IBufferService private readonly _bufferService: IBufferService, - @IMouseService private readonly _mouseService: IMouseService, - @ISelectionService private readonly _selectionService: ISelectionService, - @IOptionsService private readonly _optionsService: IOptionsService - ) { - super(); - - this.register(addDisposableDomListener(this._element, 'mousedown', e => this._onMouseDown(e))); - - // These events are expensive, only listen to it when mouse zones are active - this._mouseMoveListener = e => this._onMouseMove(e); - this._mouseLeaveListener = e => this._onMouseLeave(e); - this._clickListener = e => this._onClick(e); - } - - public dispose(): void { - super.dispose(); - this._deactivate(); - } - - public add(zone: IMouseZone): void { - this._zones.push(zone); - if (this._zones.length === 1) { - this._activate(); - } - } - - public clearAll(start?: number, end?: number): void { - // Exit if there's nothing to clear - if (this._zones.length === 0) { - return; - } - - // Clear all if start/end weren't set - if (!start || !end) { - start = 0; - end = this._bufferService.rows - 1; - } - - // Iterate through zones and clear them out if they're within the range - for (let i = 0; i < this._zones.length; i++) { - const zone = this._zones[i]; - if ((zone.y1 > start && zone.y1 <= end + 1) || - (zone.y2 > start && zone.y2 <= end + 1) || - (zone.y1 < start && zone.y2 > end + 1)) { - if (this._currentZone && this._currentZone === zone) { - this._currentZone.leaveCallback(); - this._currentZone = undefined; - } - this._zones.splice(i--, 1); - } - } - - // Deactivate the mouse zone manager if all the zones have been removed - if (this._zones.length === 0) { - this._deactivate(); - } - } - - private _activate(): void { - if (!this._areZonesActive) { - this._areZonesActive = true; - this._element.addEventListener('mousemove', this._mouseMoveListener); - this._element.addEventListener('mouseleave', this._mouseLeaveListener); - this._element.addEventListener('click', this._clickListener); - } - } - - private _deactivate(): void { - if (this._areZonesActive) { - this._areZonesActive = false; - this._element.removeEventListener('mousemove', this._mouseMoveListener); - this._element.removeEventListener('mouseleave', this._mouseLeaveListener); - this._element.removeEventListener('click', this._clickListener); - } - } - - private _onMouseMove(e: MouseEvent): void { - // TODO: Ideally this would only clear the hover state when the mouse moves - // outside of the mouse zone - if (this._lastHoverCoords[0] !== e.pageX || this._lastHoverCoords[1] !== e.pageY) { - this._onHover(e); - // Record the current coordinates - this._lastHoverCoords = [e.pageX, e.pageY]; - } - } - - private _onHover(e: MouseEvent): void { - const zone = this._findZoneEventAt(e); - - // Do nothing if the zone is the same - if (zone === this._currentZone) { - return; - } - - // Fire the hover end callback and cancel any existing timer if a new zone - // is being hovered - if (this._currentZone) { - this._currentZone.leaveCallback(); - this._currentZone = undefined; - if (this._tooltipTimeout) { - clearTimeout(this._tooltipTimeout); - } - } - - // Exit if there is not zone - if (!zone) { - return; - } - this._currentZone = zone; - - // Trigger the hover callback - if (zone.hoverCallback) { - zone.hoverCallback(e); - } - - // Restart the tooltip timeout - this._tooltipTimeout = window.setTimeout(() => this._onTooltip(e), this._optionsService.rawOptions.linkTooltipHoverDuration); - } - - private _onTooltip(e: MouseEvent): void { - this._tooltipTimeout = undefined; - const zone = this._findZoneEventAt(e); - zone?.tooltipCallback(e); - } - - private _onMouseDown(e: MouseEvent): void { - // Store current terminal selection length, to check if we're performing - // a selection operation - this._initialSelectionLength = this._getSelectionLength(); - - // Ignore the event if there are no zones active - if (!this._areZonesActive) { - return; - } - - // Find the active zone, prevent event propagation if found to prevent other - // components from handling the mouse event. - const zone = this._findZoneEventAt(e); - if (zone?.willLinkActivate(e)) { - e.preventDefault(); - e.stopImmediatePropagation(); - } - } - - private _onMouseLeave(e: MouseEvent): void { - // Fire the hover end callback and cancel any existing timer if the mouse - // leaves the terminal element - if (this._currentZone) { - this._currentZone.leaveCallback(); - this._currentZone = undefined; - if (this._tooltipTimeout) { - clearTimeout(this._tooltipTimeout); - } - } - } - - private _onClick(e: MouseEvent): void { - // Find the active zone and click it if found and no selection was - // being performed - const zone = this._findZoneEventAt(e); - const currentSelectionLength = this._getSelectionLength(); - - if (zone && currentSelectionLength === this._initialSelectionLength) { - zone.clickCallback(e); - e.preventDefault(); - e.stopImmediatePropagation(); - } - } - - private _getSelectionLength(): number { - const selectionText = this._selectionService.selectionText; - return selectionText ? selectionText.length : 0; - } - - private _findZoneEventAt(e: MouseEvent): IMouseZone | undefined { - const coords = this._mouseService.getCoords(e, this._screenElement, this._bufferService.cols, this._bufferService.rows); - if (!coords) { - return undefined; - } - const x = coords[0]; - const y = coords[1]; - for (let i = 0; i < this._zones.length; i++) { - const zone = this._zones[i]; - if (zone.y1 === zone.y2) { - // Single line link - if (y === zone.y1 && x >= zone.x1 && x < zone.x2) { - return zone; - } - } else { - // Multi-line link - if ((y === zone.y1 && x >= zone.x1) || - (y === zone.y2 && x < zone.x2) || - (y > zone.y1 && y < zone.y2)) { - return zone; - } - } - } - return undefined; - } -} diff --git a/src/browser/Terminal.test.ts b/src/browser/Terminal.test.ts index 8542ddca..8d52b505 100644 --- a/src/browser/Terminal.test.ts +++ b/src/browser/Terminal.test.ts @@ -8,7 +8,6 @@ import { MockViewport, MockCompositionHelper, MockRenderer, TestTerminal } from import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine'; import { CellData } from 'common/buffer/CellData'; import { MockUnicodeService } from 'common/TestUtils.test'; -import { IMouseZoneManager, IMouseZone } from 'browser/Types'; import { IMarker } from 'common/Types'; const INIT_COLS = 80; @@ -1417,16 +1416,3 @@ describe('Terminal', () => { }); }); }); - -class TestMouseZoneManager implements IMouseZoneManager { - public dispose(): void { - } - public clears: number = 0; - public zones: IMouseZone[] = []; - public add(zone: IMouseZone): void { - this.zones.push(zone); - } - public clearAll(): void { - this.clears++; - } -} diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index 202e4a80..053071d0 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -21,7 +21,7 @@ * http://linux.die.net/man/7/urxvt */ -import { ICompositionHelper, ITerminal, IBrowser, CustomKeyEventHandler, IMouseZoneManager, IViewport, ILinkifier2, CharacterJoinerHandler } from 'browser/Types'; +import { ICompositionHelper, ITerminal, IBrowser, CustomKeyEventHandler, IViewport, ILinkifier2, CharacterJoinerHandler } from 'browser/Types'; import { IRenderer } from 'browser/renderer/Types'; import { CompositionHelper } from 'browser/input/CompositionHelper'; import { Viewport } from 'browser/Viewport'; @@ -33,7 +33,6 @@ import { SelectionService } from 'browser/services/SelectionService'; import * as Browser from 'common/Platform'; import { addDisposableDomListener } from 'browser/Lifecycle'; import * as Strings from 'browser/LocalizableStrings'; -import { MouseZoneManager } from 'browser/MouseZoneManager'; import { AccessibilityManager } from './AccessibilityManager'; import { ITheme, IMarker, IDisposable, ISelectionPosition, ILinkProvider, IDecorationOptions, IDecoration } from 'xterm'; import { DomRenderer } from 'browser/renderer/dom/DomRenderer'; @@ -118,7 +117,6 @@ export class Terminal extends CoreTerminal implements ITerminal { public linkifier2: ILinkifier2; public viewport: IViewport | undefined; private _compositionHelper: ICompositionHelper | undefined; - private _mouseZoneManager: IMouseZoneManager | undefined; private _accessibilityManager: AccessibilityManager | undefined; private _colorManager: ColorManager | undefined; private _theme: ITheme | undefined; @@ -576,12 +574,8 @@ export class Terminal extends CoreTerminal implements ITerminal { })); this.register(addDisposableDomListener(this._viewportElement, 'scroll', () => this._selectionService!.refresh())); - this._mouseZoneManager = this._instantiationService.createInstance(MouseZoneManager, this.element, this.screenElement); - this.register(this._mouseZoneManager); - this.register(this.onScroll(() => this._mouseZoneManager!.clearAll())); this.linkifier2.attachToDom(this.screenElement, this._mouseService, this._renderService); this.register(this._instantiationService.createInstance(BufferDecorationRenderer, this.screenElement)); - // This event listener must be registered aftre MouseZoneManager is created this.register(addDisposableDomListener(this.element, 'mousedown', (e: MouseEvent) => this._selectionService!.onMouseDown(e))); // apply mouse event classes set by escape codes before terminal was attached diff --git a/src/browser/Types.d.ts b/src/browser/Types.d.ts index e328924a..f458f5c8 100644 --- a/src/browser/Types.d.ts +++ b/src/browser/Types.d.ts @@ -177,23 +177,6 @@ export interface ILinkifier2 { registerLinkProvider(linkProvider: ILinkProvider): IDisposable; } -export interface IMouseZoneManager extends IDisposable { - add(zone: IMouseZone): void; - clearAll(start?: number, end?: number): void; -} - -export interface IMouseZone { - x1: number; - x2: number; - y1: number; - y2: number; - clickCallback: (e: MouseEvent) => any; - hoverCallback: (e: MouseEvent) => any | undefined; - tooltipCallback: (e: MouseEvent) => any | undefined; - leaveCallback: () => any | undefined; - willLinkActivate: (e: MouseEvent) => boolean; -} - interface ILinkProvider { provideLinks(y: number, callback: (links: ILink[] | undefined) => void): void; } diff --git a/src/common/services/OptionsService.ts b/src/common/services/OptionsService.ts index 4889f120..0a2c81a3 100644 --- a/src/common/services/OptionsService.ts +++ b/src/common/services/OptionsService.ts @@ -23,7 +23,6 @@ export const DEFAULT_OPTIONS: Readonly = { fontWeight: 'normal', fontWeightBold: 'bold', lineHeight: 1.0, - linkTooltipHoverDuration: 500, letterSpacing: 0, logLevel: 'info', scrollback: 1000, diff --git a/src/common/services/Services.ts b/src/common/services/Services.ts index c2fdd8e5..a7830b4d 100644 --- a/src/common/services/Services.ts +++ b/src/common/services/Services.ts @@ -228,7 +228,6 @@ export interface ITerminalOptions { fontWeightBold: FontWeight; letterSpacing: number; lineHeight: number; - linkTooltipHoverDuration: number; logLevel: LogLevel; macOptionIsMeta: boolean; macOptionClickForcesSelection: boolean; diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index adfc3f54..058eb656 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -138,13 +138,6 @@ declare module 'xterm' { */ lineHeight?: number; - /** - * The duration in milliseconds before link tooltip events fire when - * hovering on a link. - * @deprecated This will be removed when the link matcher API is removed. - */ - linkTooltipHoverDuration?: number; - /** * What log level to use, this will log for all levels below and including * what is set: From 5a658ebfccbb4d7bcf7e4144918489061b04b1b5 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 27 Jul 2022 08:45:32 -0700 Subject: [PATCH 23/42] Remove remaining link hover tooltip duration --- .../typings/xterm-addon-web-links.d.ts | 3 +-- typings/xterm-headless.d.ts | 7 ------- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/addons/xterm-addon-web-links/typings/xterm-addon-web-links.d.ts b/addons/xterm-addon-web-links/typings/xterm-addon-web-links.d.ts index 4e1767b9..dc69d732 100644 --- a/addons/xterm-addon-web-links/typings/xterm-addon-web-links.d.ts +++ b/addons/xterm-addon-web-links/typings/xterm-addon-web-links.d.ts @@ -35,8 +35,7 @@ declare module 'xterm-addon-web-links' { */ export interface ILinkProviderOptions { /** - * A callback that fires when the mouse hovers over a link for a period of - * time (defined by {@link ITerminalOptions.linkTooltipHoverDuration}). + * A callback that fires when the mouse hovers over a link. */ hover?(event: MouseEvent, text: string, location: IViewportRange): void; diff --git a/typings/xterm-headless.d.ts b/typings/xterm-headless.d.ts index 93029c6f..44868cdf 100644 --- a/typings/xterm-headless.d.ts +++ b/typings/xterm-headless.d.ts @@ -101,13 +101,6 @@ declare module 'xterm-headless' { */ lineHeight?: number; - /** - * The duration in milliseconds before link tooltip events fire when - * hovering on a link. - * @deprecated This will be removed when the link matcher API is removed. - */ - linkTooltipHoverDuration?: number; - /** * What log level to use, this will log for all levels below and including * what is set: From 6d932ec812ff3eea71c281dd4aa135246a975f3b Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 27 Jul 2022 08:49:07 -0700 Subject: [PATCH 24/42] Fix addMarker in api tests --- test/api/Terminal.api.ts | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/test/api/Terminal.api.ts b/test/api/Terminal.api.ts index 0e1f7764..bf5875d0 100644 --- a/test/api/Terminal.api.ts +++ b/test/api/Terminal.api.ts @@ -569,11 +569,11 @@ describe('API Integration Tests', function(): void { await writeSync(page, '\\n\\n\\n\\n'); await writeSync(page, '\\n\\n\\n\\n'); await writeSync(page, '\\n\\n\\n\\n'); - await page.evaluate(`window.term.addMarker(1)`); - await page.evaluate(`window.term.addMarker(2)`); + await page.evaluate(`window.term.registerMarker(1)`); + await page.evaluate(`window.term.registerMarker(2)`); await page.evaluate(`window.term.scrollLines(10)`); - await page.evaluate(`window.term.addMarker(3)`); - await page.evaluate(`window.term.addMarker(4)`); + await page.evaluate(`window.term.registerMarker(3)`); + await page.evaluate(`window.term.registerMarker(4)`); await page.evaluate(` for (let i = 0; i < window.term.markers.length; ++i) { const marker = window.term.markers[i]; @@ -737,8 +737,8 @@ describe('API Integration Tests', function(): void { await page.evaluate(`window.term = new Terminal({ allowProposedApi: true })`); await page.evaluate(`window.term.open(document.querySelector('#terminal-container'))`); await page.waitForSelector('.xterm-text-layer'); - await page.evaluate(`window.marker1 = window.term.addMarker(1)`); - await page.evaluate(`window.marker2 = window.term.addMarker(2)`); + await page.evaluate(`window.marker1 = window.term.registerMarker(1)`); + await page.evaluate(`window.marker2 = window.term.registerMarker(2)`); await page.evaluate(`window.term.registerDecoration({ marker: window.marker1 })`); await page.evaluate(`window.term.registerDecoration({ marker: window.marker2 })`); await openTerminal(page); @@ -746,13 +746,13 @@ describe('API Integration Tests', function(): void { }); it('should return undefined when the marker has already been disposed of', async () => { await openTerminal(page); - await page.evaluate(`window.marker = window.term.addMarker(1)`); + await page.evaluate(`window.marker = window.term.registerMarker(1)`); await page.evaluate(`window.marker.dispose()`); await pollFor(page, `window.decoration = window.term.registerDecoration({ marker: window.marker });`, undefined); }); it('should throw when a negative x offset is provided', async () => { await openTerminal(page); - await page.evaluate(`window.marker = window.term.addMarker(1)`); + await page.evaluate(`window.marker = window.term.registerMarker(1)`); await page.evaluate(` try { window.decoration = window.term.registerDecoration({ marker: window.marker, x: -2 }); @@ -768,8 +768,8 @@ describe('API Integration Tests', function(): void { await page.evaluate(`window.term = new Terminal({ allowProposedApi: true })`); await page.evaluate(`window.term.open(document.querySelector('#terminal-container'))`); await page.waitForSelector('.xterm-text-layer'); - await page.evaluate(`window.marker1 = window.term.addMarker(1)`); - await page.evaluate(`window.marker2 = window.term.addMarker(2)`); + await page.evaluate(`window.marker1 = window.term.registerMarker(1)`); + await page.evaluate(`window.marker2 = window.term.registerMarker(2)`); await page.evaluate(`window.term.registerDecoration({ marker: window.marker1, overviewRulerOptions: { color: 'red', position: 'full' } })`); await page.evaluate(`window.term.registerDecoration({ marker: window.marker2, overviewRulerOptions: { color: 'blue', position: 'full' } })`); await openTerminal(page); @@ -779,8 +779,8 @@ describe('API Integration Tests', function(): void { await page.evaluate(`window.term = new Terminal({ allowProposedApi: true, overviewRulerWidth: 15 })`); await page.evaluate(`window.term.open(document.querySelector('#terminal-container'))`); await page.waitForSelector('.xterm-text-layer'); - await page.evaluate(`window.marker1 = window.term.addMarker(1)`); - await page.evaluate(`window.marker2 = window.term.addMarker(2)`); + await page.evaluate(`window.marker1 = window.term.registerMarker(1)`); + await page.evaluate(`window.marker2 = window.term.registerMarker(2)`); await page.evaluate(`window.term.registerDecoration({ marker: window.marker1, overviewRulerOptions: { color: 'red', position: 'full' } })`); await page.evaluate(`window.term.registerDecoration({ marker: window.marker2, overviewRulerOptions: { color: 'blue', position: 'full' } })`); await openTerminal(page); From 71911b501b5108615c680f620bcf15315bf1e012 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 27 Jul 2022 09:04:07 -0700 Subject: [PATCH 25/42] Remove set/getOption deprecated APIs --- src/browser/Terminal.test.ts | 4 +- src/browser/public/Terminal.ts | 21 ----- src/browser/services/SelectionService.ts | 2 +- src/common/TestUtils.test.ts | 6 -- src/common/services/OptionsService.test.ts | 52 ++++++------ src/common/services/OptionsService.ts | 8 -- src/common/services/Services.ts | 3 - src/headless/public/Terminal.test.ts | 6 -- src/headless/public/Terminal.ts | 18 ---- test/api/Terminal.api.ts | 7 -- typings/xterm-headless.d.ts | 76 ----------------- typings/xterm.d.ts | 95 ---------------------- 12 files changed, 29 insertions(+), 269 deletions(-) diff --git a/src/browser/Terminal.test.ts b/src/browser/Terminal.test.ts index 8d52b505..eaf420ed 100644 --- a/src/browser/Terminal.test.ts +++ b/src/browser/Terminal.test.ts @@ -469,7 +469,7 @@ describe('Terminal', () => { describe('when scrollback === 0', () => { beforeEach(() => { - term.optionsService.setOption('scrollback', 0); + term.optionsService.options.scrollback = 0; assert.equal(term.buffer.lines.maxLength, INIT_ROWS); }); @@ -1346,7 +1346,7 @@ describe('Terminal', () => { term = new TestTerminal({}); markers = []; disposeStack = []; - term.optionsService.setOption('scrollback', 1); + term.optionsService.options.scrollback = 1; term.resize(10, 5); markers.push(term.buffers.active.addMarker(term.buffers.active.y)); await term.writeP('\x1b[r0\r\n'); diff --git a/src/browser/public/Terminal.ts b/src/browser/public/Terminal.ts index 53904cf9..adcbebca 100644 --- a/src/browser/public/Terminal.ts +++ b/src/browser/public/Terminal.ts @@ -229,27 +229,6 @@ export class Terminal implements ITerminalApi { public paste(data: string): void { this._core.paste(data); } - public getOption(key: 'cursorStyle' | 'fontFamily' | 'logLevel' | 'rendererType' | 'termName' | 'wordSeparator'): string; - public getOption(key: 'allowTransparency' | 'altClickMovesCursor' | 'cancelEvents' | 'convertEol' | 'cursorBlink' | 'disableStdin' | 'macOptionIsMeta' | 'rightClickSelectsWord'): boolean; - public getOption(key: 'cols' | 'fontSize' | 'letterSpacing' | 'lineHeight' | 'rows' | 'tabStopWidth' | 'scrollback'): number; - public getOption(key: 'fontWeight' | 'fontWeightBold'): FontWeight; - public getOption(key: string): any; - public getOption(key: any): any { - return this._core.optionsService.getOption(key); - } - public setOption(key: 'fontFamily' | 'termName' | 'wordSeparator', value: string): void; - public setOption(key: 'fontWeight' | 'fontWeightBold', value: 'normal' | 'bold' | '100' | '200' | '300' | '400' | '500' | '600' | '700' | '800' | '900' | number): void; - public setOption(key: 'logLevel', value: 'debug' | 'info' | 'warn' | 'error' | 'off'): void; - public setOption(key: 'cursorStyle', value: 'block' | 'underline' | 'bar'): void; - public setOption(key: 'allowTransparency' | 'altClickMovesCursor' | 'cancelEvents' | 'convertEol' | 'cursorBlink' | 'disableStdin' | 'macOptionIsMeta' | 'rightClickSelectsWord', value: boolean): void; - public setOption(key: 'fontSize' | 'letterSpacing' | 'lineHeight' | 'tabStopWidth' | 'scrollback', value: number): void; - public setOption(key: 'theme', value: ITheme): void; - public setOption(key: 'cols' | 'rows', value: number): void; - public setOption(key: string, value: any): void; - public setOption(key: any, value: any): void { - this._checkReadonlyOptions(key); - this._core.optionsService.setOption(key, value); - } public refresh(start: number, end: number): void { this._verifyIntegers(start, end); this._core.refresh(start, end); diff --git a/src/browser/services/SelectionService.ts b/src/browser/services/SelectionService.ts index 6b5c1425..aad2c466 100644 --- a/src/browser/services/SelectionService.ts +++ b/src/browser/services/SelectionService.ts @@ -694,7 +694,7 @@ export class SelectionService extends Disposable implements ISelectionService { this._removeMouseDownListeners(); - if (this.selectionText.length <= 1 && timeElapsed < ALT_CLICK_MOVE_CURSOR_TIME && event.altKey && this._optionsService.getOption('altClickMovesCursor')) { + if (this.selectionText.length <= 1 && timeElapsed < ALT_CLICK_MOVE_CURSOR_TIME && event.altKey && this._optionsService.rawOptions.altClickMovesCursor) { if (this._bufferService.buffer.ybase === this._bufferService.buffer.ydisp) { const coordinates = this._mouseService.getCoords( event, diff --git a/src/common/TestUtils.test.ts b/src/common/TestUtils.test.ts index 11d9a8c5..48f3a69e 100644 --- a/src/common/TestUtils.test.ts +++ b/src/common/TestUtils.test.ts @@ -136,12 +136,6 @@ export class MockOptionsService implements IOptionsService { this.options[key] = options[key]; } } - public setOption(key: string, value: T): void { - throw new Error('Method not implemented.'); - } - public getOption(key: string): T { - throw new Error('Method not implemented.'); - } } // defaults to V6 always to keep tests passing diff --git a/src/common/services/OptionsService.test.ts b/src/common/services/OptionsService.test.ts index ae9e77ec..a65cb986 100644 --- a/src/common/services/OptionsService.test.ts +++ b/src/common/services/OptionsService.test.ts @@ -17,16 +17,16 @@ describe('OptionsService', () => { }); it('uses default value if invalid constructor option values passed for cols/rows', () => { const optionsService = new OptionsService({ cols: undefined, rows: undefined }); - assert.equal(optionsService.getOption('rows'), DEFAULT_OPTIONS.rows); - assert.equal(optionsService.getOption('cols'), DEFAULT_OPTIONS.cols); + assert.equal(optionsService.options.rows, DEFAULT_OPTIONS.rows); + assert.equal(optionsService.options.cols, DEFAULT_OPTIONS.cols); }); it('uses values from constructor option values if correctly passed', () => { const optionsService = new OptionsService({ cols: 80, rows: 25 }); - assert.equal(optionsService.getOption('rows'), 25); - assert.equal(optionsService.getOption('cols'), 80); + assert.equal(optionsService.options.rows, 25); + assert.equal(optionsService.options.cols, 80); }); it('uses default value if invalid constructor option value passed', () => { - assert.equal(new OptionsService({ tabStopWidth: 0 }).getOption('tabStopWidth'), DEFAULT_OPTIONS.tabStopWidth); + assert.equal(new OptionsService({ tabStopWidth: 0 }).options.tabStopWidth, DEFAULT_OPTIONS.tabStopWidth); }); it('object.keys return the correct number of options', () => { const optionsService = new OptionsService({ cols: 80, rows: 25 }); @@ -39,36 +39,36 @@ describe('OptionsService', () => { service = new OptionsService({}); }); it('applies valid fontWeight option values', () => { - service.setOption('fontWeight', 'bold'); - assert.equal(service.getOption('fontWeight'), 'bold', '"bold" keyword value should be applied'); + service.options.fontWeight = 'bold'; + assert.equal(service.options.fontWeight, 'bold', '"bold" keyword value should be applied'); - service.setOption('fontWeight', 'normal'); - assert.equal(service.getOption('fontWeight'), 'normal', '"normal" keyword value should be applied'); + service.options.fontWeight = 'normal'; + assert.equal(service.options.fontWeight, 'normal', '"normal" keyword value should be applied'); - service.setOption('fontWeight', '600'); - assert.equal(service.getOption('fontWeight'), '600', 'String numeric values should be applied'); + service.options.fontWeight = '600'; + assert.equal(service.options.fontWeight, '600', 'String numeric values should be applied'); - service.setOption('fontWeight', 350); - assert.equal(service.getOption('fontWeight'), 350, 'Values between 1 and 1000 should be applied as is'); + service.options.fontWeight = 350; + assert.equal(service.options.fontWeight, 350, 'Values between 1 and 1000 should be applied as is'); - service.setOption('fontWeight', 1); - assert.equal(service.getOption('fontWeight'), 1, 'Range should include minimum value: 1'); + service.options.fontWeight = 1; + assert.equal(service.options.fontWeight, 1, 'Range should include minimum value: 1'); - service.setOption('fontWeight', 1000); - assert.equal(service.getOption('fontWeight'), 1000, 'Range should include maximum value: 1000'); + service.options.fontWeight = 1000; + assert.equal(service.options.fontWeight, 1000, 'Range should include maximum value: 1000'); }); it('normalizes invalid fontWeight option values', () => { - service.setOption('fontWeight', 350); - assert.doesNotThrow(() => service.setOption('fontWeight', 10000), 'fontWeight should be normalized instead of throwing'); - assert.equal(service.getOption('fontWeight'), DEFAULT_OPTIONS.fontWeight, 'Values greater than 1000 should be reset to default'); + service.options.fontWeight = 350; + assert.doesNotThrow(() => service.options.fontWeight = 10000), 'fontWeight should be normalized instead of throwing'; + assert.equal(service.options.fontWeight, DEFAULT_OPTIONS.fontWeight, 'Values greater than 1000 should be reset to default'); - service.setOption('fontWeight', 350); - service.setOption('fontWeight', -10); - assert.equal(service.getOption('fontWeight'), DEFAULT_OPTIONS.fontWeight, 'Values less than 1 should be reset to default'); + service.options.fontWeight = 350; + service.options.fontWeight = -10; + assert.equal(service.options.fontWeight, DEFAULT_OPTIONS.fontWeight, 'Values less than 1 should be reset to default'); - service.setOption('fontWeight', 350); - service.setOption('fontWeight', 'bold700'); - assert.equal(service.getOption('fontWeight'), DEFAULT_OPTIONS.fontWeight, 'Wrong string literals should be reset to default'); + service.options.fontWeight = 350; + service.options.fontWeight = 'bold700' as any; + assert.equal(service.options.fontWeight, DEFAULT_OPTIONS.fontWeight, 'Wrong string literals should be reset to default'); }); }); }); diff --git a/src/common/services/OptionsService.ts b/src/common/services/OptionsService.ts index 0a2c81a3..911cc9b2 100644 --- a/src/common/services/OptionsService.ts +++ b/src/common/services/OptionsService.ts @@ -109,10 +109,6 @@ export class OptionsService implements IOptionsService { } } - public setOption(key: string, value: any): void { - this.options[key] = value; - } - private _sanitizeAndValidateOption(key: string, value: any): any { switch (key) { case 'cursorStyle': @@ -170,10 +166,6 @@ export class OptionsService implements IOptionsService { } return value; } - - public getOption(key: string): any { - return this.options[key]; - } } function isCursorStyle(value: unknown): value is CursorStyle { diff --git a/src/common/services/Services.ts b/src/common/services/Services.ts index a7830b4d..22bff06d 100644 --- a/src/common/services/Services.ts +++ b/src/common/services/Services.ts @@ -198,9 +198,6 @@ export interface IOptionsService { readonly options: ITerminalOptions; readonly onOptionChange: IEvent; - - setOption(key: string, value: T): void; - getOption(key: string): T | undefined; } export type FontWeight = 'normal' | 'bold' | '100' | '200' | '300' | '400' | '500' | '600' | '700' | '800' | '900' | number; diff --git a/src/headless/public/Terminal.test.ts b/src/headless/public/Terminal.test.ts index 403cf35a..7f341b91 100644 --- a/src/headless/public/Terminal.test.ts +++ b/src/headless/public/Terminal.test.ts @@ -114,12 +114,6 @@ describe('Headless API Tests', function (): void { } }); - it('getOption, setOption', async () => { - strictEqual(term.getOption('scrollback'), 1000); - term.setOption('scrollback', 50); - strictEqual(term.getOption('scrollback'), 50); - }); - describe('options', () => { const termOptions = { cols: 80, diff --git a/src/headless/public/Terminal.ts b/src/headless/public/Terminal.ts index 01c0eab0..673b20f6 100644 --- a/src/headless/public/Terminal.ts +++ b/src/headless/public/Terminal.ts @@ -179,24 +179,6 @@ export class Terminal implements ITerminalApi { this._core.write(data); this._core.write('\r\n', callback); } - public getOption(key: 'cursorStyle' | 'fontFamily' | 'logLevel' | 'rendererType' | 'termName' | 'wordSeparator'): string; - public getOption(key: 'allowTransparency' | 'altClickMovesCursor' | 'cancelEvents' | 'convertEol' | 'cursorBlink' | 'disableStdin' | 'macOptionIsMeta' | 'rightClickSelectsWord'): boolean; - public getOption(key: 'cols' | 'fontSize' | 'letterSpacing' | 'lineHeight' | 'rows' | 'tabStopWidth' | 'scrollback'): number; - public getOption(key: string): any; - public getOption(key: any): any { - return this._core.optionsService.getOption(key); - } - public setOption(key: 'fontFamily' | 'termName' | 'wordSeparator', value: string): void; - public setOption(key: 'fontWeight' | 'fontWeightBold', value: 'normal' | 'bold' | '100' | '200' | '300' | '400' | '500' | '600' | '700' | '800' | '900' | number): void; - public setOption(key: 'logLevel', value: 'debug' | 'info' | 'warn' | 'error' | 'off'): void; - public setOption(key: 'cursorStyle', value: 'block' | 'underline' | 'bar'): void; - public setOption(key: 'allowTransparency' | 'altClickMovesCursor' | 'cancelEvents' | 'convertEol' | 'cursorBlink' | 'disableStdin' | 'macOptionIsMeta' | 'rightClickSelectsWord', value: boolean): void; - public setOption(key: 'fontSize' | 'letterSpacing' | 'lineHeight' | 'tabStopWidth' | 'scrollback', value: number): void; - public setOption(key: 'cols' | 'rows', value: number): void; - public setOption(key: string, value: any): void; - public setOption(key: any, value: any): void { - this._core.optionsService.setOption(key, value); - } public reset(): void { this._core.reset(); } diff --git a/test/api/Terminal.api.ts b/test/api/Terminal.api.ts index bf5875d0..80e46be0 100644 --- a/test/api/Terminal.api.ts +++ b/test/api/Terminal.api.ts @@ -154,13 +154,6 @@ describe('API Integration Tests', function(): void { } }); - it('getOption, setOption', async () => { - await openTerminal(page); - assert.equal(await page.evaluate(`window.term.getOption('rendererType')`), 'canvas'); - await page.evaluate(`window.term.setOption('rendererType', 'dom')`); - assert.equal(await page.evaluate(`window.term.getOption('rendererType')`), 'dom'); - }); - describe('options', () => { it('getter', async () => { await openTerminal(page); diff --git a/typings/xterm-headless.d.ts b/typings/xterm-headless.d.ts index 44868cdf..0009dc62 100644 --- a/typings/xterm-headless.d.ts +++ b/typings/xterm-headless.d.ts @@ -697,82 +697,6 @@ declare module 'xterm-headless' { */ writeUtf8(data: Uint8Array, callback?: () => void): void; - /** - * Retrieves an option's value from the terminal. - * @param key The option key. - */ - getOption(key: 'cursorStyle' | 'fontFamily' | 'logLevel' | 'rendererType' | 'termName' | 'wordSeparator'): string; - /** - * Retrieves an option's value from the terminal. - * @param key The option key. - */ - getOption(key: 'allowTransparency' | 'cancelEvents' | 'convertEol' | 'cursorBlink' | 'disableStdin' | 'macOptionIsMeta' | 'rightClickSelectsWord' | 'windowsMode'): boolean; - /** - * Retrieves an option's value from the terminal. - * @param key The option key. - */ - getOption(key: 'cols' | 'fontSize' | 'letterSpacing' | 'lineHeight' | 'rows' | 'tabStopWidth' | 'scrollback'): number; - /** - * Retrieves an option's value from the terminal. - * @param key The option key. - */ - getOption(key: string): any; - - /** - * Sets an option on the terminal. - * @param key The option key. - * @param value The option value. - */ - setOption(key: 'fontFamily' | 'termName' | 'wordSeparator', value: string): void; - /** - * Sets an option on the terminal. - * @param key The option key. - * @param value The option value. - */ - setOption(key: 'fontWeight' | 'fontWeightBold', value: null | 'normal' | 'bold' | '100' | '200' | '300' | '400' | '500' | '600' | '700' | '800' | '900' | number): void; - /** - * Sets an option on the terminal. - * @param key The option key. - * @param value The option value. - */ - setOption(key: 'logLevel', value: LogLevel): void; - /** - * Sets an option on the terminal. - * @param key The option key. - * @param value The option value. - */ - setOption(key: 'cursorStyle', value: null | 'block' | 'underline' | 'bar'): void; - /** - * Sets an option on the terminal. - * @param key The option key. - * @param value The option value. - */ - setOption(key: 'allowTransparency' | 'cancelEvents' | 'convertEol' | 'cursorBlink' | 'disableStdin' | 'macOptionIsMeta' | 'rightClickSelectsWord' | 'windowsMode', value: boolean): void; - /** - * Sets an option on the terminal. - * @param key The option key. - * @param value The option value. - */ - setOption(key: 'fontSize' | 'letterSpacing' | 'lineHeight' | 'tabStopWidth' | 'scrollback', value: number): void; - /** - * Sets an option on the terminal. - * @param key The option key. - * @param value The option value. - */ - setOption(key: 'theme', value: ITheme): void; - /** - * Sets an option on the terminal. - * @param key The option key. - * @param value The option value. - */ - setOption(key: 'cols' | 'rows', value: number): void; - /** - * Sets an option on the terminal. - * @param key The option key. - * @param value The option value. - */ - setOption(key: string, value: any): void; - /** * Perform a full reset (RIS, aka '\x1bc'). */ diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 058eb656..efac5939 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -1035,101 +1035,6 @@ declare module 'xterm' { */ paste(data: string): void; - /** - * Retrieves an option's value from the terminal. - * @param key The option key. - * @deprecated Use `options` instead. - */ - getOption(key: 'cursorStyle' | 'fontFamily' | 'logLevel' | 'rendererType' | 'termName' | 'wordSeparator'): string; - /** - * Retrieves an option's value from the terminal. - * @param key The option key. - * @deprecated Use `options` instead. - */ - getOption(key: 'allowTransparency' | 'cancelEvents' | 'convertEol' | 'cursorBlink' | 'disableStdin' | 'macOptionIsMeta' | 'rightClickSelectsWord' | 'windowsMode'): boolean; - /** - * Retrieves an option's value from the terminal. - * @param key The option key. - * @deprecated Use `options` instead. - */ - getOption(key: 'cols' | 'fontSize' | 'letterSpacing' | 'lineHeight' | 'rows' | 'tabStopWidth' | 'scrollback'): number; - /** - * Retrieves an option's value from the terminal. - * @param key The option key. - * @deprecated Use `options` instead. - */ - getOption(key: 'fontWeight' | 'fontWeightBold'): FontWeight; - /** - * Retrieves an option's value from the terminal. - * @param key The option key. - * @deprecated Use `options` instead. - */ - getOption(key: string): any; - - /** - * Sets an option on the terminal. - * @param key The option key. - * @param value The option value. - * @deprecated Use `options` instead. - */ - setOption(key: 'fontFamily' | 'termName' | 'wordSeparator', value: string): void; - /** - * Sets an option on the terminal. - * @param key The option key. - * @param value The option value. - * @deprecated Use `options` instead. - */ - setOption(key: 'fontWeight' | 'fontWeightBold', value: null | 'normal' | 'bold' | '100' | '200' | '300' | '400' | '500' | '600' | '700' | '800' | '900' | number): void; - /** - * Sets an option on the terminal. - * @param key The option key. - * @param value The option value. - * @deprecated Use `options` instead. - */ - setOption(key: 'logLevel', value: LogLevel): void; - /** - * Sets an option on the terminal. - * @param key The option key. - * @param value The option value. - * @deprecated Use `options` instead. - */ - setOption(key: 'cursorStyle', value: null | 'block' | 'underline' | 'bar'): void; - /** - * Sets an option on the terminal. - * @param key The option key. - * @param value The option value. - * @deprecated Use `options` instead. - */ - setOption(key: 'allowTransparency' | 'cancelEvents' | 'convertEol' | 'cursorBlink' | 'disableStdin' | 'macOptionIsMeta' | 'rightClickSelectsWord' | 'windowsMode', value: boolean): void; - /** - * Sets an option on the terminal. - * @param key The option key. - * @param value The option value. - * @deprecated Use `options` instead. - */ - setOption(key: 'fontSize' | 'letterSpacing' | 'lineHeight' | 'tabStopWidth' | 'scrollback', value: number): void; - /** - * Sets an option on the terminal. - * @param key The option key. - * @param value The option value. - * @deprecated Use `options` instead. - */ - setOption(key: 'theme', value: ITheme): void; - /** - * Sets an option on the terminal. - * @param key The option key. - * @param value The option value. - * @deprecated Use `options` instead. - */ - setOption(key: 'cols' | 'rows', value: number): void; - /** - * Sets an option on the terminal. - * @param key The option key. - * @param value The option value. - * @deprecated Use `options` instead. - */ - setOption(key: string, value: any): void; - /** * Tells the renderer to refresh terminal content between two rows * (inclusive) at the next opportunity. From 8c16d9d506f20fc2690357a48e43be5557840fac Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 27 Jul 2022 09:04:43 -0700 Subject: [PATCH 26/42] Remove writeUtf8 deprecated API --- src/browser/TestUtils.test.ts | 3 --- src/browser/public/Terminal.ts | 3 --- src/headless/public/Terminal.ts | 3 --- typings/xterm-headless.d.ts | 8 -------- typings/xterm.d.ts | 8 -------- 5 files changed, 25 deletions(-) diff --git a/src/browser/TestUtils.test.ts b/src/browser/TestUtils.test.ts index 62dc611e..d59119e7 100644 --- a/src/browser/TestUtils.test.ts +++ b/src/browser/TestUtils.test.ts @@ -137,9 +137,6 @@ export class MockTerminal implements ITerminal { public write(data: string): void { throw new Error('Method not implemented.'); } - public writeUtf8(data: Uint8Array): void { - throw new Error('Method not implemented.'); - } public bracketedPasteMode!: boolean; public renderer!: IRenderer; public linkifier2!: ILinkifier2; diff --git a/src/browser/public/Terminal.ts b/src/browser/public/Terminal.ts index adcbebca..65f26db0 100644 --- a/src/browser/public/Terminal.ts +++ b/src/browser/public/Terminal.ts @@ -219,9 +219,6 @@ export class Terminal implements ITerminalApi { public write(data: string | Uint8Array, callback?: () => void): void { this._core.write(data, callback); } - public writeUtf8(data: Uint8Array, callback?: () => void): void { - this._core.write(data, callback); - } public writeln(data: string | Uint8Array, callback?: () => void): void { this._core.write(data); this._core.write('\r\n', callback); diff --git a/src/headless/public/Terminal.ts b/src/headless/public/Terminal.ts index 673b20f6..451d2372 100644 --- a/src/headless/public/Terminal.ts +++ b/src/headless/public/Terminal.ts @@ -172,9 +172,6 @@ export class Terminal implements ITerminalApi { public write(data: string | Uint8Array, callback?: () => void): void { this._core.write(data, callback); } - public writeUtf8(data: Uint8Array, callback?: () => void): void { - this._core.write(data, callback); - } public writeln(data: string | Uint8Array, callback?: () => void): void { this._core.write(data); this._core.write('\r\n', callback); diff --git a/typings/xterm-headless.d.ts b/typings/xterm-headless.d.ts index 0009dc62..c2b77acc 100644 --- a/typings/xterm-headless.d.ts +++ b/typings/xterm-headless.d.ts @@ -689,14 +689,6 @@ declare module 'xterm-headless' { */ writeln(data: string | Uint8Array, callback?: () => void): void; - /** - * Write UTF8 data to the terminal. - * @param data The data to write to the terminal. - * @param callback Optional callback when data was processed. - * @deprecated use `write` instead - */ - writeUtf8(data: Uint8Array, callback?: () => void): void; - /** * Perform a full reset (RIS, aka '\x1bc'). */ diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index efac5939..eb90202c 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -1021,14 +1021,6 @@ declare module 'xterm' { */ writeln(data: string | Uint8Array, callback?: () => void): void; - /** - * Write UTF8 data to the terminal. - * @param data The data to write to the terminal. - * @param callback Optional callback when data was processed. - * @deprecated use `write` instead - */ - writeUtf8(data: Uint8Array, callback?: () => void): void; - /** * Writes text to the terminal, performing the necessary transformations for pasted text. * @param data The text to write to the terminal. From 440ab765c458497fcb43d55b71c44afc5fe96803 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 27 Jul 2022 09:22:05 -0700 Subject: [PATCH 27/42] Remove rendererType setting --- .../xterm-addon-attach/test/AttachAddon.api.ts | 4 ++-- .../test/SerializeAddon.api.ts | 2 +- .../test/WebLinksAddon.api.ts | 2 +- .../xterm-addon-webgl/test/WebglRenderer.api.ts | 8 ++++---- demo/client.ts | 1 - src/browser/Terminal.ts | 12 +----------- src/common/services/OptionsService.ts | 2 -- src/common/services/Services.ts | 3 --- test/api/Terminal.api.ts | 17 ++++++++--------- test/api/TestUtils.ts | 6 +----- typings/xterm.d.ts | 15 --------------- 11 files changed, 18 insertions(+), 54 deletions(-) diff --git a/addons/xterm-addon-attach/test/AttachAddon.api.ts b/addons/xterm-addon-attach/test/AttachAddon.api.ts index 8335cf0f..a4827656 100644 --- a/addons/xterm-addon-attach/test/AttachAddon.api.ts +++ b/addons/xterm-addon-attach/test/AttachAddon.api.ts @@ -28,7 +28,7 @@ describe('AttachAddon', () => { beforeEach(async () => await page.goto(APP)); it('string', async function(): Promise { - await openTerminal(page, { rendererType: 'dom' }); + await openTerminal(page); const port = 8080; const server = new WebSocket.Server({ port }); server.on('connection', socket => socket.send('foo')); @@ -38,7 +38,7 @@ describe('AttachAddon', () => { }); it('utf8', async function(): Promise { - await openTerminal(page, { rendererType: 'dom' }); + await openTerminal(page); const port = 8080; const server = new WebSocket.Server({ port }); const data = new Uint8Array([102, 111, 111]); diff --git a/addons/xterm-addon-serialize/test/SerializeAddon.api.ts b/addons/xterm-addon-serialize/test/SerializeAddon.api.ts index b86b066b..157b7072 100644 --- a/addons/xterm-addon-serialize/test/SerializeAddon.api.ts +++ b/addons/xterm-addon-serialize/test/SerializeAddon.api.ts @@ -42,7 +42,7 @@ describe('SerializeAddon', () => { page = await (await browser.newContext()).newPage(); await page.setViewportSize({ width, height }); await page.goto(APP); - await openTerminal(page, { rows: 10, cols: 10, rendererType: 'dom' }); + await openTerminal(page, { rows: 10, cols: 10 }); await page.evaluate(` window.serializeAddon = new SerializeAddon(); window.term.loadAddon(window.serializeAddon); diff --git a/addons/xterm-addon-web-links/test/WebLinksAddon.api.ts b/addons/xterm-addon-web-links/test/WebLinksAddon.api.ts index fe44dc31..bc978085 100644 --- a/addons/xterm-addon-web-links/test/WebLinksAddon.api.ts +++ b/addons/xterm-addon-web-links/test/WebLinksAddon.api.ts @@ -38,7 +38,7 @@ describe('WebLinksAddon', () => { }); async function testHostName(hostname: string): Promise { - await openTerminal(page, { rendererType: 'dom', cols: 40 }); + await openTerminal(page, { cols: 40 }); await page.evaluate(`window.term.loadAddon(new window.WebLinksAddon())`); const data = ` http://${hostname} \\r\\n` + ` http://${hostname}/a~b#c~d?e~f \\r\\n` + diff --git a/addons/xterm-addon-webgl/test/WebglRenderer.api.ts b/addons/xterm-addon-webgl/test/WebglRenderer.api.ts index 13a15cf3..797f0531 100644 --- a/addons/xterm-addon-webgl/test/WebglRenderer.api.ts +++ b/addons/xterm-addon-webgl/test/WebglRenderer.api.ts @@ -860,7 +860,7 @@ describe('WebGL Renderer Integration Tests', async () => { describe('allowTransparency', async () => { if (areTestsEnabled) { - before(async () => setupBrowser({ rendererType: 'dom', allowTransparency: true })); + before(async () => setupBrowser({ allowTransparency: true })); after(async () => browser.close()); beforeEach(async () => page.evaluate(`window.term.reset()`)); } @@ -879,7 +879,7 @@ describe('WebGL Renderer Integration Tests', async () => { describe('selectionForeground', () => { if (areTestsEnabled) { - before(async () => setupBrowser({ rendererType: 'dom' })); + before(async () => setupBrowser()); after(async () => browser.close()); beforeEach(async () => page.evaluate(`window.term.reset()`)); } @@ -898,7 +898,7 @@ describe('WebGL Renderer Integration Tests', async () => { describe('decoration color overrides', async () => { if (areTestsEnabled) { - before(async () => setupBrowser({ rendererType: 'dom' })); + before(async () => setupBrowser()); after(async () => browser.close()); beforeEach(async () => page.evaluate(`window.term.reset()`)); } @@ -1014,7 +1014,7 @@ async function getCellPixels(col: number, row: number): Promise { return await page.evaluate(`Array.from(window.result)`); } -async function setupBrowser(options: ITerminalOptions = { rendererType: 'dom' }): Promise { +async function setupBrowser(options: ITerminalOptions = {}): Promise { browser = await launchBrowser(); page = await (await browser.newContext()).newPage(); await page.setViewportSize({ width, height }); diff --git a/demo/client.ts b/demo/client.ts index b2468ec3..d3b5af9c 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -331,7 +331,6 @@ function initOptions(term: TerminalType): void { fontWeight: ['normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900'], fontWeightBold: ['normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900'], logLevel: ['debug', 'info', 'warn', 'error', 'off'], - rendererType: ['dom', 'canvas'], theme: ['default', 'xtermjs', 'sapphire', 'light'], wordSeparator: null }; diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index 053071d0..91c4a920 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -296,12 +296,6 @@ export class Terminal extends CoreTerminal implements ITerminal { this.refresh(0, this.rows - 1); } break; - case 'rendererType': - if (this._renderService) { - this._renderService.setRenderer(this._createRenderer()); - this._renderService.onResize(this.cols, this.rows); - } - break; case 'scrollback': this.viewport?.syncScrollArea(); break; @@ -615,11 +609,7 @@ export class Terminal extends CoreTerminal implements ITerminal { } private _createRenderer(): IRenderer { - switch (this.options.rendererType) { - case 'canvas': return this._instantiationService.createInstance(Renderer, this._colorManager!.colors, this.screenElement!, this.linkifier2); - case 'dom': return this._instantiationService.createInstance(DomRenderer, this._colorManager!.colors, this.element!, this.screenElement!, this._viewportElement!, this.linkifier2); - default: throw new Error(`Unrecognized rendererType "${this.options.rendererType}"`); - } + return this._instantiationService.createInstance(Renderer, this._colorManager!.colors, this.screenElement!, this.linkifier2); } /** diff --git a/src/common/services/OptionsService.ts b/src/common/services/OptionsService.ts index 911cc9b2..550adb31 100644 --- a/src/common/services/OptionsService.ts +++ b/src/common/services/OptionsService.ts @@ -37,7 +37,6 @@ export const DEFAULT_OPTIONS: Readonly = { tabStopWidth: 8, theme: {}, rightClickSelectsWord: isMac, - rendererType: 'canvas', windowOptions: {}, windowsMode: false, wordSeparator: ' ()[]{}\',"`', @@ -120,7 +119,6 @@ export class OptionsService implements IOptionsService { } break; case 'cursorStyle': - case 'rendererType': case 'wordSeparator': if (!value) { value = DEFAULT_OPTIONS[key]; diff --git a/src/common/services/Services.ts b/src/common/services/Services.ts index 22bff06d..709e171f 100644 --- a/src/common/services/Services.ts +++ b/src/common/services/Services.ts @@ -203,8 +203,6 @@ export interface IOptionsService { export type FontWeight = 'normal' | 'bold' | '100' | '200' | '300' | '400' | '500' | '600' | '700' | '800' | '900' | number; export type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'off'; -export type RendererType = 'dom' | 'canvas'; - export interface ITerminalOptions { allowProposedApi: boolean; allowTransparency: boolean; @@ -229,7 +227,6 @@ export interface ITerminalOptions { macOptionIsMeta: boolean; macOptionClickForcesSelection: boolean; minimumContrastRatio: number; - rendererType: RendererType; rightClickSelectsWord: boolean; rows: number; screenReaderMode: boolean; diff --git a/test/api/Terminal.api.ts b/test/api/Terminal.api.ts index 80e46be0..fa19b5b4 100644 --- a/test/api/Terminal.api.ts +++ b/test/api/Terminal.api.ts @@ -157,7 +157,6 @@ describe('API Integration Tests', function(): void { describe('options', () => { it('getter', async () => { await openTerminal(page); - assert.equal(await page.evaluate(`window.term.options.rendererType`), 'canvas'); assert.equal(await page.evaluate(`window.term.options.cols`), 80); assert.equal(await page.evaluate(`window.term.options.rows`), 24); }); @@ -190,7 +189,7 @@ describe('API Integration Tests', function(): void { describe('renderer', () => { it('foreground', async () => { - await openTerminal(page, { rendererType: 'dom' }); + await openTerminal(page); await writeSync(page, '\\x1b[30m0\\x1b[31m1\\x1b[32m2\\x1b[33m3\\x1b[34m4\\x1b[35m5\\x1b[36m6\\x1b[37m7'); await pollFor(page, `document.querySelectorAll('.xterm-rows > :nth-child(1) > *').length`, 9); assert.deepEqual(await page.evaluate(` @@ -215,7 +214,7 @@ describe('API Integration Tests', function(): void { }); it('background', async () => { - await openTerminal(page, { rendererType: 'dom' }); + await openTerminal(page); await writeSync(page, '\\x1b[40m0\\x1b[41m1\\x1b[42m2\\x1b[43m3\\x1b[44m4\\x1b[45m5\\x1b[46m6\\x1b[47m7'); await pollFor(page, `document.querySelectorAll('.xterm-rows > :nth-child(1) > *').length`, 9); assert.deepEqual(await page.evaluate(` @@ -784,7 +783,7 @@ describe('API Integration Tests', function(): void { describe('registerLinkProvider', () => { it('should fire provideLinks when hovering cells', async () => { - await openTerminal(page, { rendererType: 'dom' }); + await openTerminal(page); // Focus the terminal as the cursor will show and trigger a rerender, which can clear the // active link await page.evaluate('window.term.focus()'); @@ -806,7 +805,7 @@ describe('API Integration Tests', function(): void { }); it('should fire hover and leave events on the link', async () => { - await openTerminal(page, { rendererType: 'dom' }); + await openTerminal(page); // Focus the terminal as the cursor will show and trigger a rerender, which can clear the // active link await page.evaluate('window.term.focus()'); @@ -844,7 +843,7 @@ describe('API Integration Tests', function(): void { }); it('should work fine when hover and leave callbacks are not provided', async () => { - await openTerminal(page, { rendererType: 'dom' }); + await openTerminal(page); // Focus the terminal as the cursor will show and trigger a rerender, which can clear the // active link await page.evaluate('window.term.focus()'); @@ -887,7 +886,7 @@ describe('API Integration Tests', function(): void { }); it('should fire activate events when clicking the link', async () => { - await openTerminal(page, { rendererType: 'dom' }); + await openTerminal(page); // Focus the terminal as the cursor will show and trigger a rerender, which can clear the // active link await page.evaluate('window.term.focus()'); @@ -929,7 +928,7 @@ describe('API Integration Tests', function(): void { }); it('should work when multiple links are provided on the same line', async () => { - await openTerminal(page, { rendererType: 'dom' }); + await openTerminal(page); // Focus the terminal as the cursor will show and trigger a rerender, which can clear the // active link await page.evaluate('window.term.focus()'); @@ -978,7 +977,7 @@ describe('API Integration Tests', function(): void { }); it('should dispose links when hovering away', async () => { - await openTerminal(page, { rendererType: 'dom' }); + await openTerminal(page); // Focus the terminal as the cursor will show and trigger a rerender, which can clear the // active link await page.evaluate('window.term.focus()'); diff --git a/test/api/TestUtils.ts b/test/api/TestUtils.ts index e059c92c..2b4e96a4 100644 --- a/test/api/TestUtils.ts +++ b/test/api/TestUtils.ts @@ -46,11 +46,7 @@ export async function timeout(ms: number): Promise { export async function openTerminal(page: playwright.Page, options: ITerminalOptions = {}): Promise { await page.evaluate(`window.term = new Terminal(${JSON.stringify({ allowProposedApi: true, ...options })})`); await page.evaluate(`window.term.open(document.querySelector('#terminal-container'))`); - if (options.rendererType === 'dom') { - await page.waitForSelector('.xterm-rows'); - } else { - await page.waitForSelector('.xterm-text-layer'); - } + await page.waitForSelector('.xterm-rows'); } export function getBrowserType(): playwright.BrowserType | playwright.BrowserType | playwright.BrowserType { diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index eb90202c..f16ff970 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -20,11 +20,6 @@ declare module 'xterm' { */ export type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'off'; - /** - * A string representing a renderer type. - */ - export type RendererType = 'dom' | 'canvas'; - /** * An object containing start up options for the terminal. */ @@ -176,16 +171,6 @@ declare module 'xterm' { */ minimumContrastRatio?: number; - /** - * The type of renderer to use, this allows using the fallback DOM renderer - * when canvas is too slow for the environment. The following features do - * not work when the DOM renderer is used: - * - * - Letter spacing - * - Cursor blink - */ - rendererType?: RendererType; - /** * Whether to select the word under the cursor on right click, this is * standard behavior in a lot of macOS applications. From ab7da06cc1bdeac48d9e679812ad1f3e7ef080ac Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 27 Jul 2022 09:26:52 -0700 Subject: [PATCH 28/42] Stub canvas addon --- .eslintrc.json | 1 + addons/xterm-addon-canvas/.gitignore | 2 + addons/xterm-addon-canvas/.npmignore | 29 +++++++++++ addons/xterm-addon-canvas/LICENSE | 19 +++++++ addons/xterm-addon-canvas/README.md | 3 ++ addons/xterm-addon-canvas/package.json | 27 ++++++++++ addons/xterm-addon-canvas/src/CanvasAddon.ts | 49 +++++++++++++++++++ addons/xterm-addon-canvas/src/tsconfig.json | 40 +++++++++++++++ addons/xterm-addon-canvas/tsconfig.json | 7 +++ .../typings/xterm-addon-canvas.d.ts | 33 +++++++++++++ addons/xterm-addon-canvas/webpack.config.js | 39 +++++++++++++++ 11 files changed, 249 insertions(+) create mode 100644 addons/xterm-addon-canvas/.gitignore create mode 100644 addons/xterm-addon-canvas/.npmignore create mode 100644 addons/xterm-addon-canvas/LICENSE create mode 100644 addons/xterm-addon-canvas/README.md create mode 100644 addons/xterm-addon-canvas/package.json create mode 100644 addons/xterm-addon-canvas/src/CanvasAddon.ts create mode 100644 addons/xterm-addon-canvas/src/tsconfig.json create mode 100644 addons/xterm-addon-canvas/tsconfig.json create mode 100644 addons/xterm-addon-canvas/typings/xterm-addon-canvas.d.ts create mode 100644 addons/xterm-addon-canvas/webpack.config.js diff --git a/.eslintrc.json b/.eslintrc.json index 427c7a16..3a60f5bc 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -14,6 +14,7 @@ "test/benchmark/tsconfig.json", "addons/xterm-addon-attach/src/tsconfig.json", "addons/xterm-addon-attach/test/tsconfig.json", + "addons/xterm-addon-canvas/src/tsconfig.json", "addons/xterm-addon-fit/src/tsconfig.json", "addons/xterm-addon-fit/test/tsconfig.json", "addons/xterm-addon-ligatures/src/tsconfig.json", diff --git a/addons/xterm-addon-canvas/.gitignore b/addons/xterm-addon-canvas/.gitignore new file mode 100644 index 00000000..a9f4ed54 --- /dev/null +++ b/addons/xterm-addon-canvas/.gitignore @@ -0,0 +1,2 @@ +lib +node_modules \ No newline at end of file diff --git a/addons/xterm-addon-canvas/.npmignore b/addons/xterm-addon-canvas/.npmignore new file mode 100644 index 00000000..b203232a --- /dev/null +++ b/addons/xterm-addon-canvas/.npmignore @@ -0,0 +1,29 @@ +# Blacklist - exclude everything except npm defaults such as LICENSE, etc +* +!*/ + +# Whitelist - lib/ +!lib/**/*.d.ts + +!lib/**/*.js +!lib/**/*.js.map + +!lib/**/*.css + +# Whitelist - src/ +!src/**/*.ts +!src/**/*.d.ts + +!src/**/*.js +!src/**/*.js.map + +!src/**/*.css + +# Blacklist - src/ test files +src/**/*.test.ts +src/**/*.test.d.ts +src/**/*.test.js +src/**/*.test.js.map + +# Whitelist - typings/ +!typings/*.d.ts diff --git a/addons/xterm-addon-canvas/LICENSE b/addons/xterm-addon-canvas/LICENSE new file mode 100644 index 00000000..b9dc26fe --- /dev/null +++ b/addons/xterm-addon-canvas/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2018, The xterm.js authors (https://github.com/xtermjs/xterm.js) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/addons/xterm-addon-canvas/README.md b/addons/xterm-addon-canvas/README.md new file mode 100644 index 00000000..deec04c6 --- /dev/null +++ b/addons/xterm-addon-canvas/README.md @@ -0,0 +1,3 @@ +## xterm-addon-canvas + +TODO: Doc diff --git a/addons/xterm-addon-canvas/package.json b/addons/xterm-addon-canvas/package.json new file mode 100644 index 00000000..3d08e9db --- /dev/null +++ b/addons/xterm-addon-canvas/package.json @@ -0,0 +1,27 @@ +{ + "name": "xterm-addon-canvas", + "version": "0.12.0", + "author": { + "name": "The xterm.js authors", + "url": "https://xtermjs.org/" + }, + "main": "lib/xterm-addon-canvas.js", + "types": "typings/xterm-addon-canvas.d.ts", + "repository": "https://github.com/xtermjs/xterm.js", + "license": "MIT", + "keywords": [ + "terminal", + "canvas", + "xterm", + "xterm.js" + ], + "scripts": { + "build": "../../node_modules/.bin/tsc -p .", + "prepackage": "npm run build", + "package": "../../node_modules/.bin/webpack", + "prepublishOnly": "npm run package" + }, + "peerDependencies": { + "xterm": "^4.0.0" + } +} diff --git a/addons/xterm-addon-canvas/src/CanvasAddon.ts b/addons/xterm-addon-canvas/src/CanvasAddon.ts new file mode 100644 index 00000000..34fffb84 --- /dev/null +++ b/addons/xterm-addon-canvas/src/CanvasAddon.ts @@ -0,0 +1,49 @@ +/** + * Copyright (c) 2022 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { ITerminalAddon, Terminal } from 'xterm'; + +export class CanvasAddon implements ITerminalAddon { + private _terminal?: Terminal; + // private _renderer?: WebglRenderer; + // private _onContextLoss = new EventEmitter(); + // public get onContextLoss(): IEvent { return this._onContextLoss.event; } + + public activate(terminal: Terminal): void { + // if (!terminal.element) { + // throw new Error('Cannot activate WebglAddon before Terminal.open'); + // } + // if (isSafari) { + // throw new Error('Webgl is not currently supported on Safari'); + // } + this._terminal = terminal; + // const renderService: IRenderService = (terminal as any)._core._renderService; + // const characterJoinerService: ICharacterJoinerService = (terminal as any)._core._characterJoinerService; + // const decorationService: IDecorationService = (terminal as any)._core._decorationService; + // const colors: IColorSet = (terminal as any)._core._colorManager.colors; + // this._renderer = new WebglRenderer(terminal, colors, characterJoinerService, decorationService, this._preserveDrawingBuffer); + // this._renderer.onContextLoss(() => this._onContextLoss.fire()); + // renderService.setRenderer(this._renderer); + } + + public dispose(): void { + // if (!this._terminal) { + // throw new Error('Cannot dispose WebglAddon because it is activated'); + // } + // const renderService: IRenderService = (this._terminal as any)._core._renderService; + // renderService.setRenderer((this._terminal as any)._core._createRenderer()); + // renderService.onResize(this._terminal.cols, this._terminal.rows); + // this._renderer?.dispose(); + // this._renderer = undefined; + } + + // public get textureAtlas(): HTMLCanvasElement | undefined { + // return this._renderer?.textureAtlas; + // } + + // public clearTextureAtlas(): void { + // this._renderer?.clearCharAtlas(); + // } +} diff --git a/addons/xterm-addon-canvas/src/tsconfig.json b/addons/xterm-addon-canvas/src/tsconfig.json new file mode 100644 index 00000000..b0c9f6be --- /dev/null +++ b/addons/xterm-addon-canvas/src/tsconfig.json @@ -0,0 +1,40 @@ +{ + "compilerOptions": { + "module": "commonjs", + "target": "es5", + "lib": [ + "dom", + "es6", + ], + "rootDir": ".", + "outDir": "../out", + "sourceMap": true, + "removeComments": true, + "baseUrl": ".", + "paths": { + "common/*": [ + "../../../src/common/*" + ], + "browser/*": [ + "../../../src/browser/*" + ] + }, + "strict": true, + "downlevelIteration": true, + "types": [ + "../../../node_modules/@types/mocha" + ] + }, + "include": [ + "./**/*", + "../../../typings/xterm.d.ts" + ], + "references": [ + { + "path": "../../../src/common" + }, + { + "path": "../../../src/browser" + } + ] +} diff --git a/addons/xterm-addon-canvas/tsconfig.json b/addons/xterm-addon-canvas/tsconfig.json new file mode 100644 index 00000000..b711f30a --- /dev/null +++ b/addons/xterm-addon-canvas/tsconfig.json @@ -0,0 +1,7 @@ +{ + "files": [], + "include": [], + "references": [ + { "path": "./src" } + ] +} diff --git a/addons/xterm-addon-canvas/typings/xterm-addon-canvas.d.ts b/addons/xterm-addon-canvas/typings/xterm-addon-canvas.d.ts new file mode 100644 index 00000000..7bbbd63d --- /dev/null +++ b/addons/xterm-addon-canvas/typings/xterm-addon-canvas.d.ts @@ -0,0 +1,33 @@ +/** + * Copyright (c) 2017 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { Terminal, ITerminalAddon, IEvent } from 'xterm'; + +declare module 'xterm-addon-canvas' { + /** + * An xterm.js addon that provides search functionality. + */ + export class CanvasAddon implements ITerminalAddon { + public textureAtlas?: HTMLCanvasElement; + + constructor(); + + /** + * Activates the addon. + * @param terminal The terminal the addon is being loaded in. + */ + public activate(terminal: Terminal): void; + + /** + * Disposes the addon. + */ + public dispose(): void; + + /** + * Clears the terminal's texture atlas and triggers a redraw. + */ + public clearTextureAtlas(): void; + } +} diff --git a/addons/xterm-addon-canvas/webpack.config.js b/addons/xterm-addon-canvas/webpack.config.js new file mode 100644 index 00000000..9c98760a --- /dev/null +++ b/addons/xterm-addon-canvas/webpack.config.js @@ -0,0 +1,39 @@ +/** + * Copyright (c) 2019 The xterm.js authors. All rights reserved. + * @license MIT + */ + +const path = require('path'); + +const addonName = 'CanvasAddon'; +const mainFile = 'xterm-addon-canvas.js'; + +module.exports = { + entry: `./out/${addonName}.js`, + devtool: 'source-map', + module: { + rules: [ + { + test: /\.js$/, + use: ["source-map-loader"], + enforce: "pre", + exclude: /node_modules/ + } + ] + }, + resolve: { + modules: ['./node_modules'], + extensions: [ '.js' ], + alias: { + common: path.resolve('../../out/common'), + browser: path.resolve('../../out/browser') + } + }, + output: { + filename: mainFile, + path: path.resolve('./lib'), + library: addonName, + libraryTarget: 'umd' + }, + mode: 'production' +}; From 1c6ce99419d5f466cb5b6de7813a176520363973 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 27 Jul 2022 10:26:02 -0700 Subject: [PATCH 29/42] Move canvas renderer contents into addon --- .../src}/BaseRenderLayer.ts | 13 +- .../src}/CursorRenderLayer.ts | 2 +- .../xterm-addon-canvas/src}/CustomGlyphs.ts | 0 .../xterm-addon-canvas/src}/GridCache.test.ts | 2 +- .../xterm-addon-canvas/src}/GridCache.ts | 0 .../src}/LinkRenderLayer.ts | 4 +- .../xterm-addon-canvas/src}/Renderer.ts | 13 +- .../src}/SelectionRenderLayer.ts | 2 +- .../src}/TextRenderLayer.ts | 4 +- addons/xterm-addon-canvas/src/Types.d.ts | 110 +++ .../src}/atlas/BaseCharAtlas.ts | 2 +- .../src}/atlas/CharAtlasCache.ts | 8 +- .../src}/atlas/CharAtlasUtils.ts | 2 +- .../src}/atlas/DynamicCharAtlas.ts | 8 +- .../src}/atlas/LRUMap.test.ts | 2 +- .../xterm-addon-canvas/src}/atlas/LRUMap.ts | 0 .../xterm-addon-canvas/src}/atlas/Types.d.ts | 0 addons/xterm-addon-canvas/src/tsconfig.json | 2 + addons/xterm-addon-webgl/src/CustomGlyphs.ts | 646 ++++++++++++++++++ .../src/atlas/WebglCharAtlas.ts | 6 +- .../src/renderLayer/BaseRenderLayer.ts | 2 +- .../src/renderLayer/LinkRenderLayer.ts | 2 +- addons/xterm-addon-webgl/src/tsconfig.json | 2 + demo/client.ts | 3 + src/browser/Terminal.ts | 3 +- src/browser/renderer/{atlas => }/Constants.ts | 3 +- src/browser/renderer/Types.d.ts | 53 -- src/browser/renderer/dom/DomRenderer.ts | 2 +- .../renderer/dom/DomRendererRowFactory.ts | 6 +- tsconfig.all.json | 1 + 30 files changed, 807 insertions(+), 96 deletions(-) rename {src/browser/renderer => addons/xterm-addon-canvas/src}/BaseRenderLayer.ts (98%) rename {src/browser/renderer => addons/xterm-addon-canvas/src}/CursorRenderLayer.ts (99%) rename {src/browser/renderer => addons/xterm-addon-canvas/src}/CustomGlyphs.ts (100%) rename {src/browser/renderer => addons/xterm-addon-canvas/src}/GridCache.test.ts (96%) rename {src/browser/renderer => addons/xterm-addon-canvas/src}/GridCache.ts (100%) rename {src/browser/renderer => addons/xterm-addon-canvas/src}/LinkRenderLayer.ts (94%) rename {src/browser/renderer => addons/xterm-addon-canvas/src}/Renderer.ts (94%) rename {src/browser/renderer => addons/xterm-addon-canvas/src}/SelectionRenderLayer.ts (98%) rename {src/browser/renderer => addons/xterm-addon-canvas/src}/TextRenderLayer.ts (98%) create mode 100644 addons/xterm-addon-canvas/src/Types.d.ts rename {src/browser/renderer => addons/xterm-addon-canvas/src}/atlas/BaseCharAtlas.ts (96%) rename {src/browser/renderer => addons/xterm-addon-canvas/src}/atlas/CharAtlasCache.ts (89%) rename {src/browser/renderer => addons/xterm-addon-canvas/src}/atlas/CharAtlasUtils.ts (96%) rename {src/browser/renderer => addons/xterm-addon-canvas/src}/atlas/DynamicCharAtlas.ts (98%) rename {src/browser/renderer => addons/xterm-addon-canvas/src}/atlas/LRUMap.test.ts (96%) rename {src/browser/renderer => addons/xterm-addon-canvas/src}/atlas/LRUMap.ts (100%) rename {src/browser/renderer => addons/xterm-addon-canvas/src}/atlas/Types.d.ts (100%) create mode 100644 addons/xterm-addon-webgl/src/CustomGlyphs.ts rename src/browser/renderer/{atlas => }/Constants.ts (93%) diff --git a/src/browser/renderer/BaseRenderLayer.ts b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts similarity index 98% rename from src/browser/renderer/BaseRenderLayer.ts rename to addons/xterm-addon-canvas/src/BaseRenderLayer.ts index 0a9b8057..54038cc0 100644 --- a/src/browser/renderer/BaseRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts @@ -3,13 +3,14 @@ * @license MIT */ -import { IRenderDimensions, IRenderLayer } from 'browser/renderer/Types'; +import { IRenderDimensions } from 'browser/renderer/Types'; +import { IRenderLayer } from './Types'; import { ICellData, IColor } from 'common/Types'; import { DEFAULT_COLOR, WHITESPACE_CELL_CHAR, WHITESPACE_CELL_CODE, Attributes } from 'common/buffer/Constants'; -import { IGlyphIdentifier } from 'browser/renderer/atlas/Types'; -import { DIM_OPACITY, INVERTED_DEFAULT_COLOR, TEXT_BASELINE } from 'browser/renderer/atlas/Constants'; -import { BaseCharAtlas } from 'browser/renderer/atlas/BaseCharAtlas'; -import { acquireCharAtlas } from 'browser/renderer/atlas/CharAtlasCache'; +import { IGlyphIdentifier } from './atlas/Types'; +import { DIM_OPACITY, INVERTED_DEFAULT_COLOR, TEXT_BASELINE } from 'browser/renderer/Constants'; +import { BaseCharAtlas } from './atlas/BaseCharAtlas'; +import { acquireCharAtlas } from './atlas/CharAtlasCache'; import { AttributeData } from 'common/buffer/AttributeData'; import { IColorSet } from 'browser/Types'; import { CellData } from 'common/buffer/CellData'; @@ -17,7 +18,7 @@ import { IBufferService, IDecorationService, IOptionsService } from 'common/serv import { excludeFromContrastRatioDemands, throwIfFalsy } from 'browser/renderer/RendererUtils'; import { channels, color, rgba } from 'common/Color'; import { removeElementFromParent } from 'browser/Dom'; -import { tryDrawCustomChar } from 'browser/renderer/CustomGlyphs'; +import { tryDrawCustomChar } from './CustomGlyphs'; export abstract class BaseRenderLayer implements IRenderLayer { private _canvas: HTMLCanvasElement; diff --git a/src/browser/renderer/CursorRenderLayer.ts b/addons/xterm-addon-canvas/src/CursorRenderLayer.ts similarity index 99% rename from src/browser/renderer/CursorRenderLayer.ts rename to addons/xterm-addon-canvas/src/CursorRenderLayer.ts index 3fa576a9..60c1301d 100644 --- a/src/browser/renderer/CursorRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/CursorRenderLayer.ts @@ -4,7 +4,7 @@ */ import { IRenderDimensions, IRequestRedrawEvent } from 'browser/renderer/Types'; -import { BaseRenderLayer } from 'browser/renderer/BaseRenderLayer'; +import { BaseRenderLayer } from './BaseRenderLayer'; import { ICellData } from 'common/Types'; import { CellData } from 'common/buffer/CellData'; import { IColorSet } from 'browser/Types'; diff --git a/src/browser/renderer/CustomGlyphs.ts b/addons/xterm-addon-canvas/src/CustomGlyphs.ts similarity index 100% rename from src/browser/renderer/CustomGlyphs.ts rename to addons/xterm-addon-canvas/src/CustomGlyphs.ts diff --git a/src/browser/renderer/GridCache.test.ts b/addons/xterm-addon-canvas/src/GridCache.test.ts similarity index 96% rename from src/browser/renderer/GridCache.test.ts rename to addons/xterm-addon-canvas/src/GridCache.test.ts index 30d22e81..c4b1c220 100644 --- a/src/browser/renderer/GridCache.test.ts +++ b/addons/xterm-addon-canvas/src/GridCache.test.ts @@ -4,7 +4,7 @@ */ import { assert } from 'chai'; -import { GridCache } from 'browser/renderer/GridCache'; +import { GridCache } from './GridCache'; describe('GridCache', () => { let grid: GridCache; diff --git a/src/browser/renderer/GridCache.ts b/addons/xterm-addon-canvas/src/GridCache.ts similarity index 100% rename from src/browser/renderer/GridCache.ts rename to addons/xterm-addon-canvas/src/GridCache.ts diff --git a/src/browser/renderer/LinkRenderLayer.ts b/addons/xterm-addon-canvas/src/LinkRenderLayer.ts similarity index 94% rename from src/browser/renderer/LinkRenderLayer.ts rename to addons/xterm-addon-canvas/src/LinkRenderLayer.ts index f1a9b13e..f92f6d54 100644 --- a/src/browser/renderer/LinkRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/LinkRenderLayer.ts @@ -5,8 +5,8 @@ import { IRenderDimensions } from 'browser/renderer/Types'; import { BaseRenderLayer } from './BaseRenderLayer'; -import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants'; -import { is256Color } from 'browser/renderer/atlas/CharAtlasUtils'; +import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/Constants'; +import { is256Color } from './atlas/CharAtlasUtils'; import { IColorSet, ILinkifierEvent, ILinkifier2 } from 'browser/Types'; import { IBufferService, IDecorationService, IOptionsService } from 'common/services/Services'; diff --git a/src/browser/renderer/Renderer.ts b/addons/xterm-addon-canvas/src/Renderer.ts similarity index 94% rename from src/browser/renderer/Renderer.ts rename to addons/xterm-addon-canvas/src/Renderer.ts index 66597931..629f69f4 100644 --- a/src/browser/renderer/Renderer.ts +++ b/addons/xterm-addon-canvas/src/Renderer.ts @@ -3,16 +3,17 @@ * @license MIT */ -import { TextRenderLayer } from 'browser/renderer/TextRenderLayer'; -import { SelectionRenderLayer } from 'browser/renderer/SelectionRenderLayer'; -import { CursorRenderLayer } from 'browser/renderer/CursorRenderLayer'; -import { IRenderLayer, IRenderer, IRenderDimensions, IRequestRedrawEvent } from 'browser/renderer/Types'; -import { LinkRenderLayer } from 'browser/renderer/LinkRenderLayer'; +import { TextRenderLayer } from './TextRenderLayer'; +import { SelectionRenderLayer } from './SelectionRenderLayer'; +import { CursorRenderLayer } from './CursorRenderLayer'; +import { IRenderer, IRenderDimensions, IRequestRedrawEvent } from 'browser/renderer/Types'; +import { IRenderLayer } from './Types'; +import { LinkRenderLayer } from './LinkRenderLayer'; import { Disposable } from 'common/Lifecycle'; import { IColorSet, ILinkifier2 } from 'browser/Types'; import { ICharSizeService } from 'browser/services/Services'; import { IBufferService, IOptionsService, IInstantiationService } from 'common/services/Services'; -import { removeTerminalFromCache } from 'browser/renderer/atlas/CharAtlasCache'; +import { removeTerminalFromCache } from './atlas/CharAtlasCache'; import { EventEmitter, IEvent } from 'common/EventEmitter'; let nextRendererId = 1; diff --git a/src/browser/renderer/SelectionRenderLayer.ts b/addons/xterm-addon-canvas/src/SelectionRenderLayer.ts similarity index 98% rename from src/browser/renderer/SelectionRenderLayer.ts rename to addons/xterm-addon-canvas/src/SelectionRenderLayer.ts index ce4fe071..2fa82c0d 100644 --- a/src/browser/renderer/SelectionRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/SelectionRenderLayer.ts @@ -4,7 +4,7 @@ */ import { IRenderDimensions } from 'browser/renderer/Types'; -import { BaseRenderLayer } from 'browser/renderer/BaseRenderLayer'; +import { BaseRenderLayer } from './BaseRenderLayer'; import { IColorSet } from 'browser/Types'; import { IBufferService, IDecorationService, IOptionsService } from 'common/services/Services'; diff --git a/src/browser/renderer/TextRenderLayer.ts b/addons/xterm-addon-canvas/src/TextRenderLayer.ts similarity index 98% rename from src/browser/renderer/TextRenderLayer.ts rename to addons/xterm-addon-canvas/src/TextRenderLayer.ts index ef5a9b62..ea5fea0b 100644 --- a/src/browser/renderer/TextRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/TextRenderLayer.ts @@ -5,8 +5,8 @@ import { IRenderDimensions } from 'browser/renderer/Types'; import { CharData, ICellData } from 'common/Types'; -import { GridCache } from 'browser/renderer/GridCache'; -import { BaseRenderLayer } from 'browser/renderer/BaseRenderLayer'; +import { GridCache } from './GridCache'; +import { BaseRenderLayer } from './BaseRenderLayer'; import { AttributeData } from 'common/buffer/AttributeData'; import { NULL_CELL_CODE, Content } from 'common/buffer/Constants'; import { IColorSet } from 'browser/Types'; diff --git a/addons/xterm-addon-canvas/src/Types.d.ts b/addons/xterm-addon-canvas/src/Types.d.ts new file mode 100644 index 00000000..6f5aff85 --- /dev/null +++ b/addons/xterm-addon-canvas/src/Types.d.ts @@ -0,0 +1,110 @@ +/** + * Copyright (c) 2019 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { IDisposable } from 'common/Types'; +import { IColorSet } from 'browser/Types'; +import { IEvent } from 'common/EventEmitter'; + +// TODO: Use core interfaces +export interface IRenderDimensions { + scaledCharWidth: number; + scaledCharHeight: number; + scaledCellWidth: number; + scaledCellHeight: number; + scaledCharLeft: number; + scaledCharTop: number; + scaledCanvasWidth: number; + scaledCanvasHeight: number; + canvasWidth: number; + canvasHeight: number; + actualCellWidth: number; + actualCellHeight: number; +} + +export interface IRequestRedrawEvent { + start: number; + end: number; +} + +/** + * Note that IRenderer implementations should emit the refresh event after + * rendering rows to the screen. + */ +export interface IRenderer extends IDisposable { + readonly dimensions: IRenderDimensions; + + /** + * Fires when the renderer is requesting to be redrawn on the next animation + * frame but is _not_ a result of content changing (eg. selection changes). + */ + readonly onRequestRedraw: IEvent; + + dispose(): void; + setColors(colors: IColorSet): void; + onDevicePixelRatioChange(): void; + onResize(cols: number, rows: number): void; + onCharSizeChanged(): void; + onBlur(): void; + onFocus(): void; + onSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void; + onCursorMove(): void; + onOptionsChanged(): void; + clear(): void; + renderRows(start: number, end: number): void; + clearTextureAtlas?(): void; +} + +export interface IRenderLayer extends IDisposable { + /** + * Called when the terminal loses focus. + */ + onBlur(): void; + + /** + * * Called when the terminal gets focus. + */ + onFocus(): void; + + /** + * Called when the cursor is moved. + */ + onCursorMove(): void; + + /** + * Called when options change. + */ + onOptionsChanged(): void; + + /** + * Called when the theme changes. + */ + setColors(colorSet: IColorSet): void; + + /** + * Called when the data in the grid has changed (or needs to be rendered + * again). + */ + onGridChanged(startRow: number, endRow: number): void; + + /** + * Calls when the selection changes. + */ + onSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void; + + /** + * Resize the render layer. + */ + resize(dim: IRenderDimensions): void; + + /** + * Clear the state of the render layer. + */ + reset(): void; + + /** + * Clears the texture atlas. + */ + clearTextureAtlas(): void; +} diff --git a/src/browser/renderer/atlas/BaseCharAtlas.ts b/addons/xterm-addon-canvas/src/atlas/BaseCharAtlas.ts similarity index 96% rename from src/browser/renderer/atlas/BaseCharAtlas.ts rename to addons/xterm-addon-canvas/src/atlas/BaseCharAtlas.ts index 83c30d2f..03cf0285 100644 --- a/src/browser/renderer/atlas/BaseCharAtlas.ts +++ b/addons/xterm-addon-canvas/src/atlas/BaseCharAtlas.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { IGlyphIdentifier } from 'browser/renderer/atlas/Types'; +import { IGlyphIdentifier } from './Types'; import { IDisposable } from 'common/Types'; export abstract class BaseCharAtlas implements IDisposable { diff --git a/src/browser/renderer/atlas/CharAtlasCache.ts b/addons/xterm-addon-canvas/src/atlas/CharAtlasCache.ts similarity index 89% rename from src/browser/renderer/atlas/CharAtlasCache.ts rename to addons/xterm-addon-canvas/src/atlas/CharAtlasCache.ts index 257835ba..7d020dcc 100644 --- a/src/browser/renderer/atlas/CharAtlasCache.ts +++ b/addons/xterm-addon-canvas/src/atlas/CharAtlasCache.ts @@ -3,10 +3,10 @@ * @license MIT */ -import { generateConfig, configEquals } from 'browser/renderer/atlas/CharAtlasUtils'; -import { BaseCharAtlas } from 'browser/renderer/atlas/BaseCharAtlas'; -import { DynamicCharAtlas } from 'browser/renderer/atlas/DynamicCharAtlas'; -import { ICharAtlasConfig } from 'browser/renderer/atlas/Types'; +import { generateConfig, configEquals } from './CharAtlasUtils'; +import { BaseCharAtlas } from './BaseCharAtlas'; +import { DynamicCharAtlas } from './DynamicCharAtlas'; +import { ICharAtlasConfig } from './Types'; import { IColorSet } from 'browser/Types'; import { ITerminalOptions } from 'common/services/Services'; diff --git a/src/browser/renderer/atlas/CharAtlasUtils.ts b/addons/xterm-addon-canvas/src/atlas/CharAtlasUtils.ts similarity index 96% rename from src/browser/renderer/atlas/CharAtlasUtils.ts rename to addons/xterm-addon-canvas/src/atlas/CharAtlasUtils.ts index 696c6c12..4c84c86a 100644 --- a/src/browser/renderer/atlas/CharAtlasUtils.ts +++ b/addons/xterm-addon-canvas/src/atlas/CharAtlasUtils.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { ICharAtlasConfig } from 'browser/renderer/atlas/Types'; +import { ICharAtlasConfig } from './Types'; import { DEFAULT_COLOR } from 'common/buffer/Constants'; import { IColorSet, IPartialColorSet } from 'browser/Types'; import { ITerminalOptions } from 'common/services/Services'; diff --git a/src/browser/renderer/atlas/DynamicCharAtlas.ts b/addons/xterm-addon-canvas/src/atlas/DynamicCharAtlas.ts similarity index 98% rename from src/browser/renderer/atlas/DynamicCharAtlas.ts rename to addons/xterm-addon-canvas/src/atlas/DynamicCharAtlas.ts index 59069879..f5fc2c99 100644 --- a/src/browser/renderer/atlas/DynamicCharAtlas.ts +++ b/addons/xterm-addon-canvas/src/atlas/DynamicCharAtlas.ts @@ -3,11 +3,11 @@ * @license MIT */ -import { DIM_OPACITY, INVERTED_DEFAULT_COLOR, TEXT_BASELINE } from 'browser/renderer/atlas/Constants'; -import { IGlyphIdentifier, ICharAtlasConfig } from 'browser/renderer/atlas/Types'; -import { BaseCharAtlas } from 'browser/renderer/atlas/BaseCharAtlas'; +import { DIM_OPACITY, INVERTED_DEFAULT_COLOR, TEXT_BASELINE } from 'browser/renderer/Constants'; +import { IGlyphIdentifier, ICharAtlasConfig } from './Types'; +import { BaseCharAtlas } from './BaseCharAtlas'; import { DEFAULT_ANSI_COLORS } from 'browser/ColorManager'; -import { LRUMap } from 'browser/renderer/atlas/LRUMap'; +import { LRUMap } from './LRUMap'; import { isFirefox, isSafari } from 'common/Platform'; import { IColor } from 'common/Types'; import { throwIfFalsy } from 'browser/renderer/RendererUtils'; diff --git a/src/browser/renderer/atlas/LRUMap.test.ts b/addons/xterm-addon-canvas/src/atlas/LRUMap.test.ts similarity index 96% rename from src/browser/renderer/atlas/LRUMap.test.ts rename to addons/xterm-addon-canvas/src/atlas/LRUMap.test.ts index 792f85a7..b24ad2af 100644 --- a/src/browser/renderer/atlas/LRUMap.test.ts +++ b/addons/xterm-addon-canvas/src/atlas/LRUMap.test.ts @@ -4,7 +4,7 @@ */ import { assert } from 'chai'; -import { LRUMap } from 'browser/renderer/atlas/LRUMap'; +import { LRUMap } from './LRUMap'; describe('LRUMap', () => { it('can be used to store and retrieve values', () => { diff --git a/src/browser/renderer/atlas/LRUMap.ts b/addons/xterm-addon-canvas/src/atlas/LRUMap.ts similarity index 100% rename from src/browser/renderer/atlas/LRUMap.ts rename to addons/xterm-addon-canvas/src/atlas/LRUMap.ts diff --git a/src/browser/renderer/atlas/Types.d.ts b/addons/xterm-addon-canvas/src/atlas/Types.d.ts similarity index 100% rename from src/browser/renderer/atlas/Types.d.ts rename to addons/xterm-addon-canvas/src/atlas/Types.d.ts diff --git a/addons/xterm-addon-canvas/src/tsconfig.json b/addons/xterm-addon-canvas/src/tsconfig.json index b0c9f6be..1ce2a5e3 100644 --- a/addons/xterm-addon-canvas/src/tsconfig.json +++ b/addons/xterm-addon-canvas/src/tsconfig.json @@ -5,6 +5,7 @@ "lib": [ "dom", "es6", + "ES2017.Object" ], "rootDir": ".", "outDir": "../out", @@ -21,6 +22,7 @@ }, "strict": true, "downlevelIteration": true, + "experimentalDecorators": true, "types": [ "../../../node_modules/@types/mocha" ] diff --git a/addons/xterm-addon-webgl/src/CustomGlyphs.ts b/addons/xterm-addon-webgl/src/CustomGlyphs.ts new file mode 100644 index 00000000..e6adf5d1 --- /dev/null +++ b/addons/xterm-addon-webgl/src/CustomGlyphs.ts @@ -0,0 +1,646 @@ +/** + * Copyright (c) 2021 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { throwIfFalsy } from 'browser/renderer/RendererUtils'; + +interface IBlockVector { + x: number; + y: number; + w: number; + h: number; +} + +export const blockElementDefinitions: { [index: string]: IBlockVector[] | undefined } = { + // Block elements (0x2580-0x2590) + '▀': [{ x: 0, y: 0, w: 8, h: 4 }], // UPPER HALF BLOCK + '▁': [{ x: 0, y: 7, w: 8, h: 1 }], // LOWER ONE EIGHTH BLOCK + '▂': [{ x: 0, y: 6, w: 8, h: 2 }], // LOWER ONE QUARTER BLOCK + '▃': [{ x: 0, y: 5, w: 8, h: 3 }], // LOWER THREE EIGHTHS BLOCK + '▄': [{ x: 0, y: 4, w: 8, h: 4 }], // LOWER HALF BLOCK + '▅': [{ x: 0, y: 3, w: 8, h: 5 }], // LOWER FIVE EIGHTHS BLOCK + '▆': [{ x: 0, y: 2, w: 8, h: 6 }], // LOWER THREE QUARTERS BLOCK + '▇': [{ x: 0, y: 1, w: 8, h: 7 }], // LOWER SEVEN EIGHTHS BLOCK + '█': [{ x: 0, y: 0, w: 8, h: 8 }], // FULL BLOCK + '▉': [{ x: 0, y: 0, w: 7, h: 8 }], // LEFT SEVEN EIGHTHS BLOCK + '▊': [{ x: 0, y: 0, w: 6, h: 8 }], // LEFT THREE QUARTERS BLOCK + '▋': [{ x: 0, y: 0, w: 5, h: 8 }], // LEFT FIVE EIGHTHS BLOCK + '▌': [{ x: 0, y: 0, w: 4, h: 8 }], // LEFT HALF BLOCK + '▍': [{ x: 0, y: 0, w: 3, h: 8 }], // LEFT THREE EIGHTHS BLOCK + '▎': [{ x: 0, y: 0, w: 2, h: 8 }], // LEFT ONE QUARTER BLOCK + '▏': [{ x: 0, y: 0, w: 1, h: 8 }], // LEFT ONE EIGHTH BLOCK + '▐': [{ x: 4, y: 0, w: 4, h: 8 }], // RIGHT HALF BLOCK + + // Block elements (0x2594-0x2595) + '▔': [{ x: 0, y: 0, w: 9, h: 1 }], // UPPER ONE EIGHTH BLOCK + '▕': [{ x: 7, y: 0, w: 1, h: 8 }], // RIGHT ONE EIGHTH BLOCK + + // Terminal graphic characters (0x2596-0x259F) + '▖': [{ x: 0, y: 4, w: 4, h: 4 }], // QUADRANT LOWER LEFT + '▗': [{ x: 4, y: 4, w: 4, h: 4 }], // QUADRANT LOWER RIGHT + '▘': [{ x: 0, y: 0, w: 4, h: 4 }], // QUADRANT UPPER LEFT + '▙': [{ x: 0, y: 0, w: 4, h: 8 }, { x: 0, y: 4, w: 8, h: 4 }], // QUADRANT UPPER LEFT AND LOWER LEFT AND LOWER RIGHT + '▚': [{ x: 0, y: 0, w: 4, h: 4 }, { x: 4, y: 4, w: 4, h: 4 }], // QUADRANT UPPER LEFT AND LOWER RIGHT + '▛': [{ x: 0, y: 0, w: 4, h: 8 }, { x: 4, y: 0, w: 4, h: 4 }], // QUADRANT UPPER LEFT AND UPPER RIGHT AND LOWER LEFT + '▜': [{ x: 0, y: 0, w: 8, h: 4 }, { x: 4, y: 0, w: 4, h: 8 }], // QUADRANT UPPER LEFT AND UPPER RIGHT AND LOWER RIGHT + '▝': [{ x: 4, y: 0, w: 4, h: 4 }], // QUADRANT UPPER RIGHT + '▞': [{ x: 4, y: 0, w: 4, h: 4 }, { x: 0, y: 4, w: 4, h: 4 }], // QUADRANT UPPER RIGHT AND LOWER LEFT + '▟': [{ x: 4, y: 0, w: 4, h: 8 }, { x: 0, y: 4, w: 8, h: 4 }], // QUADRANT UPPER RIGHT AND LOWER LEFT AND LOWER RIGHT + + // VERTICAL ONE EIGHTH BLOCK-2 through VERTICAL ONE EIGHTH BLOCK-7 + '\u{1FB70}': [{ x: 1, y: 0, w: 1, h: 8 }], + '\u{1FB71}': [{ x: 2, y: 0, w: 1, h: 8 }], + '\u{1FB72}': [{ x: 3, y: 0, w: 1, h: 8 }], + '\u{1FB73}': [{ x: 4, y: 0, w: 1, h: 8 }], + '\u{1FB74}': [{ x: 5, y: 0, w: 1, h: 8 }], + '\u{1FB75}': [{ x: 6, y: 0, w: 1, h: 8 }], + + // HORIZONTAL ONE EIGHTH BLOCK-2 through HORIZONTAL ONE EIGHTH BLOCK-7 + '\u{1FB76}': [{ x: 0, y: 1, w: 8, h: 1 }], + '\u{1FB77}': [{ x: 0, y: 2, w: 8, h: 1 }], + '\u{1FB78}': [{ x: 0, y: 3, w: 8, h: 1 }], + '\u{1FB79}': [{ x: 0, y: 4, w: 8, h: 1 }], + '\u{1FB7A}': [{ x: 0, y: 5, w: 8, h: 1 }], + '\u{1FB7B}': [{ x: 0, y: 6, w: 8, h: 1 }], + + // LEFT AND LOWER ONE EIGHTH BLOCK + '\u{1FB7C}': [{ x: 0, y: 0, w: 1, h: 8 }, { x: 0, y: 7, w: 8, h: 1 }], + // LEFT AND UPPER ONE EIGHTH BLOCK + '\u{1FB7D}': [{ x: 0, y: 0, w: 1, h: 8 }, { x: 0, y: 0, w: 8, h: 1 }], + // RIGHT AND UPPER ONE EIGHTH BLOCK + '\u{1FB7E}': [{ x: 7, y: 0, w: 1, h: 8 }, { x: 0, y: 0, w: 8, h: 1 }], + // RIGHT AND LOWER ONE EIGHTH BLOCK + '\u{1FB7F}': [{ x: 7, y: 0, w: 1, h: 8 }, { x: 0, y: 7, w: 8, h: 1 }], + // UPPER AND LOWER ONE EIGHTH BLOCK + '\u{1FB80}': [{ x: 0, y: 0, w: 8, h: 1 }, { x: 0, y: 7, w: 8, h: 1 }], + // HORIZONTAL ONE EIGHTH BLOCK-1358 + '\u{1FB81}': [{ x: 0, y: 0, w: 8, h: 1 }, { x: 0, y: 2, w: 8, h: 1 }, { x: 0, y: 4, w: 8, h: 1 }, { x: 0, y: 7, w: 8, h: 1 }], + + // UPPER ONE QUARTER BLOCK + '\u{1FB82}': [{ x: 0, y: 0, w: 8, h: 2 }], + // UPPER THREE EIGHTHS BLOCK + '\u{1FB83}': [{ x: 0, y: 0, w: 8, h: 3 }], + // UPPER FIVE EIGHTHS BLOCK + '\u{1FB84}': [{ x: 0, y: 0, w: 8, h: 5 }], + // UPPER THREE QUARTERS BLOCK + '\u{1FB85}': [{ x: 0, y: 0, w: 8, h: 6 }], + // UPPER SEVEN EIGHTHS BLOCK + '\u{1FB86}': [{ x: 0, y: 0, w: 8, h: 7 }], + + // RIGHT ONE QUARTER BLOCK + '\u{1FB87}': [{ x: 6, y: 0, w: 2, h: 8 }], + // RIGHT THREE EIGHTHS B0OCK + '\u{1FB88}': [{ x: 5, y: 0, w: 3, h: 8 }], + // RIGHT FIVE EIGHTHS BL0CK + '\u{1FB89}': [{ x: 3, y: 0, w: 5, h: 8 }], + // RIGHT THREE QUARTERS 0LOCK + '\u{1FB8A}': [{ x: 2, y: 0, w: 6, h: 8 }], + // RIGHT SEVEN EIGHTHS B0OCK + '\u{1FB8B}': [{ x: 1, y: 0, w: 7, h: 8 }], + + // CHECKER BOARD FILL + '\u{1FB95}': [ + { x: 0, y: 0, w: 2, h: 2 }, { x: 4, y: 0, w: 2, h: 2 }, + { x: 2, y: 2, w: 2, h: 2 }, { x: 6, y: 2, w: 2, h: 2 }, + { x: 0, y: 4, w: 2, h: 2 }, { x: 4, y: 4, w: 2, h: 2 }, + { x: 2, y: 6, w: 2, h: 2 }, { x: 6, y: 6, w: 2, h: 2 } + ], + // INVERSE CHECKER BOARD FILL + '\u{1FB96}': [ + { x: 2, y: 0, w: 2, h: 2 }, { x: 6, y: 0, w: 2, h: 2 }, + { x: 0, y: 2, w: 2, h: 2 }, { x: 4, y: 2, w: 2, h: 2 }, + { x: 2, y: 4, w: 2, h: 2 }, { x: 6, y: 4, w: 2, h: 2 }, + { x: 0, y: 6, w: 2, h: 2 }, { x: 4, y: 6, w: 2, h: 2 } + ], + // HEAVY HORIZONTAL FILL (upper middle and lower one quarter block) + '\u{1FB97}': [{ x: 0, y: 2, w: 8, h: 2 }, { x: 0, y: 6, w: 8, h: 2 }] +}; + +type PatternDefinition = number[][]; + +/** + * Defines the repeating pattern used by special characters, the pattern is made up of a 2d array of + * pixel values to be filled (1) or not filled (0). + */ +const patternCharacterDefinitions: { [key: string]: PatternDefinition | undefined } = { + // Shade characters (0x2591-0x2593) + '░': [ // LIGHT SHADE (25%) + [1, 0, 0, 0], + [0, 0, 0, 0], + [0, 0, 1, 0], + [0, 0, 0, 0] + ], + '▒': [ // MEDIUM SHADE (50%) + [1, 0], + [0, 0], + [0, 1], + [0, 0] + ], + '▓': [ // DARK SHADE (75%) + [0, 1], + [1, 1], + [1, 0], + [1, 1] + ] +}; + +const enum Shapes { + /** │ */ TOP_TO_BOTTOM = 'M.5,0 L.5,1', + /** ─ */ LEFT_TO_RIGHT = 'M0,.5 L1,.5', + + /** └ */ TOP_TO_RIGHT = 'M.5,0 L.5,.5 L1,.5', + /** ┘ */ TOP_TO_LEFT = 'M.5,0 L.5,.5 L0,.5', + /** ┐ */ LEFT_TO_BOTTOM = 'M0,.5 L.5,.5 L.5,1', + /** ┌ */ RIGHT_TO_BOTTOM = 'M0.5,1 L.5,.5 L1,.5', + + /** ╵ */ MIDDLE_TO_TOP = 'M.5,.5 L.5,0', + /** ╴ */ MIDDLE_TO_LEFT = 'M.5,.5 L0,.5', + /** ╶ */ MIDDLE_TO_RIGHT = 'M.5,.5 L1,.5', + /** ╷ */ MIDDLE_TO_BOTTOM = 'M.5,.5 L.5,1', + + /** ┴ */ T_TOP = 'M0,.5 L1,.5 M.5,.5 L.5,0', + /** ┤ */ T_LEFT = 'M.5,0 L.5,1 M.5,.5 L0,.5', + /** ├ */ T_RIGHT = 'M.5,0 L.5,1 M.5,.5 L1,.5', + /** ┬ */ T_BOTTOM = 'M0,.5 L1,.5 M.5,.5 L.5,1', + + /** ┼ */ CROSS = 'M0,.5 L1,.5 M.5,0 L.5,1', + + /** ╌ */ TWO_DASHES_HORIZONTAL = 'M.1,.5 L.4,.5 M.6,.5 L.9,.5', // .2 empty, .3 filled + /** ┄ */ THREE_DASHES_HORIZONTAL = 'M.0667,.5 L.2667,.5 M.4,.5 L.6,.5 M.7333,.5 L.9333,.5', // .1333 empty, .2 filled + /** ┉ */ FOUR_DASHES_HORIZONTAL = 'M.05,.5 L.2,.5 M.3,.5 L.45,.5 M.55,.5 L.7,.5 M.8,.5 L.95,.5', // .1 empty, .15 filled + /** ╎ */ TWO_DASHES_VERTICAL = 'M.5,.1 L.5,.4 M.5,.6 L.5,.9', + /** ┆ */ THREE_DASHES_VERTICAL = 'M.5,.0667 L.5,.2667 M.5,.4 L.5,.6 M.5,.7333 L.5,.9333', + /** ┊ */ FOUR_DASHES_VERTICAL = 'M.5,.05 L.5,.2 M.5,.3 L.5,.45 L.5,.55 M.5,.7 L.5,.95', +} + +const enum Style { + NORMAL = 1, + BOLD = 3 +} + +/** + * @param xp The percentage of 15% of the x axis. + * @param yp The percentage of 15% of the x axis on the y axis. + */ +type DrawFunctionDefinition = (xp: number, yp: number) => string; + +/** + * This contains the definitions of all box drawing characters in the format of SVG paths (ie. the + * svg d attribute). + */ +export const boxDrawingDefinitions: { [character: string]: { [fontWeight: number]: string | DrawFunctionDefinition } | undefined } = { + // Uniform normal and bold + '─': { [Style.NORMAL]: Shapes.LEFT_TO_RIGHT }, + '━': { [Style.BOLD]: Shapes.LEFT_TO_RIGHT }, + '│': { [Style.NORMAL]: Shapes.TOP_TO_BOTTOM }, + '┃': { [Style.BOLD]: Shapes.TOP_TO_BOTTOM }, + '┌': { [Style.NORMAL]: Shapes.RIGHT_TO_BOTTOM }, + '┏': { [Style.BOLD]: Shapes.RIGHT_TO_BOTTOM }, + '┐': { [Style.NORMAL]: Shapes.LEFT_TO_BOTTOM }, + '┓': { [Style.BOLD]: Shapes.LEFT_TO_BOTTOM }, + '└': { [Style.NORMAL]: Shapes.TOP_TO_RIGHT }, + '┗': { [Style.BOLD]: Shapes.TOP_TO_RIGHT }, + '┘': { [Style.NORMAL]: Shapes.TOP_TO_LEFT }, + '┛': { [Style.BOLD]: Shapes.TOP_TO_LEFT }, + '├': { [Style.NORMAL]: Shapes.T_RIGHT }, + '┣': { [Style.BOLD]: Shapes.T_RIGHT }, + '┤': { [Style.NORMAL]: Shapes.T_LEFT }, + '┫': { [Style.BOLD]: Shapes.T_LEFT }, + '┬': { [Style.NORMAL]: Shapes.T_BOTTOM }, + '┳': { [Style.BOLD]: Shapes.T_BOTTOM }, + '┴': { [Style.NORMAL]: Shapes.T_TOP }, + '┻': { [Style.BOLD]: Shapes.T_TOP }, + '┼': { [Style.NORMAL]: Shapes.CROSS }, + '╋': { [Style.BOLD]: Shapes.CROSS }, + '╴': { [Style.NORMAL]: Shapes.MIDDLE_TO_LEFT }, + '╸': { [Style.BOLD]: Shapes.MIDDLE_TO_LEFT }, + '╵': { [Style.NORMAL]: Shapes.MIDDLE_TO_TOP }, + '╹': { [Style.BOLD]: Shapes.MIDDLE_TO_TOP }, + '╶': { [Style.NORMAL]: Shapes.MIDDLE_TO_RIGHT }, + '╺': { [Style.BOLD]: Shapes.MIDDLE_TO_RIGHT }, + '╷': { [Style.NORMAL]: Shapes.MIDDLE_TO_BOTTOM }, + '╻': { [Style.BOLD]: Shapes.MIDDLE_TO_BOTTOM }, + + // Double border + '═': { [Style.NORMAL]: (xp, yp) => `M0,${.5 - yp} L1,${.5 - yp} M0,${.5 + yp} L1,${.5 + yp}` }, + '║': { [Style.NORMAL]: (xp, yp) => `M${.5 - xp},0 L${.5 - xp},1 M${.5 + xp},0 L${.5 + xp},1` }, + '╒': { [Style.NORMAL]: (xp, yp) => `M.5,1 L.5,${.5 - yp} L1,${.5 - yp} M.5,${.5 + yp} L1,${.5 + yp}` }, + '╓': { [Style.NORMAL]: (xp, yp) => `M${.5 - xp},1 L${.5 - xp},.5 L1,.5 M${.5 + xp},.5 L${.5 + xp},1` }, + '╔': { [Style.NORMAL]: (xp, yp) => `M1,${.5 - yp} L${.5 - xp},${.5 - yp} L${.5 - xp},1 M1,${.5 + yp} L${.5 + xp},${.5 + yp} L${.5 + xp},1` }, + '╕': { [Style.NORMAL]: (xp, yp) => `M0,${.5 - yp} L.5,${.5 - yp} L.5,1 M0,${.5 + yp} L.5,${.5 + yp}` }, + '╖': { [Style.NORMAL]: (xp, yp) => `M${.5 + xp},1 L${.5 + xp},.5 L0,.5 M${.5 - xp},.5 L${.5 - xp},1` }, + '╗': { [Style.NORMAL]: (xp, yp) => `M0,${.5 + yp} L${.5 - xp},${.5 + yp} L${.5 - xp},1 M0,${.5 - yp} L${.5 + xp},${.5 - yp} L${.5 + xp},1` }, + '╘': { [Style.NORMAL]: (xp, yp) => `M.5,0 L.5,${.5 + yp} L1,${.5 + yp} M.5,${.5 - yp} L1,${.5 - yp}` }, + '╙': { [Style.NORMAL]: (xp, yp) => `M1,.5 L${.5 - xp},.5 L${.5 - xp},0 M${.5 + xp},.5 L${.5 + xp},0` }, + '╚': { [Style.NORMAL]: (xp, yp) => `M1,${.5 - yp} L${.5 + xp},${.5 - yp} L${.5 + xp},0 M1,${.5 + yp} L${.5 - xp},${.5 + yp} L${.5 - xp},0` }, + '╛': { [Style.NORMAL]: (xp, yp) => `M0,${.5 + yp} L.5,${.5 + yp} L.5,0 M0,${.5 - yp} L.5,${.5 - yp}` }, + '╜': { [Style.NORMAL]: (xp, yp) => `M0,.5 L${.5 + xp},.5 L${.5 + xp},0 M${.5 - xp},.5 L${.5 - xp},0` }, + '╝': { [Style.NORMAL]: (xp, yp) => `M0,${.5 - yp} L${.5 - xp},${.5 - yp} L${.5 - xp},0 M0,${.5 + yp} L${.5 + xp},${.5 + yp} L${.5 + xp},0` }, + '╞': { [Style.NORMAL]: (xp, yp) => `${Shapes.TOP_TO_BOTTOM} M.5,${.5 - yp} L1,${.5 - yp} M.5,${.5 + yp} L1,${.5 + yp}` }, + '╟': { [Style.NORMAL]: (xp, yp) => `M${.5 - xp},0 L${.5 - xp},1 M${.5 + xp},0 L${.5 + xp},1 M${.5 + xp},.5 L1,.5` }, + '╠': { [Style.NORMAL]: (xp, yp) => `M${.5 - xp},0 L${.5 - xp},1 M1,${.5 + yp} L${.5 + xp},${.5 + yp} L${.5 + xp},1 M1,${.5 - yp} L${.5 + xp},${.5 - yp} L${.5 + xp},0` }, + '╡': { [Style.NORMAL]: (xp, yp) => `${Shapes.TOP_TO_BOTTOM} M0,${.5 - yp} L.5,${.5 - yp} M0,${.5 + yp} L.5,${.5 + yp}` }, + '╢': { [Style.NORMAL]: (xp, yp) => `M0,.5 L${.5 - xp},.5 M${.5 - xp},0 L${.5 - xp},1 M${.5 + xp},0 L${.5 + xp},1` }, + '╣': { [Style.NORMAL]: (xp, yp) => `M${.5 + xp},0 L${.5 + xp},1 M0,${.5 + yp} L${.5 - xp},${.5 + yp} L${.5 - xp},1 M0,${.5 - yp} L${.5 - xp},${.5 - yp} L${.5 - xp},0` }, + '╤': { [Style.NORMAL]: (xp, yp) => `M0,${.5 - yp} L1,${.5 - yp} M0,${.5 + yp} L1,${.5 + yp} M.5,${.5 + yp} L.5,1` }, + '╥': { [Style.NORMAL]: (xp, yp) => `${Shapes.LEFT_TO_RIGHT} M${.5 - xp},.5 L${.5 - xp},1 M${.5 + xp},.5 L${.5 + xp},1` }, + '╦': { [Style.NORMAL]: (xp, yp) => `M0,${.5 - yp} L1,${.5 - yp} M0,${.5 + yp} L${.5 - xp},${.5 + yp} L${.5 - xp},1 M1,${.5 + yp} L${.5 + xp},${.5 + yp} L${.5 + xp},1` }, + '╧': { [Style.NORMAL]: (xp, yp) => `M.5,0 L.5,${.5 - yp} M0,${.5 - yp} L1,${.5 - yp} M0,${.5 + yp} L1,${.5 + yp}` }, + '╨': { [Style.NORMAL]: (xp, yp) => `${Shapes.LEFT_TO_RIGHT} M${.5 - xp},.5 L${.5 - xp},0 M${.5 + xp},.5 L${.5 + xp},0` }, + '╩': { [Style.NORMAL]: (xp, yp) => `M0,${.5 + yp} L1,${.5 + yp} M0,${.5 - yp} L${.5 - xp},${.5 - yp} L${.5 - xp},0 M1,${.5 - yp} L${.5 + xp},${.5 - yp} L${.5 + xp},0` }, + '╪': { [Style.NORMAL]: (xp, yp) => `${Shapes.TOP_TO_BOTTOM} M0,${.5 - yp} L1,${.5 - yp} M0,${.5 + yp} L1,${.5 + yp}` }, + '╫': { [Style.NORMAL]: (xp, yp) => `${Shapes.LEFT_TO_RIGHT} M${.5 - xp},0 L${.5 - xp},1 M${.5 + xp},0 L${.5 + xp},1` }, + '╬': { [Style.NORMAL]: (xp, yp) => `M0,${.5 + yp} L${.5 - xp},${.5 + yp} L${.5 - xp},1 M1,${.5 + yp} L${.5 + xp},${.5 + yp} L${.5 + xp},1 M0,${.5 - yp} L${.5 - xp},${.5 - yp} L${.5 - xp},0 M1,${.5 - yp} L${.5 + xp},${.5 - yp} L${.5 + xp},0` }, + + // Diagonal + '╱': { [Style.NORMAL]: 'M1,0 L0,1' }, + '╲': { [Style.NORMAL]: 'M0,0 L1,1' }, + '╳': { [Style.NORMAL]: 'M1,0 L0,1 M0,0 L1,1' }, + + // Mixed weight + '╼': { [Style.NORMAL]: Shapes.MIDDLE_TO_LEFT, [Style.BOLD]: Shapes.MIDDLE_TO_RIGHT }, + '╽': { [Style.NORMAL]: Shapes.MIDDLE_TO_TOP, [Style.BOLD]: Shapes.MIDDLE_TO_BOTTOM }, + '╾': { [Style.NORMAL]: Shapes.MIDDLE_TO_RIGHT, [Style.BOLD]: Shapes.MIDDLE_TO_LEFT }, + '╿': { [Style.NORMAL]: Shapes.MIDDLE_TO_BOTTOM, [Style.BOLD]: Shapes.MIDDLE_TO_TOP }, + '┍': { [Style.NORMAL]: Shapes.MIDDLE_TO_BOTTOM, [Style.BOLD]: Shapes.MIDDLE_TO_RIGHT }, + '┎': { [Style.NORMAL]: Shapes.MIDDLE_TO_RIGHT, [Style.BOLD]: Shapes.MIDDLE_TO_BOTTOM }, + '┑': { [Style.NORMAL]: Shapes.MIDDLE_TO_BOTTOM, [Style.BOLD]: Shapes.MIDDLE_TO_LEFT }, + '┒': { [Style.NORMAL]: Shapes.MIDDLE_TO_LEFT, [Style.BOLD]: Shapes.MIDDLE_TO_BOTTOM }, + '┕': { [Style.NORMAL]: Shapes.MIDDLE_TO_TOP, [Style.BOLD]: Shapes.MIDDLE_TO_RIGHT }, + '┖': { [Style.NORMAL]: Shapes.MIDDLE_TO_RIGHT, [Style.BOLD]: Shapes.MIDDLE_TO_TOP }, + '┙': { [Style.NORMAL]: Shapes.MIDDLE_TO_TOP, [Style.BOLD]: Shapes.MIDDLE_TO_LEFT }, + '┚': { [Style.NORMAL]: Shapes.MIDDLE_TO_LEFT, [Style.BOLD]: Shapes.MIDDLE_TO_TOP }, + '┝': { [Style.NORMAL]: Shapes.TOP_TO_BOTTOM, [Style.BOLD]: Shapes.MIDDLE_TO_RIGHT }, + '┞': { [Style.NORMAL]: Shapes.RIGHT_TO_BOTTOM, [Style.BOLD]: Shapes.MIDDLE_TO_TOP }, + '┟': { [Style.NORMAL]: Shapes.TOP_TO_RIGHT, [Style.BOLD]: Shapes.MIDDLE_TO_BOTTOM }, + '┠': { [Style.NORMAL]: Shapes.MIDDLE_TO_RIGHT, [Style.BOLD]: Shapes.TOP_TO_BOTTOM }, + '┡': { [Style.NORMAL]: Shapes.MIDDLE_TO_BOTTOM, [Style.BOLD]: Shapes.TOP_TO_RIGHT }, + '┢': { [Style.NORMAL]: Shapes.MIDDLE_TO_TOP, [Style.BOLD]: Shapes.RIGHT_TO_BOTTOM }, + '┥': { [Style.NORMAL]: Shapes.TOP_TO_BOTTOM, [Style.BOLD]: Shapes.MIDDLE_TO_LEFT }, + '┦': { [Style.NORMAL]: Shapes.LEFT_TO_BOTTOM, [Style.BOLD]: Shapes.MIDDLE_TO_TOP }, + '┧': { [Style.NORMAL]: Shapes.TOP_TO_LEFT, [Style.BOLD]: Shapes.MIDDLE_TO_BOTTOM }, + '┨': { [Style.NORMAL]: Shapes.MIDDLE_TO_LEFT, [Style.BOLD]: Shapes.TOP_TO_BOTTOM }, + '┩': { [Style.NORMAL]: Shapes.MIDDLE_TO_BOTTOM, [Style.BOLD]: Shapes.TOP_TO_LEFT }, + '┪': { [Style.NORMAL]: Shapes.MIDDLE_TO_TOP, [Style.BOLD]: Shapes.LEFT_TO_BOTTOM }, + '┭': { [Style.NORMAL]: Shapes.RIGHT_TO_BOTTOM, [Style.BOLD]: Shapes.MIDDLE_TO_LEFT }, + '┮': { [Style.NORMAL]: Shapes.LEFT_TO_BOTTOM, [Style.BOLD]: Shapes.MIDDLE_TO_RIGHT }, + '┯': { [Style.NORMAL]: Shapes.MIDDLE_TO_BOTTOM, [Style.BOLD]: Shapes.LEFT_TO_RIGHT }, + '┰': { [Style.NORMAL]: Shapes.LEFT_TO_RIGHT, [Style.BOLD]: Shapes.MIDDLE_TO_BOTTOM }, + '┱': { [Style.NORMAL]: Shapes.MIDDLE_TO_RIGHT, [Style.BOLD]: Shapes.LEFT_TO_BOTTOM }, + '┲': { [Style.NORMAL]: Shapes.MIDDLE_TO_LEFT, [Style.BOLD]: Shapes.RIGHT_TO_BOTTOM }, + '┵': { [Style.NORMAL]: Shapes.TOP_TO_RIGHT, [Style.BOLD]: Shapes.MIDDLE_TO_LEFT }, + '┶': { [Style.NORMAL]: Shapes.TOP_TO_LEFT, [Style.BOLD]: Shapes.MIDDLE_TO_RIGHT }, + '┷': { [Style.NORMAL]: Shapes.MIDDLE_TO_TOP, [Style.BOLD]: Shapes.LEFT_TO_RIGHT }, + '┸': { [Style.NORMAL]: Shapes.LEFT_TO_RIGHT, [Style.BOLD]: Shapes.MIDDLE_TO_TOP }, + '┹': { [Style.NORMAL]: Shapes.MIDDLE_TO_RIGHT, [Style.BOLD]: Shapes.TOP_TO_LEFT }, + '┺': { [Style.NORMAL]: Shapes.MIDDLE_TO_LEFT, [Style.BOLD]: Shapes.TOP_TO_RIGHT }, + '┽': { [Style.NORMAL]: `${Shapes.TOP_TO_BOTTOM} ${Shapes.MIDDLE_TO_RIGHT}`, [Style.BOLD]: Shapes.MIDDLE_TO_LEFT }, + '┾': { [Style.NORMAL]: `${Shapes.TOP_TO_BOTTOM} ${Shapes.MIDDLE_TO_LEFT}`, [Style.BOLD]: Shapes.MIDDLE_TO_RIGHT }, + '┿': { [Style.NORMAL]: Shapes.TOP_TO_BOTTOM, [Style.BOLD]: Shapes.LEFT_TO_RIGHT }, + '╀': { [Style.NORMAL]: `${Shapes.LEFT_TO_RIGHT} ${Shapes.MIDDLE_TO_BOTTOM}`, [Style.BOLD]: Shapes.MIDDLE_TO_TOP }, + '╁': { [Style.NORMAL]: `${Shapes.MIDDLE_TO_TOP} ${Shapes.LEFT_TO_RIGHT}`, [Style.BOLD]: Shapes.MIDDLE_TO_BOTTOM }, + '╂': { [Style.NORMAL]: Shapes.LEFT_TO_RIGHT, [Style.BOLD]: Shapes.TOP_TO_BOTTOM }, + '╃': { [Style.NORMAL]: Shapes.RIGHT_TO_BOTTOM, [Style.BOLD]: Shapes.TOP_TO_LEFT }, + '╄': { [Style.NORMAL]: Shapes.LEFT_TO_BOTTOM, [Style.BOLD]: Shapes.TOP_TO_RIGHT }, + '╅': { [Style.NORMAL]: Shapes.TOP_TO_RIGHT, [Style.BOLD]: Shapes.LEFT_TO_BOTTOM }, + '╆': { [Style.NORMAL]: Shapes.TOP_TO_LEFT, [Style.BOLD]: Shapes.RIGHT_TO_BOTTOM }, + '╇': { [Style.NORMAL]: Shapes.MIDDLE_TO_BOTTOM, [Style.BOLD]: `${Shapes.MIDDLE_TO_TOP} ${Shapes.LEFT_TO_RIGHT}` }, + '╈': { [Style.NORMAL]: Shapes.MIDDLE_TO_TOP, [Style.BOLD]: `${Shapes.LEFT_TO_RIGHT} ${Shapes.MIDDLE_TO_BOTTOM}` }, + '╉': { [Style.NORMAL]: Shapes.MIDDLE_TO_RIGHT, [Style.BOLD]: `${Shapes.TOP_TO_BOTTOM} ${Shapes.MIDDLE_TO_LEFT}` }, + '╊': { [Style.NORMAL]: Shapes.MIDDLE_TO_LEFT, [Style.BOLD]: `${Shapes.TOP_TO_BOTTOM} ${Shapes.MIDDLE_TO_RIGHT}` }, + + // Dashed + '╌': { [Style.NORMAL]: Shapes.TWO_DASHES_HORIZONTAL }, + '╍': { [Style.BOLD]: Shapes.TWO_DASHES_HORIZONTAL }, + '┄': { [Style.NORMAL]: Shapes.THREE_DASHES_HORIZONTAL }, + '┅': { [Style.BOLD]: Shapes.THREE_DASHES_HORIZONTAL }, + '┈': { [Style.NORMAL]: Shapes.FOUR_DASHES_HORIZONTAL }, + '┉': { [Style.BOLD]: Shapes.FOUR_DASHES_HORIZONTAL }, + '╎': { [Style.NORMAL]: Shapes.TWO_DASHES_VERTICAL }, + '╏': { [Style.BOLD]: Shapes.TWO_DASHES_VERTICAL }, + '┆': { [Style.NORMAL]: Shapes.THREE_DASHES_VERTICAL }, + '┇': { [Style.BOLD]: Shapes.THREE_DASHES_VERTICAL }, + '┊': { [Style.NORMAL]: Shapes.FOUR_DASHES_VERTICAL }, + '┋': { [Style.BOLD]: Shapes.FOUR_DASHES_VERTICAL }, + + // Curved + '╭': { [Style.NORMAL]: (xp, yp) => `M.5,1 L.5,${.5 + (yp / .15 * .5)} C.5,${.5 + (yp / .15 * .5)},.5,.5,1,.5` }, + '╮': { [Style.NORMAL]: (xp, yp) => `M.5,1 L.5,${.5 + (yp / .15 * .5)} C.5,${.5 + (yp / .15 * .5)},.5,.5,0,.5` }, + '╯': { [Style.NORMAL]: (xp, yp) => `M.5,0 L.5,${.5 - (yp / .15 * .5)} C.5,${.5 - (yp / .15 * .5)},.5,.5,0,.5` }, + '╰': { [Style.NORMAL]: (xp, yp) => `M.5,0 L.5,${.5 - (yp / .15 * .5)} C.5,${.5 - (yp / .15 * .5)},.5,.5,1,.5` } +}; + +interface IVectorShape { + d: string; + type: VectorType; + /** Padding to apply to the vector's x axis in CSS pixels. */ + horizontalPadding?: number; +} + +const enum VectorType { + FILL, + STROKE +} + +/** + * This contains the definitions of the primarily used box drawing characters as vector shapes. The + * reason these characters are defined specially is to avoid common problems if a user's font has + * not been patched with powerline characters and also to get pixel perfect rendering as rendering + * issues can occur around AA/SPAA. + * + * Original symbols defined in https://github.com/powerline/fontpatcher + */ +export const powerlineDefinitions: { [index: string]: IVectorShape } = { + // Right triangle solid + '\u{E0B0}': { d: 'M0,0 L1,.5 L0,1', type: VectorType.FILL }, + // Right triangle line + '\u{E0B1}': { d: 'M0,0 L1,.5 L0,1', type: VectorType.STROKE, horizontalPadding: 0.5 }, + // Left triangle solid + '\u{E0B2}': { d: 'M1,0 L0,.5 L1,1', type: VectorType.FILL }, + // Left triangle line + '\u{E0B3}': { d: 'M1,0 L0,.5 L1,1', type: VectorType.STROKE, horizontalPadding: 0.5 }, + // Right semi-circle solid, + '\u{E0B4}': { d: 'M0,0 L0,1 C0.552,1,1,0.776,1,.5 C1,0.224,0.552,0,0,0', type: VectorType.FILL }, + // Right semi-circle line, + '\u{E0B5}': { d: 'M0,1 C0.552,1,1,0.776,1,.5 C1,0.224,0.552,0,0,0', type: VectorType.STROKE }, + // Left semi-circle solid, + '\u{E0B6}': { d: 'M1,0 L1,1 C0.448,1,0,0.776,0,.5 C0,0.224,0.448,0,1,0', type: VectorType.FILL }, + // Left semi-circle line, + '\u{E0B7}': { d: 'M1,1 C0.448,1,0,0.776,0,.5 C0,0.224,0.448,0,1,0', type: VectorType.STROKE } +}; + +/** + * Try drawing a custom block element or box drawing character, returning whether it was + * successfully drawn. + */ +export function tryDrawCustomChar( + ctx: CanvasRenderingContext2D, + c: string, + xOffset: number, + yOffset: number, + scaledCellWidth: number, + scaledCellHeight: number +): boolean { + const blockElementDefinition = blockElementDefinitions[c]; + if (blockElementDefinition) { + drawBlockElementChar(ctx, blockElementDefinition, xOffset, yOffset, scaledCellWidth, scaledCellHeight); + return true; + } + + const patternDefinition = patternCharacterDefinitions[c]; + if (patternDefinition) { + drawPatternChar(ctx, patternDefinition, xOffset, yOffset, scaledCellWidth, scaledCellHeight); + return true; + } + + const boxDrawingDefinition = boxDrawingDefinitions[c]; + if (boxDrawingDefinition) { + drawBoxDrawingChar(ctx, boxDrawingDefinition, xOffset, yOffset, scaledCellWidth, scaledCellHeight); + return true; + } + + const powerlineDefinition = powerlineDefinitions[c]; + if (powerlineDefinition) { + drawPowerlineChar(ctx, powerlineDefinition, xOffset, yOffset, scaledCellWidth, scaledCellHeight); + return true; + } + + return false; +} + +function drawBlockElementChar( + ctx: CanvasRenderingContext2D, + charDefinition: IBlockVector[], + xOffset: number, + yOffset: number, + scaledCellWidth: number, + scaledCellHeight: number +): void { + for (let i = 0; i < charDefinition.length; i++) { + const box = charDefinition[i]; + const xEighth = scaledCellWidth / 8; + const yEighth = scaledCellHeight / 8; + ctx.fillRect( + xOffset + box.x * xEighth, + yOffset + box.y * yEighth, + box.w * xEighth, + box.h * yEighth + ); + } +} + +const cachedPatterns: Map> = new Map(); + +function drawPatternChar( + ctx: CanvasRenderingContext2D, + charDefinition: number[][], + xOffset: number, + yOffset: number, + scaledCellWidth: number, + scaledCellHeight: number +): void { + let patternSet = cachedPatterns.get(charDefinition); + if (!patternSet) { + patternSet = new Map(); + cachedPatterns.set(charDefinition, patternSet); + } + const fillStyle = ctx.fillStyle; + if (typeof fillStyle !== 'string') { + throw new Error(`Unexpected fillStyle type "${fillStyle}"`); + } + let pattern = patternSet.get(fillStyle); + if (!pattern) { + const width = charDefinition[0].length; + const height = charDefinition.length; + const tmpCanvas = document.createElement('canvas'); + tmpCanvas.width = width; + tmpCanvas.height = height; + const tmpCtx = throwIfFalsy(tmpCanvas.getContext('2d')); + const imageData = new ImageData(width, height); + + // Extract rgba from fillStyle + let r: number; + let g: number; + let b: number; + let a: number; + if (fillStyle.startsWith('#')) { + r = parseInt(fillStyle.slice(1, 3), 16); + g = parseInt(fillStyle.slice(3, 5), 16); + b = parseInt(fillStyle.slice(5, 7), 16); + a = fillStyle.length > 7 && parseInt(fillStyle.slice(7, 9), 16) || 1; + } else if (fillStyle.startsWith('rgba')) { + ([r, g, b, a] = fillStyle.substring(5, fillStyle.length - 1).split(',').map(e => parseFloat(e))); + } else { + throw new Error(`Unexpected fillStyle color format "${fillStyle}" when drawing pattern glyph`); + } + + for (let y = 0; y < height; y++) { + for (let x = 0; x < width; x++) { + imageData.data[(y * width + x) * 4 ] = r; + imageData.data[(y * width + x) * 4 + 1] = g; + imageData.data[(y * width + x) * 4 + 2] = b; + imageData.data[(y * width + x) * 4 + 3] = charDefinition[y][x] * (a * 255); + } + } + tmpCtx.putImageData(imageData, 0, 0); + pattern = throwIfFalsy(ctx.createPattern(tmpCanvas, null)); + patternSet.set(fillStyle, pattern); + } + ctx.fillStyle = pattern; + ctx.fillRect(xOffset, yOffset, scaledCellWidth, scaledCellHeight); +} + +/** + * Draws the following box drawing characters by mapping a subset of SVG d attribute instructions to + * canvas draw calls. + * + * Box styles: ┎┰┒┍┯┑╓╥╖╒╤╕ ┏┳┓┌┲┓┌┬┐┏┱┐ + * ┌─┬─┐ ┏━┳━┓ ╔═╦═╗ ┠╂┨┝┿┥╟╫╢╞╪╡ ┡╇┩├╊┫┢╈┪┣╉┤ + * │ │ │ ┃ ┃ ┃ ║ ║ ║ ┖┸┚┕┷┙╙╨╜╘╧╛ └┴┘└┺┛┗┻┛┗┹┘ + * ├─┼─┤ ┣━╋━┫ ╠═╬═╣ ┏┱┐┌┲┓┌┬┐┌┬┐ ┏┳┓┌┮┓┌┬┐┏┭┐ + * │ │ │ ┃ ┃ ┃ ║ ║ ║ ┡╃┤├╄┩├╆┪┢╅┤ ┞╀┦├┾┫┟╁┧┣┽┤ + * └─┴─┘ ┗━┻━┛ ╚═╩═╝ └┴┘└┴┘└┺┛┗┹┘ └┴┘└┶┛┗┻┛┗┵┘ + * + * Other: + * ╭─╮ ╲ ╱ ╷╻╎╏┆┇┊┋ ╺╾╴ ╌╌╌ ┄┄┄ ┈┈┈ + * │ │ ╳ ╽╿╎╏┆┇┊┋ ╶╼╸ ╍╍╍ ┅┅┅ ┉┉┉ + * ╰─╯ ╱ ╲ ╹╵╎╏┆┇┊┋ + * + * All box drawing characters: + * ─ ━ │ ┃ ┄ ┅ ┆ ┇ ┈ ┉ ┊ ┋ ┌ ┍ ┎ ┏ + * ┐ ┑ ┒ ┓ └ ┕ ┖ ┗ ┘ ┙ ┚ ┛ ├ ┝ ┞ ┟ + * ┠ ┡ ┢ ┣ ┤ ┥ ┦ ┧ ┨ ┩ ┪ ┫ ┬ ┭ ┮ ┯ + * ┰ ┱ ┲ ┳ ┴ ┵ ┶ ┷ ┸ ┹ ┺ ┻ ┼ ┽ ┾ ┿ + * ╀ ╁ ╂ ╃ ╄ ╅ ╆ ╇ ╈ ╉ ╊ ╋ ╌ ╍ ╎ ╏ + * ═ ║ ╒ ╓ ╔ ╕ ╖ ╗ ╘ ╙ ╚ ╛ ╜ ╝ ╞ ╟ + * ╠ ╡ ╢ ╣ ╤ ╥ ╦ ╧ ╨ ╩ ╪ ╫ ╬ ╭ ╮ ╯ + * ╰ ╱ ╲ ╳ ╴ ╵ ╶ ╷ ╸ ╹ ╺ ╻ ╼ ╽ ╾ ╿ + * + * --- + * + * Box drawing alignment tests: █ + * ▉ + * ╔══╦══╗ ┌──┬──┐ ╭──┬──╮ ╭──┬──╮ ┏━━┳━━┓ ┎┒┏┑ ╷ ╻ ┏┯┓ ┌┰┐ ▊ ╱╲╱╲╳╳╳ + * ║┌─╨─┐║ │╔═╧═╗│ │╒═╪═╕│ │╓─╁─╖│ ┃┌─╂─┐┃ ┗╃╄┙ ╶┼╴╺╋╸┠┼┨ ┝╋┥ ▋ ╲╱╲╱╳╳╳ + * ║│╲ ╱│║ │║ ║│ ││ │ ││ │║ ┃ ║│ ┃│ ╿ │┃ ┍╅╆┓ ╵ ╹ ┗┷┛ └┸┘ ▌ ╱╲╱╲╳╳╳ + * ╠╡ ╳ ╞╣ ├╢ ╟┤ ├┼─┼─┼┤ ├╫─╂─╫┤ ┣┿╾┼╼┿┫ ┕┛┖┚ ┌┄┄┐ ╎ ┏┅┅┓ ┋ ▍ ╲╱╲╱╳╳╳ + * ║│╱ ╲│║ │║ ║│ ││ │ ││ │║ ┃ ║│ ┃│ ╽ │┃ ░░▒▒▓▓██ ┊ ┆ ╎ ╏ ┇ ┋ ▎ + * ║└─╥─┘║ │╚═╤═╝│ │╘═╪═╛│ │╙─╀─╜│ ┃└─╂─┘┃ ░░▒▒▓▓██ ┊ ┆ ╎ ╏ ┇ ┋ ▏ + * ╚══╩══╝ └──┴──┘ ╰──┴──╯ ╰──┴──╯ ┗━━┻━━┛ └╌╌┘ ╎ ┗╍╍┛ ┋ ▁▂▃▄▅▆▇█ + * + * Source: https://www.w3.org/2001/06/utf-8-test/UTF-8-demo.html + */ +function drawBoxDrawingChar( + ctx: CanvasRenderingContext2D, + charDefinition: { [fontWeight: number]: string | ((xp: number, yp: number) => string) }, + xOffset: number, + yOffset: number, + scaledCellWidth: number, + scaledCellHeight: number +): void { + ctx.strokeStyle = ctx.fillStyle; + for (const [fontWeight, instructions] of Object.entries(charDefinition)) { + ctx.beginPath(); + ctx.lineWidth = window.devicePixelRatio * Number.parseInt(fontWeight); + let actualInstructions: string; + if (typeof instructions === 'function') { + const xp = .15; + const yp = .15 / scaledCellHeight * scaledCellWidth; + actualInstructions = instructions(xp, yp); + } else { + actualInstructions = instructions; + } + for (const instruction of actualInstructions.split(' ')) { + const type = instruction[0]; + const f = svgToCanvasInstructionMap[type]; + if (!f) { + console.error(`Could not find drawing instructions for "${type}"`); + continue; + } + const args: string[] = instruction.substring(1).split(','); + if (!args[0] || !args[1]) { + continue; + } + f(ctx, translateArgs(args, scaledCellWidth, scaledCellHeight, xOffset, yOffset)); + } + ctx.stroke(); + ctx.closePath(); + } +} + +function drawPowerlineChar( + ctx: CanvasRenderingContext2D, + charDefinition: IVectorShape, + xOffset: number, + yOffset: number, + scaledCellWidth: number, + scaledCellHeight: number +): void { + ctx.beginPath(); + ctx.lineWidth = window.devicePixelRatio; + for (const instruction of charDefinition.d.split(' ')) { + const type = instruction[0]; + const f = svgToCanvasInstructionMap[type]; + if (!f) { + console.error(`Could not find drawing instructions for "${type}"`); + continue; + } + const args: string[] = instruction.substring(1).split(','); + if (!args[0] || !args[1]) { + continue; + } + f(ctx, translateArgs(args, scaledCellWidth, scaledCellHeight, xOffset, yOffset, charDefinition.horizontalPadding)); + } + if (charDefinition.type === VectorType.STROKE) { + ctx.strokeStyle = ctx.fillStyle; + ctx.stroke(); + } else { + ctx.fill(); + } + ctx.closePath(); +} + +function clamp(value: number, max: number, min: number = 0): number { + return Math.max(Math.min(value, max), min); +} + +const svgToCanvasInstructionMap: { [index: string]: any } = { + 'C': (ctx: CanvasRenderingContext2D, args: number[]) => ctx.bezierCurveTo(args[0], args[1], args[2], args[3], args[4], args[5]), + 'L': (ctx: CanvasRenderingContext2D, args: number[]) => ctx.lineTo(args[0], args[1]), + 'M': (ctx: CanvasRenderingContext2D, args: number[]) => ctx.moveTo(args[0], args[1]) +}; + +function translateArgs(args: string[], cellWidth: number, cellHeight: number, xOffset: number, yOffset: number, horizontalPadding: number = 0): number[] { + const result = args.map(e => parseFloat(e) || parseInt(e)); + + if (result.length < 2) { + throw new Error('Too few arguments for instruction'); + } + + for (let x = 0; x < result.length; x += 2) { + // Translate from 0-1 to 0-cellWidth + result[x] *= cellWidth - (horizontalPadding * 2 * window.devicePixelRatio); + // Ensure coordinate doesn't escape cell bounds and round to the nearest 0.5 to ensure a crisp + // line at 100% devicePixelRatio + if (result[x] !== 0) { + result[x] = clamp(Math.round(result[x] + 0.5) - 0.5, cellWidth, 0); + } + // Apply the cell's offset (ie. x*cellWidth) + result[x] += xOffset + (horizontalPadding * window.devicePixelRatio); + } + + for (let y = 1; y < result.length; y += 2) { + // Translate from 0-1 to 0-cellHeight + result[y] *= cellHeight; + // Ensure coordinate doesn't escape cell bounds and round to the nearest 0.5 to ensure a crisp + // line at 100% devicePixelRatio + if (result[y] !== 0) { + result[y] = clamp(Math.round(result[y] + 0.5) - 0.5, cellHeight, 0); + } + // Apply the cell's offset (ie. x*cellHeight) + result[y] += yOffset; + } + + return result; +} diff --git a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts index 05751c42..b3b9b973 100644 --- a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts +++ b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts @@ -4,15 +4,15 @@ */ import { ICharAtlasConfig } from './Types'; -import { DIM_OPACITY, TEXT_BASELINE } from 'browser/renderer/atlas/Constants'; +import { DIM_OPACITY, TEXT_BASELINE } from 'browser/renderer/Constants'; import { IRasterizedGlyph, IBoundingBox, IRasterizedGlyphSet } from '../Types'; import { DEFAULT_COLOR, Attributes } from 'common/buffer/Constants'; import { throwIfFalsy } from '../WebglUtils'; import { IColor } from 'common/Types'; import { IDisposable } from 'xterm'; import { AttributeData } from 'common/buffer/AttributeData'; -import { channels, color, rgba } from 'common/Color'; -import { tryDrawCustomChar } from 'browser/renderer/CustomGlyphs'; +import { color, rgba } from 'common/Color'; +import { tryDrawCustomChar } from '../CustomGlyphs'; import { excludeFromContrastRatioDemands, isPowerlineGlyph } from 'browser/renderer/RendererUtils'; // For debugging purposes, it can be useful to set this to a really tiny value, diff --git a/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts b/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts index 0a5a0065..0aa2049c 100644 --- a/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts +++ b/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts @@ -7,7 +7,7 @@ import { IRenderLayer } from './Types'; import { acquireCharAtlas } from '../atlas/CharAtlasCache'; import { Terminal } from 'xterm'; import { IColorSet } from 'browser/Types'; -import { TEXT_BASELINE } from 'browser/renderer/atlas/Constants'; +import { TEXT_BASELINE } from 'browser/renderer/Constants'; import { IRenderDimensions } from 'browser/renderer/Types'; import { CellData } from 'common/buffer/CellData'; import { WebglCharAtlas } from 'atlas/WebglCharAtlas'; diff --git a/addons/xterm-addon-webgl/src/renderLayer/LinkRenderLayer.ts b/addons/xterm-addon-webgl/src/renderLayer/LinkRenderLayer.ts index b2d004f9..440dbe7b 100644 --- a/addons/xterm-addon-webgl/src/renderLayer/LinkRenderLayer.ts +++ b/addons/xterm-addon-webgl/src/renderLayer/LinkRenderLayer.ts @@ -5,7 +5,7 @@ import { Terminal } from 'xterm'; import { BaseRenderLayer } from './BaseRenderLayer'; -import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants'; +import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/Constants'; import { is256Color } from '../atlas/CharAtlasUtils'; import { ITerminal, IColorSet, ILinkifierEvent } from 'browser/Types'; import { IRenderDimensions } from 'browser/renderer/Types'; diff --git a/addons/xterm-addon-webgl/src/tsconfig.json b/addons/xterm-addon-webgl/src/tsconfig.json index b0c9f6be..b111ae55 100644 --- a/addons/xterm-addon-webgl/src/tsconfig.json +++ b/addons/xterm-addon-webgl/src/tsconfig.json @@ -5,6 +5,8 @@ "lib": [ "dom", "es6", + "es2016.Array.Include", + "ES2017.Object" ], "rootDir": ".", "outDir": "../out", diff --git a/demo/client.ts b/demo/client.ts index d3b5af9c..e00279d7 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -10,6 +10,7 @@ // Use tsc version (yarn watch) import { Terminal } from '../out/browser/public/Terminal'; import { AttachAddon } from '../addons/xterm-addon-attach/out/AttachAddon'; +import { CanvasAddon } from '../addons/xterm-addon-canvas/out/CanvasAddon'; import { FitAddon } from '../addons/xterm-addon-fit/out/FitAddon'; import { SearchAddon, ISearchOptions } from '../addons/xterm-addon-search/out/SearchAddon'; import { SerializeAddon } from '../addons/xterm-addon-serialize/out/SerializeAddon'; @@ -60,6 +61,7 @@ interface IDemoAddon { canChange: boolean; ctor: T extends 'attach' ? typeof AttachAddon : + T extends 'canvas' ? typeof CanvasAddon : T extends 'fit' ? typeof FitAddon : T extends 'search' ? typeof SearchAddon : T extends 'serialize' ? typeof SerializeAddon : @@ -69,6 +71,7 @@ interface IDemoAddon { typeof WebglAddon; instance?: T extends 'attach' ? AttachAddon : + T extends 'canvas' ? CanvasAddon : T extends 'fit' ? FitAddon : T extends 'search' ? SearchAddon : T extends 'serialize' ? SerializeAddon : diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index 91c4a920..c59a065a 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -28,7 +28,6 @@ import { Viewport } from 'browser/Viewport'; import { rightClickHandler, moveTextAreaUnderMouseCursor, handlePasteEvent, copyHandler, paste } from 'browser/Clipboard'; import { C0, C1_ESCAPED } from 'common/data/EscapeSequences'; import { WindowsOptionsReportType } from '../common/InputHandler'; -import { Renderer } from 'browser/renderer/Renderer'; import { SelectionService } from 'browser/services/SelectionService'; import * as Browser from 'common/Platform'; import { addDisposableDomListener } from 'browser/Lifecycle'; @@ -609,7 +608,7 @@ export class Terminal extends CoreTerminal implements ITerminal { } private _createRenderer(): IRenderer { - return this._instantiationService.createInstance(Renderer, this._colorManager!.colors, this.screenElement!, this.linkifier2); + return this._instantiationService.createInstance(DomRenderer, this._colorManager!.colors, this.element!, this.screenElement!, this._viewportElement!, this.linkifier2); } /** diff --git a/src/browser/renderer/atlas/Constants.ts b/src/browser/renderer/Constants.ts similarity index 93% rename from src/browser/renderer/atlas/Constants.ts rename to src/browser/renderer/Constants.ts index c1701e97..ac698b85 100644 --- a/src/browser/renderer/atlas/Constants.ts +++ b/src/browser/renderer/Constants.ts @@ -6,10 +6,9 @@ import { isFirefox, isLegacyEdge } from 'common/Platform'; export const INVERTED_DEFAULT_COLOR = 257; + export const DIM_OPACITY = 0.5; // The text baseline is set conditionally by browser. Using 'ideographic' for Firefox or Legacy Edge would // result in truncated text (Issue 3353). Using 'bottom' for Chrome would result in slightly // unaligned Powerline fonts (PR 3356#issuecomment-850928179). export const TEXT_BASELINE: CanvasTextBaseline = isFirefox || isLegacyEdge ? 'bottom' : 'ideographic'; - -export const CHAR_ATLAS_CELL_SPACING = 1; diff --git a/src/browser/renderer/Types.d.ts b/src/browser/renderer/Types.d.ts index 6818a926..cb1a85b4 100644 --- a/src/browser/renderer/Types.d.ts +++ b/src/browser/renderer/Types.d.ts @@ -54,56 +54,3 @@ export interface IRenderer extends IDisposable { renderRows(start: number, end: number): void; clearTextureAtlas?(): void; } - -export interface IRenderLayer extends IDisposable { - /** - * Called when the terminal loses focus. - */ - onBlur(): void; - - /** - * * Called when the terminal gets focus. - */ - onFocus(): void; - - /** - * Called when the cursor is moved. - */ - onCursorMove(): void; - - /** - * Called when options change. - */ - onOptionsChanged(): void; - - /** - * Called when the theme changes. - */ - setColors(colorSet: IColorSet): void; - - /** - * Called when the data in the grid has changed (or needs to be rendered - * again). - */ - onGridChanged(startRow: number, endRow: number): void; - - /** - * Calls when the selection changes. - */ - onSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void; - - /** - * Resize the render layer. - */ - resize(dim: IRenderDimensions): void; - - /** - * Clear the state of the render layer. - */ - reset(): void; - - /** - * Clears the texture atlas. - */ - clearTextureAtlas(): void; -} diff --git a/src/browser/renderer/dom/DomRenderer.ts b/src/browser/renderer/dom/DomRenderer.ts index a4500e39..991938e4 100644 --- a/src/browser/renderer/dom/DomRenderer.ts +++ b/src/browser/renderer/dom/DomRenderer.ts @@ -5,7 +5,7 @@ import { IRenderer, IRenderDimensions, IRequestRedrawEvent } from 'browser/renderer/Types'; import { BOLD_CLASS, ITALIC_CLASS, CURSOR_CLASS, CURSOR_STYLE_BLOCK_CLASS, CURSOR_BLINK_CLASS, CURSOR_STYLE_BAR_CLASS, CURSOR_STYLE_UNDERLINE_CLASS, DomRendererRowFactory } from 'browser/renderer/dom/DomRendererRowFactory'; -import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants'; +import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/Constants'; import { Disposable } from 'common/Lifecycle'; import { IColorSet, ILinkifierEvent, ILinkifier2 } from 'browser/Types'; import { ICharSizeService } from 'browser/services/Services'; diff --git a/src/browser/renderer/dom/DomRendererRowFactory.ts b/src/browser/renderer/dom/DomRendererRowFactory.ts index fadf5032..35b8b0e7 100644 --- a/src/browser/renderer/dom/DomRendererRowFactory.ts +++ b/src/browser/renderer/dom/DomRendererRowFactory.ts @@ -4,13 +4,13 @@ */ import { IBufferLine, ICellData, IColor } from 'common/Types'; -import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants'; +import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/Constants'; import { NULL_CELL_CODE, WHITESPACE_CELL_CHAR, Attributes } from 'common/buffer/Constants'; import { CellData } from 'common/buffer/CellData'; -import { IBufferService, ICoreService, IDecorationService, IOptionsService } from 'common/services/Services'; +import { ICoreService, IDecorationService, IOptionsService } from 'common/services/Services'; import { color, rgba } from 'common/Color'; import { IColorSet } from 'browser/Types'; -import { ICharacterJoinerService, ISelectionService } from 'browser/services/Services'; +import { ICharacterJoinerService } from 'browser/services/Services'; import { JoinedCellData } from 'browser/services/CharacterJoinerService'; import { excludeFromContrastRatioDemands } from 'browser/renderer/RendererUtils'; diff --git a/tsconfig.all.json b/tsconfig.all.json index 6fa02446..4d2df306 100644 --- a/tsconfig.all.json +++ b/tsconfig.all.json @@ -7,6 +7,7 @@ { "path": "./test/api" }, { "path": "./test/benchmark" }, { "path": "./addons/xterm-addon-attach" }, + { "path": "./addons/xterm-addon-canvas" }, { "path": "./addons/xterm-addon-fit" }, { "path": "./addons/xterm-addon-ligatures" }, { "path": "./addons/xterm-addon-search" }, From 48cfd232195d171a980051f5f8ced0a94e91a06b Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 27 Jul 2022 10:33:10 -0700 Subject: [PATCH 30/42] Fix tests that depended on canvas renderer --- addons/xterm-addon-fit/test/FitAddon.api.ts | 8 ++++---- test/api/Terminal.api.ts | 12 +++--------- test/api/TestUtils.ts | 2 +- 3 files changed, 8 insertions(+), 14 deletions(-) diff --git a/addons/xterm-addon-fit/test/FitAddon.api.ts b/addons/xterm-addon-fit/test/FitAddon.api.ts index 987092ad..c35673cc 100644 --- a/addons/xterm-addon-fit/test/FitAddon.api.ts +++ b/addons/xterm-addon-fit/test/FitAddon.api.ts @@ -48,7 +48,7 @@ describe('FitAddon', () => { it('default', async function(): Promise { await loadFit(); const dimensions: {cols: number, rows: number} = await page.evaluate(`window.fit.proposeDimensions()`); - assert.equal(dimensions.cols, 87); + assert.equal(dimensions.cols, 86); assert.isAbove(dimensions.rows, 24); assert.isBelow(dimensions.rows, 29); }); @@ -56,7 +56,7 @@ describe('FitAddon', () => { it('width', async function(): Promise { await loadFit(1008); const dimensions: {cols: number, rows: number} = await page.evaluate(`window.fit.proposeDimensions()`); - assert.equal(dimensions.cols, 110); + assert.equal(dimensions.cols, 109); assert.isAbove(dimensions.rows, 24); assert.isBelow(dimensions.rows, 29); }); @@ -89,7 +89,7 @@ describe('FitAddon', () => { await page.evaluate(`window.fit.fit()`); const cols: number = await page.evaluate(`window.term.cols`); const rows: number = await page.evaluate(`window.term.rows`); - assert.equal(cols, 87); + assert.equal(cols, 86); assert.isAbove(rows, 24); assert.isBelow(rows, 29); }); @@ -99,7 +99,7 @@ describe('FitAddon', () => { await page.evaluate(`window.fit.fit()`); const cols: number = await page.evaluate(`window.term.cols`); const rows: number = await page.evaluate(`window.term.rows`); - assert.equal(cols, 110); + assert.equal(cols, 109); assert.isAbove(rows, 24); assert.isBelow(rows, 29); }); diff --git a/test/api/Terminal.api.ts b/test/api/Terminal.api.ts index fa19b5b4..72b1ba34 100644 --- a/test/api/Terminal.api.ts +++ b/test/api/Terminal.api.ts @@ -726,9 +726,7 @@ describe('API Integration Tests', function(): void { describe('registerDecoration', () => { describe('bufferDecorations', () => { it('should register decorations and render them when terminal open is called', async () => { - await page.evaluate(`window.term = new Terminal({ allowProposedApi: true })`); - await page.evaluate(`window.term.open(document.querySelector('#terminal-container'))`); - await page.waitForSelector('.xterm-text-layer'); + await openTerminal(page); await page.evaluate(`window.marker1 = window.term.registerMarker(1)`); await page.evaluate(`window.marker2 = window.term.registerMarker(2)`); await page.evaluate(`window.term.registerDecoration({ marker: window.marker1 })`); @@ -757,9 +755,7 @@ describe('API Integration Tests', function(): void { }); describe('overviewRulerDecorations', () => { it('should not add an overview ruler when width is not set', async () => { - await page.evaluate(`window.term = new Terminal({ allowProposedApi: true })`); - await page.evaluate(`window.term.open(document.querySelector('#terminal-container'))`); - await page.waitForSelector('.xterm-text-layer'); + await openTerminal(page); await page.evaluate(`window.marker1 = window.term.registerMarker(1)`); await page.evaluate(`window.marker2 = window.term.registerMarker(2)`); await page.evaluate(`window.term.registerDecoration({ marker: window.marker1, overviewRulerOptions: { color: 'red', position: 'full' } })`); @@ -768,9 +764,7 @@ describe('API Integration Tests', function(): void { await pollFor(page, `document.querySelectorAll('.xterm-decoration-overview-ruler').length`, 0); }); it('should add an overview ruler when width is set', async () => { - await page.evaluate(`window.term = new Terminal({ allowProposedApi: true, overviewRulerWidth: 15 })`); - await page.evaluate(`window.term.open(document.querySelector('#terminal-container'))`); - await page.waitForSelector('.xterm-text-layer'); + await openTerminal(page, { overviewRulerWidth: 15 }); await page.evaluate(`window.marker1 = window.term.registerMarker(1)`); await page.evaluate(`window.marker2 = window.term.registerMarker(2)`); await page.evaluate(`window.term.registerDecoration({ marker: window.marker1, overviewRulerOptions: { color: 'red', position: 'full' } })`); diff --git a/test/api/TestUtils.ts b/test/api/TestUtils.ts index 2b4e96a4..5731009b 100644 --- a/test/api/TestUtils.ts +++ b/test/api/TestUtils.ts @@ -64,7 +64,7 @@ export function getBrowserType(): playwright.BrowserType { const browserType = getBrowserType(); const options: Record = { headless: process.argv.includes('--headless') From cbddd2029ebdb8ed7db699d07b43191f056e0adf Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 27 Jul 2022 16:04:24 -0700 Subject: [PATCH 31/42] Get canvas renderer addon working --- addons/xterm-addon-canvas/src/CanvasAddon.ts | 46 ++++++++++--------- .../src/{Renderer.ts => CanvasRenderer.ts} | 2 +- demo/client.ts | 8 ++-- 3 files changed, 31 insertions(+), 25 deletions(-) rename addons/xterm-addon-canvas/src/{Renderer.ts => CanvasRenderer.ts} (99%) diff --git a/addons/xterm-addon-canvas/src/CanvasAddon.ts b/addons/xterm-addon-canvas/src/CanvasAddon.ts index 34fffb84..81594ef0 100644 --- a/addons/xterm-addon-canvas/src/CanvasAddon.ts +++ b/addons/xterm-addon-canvas/src/CanvasAddon.ts @@ -3,40 +3,44 @@ * @license MIT */ +import { IRenderService } from 'browser/services/Services'; +import { IColorSet } from 'browser/Types'; +import { CanvasRenderer } from './CanvasRenderer'; +import { IBufferService, IInstantiationService } from 'common/services/Services'; import { ITerminalAddon, Terminal } from 'xterm'; export class CanvasAddon implements ITerminalAddon { private _terminal?: Terminal; - // private _renderer?: WebglRenderer; + private _renderer?: CanvasRenderer; // private _onContextLoss = new EventEmitter(); // public get onContextLoss(): IEvent { return this._onContextLoss.event; } public activate(terminal: Terminal): void { - // if (!terminal.element) { - // throw new Error('Cannot activate WebglAddon before Terminal.open'); - // } - // if (isSafari) { - // throw new Error('Webgl is not currently supported on Safari'); - // } + if (!terminal.element) { + throw new Error('Cannot activate CanvasAddon before Terminal.open'); + } this._terminal = terminal; - // const renderService: IRenderService = (terminal as any)._core._renderService; - // const characterJoinerService: ICharacterJoinerService = (terminal as any)._core._characterJoinerService; - // const decorationService: IDecorationService = (terminal as any)._core._decorationService; - // const colors: IColorSet = (terminal as any)._core._colorManager.colors; - // this._renderer = new WebglRenderer(terminal, colors, characterJoinerService, decorationService, this._preserveDrawingBuffer); + const instantiationService: IInstantiationService = (terminal as any)._core._instantiationService; + const bufferService: IBufferService = (terminal as any)._core._renderService; + const renderService: IRenderService = (terminal as any)._core._renderService; + const colors: IColorSet = (terminal as any)._core._colorManager.colors; + const screenElement: HTMLElement = (terminal as any)._core.screenElement; + const linkifier = (terminal as any)._core.linkifier2; + this._renderer = instantiationService.createInstance(CanvasRenderer, colors, screenElement, linkifier); // this._renderer.onContextLoss(() => this._onContextLoss.fire()); - // renderService.setRenderer(this._renderer); + renderService.setRenderer(this._renderer); + renderService.onResize(bufferService.cols, bufferService.rows); } public dispose(): void { - // if (!this._terminal) { - // throw new Error('Cannot dispose WebglAddon because it is activated'); - // } - // const renderService: IRenderService = (this._terminal as any)._core._renderService; - // renderService.setRenderer((this._terminal as any)._core._createRenderer()); - // renderService.onResize(this._terminal.cols, this._terminal.rows); - // this._renderer?.dispose(); - // this._renderer = undefined; + if (!this._terminal) { + throw new Error('Cannot dispose CanvasAddon because it is activated'); + } + const renderService: IRenderService = (this._terminal as any)._core._renderService; + renderService.setRenderer((this._terminal as any)._core._createRenderer()); + renderService.onResize(this._terminal.cols, this._terminal.rows); + this._renderer?.dispose(); + this._renderer = undefined; } // public get textureAtlas(): HTMLCanvasElement | undefined { diff --git a/addons/xterm-addon-canvas/src/Renderer.ts b/addons/xterm-addon-canvas/src/CanvasRenderer.ts similarity index 99% rename from addons/xterm-addon-canvas/src/Renderer.ts rename to addons/xterm-addon-canvas/src/CanvasRenderer.ts index 629f69f4..88c81ea7 100644 --- a/addons/xterm-addon-canvas/src/Renderer.ts +++ b/addons/xterm-addon-canvas/src/CanvasRenderer.ts @@ -18,7 +18,7 @@ import { EventEmitter, IEvent } from 'common/EventEmitter'; let nextRendererId = 1; -export class Renderer extends Disposable implements IRenderer { +export class CanvasRenderer extends Disposable implements IRenderer { private _id = nextRendererId++; private _renderLayers: IRenderLayer[]; diff --git a/demo/client.ts b/demo/client.ts index e00279d7..3ee533d0 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -54,7 +54,7 @@ let socketURL; let socket; let pid; -type AddonType = 'attach' | 'fit' | 'search' | 'serialize' | 'unicode11' | 'web-links' | 'webgl' | 'ligatures'; +type AddonType = 'attach' | 'canvas' | 'fit' | 'search' | 'serialize' | 'unicode11' | 'web-links' | 'webgl' | 'ligatures'; interface IDemoAddon { name: T; @@ -84,6 +84,7 @@ interface IDemoAddon { const addons: { [T in AddonType]: IDemoAddon} = { attach: { name: 'attach', ctor: AttachAddon, canChange: false }, + canvas: { name: 'canvas', ctor: CanvasAddon, canChange: true }, fit: { name: 'fit', ctor: FitAddon, canChange: false }, search: { name: 'search', ctor: SearchAddon, canChange: true }, serialize: { name: 'serialize', ctor: SerializeAddon, canChange: true }, @@ -153,6 +154,7 @@ const disposeRecreateButtonHandler = () => { window.term = null; socket = null; addons.attach.instance = undefined; + addons.canvas.instance = undefined; addons.fit.instance = undefined; addons.search.instance = undefined; addons.serialize.instance = undefined; @@ -630,7 +632,7 @@ function writeCustomGlyphHandler() { } function loadTest() { - const isWebglEnabled = !!addons.webgl.instance; + const rendererName = addons.webgl.instance ? 'webgl' : !!addons.canvas.instance ? 'canvas' : 'dom'; const testData = []; let byteCount = 0; for (let i = 0; i < 50; i++) { @@ -656,7 +658,7 @@ function loadTest() { term.write('', () => { const time = Math.round(performance.now() - start); const mbs = ((byteCount / 1024) * (1 / (time / 1000))).toFixed(2); - term.write(`\n\r\nWrote ${byteCount}kB in ${time}ms (${mbs}MB/s) using the (${isWebglEnabled ? 'webgl' : 'canvas'} renderer)`); + term.write(`\n\r\nWrote ${byteCount}kB in ${time}ms (${mbs}MB/s) using the (${rendererName} renderer)`); // Send ^C to get a new prompt term._core._onData.fire('\x03'); }); From 73b3e31b388c5eb014fb7e0d288b8def3ca74815 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 28 Jul 2022 04:37:42 -0700 Subject: [PATCH 32/42] Canvas readme --- addons/xterm-addon-canvas/README.md | 22 +++++++++++++++++++- addons/xterm-addon-canvas/src/CanvasAddon.ts | 11 ---------- addons/xterm-addon-webgl/README.md | 1 + 3 files changed, 22 insertions(+), 12 deletions(-) diff --git a/addons/xterm-addon-canvas/README.md b/addons/xterm-addon-canvas/README.md index deec04c6..ed65967d 100644 --- a/addons/xterm-addon-canvas/README.md +++ b/addons/xterm-addon-canvas/README.md @@ -1,3 +1,23 @@ ## xterm-addon-canvas -TODO: Doc +An addon for [xterm.js](https://github.com/xtermjs/xterm.js) that enables a canvas-based renderer using a 2d context to draw. This addon requires xterm.js v5+. + + +### Install + +```bash +npm install --save xterm-addon-canvas +``` + +### Usage + +```ts +import { Terminal } from 'xterm'; +import { CanvasAddon } from 'xterm-addon-canvas'; + +const terminal = new Terminal(); +terminal.open(element); +terminal.loadAddon(new CanvasAddon()); +``` + +See the full [API](https://github.com/xtermjs/xterm.js/blob/master/addons/xterm-addon-canvas/typings/xterm-addon-canvas.d.ts) for more advanced usage. diff --git a/addons/xterm-addon-canvas/src/CanvasAddon.ts b/addons/xterm-addon-canvas/src/CanvasAddon.ts index 81594ef0..8b8dcee6 100644 --- a/addons/xterm-addon-canvas/src/CanvasAddon.ts +++ b/addons/xterm-addon-canvas/src/CanvasAddon.ts @@ -12,8 +12,6 @@ import { ITerminalAddon, Terminal } from 'xterm'; export class CanvasAddon implements ITerminalAddon { private _terminal?: Terminal; private _renderer?: CanvasRenderer; - // private _onContextLoss = new EventEmitter(); - // public get onContextLoss(): IEvent { return this._onContextLoss.event; } public activate(terminal: Terminal): void { if (!terminal.element) { @@ -27,7 +25,6 @@ export class CanvasAddon implements ITerminalAddon { const screenElement: HTMLElement = (terminal as any)._core.screenElement; const linkifier = (terminal as any)._core.linkifier2; this._renderer = instantiationService.createInstance(CanvasRenderer, colors, screenElement, linkifier); - // this._renderer.onContextLoss(() => this._onContextLoss.fire()); renderService.setRenderer(this._renderer); renderService.onResize(bufferService.cols, bufferService.rows); } @@ -42,12 +39,4 @@ export class CanvasAddon implements ITerminalAddon { this._renderer?.dispose(); this._renderer = undefined; } - - // public get textureAtlas(): HTMLCanvasElement | undefined { - // return this._renderer?.textureAtlas; - // } - - // public clearTextureAtlas(): void { - // this._renderer?.clearCharAtlas(); - // } } diff --git a/addons/xterm-addon-webgl/README.md b/addons/xterm-addon-webgl/README.md index da63e743..2519fb7b 100644 --- a/addons/xterm-addon-webgl/README.md +++ b/addons/xterm-addon-webgl/README.md @@ -16,6 +16,7 @@ import { Terminal } from 'xterm'; import { WebglAddon } from 'xterm-addon-webgl'; const terminal = new Terminal(); +terminal.open(element); terminal.loadAddon(new WebglAddon()); ``` From b3cffe2d309b13d760becd74cf10052392017a83 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 28 Jul 2022 04:43:03 -0700 Subject: [PATCH 33/42] Fix fit addon api tests on linux --- addons/xterm-addon-fit/test/FitAddon.api.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/addons/xterm-addon-fit/test/FitAddon.api.ts b/addons/xterm-addon-fit/test/FitAddon.api.ts index c35673cc..caf8cc7d 100644 --- a/addons/xterm-addon-fit/test/FitAddon.api.ts +++ b/addons/xterm-addon-fit/test/FitAddon.api.ts @@ -48,7 +48,8 @@ describe('FitAddon', () => { it('default', async function(): Promise { await loadFit(); const dimensions: {cols: number, rows: number} = await page.evaluate(`window.fit.proposeDimensions()`); - assert.equal(dimensions.cols, 86); + assert.isAbove(dimensions.cols, 86); + assert.isBelow(dimensions.cols, 87); assert.isAbove(dimensions.rows, 24); assert.isBelow(dimensions.rows, 29); }); @@ -56,7 +57,8 @@ describe('FitAddon', () => { it('width', async function(): Promise { await loadFit(1008); const dimensions: {cols: number, rows: number} = await page.evaluate(`window.fit.proposeDimensions()`); - assert.equal(dimensions.cols, 109); + assert.isAbove(dimensions.cols, 109); + assert.isBelow(dimensions.cols, 110); assert.isAbove(dimensions.rows, 24); assert.isBelow(dimensions.rows, 29); }); @@ -89,7 +91,8 @@ describe('FitAddon', () => { await page.evaluate(`window.fit.fit()`); const cols: number = await page.evaluate(`window.term.cols`); const rows: number = await page.evaluate(`window.term.rows`); - assert.equal(cols, 86); + assert.isAbove(cols, 86); + assert.isBelow(cols, 87); assert.isAbove(rows, 24); assert.isBelow(rows, 29); }); @@ -99,7 +102,8 @@ describe('FitAddon', () => { await page.evaluate(`window.fit.fit()`); const cols: number = await page.evaluate(`window.term.cols`); const rows: number = await page.evaluate(`window.term.rows`); - assert.equal(cols, 109); + assert.isAbove(cols, 109); + assert.isBelow(cols, 110); assert.isAbove(rows, 24); assert.isBelow(rows, 29); }); From ced5c304f1fd8ffe90171309130a9d138819adc0 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 28 Jul 2022 04:46:18 -0700 Subject: [PATCH 34/42] Move CustomGlyphs.ts into core --- addons/xterm-addon-canvas/LICENSE | 2 +- .../xterm-addon-canvas/src/BaseRenderLayer.ts | 2 +- addons/xterm-addon-webgl/src/CustomGlyphs.ts | 646 ------------------ .../src/atlas/WebglCharAtlas.ts | 2 +- .../browser/renderer}/CustomGlyphs.ts | 0 5 files changed, 3 insertions(+), 649 deletions(-) delete mode 100644 addons/xterm-addon-webgl/src/CustomGlyphs.ts rename {addons/xterm-addon-canvas/src => src/browser/renderer}/CustomGlyphs.ts (100%) diff --git a/addons/xterm-addon-canvas/LICENSE b/addons/xterm-addon-canvas/LICENSE index b9dc26fe..e597698c 100644 --- a/addons/xterm-addon-canvas/LICENSE +++ b/addons/xterm-addon-canvas/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2018, The xterm.js authors (https://github.com/xtermjs/xterm.js) +Copyright (c) 2017, The xterm.js authors (https://github.com/xtermjs/xterm.js) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts index 54038cc0..9a6464c1 100644 --- a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts @@ -18,7 +18,7 @@ import { IBufferService, IDecorationService, IOptionsService } from 'common/serv import { excludeFromContrastRatioDemands, throwIfFalsy } from 'browser/renderer/RendererUtils'; import { channels, color, rgba } from 'common/Color'; import { removeElementFromParent } from 'browser/Dom'; -import { tryDrawCustomChar } from './CustomGlyphs'; +import { tryDrawCustomChar } from 'browser/renderer/CustomGlyphs'; export abstract class BaseRenderLayer implements IRenderLayer { private _canvas: HTMLCanvasElement; diff --git a/addons/xterm-addon-webgl/src/CustomGlyphs.ts b/addons/xterm-addon-webgl/src/CustomGlyphs.ts deleted file mode 100644 index e6adf5d1..00000000 --- a/addons/xterm-addon-webgl/src/CustomGlyphs.ts +++ /dev/null @@ -1,646 +0,0 @@ -/** - * Copyright (c) 2021 The xterm.js authors. All rights reserved. - * @license MIT - */ - -import { throwIfFalsy } from 'browser/renderer/RendererUtils'; - -interface IBlockVector { - x: number; - y: number; - w: number; - h: number; -} - -export const blockElementDefinitions: { [index: string]: IBlockVector[] | undefined } = { - // Block elements (0x2580-0x2590) - '▀': [{ x: 0, y: 0, w: 8, h: 4 }], // UPPER HALF BLOCK - '▁': [{ x: 0, y: 7, w: 8, h: 1 }], // LOWER ONE EIGHTH BLOCK - '▂': [{ x: 0, y: 6, w: 8, h: 2 }], // LOWER ONE QUARTER BLOCK - '▃': [{ x: 0, y: 5, w: 8, h: 3 }], // LOWER THREE EIGHTHS BLOCK - '▄': [{ x: 0, y: 4, w: 8, h: 4 }], // LOWER HALF BLOCK - '▅': [{ x: 0, y: 3, w: 8, h: 5 }], // LOWER FIVE EIGHTHS BLOCK - '▆': [{ x: 0, y: 2, w: 8, h: 6 }], // LOWER THREE QUARTERS BLOCK - '▇': [{ x: 0, y: 1, w: 8, h: 7 }], // LOWER SEVEN EIGHTHS BLOCK - '█': [{ x: 0, y: 0, w: 8, h: 8 }], // FULL BLOCK - '▉': [{ x: 0, y: 0, w: 7, h: 8 }], // LEFT SEVEN EIGHTHS BLOCK - '▊': [{ x: 0, y: 0, w: 6, h: 8 }], // LEFT THREE QUARTERS BLOCK - '▋': [{ x: 0, y: 0, w: 5, h: 8 }], // LEFT FIVE EIGHTHS BLOCK - '▌': [{ x: 0, y: 0, w: 4, h: 8 }], // LEFT HALF BLOCK - '▍': [{ x: 0, y: 0, w: 3, h: 8 }], // LEFT THREE EIGHTHS BLOCK - '▎': [{ x: 0, y: 0, w: 2, h: 8 }], // LEFT ONE QUARTER BLOCK - '▏': [{ x: 0, y: 0, w: 1, h: 8 }], // LEFT ONE EIGHTH BLOCK - '▐': [{ x: 4, y: 0, w: 4, h: 8 }], // RIGHT HALF BLOCK - - // Block elements (0x2594-0x2595) - '▔': [{ x: 0, y: 0, w: 9, h: 1 }], // UPPER ONE EIGHTH BLOCK - '▕': [{ x: 7, y: 0, w: 1, h: 8 }], // RIGHT ONE EIGHTH BLOCK - - // Terminal graphic characters (0x2596-0x259F) - '▖': [{ x: 0, y: 4, w: 4, h: 4 }], // QUADRANT LOWER LEFT - '▗': [{ x: 4, y: 4, w: 4, h: 4 }], // QUADRANT LOWER RIGHT - '▘': [{ x: 0, y: 0, w: 4, h: 4 }], // QUADRANT UPPER LEFT - '▙': [{ x: 0, y: 0, w: 4, h: 8 }, { x: 0, y: 4, w: 8, h: 4 }], // QUADRANT UPPER LEFT AND LOWER LEFT AND LOWER RIGHT - '▚': [{ x: 0, y: 0, w: 4, h: 4 }, { x: 4, y: 4, w: 4, h: 4 }], // QUADRANT UPPER LEFT AND LOWER RIGHT - '▛': [{ x: 0, y: 0, w: 4, h: 8 }, { x: 4, y: 0, w: 4, h: 4 }], // QUADRANT UPPER LEFT AND UPPER RIGHT AND LOWER LEFT - '▜': [{ x: 0, y: 0, w: 8, h: 4 }, { x: 4, y: 0, w: 4, h: 8 }], // QUADRANT UPPER LEFT AND UPPER RIGHT AND LOWER RIGHT - '▝': [{ x: 4, y: 0, w: 4, h: 4 }], // QUADRANT UPPER RIGHT - '▞': [{ x: 4, y: 0, w: 4, h: 4 }, { x: 0, y: 4, w: 4, h: 4 }], // QUADRANT UPPER RIGHT AND LOWER LEFT - '▟': [{ x: 4, y: 0, w: 4, h: 8 }, { x: 0, y: 4, w: 8, h: 4 }], // QUADRANT UPPER RIGHT AND LOWER LEFT AND LOWER RIGHT - - // VERTICAL ONE EIGHTH BLOCK-2 through VERTICAL ONE EIGHTH BLOCK-7 - '\u{1FB70}': [{ x: 1, y: 0, w: 1, h: 8 }], - '\u{1FB71}': [{ x: 2, y: 0, w: 1, h: 8 }], - '\u{1FB72}': [{ x: 3, y: 0, w: 1, h: 8 }], - '\u{1FB73}': [{ x: 4, y: 0, w: 1, h: 8 }], - '\u{1FB74}': [{ x: 5, y: 0, w: 1, h: 8 }], - '\u{1FB75}': [{ x: 6, y: 0, w: 1, h: 8 }], - - // HORIZONTAL ONE EIGHTH BLOCK-2 through HORIZONTAL ONE EIGHTH BLOCK-7 - '\u{1FB76}': [{ x: 0, y: 1, w: 8, h: 1 }], - '\u{1FB77}': [{ x: 0, y: 2, w: 8, h: 1 }], - '\u{1FB78}': [{ x: 0, y: 3, w: 8, h: 1 }], - '\u{1FB79}': [{ x: 0, y: 4, w: 8, h: 1 }], - '\u{1FB7A}': [{ x: 0, y: 5, w: 8, h: 1 }], - '\u{1FB7B}': [{ x: 0, y: 6, w: 8, h: 1 }], - - // LEFT AND LOWER ONE EIGHTH BLOCK - '\u{1FB7C}': [{ x: 0, y: 0, w: 1, h: 8 }, { x: 0, y: 7, w: 8, h: 1 }], - // LEFT AND UPPER ONE EIGHTH BLOCK - '\u{1FB7D}': [{ x: 0, y: 0, w: 1, h: 8 }, { x: 0, y: 0, w: 8, h: 1 }], - // RIGHT AND UPPER ONE EIGHTH BLOCK - '\u{1FB7E}': [{ x: 7, y: 0, w: 1, h: 8 }, { x: 0, y: 0, w: 8, h: 1 }], - // RIGHT AND LOWER ONE EIGHTH BLOCK - '\u{1FB7F}': [{ x: 7, y: 0, w: 1, h: 8 }, { x: 0, y: 7, w: 8, h: 1 }], - // UPPER AND LOWER ONE EIGHTH BLOCK - '\u{1FB80}': [{ x: 0, y: 0, w: 8, h: 1 }, { x: 0, y: 7, w: 8, h: 1 }], - // HORIZONTAL ONE EIGHTH BLOCK-1358 - '\u{1FB81}': [{ x: 0, y: 0, w: 8, h: 1 }, { x: 0, y: 2, w: 8, h: 1 }, { x: 0, y: 4, w: 8, h: 1 }, { x: 0, y: 7, w: 8, h: 1 }], - - // UPPER ONE QUARTER BLOCK - '\u{1FB82}': [{ x: 0, y: 0, w: 8, h: 2 }], - // UPPER THREE EIGHTHS BLOCK - '\u{1FB83}': [{ x: 0, y: 0, w: 8, h: 3 }], - // UPPER FIVE EIGHTHS BLOCK - '\u{1FB84}': [{ x: 0, y: 0, w: 8, h: 5 }], - // UPPER THREE QUARTERS BLOCK - '\u{1FB85}': [{ x: 0, y: 0, w: 8, h: 6 }], - // UPPER SEVEN EIGHTHS BLOCK - '\u{1FB86}': [{ x: 0, y: 0, w: 8, h: 7 }], - - // RIGHT ONE QUARTER BLOCK - '\u{1FB87}': [{ x: 6, y: 0, w: 2, h: 8 }], - // RIGHT THREE EIGHTHS B0OCK - '\u{1FB88}': [{ x: 5, y: 0, w: 3, h: 8 }], - // RIGHT FIVE EIGHTHS BL0CK - '\u{1FB89}': [{ x: 3, y: 0, w: 5, h: 8 }], - // RIGHT THREE QUARTERS 0LOCK - '\u{1FB8A}': [{ x: 2, y: 0, w: 6, h: 8 }], - // RIGHT SEVEN EIGHTHS B0OCK - '\u{1FB8B}': [{ x: 1, y: 0, w: 7, h: 8 }], - - // CHECKER BOARD FILL - '\u{1FB95}': [ - { x: 0, y: 0, w: 2, h: 2 }, { x: 4, y: 0, w: 2, h: 2 }, - { x: 2, y: 2, w: 2, h: 2 }, { x: 6, y: 2, w: 2, h: 2 }, - { x: 0, y: 4, w: 2, h: 2 }, { x: 4, y: 4, w: 2, h: 2 }, - { x: 2, y: 6, w: 2, h: 2 }, { x: 6, y: 6, w: 2, h: 2 } - ], - // INVERSE CHECKER BOARD FILL - '\u{1FB96}': [ - { x: 2, y: 0, w: 2, h: 2 }, { x: 6, y: 0, w: 2, h: 2 }, - { x: 0, y: 2, w: 2, h: 2 }, { x: 4, y: 2, w: 2, h: 2 }, - { x: 2, y: 4, w: 2, h: 2 }, { x: 6, y: 4, w: 2, h: 2 }, - { x: 0, y: 6, w: 2, h: 2 }, { x: 4, y: 6, w: 2, h: 2 } - ], - // HEAVY HORIZONTAL FILL (upper middle and lower one quarter block) - '\u{1FB97}': [{ x: 0, y: 2, w: 8, h: 2 }, { x: 0, y: 6, w: 8, h: 2 }] -}; - -type PatternDefinition = number[][]; - -/** - * Defines the repeating pattern used by special characters, the pattern is made up of a 2d array of - * pixel values to be filled (1) or not filled (0). - */ -const patternCharacterDefinitions: { [key: string]: PatternDefinition | undefined } = { - // Shade characters (0x2591-0x2593) - '░': [ // LIGHT SHADE (25%) - [1, 0, 0, 0], - [0, 0, 0, 0], - [0, 0, 1, 0], - [0, 0, 0, 0] - ], - '▒': [ // MEDIUM SHADE (50%) - [1, 0], - [0, 0], - [0, 1], - [0, 0] - ], - '▓': [ // DARK SHADE (75%) - [0, 1], - [1, 1], - [1, 0], - [1, 1] - ] -}; - -const enum Shapes { - /** │ */ TOP_TO_BOTTOM = 'M.5,0 L.5,1', - /** ─ */ LEFT_TO_RIGHT = 'M0,.5 L1,.5', - - /** └ */ TOP_TO_RIGHT = 'M.5,0 L.5,.5 L1,.5', - /** ┘ */ TOP_TO_LEFT = 'M.5,0 L.5,.5 L0,.5', - /** ┐ */ LEFT_TO_BOTTOM = 'M0,.5 L.5,.5 L.5,1', - /** ┌ */ RIGHT_TO_BOTTOM = 'M0.5,1 L.5,.5 L1,.5', - - /** ╵ */ MIDDLE_TO_TOP = 'M.5,.5 L.5,0', - /** ╴ */ MIDDLE_TO_LEFT = 'M.5,.5 L0,.5', - /** ╶ */ MIDDLE_TO_RIGHT = 'M.5,.5 L1,.5', - /** ╷ */ MIDDLE_TO_BOTTOM = 'M.5,.5 L.5,1', - - /** ┴ */ T_TOP = 'M0,.5 L1,.5 M.5,.5 L.5,0', - /** ┤ */ T_LEFT = 'M.5,0 L.5,1 M.5,.5 L0,.5', - /** ├ */ T_RIGHT = 'M.5,0 L.5,1 M.5,.5 L1,.5', - /** ┬ */ T_BOTTOM = 'M0,.5 L1,.5 M.5,.5 L.5,1', - - /** ┼ */ CROSS = 'M0,.5 L1,.5 M.5,0 L.5,1', - - /** ╌ */ TWO_DASHES_HORIZONTAL = 'M.1,.5 L.4,.5 M.6,.5 L.9,.5', // .2 empty, .3 filled - /** ┄ */ THREE_DASHES_HORIZONTAL = 'M.0667,.5 L.2667,.5 M.4,.5 L.6,.5 M.7333,.5 L.9333,.5', // .1333 empty, .2 filled - /** ┉ */ FOUR_DASHES_HORIZONTAL = 'M.05,.5 L.2,.5 M.3,.5 L.45,.5 M.55,.5 L.7,.5 M.8,.5 L.95,.5', // .1 empty, .15 filled - /** ╎ */ TWO_DASHES_VERTICAL = 'M.5,.1 L.5,.4 M.5,.6 L.5,.9', - /** ┆ */ THREE_DASHES_VERTICAL = 'M.5,.0667 L.5,.2667 M.5,.4 L.5,.6 M.5,.7333 L.5,.9333', - /** ┊ */ FOUR_DASHES_VERTICAL = 'M.5,.05 L.5,.2 M.5,.3 L.5,.45 L.5,.55 M.5,.7 L.5,.95', -} - -const enum Style { - NORMAL = 1, - BOLD = 3 -} - -/** - * @param xp The percentage of 15% of the x axis. - * @param yp The percentage of 15% of the x axis on the y axis. - */ -type DrawFunctionDefinition = (xp: number, yp: number) => string; - -/** - * This contains the definitions of all box drawing characters in the format of SVG paths (ie. the - * svg d attribute). - */ -export const boxDrawingDefinitions: { [character: string]: { [fontWeight: number]: string | DrawFunctionDefinition } | undefined } = { - // Uniform normal and bold - '─': { [Style.NORMAL]: Shapes.LEFT_TO_RIGHT }, - '━': { [Style.BOLD]: Shapes.LEFT_TO_RIGHT }, - '│': { [Style.NORMAL]: Shapes.TOP_TO_BOTTOM }, - '┃': { [Style.BOLD]: Shapes.TOP_TO_BOTTOM }, - '┌': { [Style.NORMAL]: Shapes.RIGHT_TO_BOTTOM }, - '┏': { [Style.BOLD]: Shapes.RIGHT_TO_BOTTOM }, - '┐': { [Style.NORMAL]: Shapes.LEFT_TO_BOTTOM }, - '┓': { [Style.BOLD]: Shapes.LEFT_TO_BOTTOM }, - '└': { [Style.NORMAL]: Shapes.TOP_TO_RIGHT }, - '┗': { [Style.BOLD]: Shapes.TOP_TO_RIGHT }, - '┘': { [Style.NORMAL]: Shapes.TOP_TO_LEFT }, - '┛': { [Style.BOLD]: Shapes.TOP_TO_LEFT }, - '├': { [Style.NORMAL]: Shapes.T_RIGHT }, - '┣': { [Style.BOLD]: Shapes.T_RIGHT }, - '┤': { [Style.NORMAL]: Shapes.T_LEFT }, - '┫': { [Style.BOLD]: Shapes.T_LEFT }, - '┬': { [Style.NORMAL]: Shapes.T_BOTTOM }, - '┳': { [Style.BOLD]: Shapes.T_BOTTOM }, - '┴': { [Style.NORMAL]: Shapes.T_TOP }, - '┻': { [Style.BOLD]: Shapes.T_TOP }, - '┼': { [Style.NORMAL]: Shapes.CROSS }, - '╋': { [Style.BOLD]: Shapes.CROSS }, - '╴': { [Style.NORMAL]: Shapes.MIDDLE_TO_LEFT }, - '╸': { [Style.BOLD]: Shapes.MIDDLE_TO_LEFT }, - '╵': { [Style.NORMAL]: Shapes.MIDDLE_TO_TOP }, - '╹': { [Style.BOLD]: Shapes.MIDDLE_TO_TOP }, - '╶': { [Style.NORMAL]: Shapes.MIDDLE_TO_RIGHT }, - '╺': { [Style.BOLD]: Shapes.MIDDLE_TO_RIGHT }, - '╷': { [Style.NORMAL]: Shapes.MIDDLE_TO_BOTTOM }, - '╻': { [Style.BOLD]: Shapes.MIDDLE_TO_BOTTOM }, - - // Double border - '═': { [Style.NORMAL]: (xp, yp) => `M0,${.5 - yp} L1,${.5 - yp} M0,${.5 + yp} L1,${.5 + yp}` }, - '║': { [Style.NORMAL]: (xp, yp) => `M${.5 - xp},0 L${.5 - xp},1 M${.5 + xp},0 L${.5 + xp},1` }, - '╒': { [Style.NORMAL]: (xp, yp) => `M.5,1 L.5,${.5 - yp} L1,${.5 - yp} M.5,${.5 + yp} L1,${.5 + yp}` }, - '╓': { [Style.NORMAL]: (xp, yp) => `M${.5 - xp},1 L${.5 - xp},.5 L1,.5 M${.5 + xp},.5 L${.5 + xp},1` }, - '╔': { [Style.NORMAL]: (xp, yp) => `M1,${.5 - yp} L${.5 - xp},${.5 - yp} L${.5 - xp},1 M1,${.5 + yp} L${.5 + xp},${.5 + yp} L${.5 + xp},1` }, - '╕': { [Style.NORMAL]: (xp, yp) => `M0,${.5 - yp} L.5,${.5 - yp} L.5,1 M0,${.5 + yp} L.5,${.5 + yp}` }, - '╖': { [Style.NORMAL]: (xp, yp) => `M${.5 + xp},1 L${.5 + xp},.5 L0,.5 M${.5 - xp},.5 L${.5 - xp},1` }, - '╗': { [Style.NORMAL]: (xp, yp) => `M0,${.5 + yp} L${.5 - xp},${.5 + yp} L${.5 - xp},1 M0,${.5 - yp} L${.5 + xp},${.5 - yp} L${.5 + xp},1` }, - '╘': { [Style.NORMAL]: (xp, yp) => `M.5,0 L.5,${.5 + yp} L1,${.5 + yp} M.5,${.5 - yp} L1,${.5 - yp}` }, - '╙': { [Style.NORMAL]: (xp, yp) => `M1,.5 L${.5 - xp},.5 L${.5 - xp},0 M${.5 + xp},.5 L${.5 + xp},0` }, - '╚': { [Style.NORMAL]: (xp, yp) => `M1,${.5 - yp} L${.5 + xp},${.5 - yp} L${.5 + xp},0 M1,${.5 + yp} L${.5 - xp},${.5 + yp} L${.5 - xp},0` }, - '╛': { [Style.NORMAL]: (xp, yp) => `M0,${.5 + yp} L.5,${.5 + yp} L.5,0 M0,${.5 - yp} L.5,${.5 - yp}` }, - '╜': { [Style.NORMAL]: (xp, yp) => `M0,.5 L${.5 + xp},.5 L${.5 + xp},0 M${.5 - xp},.5 L${.5 - xp},0` }, - '╝': { [Style.NORMAL]: (xp, yp) => `M0,${.5 - yp} L${.5 - xp},${.5 - yp} L${.5 - xp},0 M0,${.5 + yp} L${.5 + xp},${.5 + yp} L${.5 + xp},0` }, - '╞': { [Style.NORMAL]: (xp, yp) => `${Shapes.TOP_TO_BOTTOM} M.5,${.5 - yp} L1,${.5 - yp} M.5,${.5 + yp} L1,${.5 + yp}` }, - '╟': { [Style.NORMAL]: (xp, yp) => `M${.5 - xp},0 L${.5 - xp},1 M${.5 + xp},0 L${.5 + xp},1 M${.5 + xp},.5 L1,.5` }, - '╠': { [Style.NORMAL]: (xp, yp) => `M${.5 - xp},0 L${.5 - xp},1 M1,${.5 + yp} L${.5 + xp},${.5 + yp} L${.5 + xp},1 M1,${.5 - yp} L${.5 + xp},${.5 - yp} L${.5 + xp},0` }, - '╡': { [Style.NORMAL]: (xp, yp) => `${Shapes.TOP_TO_BOTTOM} M0,${.5 - yp} L.5,${.5 - yp} M0,${.5 + yp} L.5,${.5 + yp}` }, - '╢': { [Style.NORMAL]: (xp, yp) => `M0,.5 L${.5 - xp},.5 M${.5 - xp},0 L${.5 - xp},1 M${.5 + xp},0 L${.5 + xp},1` }, - '╣': { [Style.NORMAL]: (xp, yp) => `M${.5 + xp},0 L${.5 + xp},1 M0,${.5 + yp} L${.5 - xp},${.5 + yp} L${.5 - xp},1 M0,${.5 - yp} L${.5 - xp},${.5 - yp} L${.5 - xp},0` }, - '╤': { [Style.NORMAL]: (xp, yp) => `M0,${.5 - yp} L1,${.5 - yp} M0,${.5 + yp} L1,${.5 + yp} M.5,${.5 + yp} L.5,1` }, - '╥': { [Style.NORMAL]: (xp, yp) => `${Shapes.LEFT_TO_RIGHT} M${.5 - xp},.5 L${.5 - xp},1 M${.5 + xp},.5 L${.5 + xp},1` }, - '╦': { [Style.NORMAL]: (xp, yp) => `M0,${.5 - yp} L1,${.5 - yp} M0,${.5 + yp} L${.5 - xp},${.5 + yp} L${.5 - xp},1 M1,${.5 + yp} L${.5 + xp},${.5 + yp} L${.5 + xp},1` }, - '╧': { [Style.NORMAL]: (xp, yp) => `M.5,0 L.5,${.5 - yp} M0,${.5 - yp} L1,${.5 - yp} M0,${.5 + yp} L1,${.5 + yp}` }, - '╨': { [Style.NORMAL]: (xp, yp) => `${Shapes.LEFT_TO_RIGHT} M${.5 - xp},.5 L${.5 - xp},0 M${.5 + xp},.5 L${.5 + xp},0` }, - '╩': { [Style.NORMAL]: (xp, yp) => `M0,${.5 + yp} L1,${.5 + yp} M0,${.5 - yp} L${.5 - xp},${.5 - yp} L${.5 - xp},0 M1,${.5 - yp} L${.5 + xp},${.5 - yp} L${.5 + xp},0` }, - '╪': { [Style.NORMAL]: (xp, yp) => `${Shapes.TOP_TO_BOTTOM} M0,${.5 - yp} L1,${.5 - yp} M0,${.5 + yp} L1,${.5 + yp}` }, - '╫': { [Style.NORMAL]: (xp, yp) => `${Shapes.LEFT_TO_RIGHT} M${.5 - xp},0 L${.5 - xp},1 M${.5 + xp},0 L${.5 + xp},1` }, - '╬': { [Style.NORMAL]: (xp, yp) => `M0,${.5 + yp} L${.5 - xp},${.5 + yp} L${.5 - xp},1 M1,${.5 + yp} L${.5 + xp},${.5 + yp} L${.5 + xp},1 M0,${.5 - yp} L${.5 - xp},${.5 - yp} L${.5 - xp},0 M1,${.5 - yp} L${.5 + xp},${.5 - yp} L${.5 + xp},0` }, - - // Diagonal - '╱': { [Style.NORMAL]: 'M1,0 L0,1' }, - '╲': { [Style.NORMAL]: 'M0,0 L1,1' }, - '╳': { [Style.NORMAL]: 'M1,0 L0,1 M0,0 L1,1' }, - - // Mixed weight - '╼': { [Style.NORMAL]: Shapes.MIDDLE_TO_LEFT, [Style.BOLD]: Shapes.MIDDLE_TO_RIGHT }, - '╽': { [Style.NORMAL]: Shapes.MIDDLE_TO_TOP, [Style.BOLD]: Shapes.MIDDLE_TO_BOTTOM }, - '╾': { [Style.NORMAL]: Shapes.MIDDLE_TO_RIGHT, [Style.BOLD]: Shapes.MIDDLE_TO_LEFT }, - '╿': { [Style.NORMAL]: Shapes.MIDDLE_TO_BOTTOM, [Style.BOLD]: Shapes.MIDDLE_TO_TOP }, - '┍': { [Style.NORMAL]: Shapes.MIDDLE_TO_BOTTOM, [Style.BOLD]: Shapes.MIDDLE_TO_RIGHT }, - '┎': { [Style.NORMAL]: Shapes.MIDDLE_TO_RIGHT, [Style.BOLD]: Shapes.MIDDLE_TO_BOTTOM }, - '┑': { [Style.NORMAL]: Shapes.MIDDLE_TO_BOTTOM, [Style.BOLD]: Shapes.MIDDLE_TO_LEFT }, - '┒': { [Style.NORMAL]: Shapes.MIDDLE_TO_LEFT, [Style.BOLD]: Shapes.MIDDLE_TO_BOTTOM }, - '┕': { [Style.NORMAL]: Shapes.MIDDLE_TO_TOP, [Style.BOLD]: Shapes.MIDDLE_TO_RIGHT }, - '┖': { [Style.NORMAL]: Shapes.MIDDLE_TO_RIGHT, [Style.BOLD]: Shapes.MIDDLE_TO_TOP }, - '┙': { [Style.NORMAL]: Shapes.MIDDLE_TO_TOP, [Style.BOLD]: Shapes.MIDDLE_TO_LEFT }, - '┚': { [Style.NORMAL]: Shapes.MIDDLE_TO_LEFT, [Style.BOLD]: Shapes.MIDDLE_TO_TOP }, - '┝': { [Style.NORMAL]: Shapes.TOP_TO_BOTTOM, [Style.BOLD]: Shapes.MIDDLE_TO_RIGHT }, - '┞': { [Style.NORMAL]: Shapes.RIGHT_TO_BOTTOM, [Style.BOLD]: Shapes.MIDDLE_TO_TOP }, - '┟': { [Style.NORMAL]: Shapes.TOP_TO_RIGHT, [Style.BOLD]: Shapes.MIDDLE_TO_BOTTOM }, - '┠': { [Style.NORMAL]: Shapes.MIDDLE_TO_RIGHT, [Style.BOLD]: Shapes.TOP_TO_BOTTOM }, - '┡': { [Style.NORMAL]: Shapes.MIDDLE_TO_BOTTOM, [Style.BOLD]: Shapes.TOP_TO_RIGHT }, - '┢': { [Style.NORMAL]: Shapes.MIDDLE_TO_TOP, [Style.BOLD]: Shapes.RIGHT_TO_BOTTOM }, - '┥': { [Style.NORMAL]: Shapes.TOP_TO_BOTTOM, [Style.BOLD]: Shapes.MIDDLE_TO_LEFT }, - '┦': { [Style.NORMAL]: Shapes.LEFT_TO_BOTTOM, [Style.BOLD]: Shapes.MIDDLE_TO_TOP }, - '┧': { [Style.NORMAL]: Shapes.TOP_TO_LEFT, [Style.BOLD]: Shapes.MIDDLE_TO_BOTTOM }, - '┨': { [Style.NORMAL]: Shapes.MIDDLE_TO_LEFT, [Style.BOLD]: Shapes.TOP_TO_BOTTOM }, - '┩': { [Style.NORMAL]: Shapes.MIDDLE_TO_BOTTOM, [Style.BOLD]: Shapes.TOP_TO_LEFT }, - '┪': { [Style.NORMAL]: Shapes.MIDDLE_TO_TOP, [Style.BOLD]: Shapes.LEFT_TO_BOTTOM }, - '┭': { [Style.NORMAL]: Shapes.RIGHT_TO_BOTTOM, [Style.BOLD]: Shapes.MIDDLE_TO_LEFT }, - '┮': { [Style.NORMAL]: Shapes.LEFT_TO_BOTTOM, [Style.BOLD]: Shapes.MIDDLE_TO_RIGHT }, - '┯': { [Style.NORMAL]: Shapes.MIDDLE_TO_BOTTOM, [Style.BOLD]: Shapes.LEFT_TO_RIGHT }, - '┰': { [Style.NORMAL]: Shapes.LEFT_TO_RIGHT, [Style.BOLD]: Shapes.MIDDLE_TO_BOTTOM }, - '┱': { [Style.NORMAL]: Shapes.MIDDLE_TO_RIGHT, [Style.BOLD]: Shapes.LEFT_TO_BOTTOM }, - '┲': { [Style.NORMAL]: Shapes.MIDDLE_TO_LEFT, [Style.BOLD]: Shapes.RIGHT_TO_BOTTOM }, - '┵': { [Style.NORMAL]: Shapes.TOP_TO_RIGHT, [Style.BOLD]: Shapes.MIDDLE_TO_LEFT }, - '┶': { [Style.NORMAL]: Shapes.TOP_TO_LEFT, [Style.BOLD]: Shapes.MIDDLE_TO_RIGHT }, - '┷': { [Style.NORMAL]: Shapes.MIDDLE_TO_TOP, [Style.BOLD]: Shapes.LEFT_TO_RIGHT }, - '┸': { [Style.NORMAL]: Shapes.LEFT_TO_RIGHT, [Style.BOLD]: Shapes.MIDDLE_TO_TOP }, - '┹': { [Style.NORMAL]: Shapes.MIDDLE_TO_RIGHT, [Style.BOLD]: Shapes.TOP_TO_LEFT }, - '┺': { [Style.NORMAL]: Shapes.MIDDLE_TO_LEFT, [Style.BOLD]: Shapes.TOP_TO_RIGHT }, - '┽': { [Style.NORMAL]: `${Shapes.TOP_TO_BOTTOM} ${Shapes.MIDDLE_TO_RIGHT}`, [Style.BOLD]: Shapes.MIDDLE_TO_LEFT }, - '┾': { [Style.NORMAL]: `${Shapes.TOP_TO_BOTTOM} ${Shapes.MIDDLE_TO_LEFT}`, [Style.BOLD]: Shapes.MIDDLE_TO_RIGHT }, - '┿': { [Style.NORMAL]: Shapes.TOP_TO_BOTTOM, [Style.BOLD]: Shapes.LEFT_TO_RIGHT }, - '╀': { [Style.NORMAL]: `${Shapes.LEFT_TO_RIGHT} ${Shapes.MIDDLE_TO_BOTTOM}`, [Style.BOLD]: Shapes.MIDDLE_TO_TOP }, - '╁': { [Style.NORMAL]: `${Shapes.MIDDLE_TO_TOP} ${Shapes.LEFT_TO_RIGHT}`, [Style.BOLD]: Shapes.MIDDLE_TO_BOTTOM }, - '╂': { [Style.NORMAL]: Shapes.LEFT_TO_RIGHT, [Style.BOLD]: Shapes.TOP_TO_BOTTOM }, - '╃': { [Style.NORMAL]: Shapes.RIGHT_TO_BOTTOM, [Style.BOLD]: Shapes.TOP_TO_LEFT }, - '╄': { [Style.NORMAL]: Shapes.LEFT_TO_BOTTOM, [Style.BOLD]: Shapes.TOP_TO_RIGHT }, - '╅': { [Style.NORMAL]: Shapes.TOP_TO_RIGHT, [Style.BOLD]: Shapes.LEFT_TO_BOTTOM }, - '╆': { [Style.NORMAL]: Shapes.TOP_TO_LEFT, [Style.BOLD]: Shapes.RIGHT_TO_BOTTOM }, - '╇': { [Style.NORMAL]: Shapes.MIDDLE_TO_BOTTOM, [Style.BOLD]: `${Shapes.MIDDLE_TO_TOP} ${Shapes.LEFT_TO_RIGHT}` }, - '╈': { [Style.NORMAL]: Shapes.MIDDLE_TO_TOP, [Style.BOLD]: `${Shapes.LEFT_TO_RIGHT} ${Shapes.MIDDLE_TO_BOTTOM}` }, - '╉': { [Style.NORMAL]: Shapes.MIDDLE_TO_RIGHT, [Style.BOLD]: `${Shapes.TOP_TO_BOTTOM} ${Shapes.MIDDLE_TO_LEFT}` }, - '╊': { [Style.NORMAL]: Shapes.MIDDLE_TO_LEFT, [Style.BOLD]: `${Shapes.TOP_TO_BOTTOM} ${Shapes.MIDDLE_TO_RIGHT}` }, - - // Dashed - '╌': { [Style.NORMAL]: Shapes.TWO_DASHES_HORIZONTAL }, - '╍': { [Style.BOLD]: Shapes.TWO_DASHES_HORIZONTAL }, - '┄': { [Style.NORMAL]: Shapes.THREE_DASHES_HORIZONTAL }, - '┅': { [Style.BOLD]: Shapes.THREE_DASHES_HORIZONTAL }, - '┈': { [Style.NORMAL]: Shapes.FOUR_DASHES_HORIZONTAL }, - '┉': { [Style.BOLD]: Shapes.FOUR_DASHES_HORIZONTAL }, - '╎': { [Style.NORMAL]: Shapes.TWO_DASHES_VERTICAL }, - '╏': { [Style.BOLD]: Shapes.TWO_DASHES_VERTICAL }, - '┆': { [Style.NORMAL]: Shapes.THREE_DASHES_VERTICAL }, - '┇': { [Style.BOLD]: Shapes.THREE_DASHES_VERTICAL }, - '┊': { [Style.NORMAL]: Shapes.FOUR_DASHES_VERTICAL }, - '┋': { [Style.BOLD]: Shapes.FOUR_DASHES_VERTICAL }, - - // Curved - '╭': { [Style.NORMAL]: (xp, yp) => `M.5,1 L.5,${.5 + (yp / .15 * .5)} C.5,${.5 + (yp / .15 * .5)},.5,.5,1,.5` }, - '╮': { [Style.NORMAL]: (xp, yp) => `M.5,1 L.5,${.5 + (yp / .15 * .5)} C.5,${.5 + (yp / .15 * .5)},.5,.5,0,.5` }, - '╯': { [Style.NORMAL]: (xp, yp) => `M.5,0 L.5,${.5 - (yp / .15 * .5)} C.5,${.5 - (yp / .15 * .5)},.5,.5,0,.5` }, - '╰': { [Style.NORMAL]: (xp, yp) => `M.5,0 L.5,${.5 - (yp / .15 * .5)} C.5,${.5 - (yp / .15 * .5)},.5,.5,1,.5` } -}; - -interface IVectorShape { - d: string; - type: VectorType; - /** Padding to apply to the vector's x axis in CSS pixels. */ - horizontalPadding?: number; -} - -const enum VectorType { - FILL, - STROKE -} - -/** - * This contains the definitions of the primarily used box drawing characters as vector shapes. The - * reason these characters are defined specially is to avoid common problems if a user's font has - * not been patched with powerline characters and also to get pixel perfect rendering as rendering - * issues can occur around AA/SPAA. - * - * Original symbols defined in https://github.com/powerline/fontpatcher - */ -export const powerlineDefinitions: { [index: string]: IVectorShape } = { - // Right triangle solid - '\u{E0B0}': { d: 'M0,0 L1,.5 L0,1', type: VectorType.FILL }, - // Right triangle line - '\u{E0B1}': { d: 'M0,0 L1,.5 L0,1', type: VectorType.STROKE, horizontalPadding: 0.5 }, - // Left triangle solid - '\u{E0B2}': { d: 'M1,0 L0,.5 L1,1', type: VectorType.FILL }, - // Left triangle line - '\u{E0B3}': { d: 'M1,0 L0,.5 L1,1', type: VectorType.STROKE, horizontalPadding: 0.5 }, - // Right semi-circle solid, - '\u{E0B4}': { d: 'M0,0 L0,1 C0.552,1,1,0.776,1,.5 C1,0.224,0.552,0,0,0', type: VectorType.FILL }, - // Right semi-circle line, - '\u{E0B5}': { d: 'M0,1 C0.552,1,1,0.776,1,.5 C1,0.224,0.552,0,0,0', type: VectorType.STROKE }, - // Left semi-circle solid, - '\u{E0B6}': { d: 'M1,0 L1,1 C0.448,1,0,0.776,0,.5 C0,0.224,0.448,0,1,0', type: VectorType.FILL }, - // Left semi-circle line, - '\u{E0B7}': { d: 'M1,1 C0.448,1,0,0.776,0,.5 C0,0.224,0.448,0,1,0', type: VectorType.STROKE } -}; - -/** - * Try drawing a custom block element or box drawing character, returning whether it was - * successfully drawn. - */ -export function tryDrawCustomChar( - ctx: CanvasRenderingContext2D, - c: string, - xOffset: number, - yOffset: number, - scaledCellWidth: number, - scaledCellHeight: number -): boolean { - const blockElementDefinition = blockElementDefinitions[c]; - if (blockElementDefinition) { - drawBlockElementChar(ctx, blockElementDefinition, xOffset, yOffset, scaledCellWidth, scaledCellHeight); - return true; - } - - const patternDefinition = patternCharacterDefinitions[c]; - if (patternDefinition) { - drawPatternChar(ctx, patternDefinition, xOffset, yOffset, scaledCellWidth, scaledCellHeight); - return true; - } - - const boxDrawingDefinition = boxDrawingDefinitions[c]; - if (boxDrawingDefinition) { - drawBoxDrawingChar(ctx, boxDrawingDefinition, xOffset, yOffset, scaledCellWidth, scaledCellHeight); - return true; - } - - const powerlineDefinition = powerlineDefinitions[c]; - if (powerlineDefinition) { - drawPowerlineChar(ctx, powerlineDefinition, xOffset, yOffset, scaledCellWidth, scaledCellHeight); - return true; - } - - return false; -} - -function drawBlockElementChar( - ctx: CanvasRenderingContext2D, - charDefinition: IBlockVector[], - xOffset: number, - yOffset: number, - scaledCellWidth: number, - scaledCellHeight: number -): void { - for (let i = 0; i < charDefinition.length; i++) { - const box = charDefinition[i]; - const xEighth = scaledCellWidth / 8; - const yEighth = scaledCellHeight / 8; - ctx.fillRect( - xOffset + box.x * xEighth, - yOffset + box.y * yEighth, - box.w * xEighth, - box.h * yEighth - ); - } -} - -const cachedPatterns: Map> = new Map(); - -function drawPatternChar( - ctx: CanvasRenderingContext2D, - charDefinition: number[][], - xOffset: number, - yOffset: number, - scaledCellWidth: number, - scaledCellHeight: number -): void { - let patternSet = cachedPatterns.get(charDefinition); - if (!patternSet) { - patternSet = new Map(); - cachedPatterns.set(charDefinition, patternSet); - } - const fillStyle = ctx.fillStyle; - if (typeof fillStyle !== 'string') { - throw new Error(`Unexpected fillStyle type "${fillStyle}"`); - } - let pattern = patternSet.get(fillStyle); - if (!pattern) { - const width = charDefinition[0].length; - const height = charDefinition.length; - const tmpCanvas = document.createElement('canvas'); - tmpCanvas.width = width; - tmpCanvas.height = height; - const tmpCtx = throwIfFalsy(tmpCanvas.getContext('2d')); - const imageData = new ImageData(width, height); - - // Extract rgba from fillStyle - let r: number; - let g: number; - let b: number; - let a: number; - if (fillStyle.startsWith('#')) { - r = parseInt(fillStyle.slice(1, 3), 16); - g = parseInt(fillStyle.slice(3, 5), 16); - b = parseInt(fillStyle.slice(5, 7), 16); - a = fillStyle.length > 7 && parseInt(fillStyle.slice(7, 9), 16) || 1; - } else if (fillStyle.startsWith('rgba')) { - ([r, g, b, a] = fillStyle.substring(5, fillStyle.length - 1).split(',').map(e => parseFloat(e))); - } else { - throw new Error(`Unexpected fillStyle color format "${fillStyle}" when drawing pattern glyph`); - } - - for (let y = 0; y < height; y++) { - for (let x = 0; x < width; x++) { - imageData.data[(y * width + x) * 4 ] = r; - imageData.data[(y * width + x) * 4 + 1] = g; - imageData.data[(y * width + x) * 4 + 2] = b; - imageData.data[(y * width + x) * 4 + 3] = charDefinition[y][x] * (a * 255); - } - } - tmpCtx.putImageData(imageData, 0, 0); - pattern = throwIfFalsy(ctx.createPattern(tmpCanvas, null)); - patternSet.set(fillStyle, pattern); - } - ctx.fillStyle = pattern; - ctx.fillRect(xOffset, yOffset, scaledCellWidth, scaledCellHeight); -} - -/** - * Draws the following box drawing characters by mapping a subset of SVG d attribute instructions to - * canvas draw calls. - * - * Box styles: ┎┰┒┍┯┑╓╥╖╒╤╕ ┏┳┓┌┲┓┌┬┐┏┱┐ - * ┌─┬─┐ ┏━┳━┓ ╔═╦═╗ ┠╂┨┝┿┥╟╫╢╞╪╡ ┡╇┩├╊┫┢╈┪┣╉┤ - * │ │ │ ┃ ┃ ┃ ║ ║ ║ ┖┸┚┕┷┙╙╨╜╘╧╛ └┴┘└┺┛┗┻┛┗┹┘ - * ├─┼─┤ ┣━╋━┫ ╠═╬═╣ ┏┱┐┌┲┓┌┬┐┌┬┐ ┏┳┓┌┮┓┌┬┐┏┭┐ - * │ │ │ ┃ ┃ ┃ ║ ║ ║ ┡╃┤├╄┩├╆┪┢╅┤ ┞╀┦├┾┫┟╁┧┣┽┤ - * └─┴─┘ ┗━┻━┛ ╚═╩═╝ └┴┘└┴┘└┺┛┗┹┘ └┴┘└┶┛┗┻┛┗┵┘ - * - * Other: - * ╭─╮ ╲ ╱ ╷╻╎╏┆┇┊┋ ╺╾╴ ╌╌╌ ┄┄┄ ┈┈┈ - * │ │ ╳ ╽╿╎╏┆┇┊┋ ╶╼╸ ╍╍╍ ┅┅┅ ┉┉┉ - * ╰─╯ ╱ ╲ ╹╵╎╏┆┇┊┋ - * - * All box drawing characters: - * ─ ━ │ ┃ ┄ ┅ ┆ ┇ ┈ ┉ ┊ ┋ ┌ ┍ ┎ ┏ - * ┐ ┑ ┒ ┓ └ ┕ ┖ ┗ ┘ ┙ ┚ ┛ ├ ┝ ┞ ┟ - * ┠ ┡ ┢ ┣ ┤ ┥ ┦ ┧ ┨ ┩ ┪ ┫ ┬ ┭ ┮ ┯ - * ┰ ┱ ┲ ┳ ┴ ┵ ┶ ┷ ┸ ┹ ┺ ┻ ┼ ┽ ┾ ┿ - * ╀ ╁ ╂ ╃ ╄ ╅ ╆ ╇ ╈ ╉ ╊ ╋ ╌ ╍ ╎ ╏ - * ═ ║ ╒ ╓ ╔ ╕ ╖ ╗ ╘ ╙ ╚ ╛ ╜ ╝ ╞ ╟ - * ╠ ╡ ╢ ╣ ╤ ╥ ╦ ╧ ╨ ╩ ╪ ╫ ╬ ╭ ╮ ╯ - * ╰ ╱ ╲ ╳ ╴ ╵ ╶ ╷ ╸ ╹ ╺ ╻ ╼ ╽ ╾ ╿ - * - * --- - * - * Box drawing alignment tests: █ - * ▉ - * ╔══╦══╗ ┌──┬──┐ ╭──┬──╮ ╭──┬──╮ ┏━━┳━━┓ ┎┒┏┑ ╷ ╻ ┏┯┓ ┌┰┐ ▊ ╱╲╱╲╳╳╳ - * ║┌─╨─┐║ │╔═╧═╗│ │╒═╪═╕│ │╓─╁─╖│ ┃┌─╂─┐┃ ┗╃╄┙ ╶┼╴╺╋╸┠┼┨ ┝╋┥ ▋ ╲╱╲╱╳╳╳ - * ║│╲ ╱│║ │║ ║│ ││ │ ││ │║ ┃ ║│ ┃│ ╿ │┃ ┍╅╆┓ ╵ ╹ ┗┷┛ └┸┘ ▌ ╱╲╱╲╳╳╳ - * ╠╡ ╳ ╞╣ ├╢ ╟┤ ├┼─┼─┼┤ ├╫─╂─╫┤ ┣┿╾┼╼┿┫ ┕┛┖┚ ┌┄┄┐ ╎ ┏┅┅┓ ┋ ▍ ╲╱╲╱╳╳╳ - * ║│╱ ╲│║ │║ ║│ ││ │ ││ │║ ┃ ║│ ┃│ ╽ │┃ ░░▒▒▓▓██ ┊ ┆ ╎ ╏ ┇ ┋ ▎ - * ║└─╥─┘║ │╚═╤═╝│ │╘═╪═╛│ │╙─╀─╜│ ┃└─╂─┘┃ ░░▒▒▓▓██ ┊ ┆ ╎ ╏ ┇ ┋ ▏ - * ╚══╩══╝ └──┴──┘ ╰──┴──╯ ╰──┴──╯ ┗━━┻━━┛ └╌╌┘ ╎ ┗╍╍┛ ┋ ▁▂▃▄▅▆▇█ - * - * Source: https://www.w3.org/2001/06/utf-8-test/UTF-8-demo.html - */ -function drawBoxDrawingChar( - ctx: CanvasRenderingContext2D, - charDefinition: { [fontWeight: number]: string | ((xp: number, yp: number) => string) }, - xOffset: number, - yOffset: number, - scaledCellWidth: number, - scaledCellHeight: number -): void { - ctx.strokeStyle = ctx.fillStyle; - for (const [fontWeight, instructions] of Object.entries(charDefinition)) { - ctx.beginPath(); - ctx.lineWidth = window.devicePixelRatio * Number.parseInt(fontWeight); - let actualInstructions: string; - if (typeof instructions === 'function') { - const xp = .15; - const yp = .15 / scaledCellHeight * scaledCellWidth; - actualInstructions = instructions(xp, yp); - } else { - actualInstructions = instructions; - } - for (const instruction of actualInstructions.split(' ')) { - const type = instruction[0]; - const f = svgToCanvasInstructionMap[type]; - if (!f) { - console.error(`Could not find drawing instructions for "${type}"`); - continue; - } - const args: string[] = instruction.substring(1).split(','); - if (!args[0] || !args[1]) { - continue; - } - f(ctx, translateArgs(args, scaledCellWidth, scaledCellHeight, xOffset, yOffset)); - } - ctx.stroke(); - ctx.closePath(); - } -} - -function drawPowerlineChar( - ctx: CanvasRenderingContext2D, - charDefinition: IVectorShape, - xOffset: number, - yOffset: number, - scaledCellWidth: number, - scaledCellHeight: number -): void { - ctx.beginPath(); - ctx.lineWidth = window.devicePixelRatio; - for (const instruction of charDefinition.d.split(' ')) { - const type = instruction[0]; - const f = svgToCanvasInstructionMap[type]; - if (!f) { - console.error(`Could not find drawing instructions for "${type}"`); - continue; - } - const args: string[] = instruction.substring(1).split(','); - if (!args[0] || !args[1]) { - continue; - } - f(ctx, translateArgs(args, scaledCellWidth, scaledCellHeight, xOffset, yOffset, charDefinition.horizontalPadding)); - } - if (charDefinition.type === VectorType.STROKE) { - ctx.strokeStyle = ctx.fillStyle; - ctx.stroke(); - } else { - ctx.fill(); - } - ctx.closePath(); -} - -function clamp(value: number, max: number, min: number = 0): number { - return Math.max(Math.min(value, max), min); -} - -const svgToCanvasInstructionMap: { [index: string]: any } = { - 'C': (ctx: CanvasRenderingContext2D, args: number[]) => ctx.bezierCurveTo(args[0], args[1], args[2], args[3], args[4], args[5]), - 'L': (ctx: CanvasRenderingContext2D, args: number[]) => ctx.lineTo(args[0], args[1]), - 'M': (ctx: CanvasRenderingContext2D, args: number[]) => ctx.moveTo(args[0], args[1]) -}; - -function translateArgs(args: string[], cellWidth: number, cellHeight: number, xOffset: number, yOffset: number, horizontalPadding: number = 0): number[] { - const result = args.map(e => parseFloat(e) || parseInt(e)); - - if (result.length < 2) { - throw new Error('Too few arguments for instruction'); - } - - for (let x = 0; x < result.length; x += 2) { - // Translate from 0-1 to 0-cellWidth - result[x] *= cellWidth - (horizontalPadding * 2 * window.devicePixelRatio); - // Ensure coordinate doesn't escape cell bounds and round to the nearest 0.5 to ensure a crisp - // line at 100% devicePixelRatio - if (result[x] !== 0) { - result[x] = clamp(Math.round(result[x] + 0.5) - 0.5, cellWidth, 0); - } - // Apply the cell's offset (ie. x*cellWidth) - result[x] += xOffset + (horizontalPadding * window.devicePixelRatio); - } - - for (let y = 1; y < result.length; y += 2) { - // Translate from 0-1 to 0-cellHeight - result[y] *= cellHeight; - // Ensure coordinate doesn't escape cell bounds and round to the nearest 0.5 to ensure a crisp - // line at 100% devicePixelRatio - if (result[y] !== 0) { - result[y] = clamp(Math.round(result[y] + 0.5) - 0.5, cellHeight, 0); - } - // Apply the cell's offset (ie. x*cellHeight) - result[y] += yOffset; - } - - return result; -} diff --git a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts index b3b9b973..8294c651 100644 --- a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts +++ b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts @@ -12,7 +12,7 @@ import { IColor } from 'common/Types'; import { IDisposable } from 'xterm'; import { AttributeData } from 'common/buffer/AttributeData'; import { color, rgba } from 'common/Color'; -import { tryDrawCustomChar } from '../CustomGlyphs'; +import { tryDrawCustomChar } from 'browser/renderer/CustomGlyphs'; import { excludeFromContrastRatioDemands, isPowerlineGlyph } from 'browser/renderer/RendererUtils'; // For debugging purposes, it can be useful to set this to a really tiny value, diff --git a/addons/xterm-addon-canvas/src/CustomGlyphs.ts b/src/browser/renderer/CustomGlyphs.ts similarity index 100% rename from addons/xterm-addon-canvas/src/CustomGlyphs.ts rename to src/browser/renderer/CustomGlyphs.ts From c52eb8898f1b860cbe697bf4850744ba0de90a5c Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 28 Jul 2022 04:50:40 -0700 Subject: [PATCH 35/42] Simplify addon tsconfigs --- addons/xterm-addon-canvas/src/tsconfig.json | 3 +-- addons/xterm-addon-webgl/src/tsconfig.json | 4 +--- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/addons/xterm-addon-canvas/src/tsconfig.json b/addons/xterm-addon-canvas/src/tsconfig.json index 1ce2a5e3..d954ec49 100644 --- a/addons/xterm-addon-canvas/src/tsconfig.json +++ b/addons/xterm-addon-canvas/src/tsconfig.json @@ -4,8 +4,7 @@ "target": "es5", "lib": [ "dom", - "es6", - "ES2017.Object" + "es6" ], "rootDir": ".", "outDir": "../out", diff --git a/addons/xterm-addon-webgl/src/tsconfig.json b/addons/xterm-addon-webgl/src/tsconfig.json index b111ae55..206d52ae 100644 --- a/addons/xterm-addon-webgl/src/tsconfig.json +++ b/addons/xterm-addon-webgl/src/tsconfig.json @@ -4,9 +4,7 @@ "target": "es5", "lib": [ "dom", - "es6", - "es2016.Array.Include", - "ES2017.Object" + "es6" ], "rootDir": ".", "outDir": "../out", From 119b64181b8af5b5bff677e1d68a3d55a3cb1772 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 28 Jul 2022 04:51:49 -0700 Subject: [PATCH 36/42] Fix fit addon api tests --- addons/xterm-addon-fit/test/FitAddon.api.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/addons/xterm-addon-fit/test/FitAddon.api.ts b/addons/xterm-addon-fit/test/FitAddon.api.ts index caf8cc7d..8a1c647a 100644 --- a/addons/xterm-addon-fit/test/FitAddon.api.ts +++ b/addons/xterm-addon-fit/test/FitAddon.api.ts @@ -49,7 +49,7 @@ describe('FitAddon', () => { await loadFit(); const dimensions: {cols: number, rows: number} = await page.evaluate(`window.fit.proposeDimensions()`); assert.isAbove(dimensions.cols, 86); - assert.isBelow(dimensions.cols, 87); + assert.isBelow(dimensions.cols, 88); assert.isAbove(dimensions.rows, 24); assert.isBelow(dimensions.rows, 29); }); @@ -58,7 +58,7 @@ describe('FitAddon', () => { await loadFit(1008); const dimensions: {cols: number, rows: number} = await page.evaluate(`window.fit.proposeDimensions()`); assert.isAbove(dimensions.cols, 109); - assert.isBelow(dimensions.cols, 110); + assert.isBelow(dimensions.cols, 111); assert.isAbove(dimensions.rows, 24); assert.isBelow(dimensions.rows, 29); }); @@ -92,7 +92,7 @@ describe('FitAddon', () => { const cols: number = await page.evaluate(`window.term.cols`); const rows: number = await page.evaluate(`window.term.rows`); assert.isAbove(cols, 86); - assert.isBelow(cols, 87); + assert.isBelow(cols, 88); assert.isAbove(rows, 24); assert.isBelow(rows, 29); }); @@ -103,7 +103,7 @@ describe('FitAddon', () => { const cols: number = await page.evaluate(`window.term.cols`); const rows: number = await page.evaluate(`window.term.rows`); assert.isAbove(cols, 109); - assert.isBelow(cols, 110); + assert.isBelow(cols, 111); assert.isAbove(rows, 24); assert.isBelow(rows, 29); }); From e5037a6d1e2806f1e8f0e6a4abd5e8e39902af1e Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 28 Jul 2022 05:00:10 -0700 Subject: [PATCH 37/42] Fix fit addon api tests on linux+firefox --- addons/xterm-addon-fit/test/FitAddon.api.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/addons/xterm-addon-fit/test/FitAddon.api.ts b/addons/xterm-addon-fit/test/FitAddon.api.ts index 8a1c647a..ef4618ea 100644 --- a/addons/xterm-addon-fit/test/FitAddon.api.ts +++ b/addons/xterm-addon-fit/test/FitAddon.api.ts @@ -48,7 +48,7 @@ describe('FitAddon', () => { it('default', async function(): Promise { await loadFit(); const dimensions: {cols: number, rows: number} = await page.evaluate(`window.fit.proposeDimensions()`); - assert.isAbove(dimensions.cols, 86); + assert.isAbove(dimensions.cols, 85); assert.isBelow(dimensions.cols, 88); assert.isAbove(dimensions.rows, 24); assert.isBelow(dimensions.rows, 29); @@ -57,7 +57,7 @@ describe('FitAddon', () => { it('width', async function(): Promise { await loadFit(1008); const dimensions: {cols: number, rows: number} = await page.evaluate(`window.fit.proposeDimensions()`); - assert.isAbove(dimensions.cols, 109); + assert.isAbove(dimensions.cols, 108); assert.isBelow(dimensions.cols, 111); assert.isAbove(dimensions.rows, 24); assert.isBelow(dimensions.rows, 29); @@ -91,7 +91,7 @@ describe('FitAddon', () => { await page.evaluate(`window.fit.fit()`); const cols: number = await page.evaluate(`window.term.cols`); const rows: number = await page.evaluate(`window.term.rows`); - assert.isAbove(cols, 86); + assert.isAbove(cols, 85); assert.isBelow(cols, 88); assert.isAbove(rows, 24); assert.isBelow(rows, 29); @@ -102,7 +102,7 @@ describe('FitAddon', () => { await page.evaluate(`window.fit.fit()`); const cols: number = await page.evaluate(`window.term.cols`); const rows: number = await page.evaluate(`window.term.rows`); - assert.isAbove(cols, 109); + assert.isAbove(cols, 108); assert.isBelow(cols, 111); assert.isAbove(rows, 24); assert.isBelow(rows, 29); From 3271b45681d0d4f3467fd21fef0f94ab7ef9924e Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 28 Jul 2022 05:08:46 -0700 Subject: [PATCH 38/42] Publish canvas addon in CI release step Part of #3271 --- bin/publish.js | 1 + 1 file changed, 1 insertion(+) diff --git a/bin/publish.js b/bin/publish.js index a43b8cd4..91c6d09a 100644 --- a/bin/publish.js +++ b/bin/publish.js @@ -28,6 +28,7 @@ if (changedFiles.some(e => e.search(/^addons\//) === -1)) { // Publish addons if any files were changed inside of the addon const addonPackageDirs = [ path.resolve(__dirname, '../addons/xterm-addon-attach'), + path.resolve(__dirname, '../addons/xterm-addon-canvas'), path.resolve(__dirname, '../addons/xterm-addon-fit'), path.resolve(__dirname, '../addons/xterm-addon-ligatures'), path.resolve(__dirname, '../addons/xterm-addon-search'), From 1a5c881c7ca9bf2a93cc6696446da3d780060f0b Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 28 Jul 2022 05:28:28 -0700 Subject: [PATCH 39/42] Stabilize buffer and parser APIs --- typings/xterm.d.ts | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index f16ff970..5f9d717f 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -649,9 +649,7 @@ declare module 'xterm' { readonly cols: number; /** - * (EXPERIMENTAL) The terminal's current buffer, this might be either the - * normal buffer or the alt buffer depending on what's running in the - * terminal. + * Access to the terminal's normal and alt buffer. */ readonly buffer: IBufferNamespace; @@ -662,8 +660,7 @@ declare module 'xterm' { readonly markers: ReadonlyArray; /** - * (EXPERIMENTAL) Get the parser interface to register - * custom escape sequence handlers. + * Get the parser interface to register custom escape sequence handlers. */ readonly parser: IParser; From d1f53e2b026db85ff734eaea3652ab8a4a20a199 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 28 Jul 2022 05:58:15 -0700 Subject: [PATCH 40/42] Replace ISelectionPosition with IBufferRange Fixes #2480 --- addons/xterm-addon-search/src/SearchAddon.ts | 20 +++++++------- .../src/SerializeAddon.ts | 4 +-- src/browser/Terminal.ts | 18 ++++++++----- src/browser/TestUtils.test.ts | 6 ++--- src/browser/Types.d.ts | 4 +-- src/browser/public/Terminal.ts | 6 ++--- typings/xterm-headless.d.ts | 25 ----------------- typings/xterm.d.ts | 27 +------------------ 8 files changed, 32 insertions(+), 78 deletions(-) diff --git a/addons/xterm-addon-search/src/SearchAddon.ts b/addons/xterm-addon-search/src/SearchAddon.ts index 2553d11a..689899ef 100644 --- a/addons/xterm-addon-search/src/SearchAddon.ts +++ b/addons/xterm-addon-search/src/SearchAddon.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { Terminal, IDisposable, ITerminalAddon, ISelectionPosition, IDecoration } from 'xterm'; +import { Terminal, IDisposable, ITerminalAddon, IBufferRange, IDecoration } from 'xterm'; import { EventEmitter } from 'common/EventEmitter'; export interface ISearchOptions { @@ -235,14 +235,14 @@ export class SearchAddon implements ITerminalAddon { let startCol = 0; let startRow = 0; - let currentSelection: ISelectionPosition | undefined; + let currentSelection: IBufferRange | undefined; if (this._terminal.hasSelection()) { const incremental = searchOptions ? searchOptions.incremental : false; // Start from the selection end if there is a selection // For incremental search, use existing row currentSelection = this._terminal.getSelectionPosition()!; - startRow = incremental ? currentSelection.startRow : currentSelection.endRow; - startCol = incremental ? currentSelection.startColumn : currentSelection.endColumn; + startRow = incremental ? currentSelection.start.y : currentSelection.end.y; + startCol = incremental ? currentSelection.start.x : currentSelection.end.x; } this._initLinesCache(); @@ -282,7 +282,7 @@ export class SearchAddon implements ITerminalAddon { // If there is only one result, wrap back and return selection if it exists. if (!result && currentSelection) { - searchPosition.startRow = currentSelection.startRow; + searchPosition.startRow = currentSelection.start.y; searchPosition.startCol = 0; result = this._findInLine(term, searchPosition, searchOptions); } @@ -357,12 +357,12 @@ export class SearchAddon implements ITerminalAddon { const isReverseSearch = true; const incremental = searchOptions ? searchOptions.incremental : false; - let currentSelection: ISelectionPosition | undefined; + let currentSelection: IBufferRange | undefined; if (this._terminal.hasSelection()) { currentSelection = this._terminal.getSelectionPosition()!; // Start from selection start if there is a selection - startRow = currentSelection.startRow; - startCol = currentSelection.startColumn; + startRow = currentSelection.start.y; + startCol = currentSelection.start.x; } this._initLinesCache(); @@ -378,8 +378,8 @@ export class SearchAddon implements ITerminalAddon { if (!isOldResultHighlighted) { // If selection was not able to be expanded to the right, then try reverse search if (currentSelection) { - searchPosition.startRow = currentSelection.endRow; - searchPosition.startCol = currentSelection.endColumn; + searchPosition.startRow = currentSelection.end.y; + searchPosition.startCol = currentSelection.end.x; } result = this._findInLine(term, searchPosition, searchOptions, true); } diff --git a/addons/xterm-addon-serialize/src/SerializeAddon.ts b/addons/xterm-addon-serialize/src/SerializeAddon.ts index ed71e4e1..99967374 100644 --- a/addons/xterm-addon-serialize/src/SerializeAddon.ts +++ b/addons/xterm-addon-serialize/src/SerializeAddon.ts @@ -443,8 +443,8 @@ export class SerializeAddon implements ITerminalAddon { const selection = this._terminal?.getSelectionPosition(); if (selection !== undefined) { return handler.serialize({ - start: { x: selection.startRow, y: selection.startColumn }, - end: { x: selection.endRow, y: selection.endColumn } + start: { x: selection.start.y, y: selection.start.x }, + end: { x: selection.end.y, y: selection.end.x } }); } diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index c59a065a..ac6f887e 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -21,7 +21,7 @@ * http://linux.die.net/man/7/urxvt */ -import { ICompositionHelper, ITerminal, IBrowser, CustomKeyEventHandler, IViewport, ILinkifier2, CharacterJoinerHandler } from 'browser/Types'; +import { ICompositionHelper, ITerminal, IBrowser, CustomKeyEventHandler, IViewport, ILinkifier2, CharacterJoinerHandler, IBufferRange } from 'browser/Types'; import { IRenderer } from 'browser/renderer/Types'; import { CompositionHelper } from 'browser/input/CompositionHelper'; import { Viewport } from 'browser/Viewport'; @@ -33,7 +33,7 @@ import * as Browser from 'common/Platform'; import { addDisposableDomListener } from 'browser/Lifecycle'; import * as Strings from 'browser/LocalizableStrings'; import { AccessibilityManager } from './AccessibilityManager'; -import { ITheme, IMarker, IDisposable, ISelectionPosition, ILinkProvider, IDecorationOptions, IDecoration } from 'xterm'; +import { ITheme, IMarker, IDisposable, ILinkProvider, IDecorationOptions, IDecoration } from 'xterm'; import { DomRenderer } from 'browser/renderer/dom/DomRenderer'; import { KeyboardResultType, CoreMouseEventType, CoreMouseButton, CoreMouseAction, ITerminalOptions, ScrollSource, IColorEvent, ColorIndex, ColorRequestType } from 'common/Types'; import { evaluateKeyboardEvent } from 'common/input/Keyboard'; @@ -993,16 +993,20 @@ export class Terminal extends CoreTerminal implements ITerminal { return this._selectionService ? this._selectionService.selectionText : ''; } - public getSelectionPosition(): ISelectionPosition | undefined { + public getSelectionPosition(): IBufferRange | undefined { if (!this._selectionService || !this._selectionService.hasSelection) { return undefined; } return { - startColumn: this._selectionService.selectionStart![0], - startRow: this._selectionService.selectionStart![1], - endColumn: this._selectionService.selectionEnd![0], - endRow: this._selectionService.selectionEnd![1] + start: { + x: this._selectionService.selectionStart![0], + y: this._selectionService.selectionStart![1] + }, + end: { + x: this._selectionService.selectionEnd![0], + y: this._selectionService.selectionEnd![1] + } }; } diff --git a/src/browser/TestUtils.test.ts b/src/browser/TestUtils.test.ts index d59119e7..7c509160 100644 --- a/src/browser/TestUtils.test.ts +++ b/src/browser/TestUtils.test.ts @@ -3,11 +3,11 @@ * @license MIT */ -import { IDisposable, IMarker, ISelectionPosition, ILinkProvider, IDecorationOptions, IDecoration } from 'xterm'; +import { IDisposable, IMarker, ILinkProvider, IDecorationOptions, IDecoration } from 'xterm'; import { IEvent, EventEmitter } from 'common/EventEmitter'; import { ICharacterJoinerService, ICharSizeService, IMouseService, IRenderService, ISelectionService } from 'browser/services/Services'; import { IRenderDimensions, IRenderer, IRequestRedrawEvent } from 'browser/renderer/Types'; -import { IColorSet, ITerminal, ILinkifier2, IBrowser, IViewport, IColorManager, ICompositionHelper, CharacterJoinerHandler, IRenderDebouncer } from 'browser/Types'; +import { IColorSet, ITerminal, ILinkifier2, IBrowser, IViewport, IColorManager, ICompositionHelper, CharacterJoinerHandler, IRenderDebouncer, IBufferRange } from 'browser/Types'; import { IBuffer, IBufferStringIterator, IBufferSet } from 'common/buffer/Types'; import { IBufferLine, ICellData, IAttributeData, ICircularList, XtermListener, ICharset, ITerminalOptions } from 'common/Types'; import { Buffer } from 'common/buffer/Buffer'; @@ -107,7 +107,7 @@ export class MockTerminal implements ITerminal { public getSelection(): string { throw new Error('Method not implemented.'); } - public getSelectionPosition(): ISelectionPosition | undefined { + public getSelectionPosition(): IBufferRange | undefined { throw new Error('Method not implemented.'); } public clearSelection(): void { diff --git a/src/browser/Types.d.ts b/src/browser/Types.d.ts index f458f5c8..f7011200 100644 --- a/src/browser/Types.d.ts +++ b/src/browser/Types.d.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { IDecorationOptions, IDecoration, IDisposable, IMarker, ISelectionPosition } from 'xterm'; +import { IDecorationOptions, IDecoration, IDisposable, IMarker } from 'xterm'; import { IEvent } from 'common/EventEmitter'; import { ICoreTerminal, CharData, ITerminalOptions, IColor } from 'common/Types'; import { IMouseService, IRenderService } from './services/Services'; @@ -62,7 +62,7 @@ export interface IPublicTerminal extends IDisposable { registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined; hasSelection(): boolean; getSelection(): string; - getSelectionPosition(): ISelectionPosition | undefined; + getSelectionPosition(): IBufferRange | undefined; clearSelection(): void; select(column: number, row: number, length: number): void; selectAll(): void; diff --git a/src/browser/public/Terminal.ts b/src/browser/public/Terminal.ts index 65f26db0..57efdbd0 100644 --- a/src/browser/public/Terminal.ts +++ b/src/browser/public/Terminal.ts @@ -3,8 +3,8 @@ * @license MIT */ -import { Terminal as ITerminalApi, IMarker, IDisposable, ITheme, ILocalizableStrings, ITerminalAddon, ISelectionPosition, IBufferNamespace as IBufferNamespaceApi, IParser, ILinkProvider, IUnicodeHandling, FontWeight, IModes, IDecorationOptions, IDecoration } from 'xterm'; -import { ITerminal } from 'browser/Types'; +import { Terminal as ITerminalApi, IMarker, IDisposable, ILocalizableStrings, ITerminalAddon, IBufferNamespace as IBufferNamespaceApi, IParser, ILinkProvider, IUnicodeHandling, FontWeight, IModes, IDecorationOptions, IDecoration } from 'xterm'; +import { IBufferRange, ITerminal } from 'browser/Types'; import { Terminal as TerminalCore } from 'browser/Terminal'; import * as Strings from 'browser/LocalizableStrings'; import { IEvent } from 'common/EventEmitter'; @@ -178,7 +178,7 @@ export class Terminal implements ITerminalApi { public getSelection(): string { return this._core.getSelection(); } - public getSelectionPosition(): ISelectionPosition | undefined { + public getSelectionPosition(): IBufferRange | undefined { return this._core.getSelectionPosition(); } public clearSelection(): void { diff --git a/typings/xterm-headless.d.ts b/typings/xterm-headless.d.ts index c2b77acc..76527e37 100644 --- a/typings/xterm-headless.d.ts +++ b/typings/xterm-headless.d.ts @@ -711,31 +711,6 @@ declare module 'xterm-headless' { activate(terminal: Terminal): void; } - /** - * An object representing a selection within the terminal. - */ - interface ISelectionPosition { - /** - * The start column of the selection. - */ - startColumn: number; - - /** - * The start row of the selection. - */ - startRow: number; - - /** - * The end column of the selection. - */ - endColumn: number; - - /** - * The end row of the selection. - */ - endRow: number; - } - /** * An object representing a range within the viewport of the terminal. */ diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 5f9d717f..43e52ee6 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -917,7 +917,7 @@ declare module 'xterm' { /** * Gets the selection position or undefined if there is no selection. */ - getSelectionPosition(): ISelectionPosition | undefined; + getSelectionPosition(): IBufferRange | undefined; /** * Clears the current terminal selection. @@ -1047,31 +1047,6 @@ declare module 'xterm' { activate(terminal: Terminal): void; } - /** - * An object representing a selection within the terminal. - */ - interface ISelectionPosition { - /** - * The start column of the selection. - */ - startColumn: number; - - /** - * The start row of the selection. - */ - startRow: number; - - /** - * The end column of the selection. - */ - endColumn: number; - - /** - * The end row of the selection. - */ - endRow: number; - } - /** * An object representing a range within the viewport of the terminal. */ From 520a270afcb17c5c6d203c9682fe2304977c67a7 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 28 Jul 2022 06:17:44 -0700 Subject: [PATCH 41/42] Update tests to use new getSelectionPosition API --- .../test/SearchAddon.api.ts | 79 ++++++++++--------- test/api/Terminal.api.ts | 4 +- 2 files changed, 44 insertions(+), 39 deletions(-) diff --git a/addons/xterm-addon-search/test/SearchAddon.api.ts b/addons/xterm-addon-search/test/SearchAddon.api.ts index 4a801776..f17ecd1b 100644 --- a/addons/xterm-addon-search/test/SearchAddon.api.ts +++ b/addons/xterm-addon-search/test/SearchAddon.api.ts @@ -60,35 +60,35 @@ describe('Search Tests', function (): void { await page.evaluate(`window.term.writeln('package.jsonc\\n')`); await writeSync(page, 'package.json pack package.lock'); await page.evaluate(`window.search.findPrevious('pack', {incremental: true})`); - let line: string = await page.evaluate(`window.term.buffer.active.getLine(window.term.getSelectionPosition().startRow).translateToString()`); - let selectionPosition: { startColumn: number, startRow: number, endColumn: number, endRow: number } = await page.evaluate(`window.term.getSelectionPosition()`); + let line: string = await page.evaluate(`window.term.buffer.active.getLine(window.term.getSelectionPosition().start.y).translateToString()`); + let selectionPosition: { start: { x: number, y: number }, end: { x: number, y: number } } = await page.evaluate(`window.term.getSelectionPosition()`); // We look further ahead in the line to ensure that pack was selected from package.lock - assert.deepEqual(line.substring(selectionPosition.startColumn, selectionPosition.endColumn + 8), 'package.lock'); + assert.deepEqual(line.substring(selectionPosition.start.x, selectionPosition.end.x + 8), 'package.lock'); await page.evaluate(`window.search.findPrevious('package.j', {incremental: true})`); selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`); - assert.deepEqual(line.substring(selectionPosition.startColumn, selectionPosition.endColumn + 3), 'package.json'); + assert.deepEqual(line.substring(selectionPosition.start.x, selectionPosition.end.x + 3), 'package.json'); await page.evaluate(`window.search.findPrevious('package.jsonc', {incremental: true})`); // We have to reevaluate line because it should have switched starting rows at this point - line = await page.evaluate(`window.term.buffer.active.getLine(window.term.getSelectionPosition().startRow).translateToString()`); + line = await page.evaluate(`window.term.buffer.active.getLine(window.term.getSelectionPosition().start.y).translateToString()`); selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`); - assert.deepEqual(line.substring(selectionPosition.startColumn, selectionPosition.endColumn), 'package.jsonc'); + assert.deepEqual(line.substring(selectionPosition.start.x, selectionPosition.end.x), 'package.jsonc'); }); it('Incremental Find Next', async () => { await page.evaluate(`window.term.writeln('package.lock pack package.json package.ups\\n')`); await writeSync(page, 'package.jsonc'); await page.evaluate(`window.search.findNext('pack', {incremental: true})`); - let line: string = await page.evaluate(`window.term.buffer.active.getLine(window.term.getSelectionPosition().startRow).translateToString()`); - let selectionPosition: { startColumn: number, startRow: number, endColumn: number, endRow: number } = await page.evaluate(`window.term.getSelectionPosition()`); + let line: string = await page.evaluate(`window.term.buffer.active.getLine(window.term.getSelectionPosition().start.y).translateToString()`); + let selectionPosition: { start: { x: number, y: number }, end: { x: number, y: number } } = await page.evaluate(`window.term.getSelectionPosition()`); // We look further ahead in the line to ensure that pack was selected from package.lock - assert.deepEqual(line.substring(selectionPosition.startColumn, selectionPosition.endColumn + 8), 'package.lock'); + assert.deepEqual(line.substring(selectionPosition.start.x, selectionPosition.end.x + 8), 'package.lock'); await page.evaluate(`window.search.findNext('package.j', {incremental: true})`); selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`); - assert.deepEqual(line.substring(selectionPosition.startColumn, selectionPosition.endColumn + 3), 'package.json'); + assert.deepEqual(line.substring(selectionPosition.start.x, selectionPosition.end.x + 3), 'package.json'); await page.evaluate(`window.search.findNext('package.jsonc', {incremental: true})`); // We have to reevaluate line because it should have switched starting rows at this point - line = await page.evaluate(`window.term.buffer.active.getLine(window.term.getSelectionPosition().startRow).translateToString()`); + line = await page.evaluate(`window.term.buffer.active.getLine(window.term.getSelectionPosition().start.y).translateToString()`); selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`); - assert.deepEqual(line.substring(selectionPosition.startColumn, selectionPosition.endColumn), 'package.jsonc'); + assert.deepEqual(line.substring(selectionPosition.start.x, selectionPosition.end.x), 'package.jsonc'); }); it('Simple Regex', async () => { await writeSync(page, 'abc123defABCD'); @@ -116,10 +116,14 @@ describe('Search Tests', function (): void { assert.deepEqual(await page.evaluate(`window.term.getSelection()`), '𝄞'); assert.deepEqual(await page.evaluate(`window.search.findNext('𝄞')`), true); assert.deepEqual(await page.evaluate(`window.term.getSelectionPosition()`), { - startRow: 0, - endRow: 0, - startColumn: 7, - endColumn: 8 + start: { + x: 7, + y: 0 + }, + end: { + x: 8, + y: 0 + } }); }); @@ -313,69 +317,70 @@ describe('Search Tests', function (): void { await writeSync(page, fixture); assert.deepEqual(await page.evaluate(`window.search.findNext('opencv')`), true); let selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`); - assert.deepEqual(selectionPosition, { startColumn: 24, startRow: 53, endColumn: 30, endRow: 53 }); + assert.deepEqual(selectionPosition, { start: { x: 24, y: 53 }, end: { x: 30, y: 53 } }); assert.deepEqual(await page.evaluate(`window.search.findNext('opencv')`), true); selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`); - assert.deepEqual(selectionPosition, { startColumn: 24, startRow: 76, endColumn: 30, endRow: 76 }); + assert.deepEqual(selectionPosition, { start: { x: 24, y: 76 }, end: { x: 30, y: 76 } }); assert.deepEqual(await page.evaluate(`window.search.findNext('opencv')`), true); selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`); - assert.deepEqual(selectionPosition, { startColumn: 24, startRow: 96, endColumn: 30, endRow: 96 }); + assert.deepEqual(selectionPosition, { start: { x: 24, y: 96 }, end: { x: 30, y: 96 } }); assert.deepEqual(await page.evaluate(`window.search.findNext('opencv')`), true); selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`); - assert.deepEqual(selectionPosition, { startColumn: 1, startRow: 114, endColumn: 7, endRow: 114 }); + assert.deepEqual(selectionPosition, { start: { x: 1, y: 114 }, end: { x: 7, y: 114 } }); assert.deepEqual(await page.evaluate(`window.search.findNext('opencv')`), true); selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`); - assert.deepEqual(selectionPosition, { startColumn: 11, startRow: 115, endColumn: 17, endRow: 115 }); + assert.deepEqual(selectionPosition, { start: { x: 11, y: 115 }, end: { x: 17, y: 115 } }); assert.deepEqual(await page.evaluate(`window.search.findNext('opencv')`), true); selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`); - assert.deepEqual(selectionPosition, { startColumn: 1, startRow: 126, endColumn: 7, endRow: 126 }); + assert.deepEqual(selectionPosition, { start: { x: 1, y: 126 }, end: { x: 7, y: 126 } }); assert.deepEqual(await page.evaluate(`window.search.findNext('opencv')`), true); selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`); - assert.deepEqual(selectionPosition, { startColumn: 11, startRow: 127, endColumn: 17, endRow: 127 }); + assert.deepEqual(selectionPosition, { start: { x: 11, y: 127 }, end: { x: 17, y: 127 } }); assert.deepEqual(await page.evaluate(`window.search.findNext('opencv')`), true); selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`); - assert.deepEqual(selectionPosition, { startColumn: 1, startRow: 135, endColumn: 7, endRow: 135 }); + assert.deepEqual(selectionPosition, { start: { x: 1, y: 135 }, end: { x: 7, y: 135 } }); assert.deepEqual(await page.evaluate(`window.search.findNext('opencv')`), true); selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`); - assert.deepEqual(selectionPosition, { startColumn: 11, startRow: 136, endColumn: 17, endRow: 136 }); + assert.deepEqual(selectionPosition, { start: { x: 11, y: 136 }, end: { x: 17, y: 136 } }); // Wrap around to first result assert.deepEqual(await page.evaluate(`window.search.findNext('opencv')`), true); selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`); - assert.deepEqual(selectionPosition, { startColumn: 24, startRow: 53, endColumn: 30, endRow: 53 }); + assert.deepEqual(selectionPosition, { start: { x: 24, y: 53 }, end: { x: 30, y: 53 } }); }); - it('should find all occurrences using findPrevious', async () => { + + it('should y all occurrences using findPrevious', async () => { await writeSync(page, fixture); assert.deepEqual(await page.evaluate(`window.search.findPrevious('opencv')`), true); let selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`); - assert.deepEqual(selectionPosition, { startColumn: 11, startRow: 136, endColumn: 17, endRow: 136 }); + assert.deepEqual(selectionPosition, { start: { x: 11, y: 136 }, end: { x: 17, y: 136 } }); assert.deepEqual(await page.evaluate(`window.search.findPrevious('opencv')`), true); selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`); - assert.deepEqual(selectionPosition, { startColumn: 1, startRow: 135, endColumn: 7, endRow: 135 }); + assert.deepEqual(selectionPosition, { start: { x: 1, y: 135 }, end: { x: 7, y: 135 } }); assert.deepEqual(await page.evaluate(`window.search.findPrevious('opencv')`), true); selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`); - assert.deepEqual(selectionPosition, { startColumn: 11, startRow: 127, endColumn: 17, endRow: 127 }); + assert.deepEqual(selectionPosition, { start: { x: 11, y: 127 }, end: { x: 17, y: 127 } }); assert.deepEqual(await page.evaluate(`window.search.findPrevious('opencv')`), true); selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`); - assert.deepEqual(selectionPosition, { startColumn: 1, startRow: 126, endColumn: 7, endRow: 126 }); + assert.deepEqual(selectionPosition, { start: { x: 1, y: 126 }, end: { x: 7, y: 126 } }); assert.deepEqual(await page.evaluate(`window.search.findPrevious('opencv')`), true); selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`); - assert.deepEqual(selectionPosition, { startColumn: 11, startRow: 115, endColumn: 17, endRow: 115 }); + assert.deepEqual(selectionPosition, { start: { x: 11, y: 115 }, end: { x: 17, y: 115 } }); assert.deepEqual(await page.evaluate(`window.search.findPrevious('opencv')`), true); selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`); - assert.deepEqual(selectionPosition, { startColumn: 1, startRow: 114, endColumn: 7, endRow: 114 }); + assert.deepEqual(selectionPosition, { start: { x: 1, y: 114 }, end: { x: 7, y: 114 } }); assert.deepEqual(await page.evaluate(`window.search.findPrevious('opencv')`), true); selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`); - assert.deepEqual(selectionPosition, { startColumn: 24, startRow: 96, endColumn: 30, endRow: 96 }); + assert.deepEqual(selectionPosition, { start: { x: 24, y: 96 }, end: { x: 30, y: 96 } }); assert.deepEqual(await page.evaluate(`window.search.findPrevious('opencv')`), true); selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`); - assert.deepEqual(selectionPosition, { startColumn: 24, startRow: 76, endColumn: 30, endRow: 76 }); + assert.deepEqual(selectionPosition, { start: { x: 24, y: 76 }, end: { x: 30, y: 76 } }); assert.deepEqual(await page.evaluate(`window.search.findPrevious('opencv')`), true); selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`); - assert.deepEqual(selectionPosition, { startColumn: 24, startRow: 53, endColumn: 30, endRow: 53 }); + assert.deepEqual(selectionPosition, { start: { x: 24, y: 53 }, end: { x: 30, y: 53 } }); // Wrap around to first result assert.deepEqual(await page.evaluate(`window.search.findPrevious('opencv')`), true); selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`); - assert.deepEqual(selectionPosition, { startColumn: 11, startRow: 136, endColumn: 17, endRow: 136 }); + assert.deepEqual(selectionPosition, { start: { x: 11, y: 136 }, end: { x: 17, y: 136 } }); }); }); }); diff --git a/test/api/Terminal.api.ts b/test/api/Terminal.api.ts index 72b1ba34..502f438c 100644 --- a/test/api/Terminal.api.ts +++ b/test/api/Terminal.api.ts @@ -252,7 +252,7 @@ describe('API Integration Tests', function(): void { } else { assert.equal(await page.evaluate(`window.term.getSelection()`), '\n\nfoo\n\nbar\n\nbaz'); } - assert.deepEqual(await page.evaluate(`window.term.getSelectionPosition()`), { startColumn: 0, startRow: 0, endColumn: 5, endRow: 6 }); + assert.deepEqual(await page.evaluate(`window.term.getSelectionPosition()`), { start: { x: 0, y: 0 }, end: { x: 5, y: 6 } }); await page.evaluate(`window.term.clearSelection()`); assert.equal(await page.evaluate(`window.term.hasSelection()`), false); assert.equal(await page.evaluate(`window.term.getSelection()`), ''); @@ -260,7 +260,7 @@ describe('API Integration Tests', function(): void { await page.evaluate(`window.term.select(1, 2, 2)`); assert.equal(await page.evaluate(`window.term.hasSelection()`), true); assert.equal(await page.evaluate(`window.term.getSelection()`), 'oo'); - assert.deepEqual(await page.evaluate(`window.term.getSelectionPosition()`), { startColumn: 1, startRow: 2, endColumn: 3, endRow: 2 }); + assert.deepEqual(await page.evaluate(`window.term.getSelectionPosition()`), { start: { x: 1, y: 2 }, end: { x: 3, y: 2 } }); }); it('focus, blur', async () => { From 3bdbfb6d5feb64d89d9c053193fb66ff3a8a2679 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 28 Jul 2022 06:31:52 -0700 Subject: [PATCH 42/42] Hardcode release step to publish v5.0.0 beta --- bin/publish.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/bin/publish.js b/bin/publish.js index 91c6d09a..cb6c836a 100644 --- a/bin/publish.js +++ b/bin/publish.js @@ -98,8 +98,9 @@ function getNextBetaVersion(packageJson) { process.exit(1); } const tag = 'beta'; - const stableVersion = packageJson.version.split('.'); - const nextStableVersion = `${stableVersion[0]}.${parseInt(stableVersion[1]) + 1}.0`; + // const stableVersion = packageJson.version.split('.'); + // const nextStableVersion = `${stableVersion[0]}.${parseInt(stableVersion[1]) + 1}.0`; + const nextStableVersion = `5.0.0`; const publishedVersions = getPublishedVersions(packageJson, nextStableVersion, tag); if (publishedVersions.length === 0) { return `${nextStableVersion}-${tag}.1`;