Compare commits

..
210 Commits
Author SHA1 Message Date
Daniel Imms 4809a2e0df Merge pull request #2828 from Tyriar/release45
v4.5.0
2020-04-10 12:53:39 -07:00
Daniel Imms f61d6320a3 v4.5.0 2020-04-10 12:44:39 -07:00
Daniel Imms c66b25b4cc Merge pull request #2827 from Tyriar/eslint_types
Convert eslintrc to json and fix errors
2020-04-10 10:29:46 -07:00
Daniel Imms a66edbdda8 Merge pull request #2713 from JavaCS3/master
expose IBufferSet API
2020-04-10 10:18:51 -07:00
Daniel Imms ef0d834db8 Convert eslintrc to json and fix errors 2020-04-10 10:05:23 -07:00
Daniel Imms 89daacac32 Add missing api docs 2020-04-10 10:02:09 -07:00
Daniel Imms f4f15ee5e9 Add missing line in api 2020-04-10 09:57:10 -07:00
Daniel Imms 4c36ef9d24 Don't pass buffers to BufferApiView 2020-04-10 09:56:26 -07:00
Daniel Imms 886af692e2 Use a separate emitter for BufferNamespaceApi.onBufferChange 2020-04-10 09:53:51 -07:00
Daniel Imms c63949f384 Merge branch 'master' into master 2020-04-10 09:37:51 -07:00
Daniel Imms 34a324f551 Merge pull request #2825 from Tyriar/debug_log
Use debug log level for open not attached to the DOM
2020-04-10 07:50:51 -07:00
Daniel Imms 996694aec4 Use debug log level for open not attached to the DOM
It seems mostly harmless now

Part of microsoft/vscode#88529
2020-04-10 07:50:10 -07:00
Daniel Imms e21060a259 Merge pull request #2725 from Tyriar/webkit
Improve Linux dependencies, remove browser args
2020-04-10 06:45:37 -07:00
Daniel Imms 951d342ed7 Disable webkit 2020-04-10 06:38:54 -07:00
Daniel Imms 74b76704f8 Merge pull request #2746 from jerch/fix_2745
cursor handling in DECSTR
2020-04-10 06:36:39 -07:00
Daniel Imms 6efc37eaf3 Merge branch 'master' into webkit 2020-04-10 06:34:45 -07:00
Daniel Imms 9cf4e68ac4 Merge pull request #2739 from felixse/fix-edge-selection-color
Fix selection color in Legacy Edge
2020-04-10 06:34:05 -07:00
Daniel Imms 1c35c8b617 Merge pull request #2817 from Tyriar/2764
Pause cursor render layer before resuming
2020-04-10 06:25:30 -07:00
Charles Wei 94dabe225b Merge branch 'master' into master 2020-04-10 12:11:17 +08:00
Daniel Imms 21b490f03a Merge pull request #2819 from xtermjs/dependabot/npm_and_yarn/typescript-eslint/parser-2.27.0
Bump @typescript-eslint/parser from 2.26.0 to 2.27.0
2020-04-07 06:31:30 -07:00
dependabot-preview[bot] f8f509a296 Bump @typescript-eslint/parser from 2.26.0 to 2.27.0
Bumps [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) from 2.26.0 to 2.27.0.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/parser/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v2.27.0/packages/parser)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-04-07 13:17:32 +00:00
Daniel Imms 062b771622 Merge pull request #2820 from xtermjs/dependabot/npm_and_yarn/typescript-eslint/eslint-plugin-2.27.0
Bump @typescript-eslint/eslint-plugin from 2.26.0 to 2.27.0
2020-04-07 06:15:48 -07:00
dependabot-preview[bot] adff472011 Bump @typescript-eslint/eslint-plugin from 2.26.0 to 2.27.0
Bumps [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) from 2.26.0 to 2.27.0.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v2.27.0/packages/eslint-plugin)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-04-07 08:17:08 +00:00
Charles Wei 9c043dbf9f Merge branch 'master' into master 2020-04-07 11:27:32 +08:00
Daniel Imms 59d28e7b99 Use browser types instead of nodejs 2020-04-06 05:33:09 -07:00
Daniel Imms 8b5a133106 Pause cursor render layer before resuming
Fixes #2764
2020-04-06 05:32:11 -07:00
Daniel Imms 86a41dd6dd Merge pull request #2815 from xtermjs/dependabot/npm_and_yarn/nyc-15.0.1
Bump nyc from 15.0.0 to 15.0.1
2020-04-06 04:23:57 -07:00
dependabot-preview[bot] 2d77b25e26 Bump nyc from 15.0.0 to 15.0.1
Bumps [nyc](https://github.com/istanbuljs/nyc) from 15.0.0 to 15.0.1.
- [Release notes](https://github.com/istanbuljs/nyc/releases)
- [Changelog](https://github.com/istanbuljs/nyc/blob/master/CHANGELOG.md)
- [Commits](https://github.com/istanbuljs/nyc/compare/v15.0.0...v15.0.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-04-06 08:41:11 +00:00
Daniel Imms 43526c2297 Merge pull request #2814 from Tyriar/role_list
Use role=list in screen reader mode
2020-04-05 09:54:56 -07:00
Daniel Imms 192762add8 Use role=list in screen reader mode 2020-04-05 09:54:19 -07:00
javacs3 0f64847351 fix lint error 2020-04-05 15:31:05 +08:00
Charles Wei 88d2f26269 Merge branch 'master' into master 2020-04-05 12:15:37 +08:00
Daniel Imms 21aabb1215 Merge pull request #2810 from xtermjs/dependabot/npm_and_yarn/deep-equal-2.0.2
Bump deep-equal from 2.0.1 to 2.0.2
2020-04-02 07:58:40 -07:00
dependabot-preview[bot] e6ea51f8cb Bump deep-equal from 2.0.1 to 2.0.2
Bumps [deep-equal](https://github.com/inspect-js/node-deep-equal) from 2.0.1 to 2.0.2.
- [Release notes](https://github.com/inspect-js/node-deep-equal/releases)
- [Changelog](https://github.com/inspect-js/node-deep-equal/blob/master/CHANGELOG.md)
- [Commits](https://github.com/inspect-js/node-deep-equal/compare/v2.0.1...v2.0.2)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-04-02 08:07:09 +00:00
Daniel Imms 6d52e1709f Merge pull request #2805 from xtermjs/dependabot/npm_and_yarn/mustache-4.0.1
Bump mustache from 3.2.1 to 4.0.1
2020-04-01 06:59:52 -07:00
dependabot-preview[bot] aaf1fd0256 Bump mustache from 3.2.1 to 4.0.1
Bumps [mustache](https://github.com/janl/mustache.js) from 3.2.1 to 4.0.1.
- [Release notes](https://github.com/janl/mustache.js/releases)
- [Changelog](https://github.com/janl/mustache.js/blob/master/CHANGELOG.md)
- [Commits](https://github.com/janl/mustache.js/compare/v3.2.1...v4.0.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-04-01 13:27:45 +00:00
Daniel Imms 97ef539a6c Merge pull request #2803 from Tyriar/more_deps
Upgrade more deps
2020-04-01 06:25:58 -07:00
Daniel Imms b630aad44a Namespace window 2020-04-01 05:25:39 -07:00
Daniel Imms 2f6fb4b97c Upgrade jsdom 2020-03-31 11:10:39 -07:00
Daniel Imms 5c99114b02 Upgrade nyc 2020-03-31 11:07:26 -07:00
Daniel Imms 318421df9d Update more types 2020-03-31 11:01:33 -07:00
Daniel Imms 2f42dc6473 Upgrade chai to 4 2020-03-31 10:59:15 -07:00
Daniel Imms 66bcdc3c00 Update some more dependencies 2020-03-31 10:55:55 -07:00
Daniel Imms 8b042caf60 Merge pull request #2792 from xtermjs/dependabot/npm_and_yarn/mocha-7.1.1
Bump mocha from 6.2.3 to 7.1.1
2020-03-31 09:12:50 -07:00
dependabot-preview[bot] b3408f5ad4 Bump mocha from 6.2.3 to 7.1.1
Bumps [mocha](https://github.com/mochajs/mocha) from 6.2.3 to 7.1.1.
- [Release notes](https://github.com/mochajs/mocha/releases)
- [Changelog](https://github.com/mochajs/mocha/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mochajs/mocha/compare/v6.2.3...v7.1.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-03-31 14:33:22 +00:00
Daniel Imms 5558bc1ef3 Merge pull request #2780 from xtermjs/dependabot/npm_and_yarn/types/ws-7.2.3
Bump @types/ws from 6.0.1 to 7.2.3
2020-03-31 07:31:37 -07:00
dependabot-preview[bot] 2426a731fc Bump @types/ws from 6.0.1 to 7.2.3
Bumps [@types/ws](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/ws) from 6.0.1 to 7.2.3.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/ws)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-03-31 14:14:25 +00:00
Daniel Imms 47e2b0991a Merge pull request #2801 from xtermjs/dependabot/npm_and_yarn/types/webpack-4.41.10
Bump @types/webpack from 4.41.9 to 4.41.10
2020-03-31 07:12:46 -07:00
dependabot-preview[bot] ceec98ffc3 Bump @types/webpack from 4.41.9 to 4.41.10
Bumps [@types/webpack](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/webpack) from 4.41.9 to 4.41.10.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/webpack)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-03-31 08:10:36 +00:00
Daniel Imms a24b977f72 Merge pull request #2800 from Tyriar/upgrade_mac
Upgrade mac agent in pipelines
2020-03-30 10:00:29 -07:00
Daniel Imms 4602ffba29 Merge pull request #2799 from Tyriar/enum_like_upper_case
Allow enum-like objects to use upper case naming
2020-03-30 06:46:48 -07:00
Daniel Imms 21d4ee8e2e Upgrade mac agent in pipelines
Looks like capacity has run out for old macOS, this should get builds
green again.
2020-03-30 06:43:45 -07:00
Daniel Imms 8ec41a7128 Merge pull request #2795 from xtermjs/dependabot/npm_and_yarn/types/webpack-4.41.9
Bump @types/webpack from 4.41.8 to 4.41.9
2020-03-30 06:42:12 -07:00
Daniel Imms 8842625bb4 Allow enum-like objects to use upper case naming 2020-03-30 06:17:37 -07:00
dependabot-preview[bot] 586be86b41 Bump @types/webpack from 4.41.8 to 4.41.9
Bumps [@types/webpack](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/webpack) from 4.41.8 to 4.41.9.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/webpack)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-03-30 08:38:01 +00:00
Daniel Imms 55190fd703 Merge pull request #2786 from Tyriar/eslint
Migrate from tslint to eslint
2020-03-28 08:57:58 -07:00
Daniel Imms 64f7978037 Format eslintrc 2020-03-28 07:50:30 -07:00
Daniel Imms 3f33751ff2 Merge remote-tracking branch 'ups/master' into eslint 2020-03-28 07:45:06 -07:00
Daniel Imms 392f349da1 Remove tslint completely 2020-03-28 07:39:51 -07:00
Daniel Imms bbd83b9dba Migrate whitespace tslint rule 2020-03-28 07:34:49 -07:00
Daniel Imms 2e9b4779c9 Migrate from typedef to explicit-function-return-type 2020-03-28 07:10:08 -07:00
Daniel Imms 8475f95b19 Disable naming convention on CoreMouseService
Until #2787 gets looked at
2020-03-28 07:04:21 -07:00
Daniel Imms 2c0669c865 Fix some naming conventions issues 2020-03-28 06:57:55 -07:00
Daniel Imms 937b551ae4 Migrate naming-convention rules to @typescript/eslint equivalent 2020-03-28 06:53:15 -07:00
Daniel Imms 88a50d3779 Fix most rules 2020-03-28 06:51:36 -07:00
Daniel Imms 57be6616ce Initial eslintrc.js
Generated by tool
2020-03-28 05:10:03 -07:00
Daniel Imms 7e073e6e26 Merge pull request #2783 from Tyriar/deps
Update several dependencies
2020-03-27 18:31:28 -07:00
Daniel Imms 10ff95af6a Update several dependencies 2020-03-27 16:50:49 -07:00
Daniel Imms 378d3e3889 Merge pull request #2772 from xtermjs/dependabot/npm_and_yarn/https-proxy-agent-2.2.4
[Security] Bump https-proxy-agent from 2.2.1 to 2.2.4
2020-03-27 16:06:29 -07:00
dependabot-preview[bot] 88f1353670 [Security] Bump https-proxy-agent from 2.2.1 to 2.2.4
Bumps [https-proxy-agent](https://github.com/TooTallNate/node-https-proxy-agent) from 2.2.1 to 2.2.4. **This update includes security fixes.**
- [Release notes](https://github.com/TooTallNate/node-https-proxy-agent/releases)
- [Commits](https://github.com/TooTallNate/node-https-proxy-agent/compare/2.2.1...2.2.4)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-03-27 22:52:12 +00:00
Daniel Imms c229aa8cfe Merge pull request #2760 from xtermjs/dependabot/npm_and_yarn/acorn-5.7.4
Bump acorn from 5.7.1 to 5.7.4
2020-03-27 15:50:55 -07:00
dependabot[bot] 96549a5421 Bump acorn from 5.7.1 to 5.7.4
Bumps [acorn](https://github.com/acornjs/acorn) from 5.7.1 to 5.7.4.
- [Release notes](https://github.com/acornjs/acorn/releases)
- [Commits](https://github.com/acornjs/acorn/compare/5.7.1...5.7.4)

Signed-off-by: dependabot[bot] <support@github.com>
2020-03-27 22:45:12 +00:00
Daniel Imms a99c6145a7 Merge pull request #2779 from xtermjs/dependabot/npm_and_yarn/webpack-cli-3.3.11
Bump webpack-cli from 3.1.0 to 3.3.11
2020-03-27 15:44:21 -07:00
dependabot-preview[bot] c85ab8dcb6 Bump webpack-cli from 3.1.0 to 3.3.11
Bumps [webpack-cli](https://github.com/webpack/webpack-cli) from 3.1.0 to 3.3.11.
- [Release notes](https://github.com/webpack/webpack-cli/releases)
- [Changelog](https://github.com/webpack/webpack-cli/blob/next/CHANGELOG_v3.md)
- [Commits](https://github.com/webpack/webpack-cli/compare/v.3.1.0...v3.3.11)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-03-27 21:32:21 +00:00
Daniel Imms db172b341e Merge pull request #2770 from xtermjs/dependabot/npm_and_yarn/deep-equal-2.0.1
Bump deep-equal from 1.1.0 to 2.0.1
2020-03-27 14:28:06 -07:00
dependabot-preview[bot] 11dbba422f Bump deep-equal from 1.1.0 to 2.0.1
Bumps [deep-equal](https://github.com/inspect-js/node-deep-equal) from 1.1.0 to 2.0.1.
- [Release notes](https://github.com/inspect-js/node-deep-equal/releases)
- [Changelog](https://github.com/inspect-js/node-deep-equal/blob/master/CHANGELOG.md)
- [Commits](https://github.com/inspect-js/node-deep-equal/compare/v1.1.0...v2.0.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-03-27 21:01:51 +00:00
Felix Seidl a1e60045d9 Merge branch 'master' into fix-edge-selection-color 2020-03-25 17:53:08 +01:00
Charles Wei b36a9d5af3 Merge branch 'master' into master 2020-03-24 21:15:20 +08:00
Daniel Imms fb5ff49008 Merge pull request #2767 from JavaCS3/fix/addDisposableDomListener-leak
fix: addDisposableDomListener dispose() leaking
2020-03-23 06:42:22 -07:00
javacs3 1b857f743e fix: addDisposableDomListener dispose() leaking 2020-03-22 12:56:50 +08:00
javacs3 c2df14cfa0 reduce BufferApiView object creation times 2020-03-22 12:13:53 +08:00
javacs3 44ff7b0f5f change buffer interface from IBuffer to IBufferNamespace 2020-03-22 12:13:52 +08:00
javacs3 5fdbd7428b expose IBufferSet API 2020-03-22 12:10:02 +08:00
Daniel Imms bc9aaebd04 Merge pull request #2766 from Tyriar/readme
Improve demo warning and add to server.js as well
2020-03-19 18:29:01 -07:00
Daniel Imms d90a0ce910 Improve demo warning and add to server.js as well
Fixes #2765
2020-03-19 18:26:45 -07:00
Daniel Imms 9c19043fed Merge pull request #2761 from slel/patch-1
Add CoCalc to "Real-world uses" in README
2020-03-16 06:59:22 -07:00
Samuel Lelièvre beb39ada0c Add CoCalc at end of list of real-world uses 2020-03-16 13:43:42 +01:00
Samuel Lelièvre 47f3eb28a6 Add CoCalc to "Real-world uses" in README 2020-03-16 04:32:34 +01:00
Daniel Imms 28105af761 Merge pull request #2731 from jerch/fix_2729
Clamp visual cursor to cols - 1
2020-03-13 19:26:46 -07:00
Daniel Imms e0ac87e702 Update xterm.d.ts 2020-03-13 19:17:34 -07:00
Daniel Imms b9ea05f97a Merge branch 'master' into fix_2729 2020-03-13 19:09:13 -07:00
Daniel Imms e348163689 Merge pull request #2758 from Tyriar/acorn
Upgrade acorn due to CVE
2020-03-13 18:53:48 -07:00
Daniel Imms 90375531bc Merge branch 'master' into acorn 2020-03-13 18:42:49 -07:00
Daniel Imms ca85f2d725 Upgrade acorn due to CVE 2020-03-13 18:42:10 -07:00
jerch eaadb75d39 Merge pull request #2754 from jerch/fix_vtfeatures_2
vtfeatures 2nd fix
2020-03-11 00:09:53 +01:00
Jörg Breitbart f6793df94a remove leading empty line 2020-03-10 23:59:41 +01:00
jerch 90e716b932 Merge pull request #2753 from jerch/fix_vtfeatures_template
fix missing jekyll header in vtfeatures template
2020-03-10 23:43:48 +01:00
Jörg Breitbart db2853c8ae add missing jekyll yaml entries 2020-03-10 23:35:08 +01:00
Jörg Breitbart 47d7a71ad0 fix cursor reset in DECSTR, some tests 2020-03-01 13:29:18 +01:00
Felix Seidl 88a6be16af use original css color string 2020-02-27 23:03:23 +01:00
Jörg Breitbart db304513b1 unify naming, adjust addon version, fix interface description 2020-02-22 20:30:36 +01:00
Jörg Breitbart 8d1b4bf78f fix webgl renderer 2020-02-22 20:18:45 +01:00
Jörg Breitbart 1cff37af99 fix dom renderer 2020-02-22 20:08:34 +01:00
jerch ecc7f2d780 Merge branch 'master' into fix_2729 2020-02-22 19:57:53 +01:00
Daniel Imms dbb6610d50 Merge pull request #2730 from Tyriar/ts38
typescript@3.8
2020-02-22 19:53:40 +01:00
Daniel Imms ae135ac181 Merge pull request #2730 from Tyriar/ts38
typescript@3.8
2020-02-22 10:04:07 -08:00
Daniel Imms df1d68b626 typescript@3.8 2020-02-22 09:43:16 -08:00
Daniel Imms a18bac7380 Remove browser args 2020-02-18 07:47:01 -08:00
Daniel Imms 9a1c7953e7 dumpio in browser launch 2020-02-18 07:39:15 -08:00
Daniel Imms 53c526f48c Improve Linux dependencies, enable WebKit 2020-02-18 06:49:11 -08:00
Daniel Imms 49bfd05d7c Merge pull request #2723 from UziTech/patch-1
atom-xterm -> x-terminal
2020-02-18 06:33:24 -08:00
Tony Brix 11ac8da14e Update README.md 2020-02-17 00:04:47 -06:00
Daniel Imms 986baed290 Merge pull request #2722 from Tyriar/linux_tests
Install deps for webkit and firefox on Linux
2020-02-16 08:08:20 -08:00
Daniel Imms 84c5f76b76 Remove webkit tests on Linux 2020-02-16 07:57:28 -08:00
Tony Brix 39c6f41f0e atom-xterm -> x-terminal 2020-02-15 15:16:37 -06:00
Daniel Imms bdb01e6166 Remove DEBUG, test fit addon based on a range 2020-02-15 12:26:59 -08:00
Daniel Imms 35ddcdd266 Upgrade to ubuntu-18.04 2020-02-15 12:13:38 -08:00
Daniel Imms 7e8e1215ad Fix order 2020-02-15 12:09:57 -08:00
Daniel Imms 83abd7ef59 Change pipeline property names 2020-02-15 12:07:19 -08:00
Daniel Imms 26fa6c97bf Install deps for webkit and firefox on Linux
Fixes #2721
2020-02-15 07:38:40 -08:00
Daniel Imms 708c6bdeb8 Merge pull request #2712 from jmbockhorst/playwright
Use playwright instead of puppeteer
2020-02-15 07:22:52 -08:00
Daniel Imms c73adf02bc Disable windows tests 2020-02-15 07:14:09 -08:00
Daniel Imms c36a468561 script -> pwsh 2020-02-15 07:08:42 -08:00
Daniel Imms 83794c433f Tweak windows server start 2020-02-15 07:05:20 -08:00
Daniel Imms eb82346ce7 Add a Windows test job 2020-02-15 06:40:04 -08:00
Daniel Imms 1a6ec0d768 Disable webkit on Linux 2020-02-15 06:39:10 -08:00
Daniel Imms 446f8f93c6 Disable firefox on linux 2020-02-15 06:27:03 -08:00
Daniel Imms 445a801d5d Fix syntax error 2020-02-15 06:21:05 -08:00
Daniel Imms 579a135087 Try firefox deps
https://bugzilla.mozilla.org/show_bug.cgi?id=1372998
2020-02-15 06:17:53 -08:00
Daniel Imms cd1b251cef Up timeout for slower CI agents 2020-02-15 06:07:33 -08:00
Daniel Imms ed23194abd Update macOS version 2020-02-15 06:00:59 -08:00
Daniel Imms 2ac0c0a957 Move playwright-core back to dev 2020-02-15 05:55:01 -08:00
Daniel Imms 477b161ba2 Change browserType back to sync 2020-02-15 05:51:39 -08:00
Daniel Imms cb1353c898 Cache yarn modules on integration test jobs 2020-02-15 05:48:38 -08:00
Daniel Imms 47dc137de0 Download browser at start of test 2020-02-15 05:47:42 -08:00
Daniel Imms 03c6b0c688 Fix typo in firefox job 2020-02-15 05:30:33 -08:00
Daniel Imms ffc5e2f446 Move to playwright-core, download browser if needed 2020-02-15 05:28:21 -08:00
Daniel Imms 4ebb61b580 Adopt playwright breaking changes 2020-02-15 05:18:46 -08:00
Daniel Imms 4ae25e57a3 playwright@0.11.1 2020-02-15 05:14:00 -08:00
Daniel Imms 7bd4f3874e Fix lint 2020-02-15 05:13:52 -08:00
Daniel Imms 2b7080294d Only start server once in pipelines 2020-02-15 05:06:44 -08:00
Daniel Imms 3b51565a13 Fix pipelines file 2020-02-15 05:02:28 -08:00
Daniel Imms 7400b888df Skip mouse wheel-related tests on non-chromium 2020-02-15 05:00:52 -08:00
Daniel Imms 3f15a7251b Skip webgl tests instead of not registering 2020-02-15 04:53:45 -08:00
Daniel Imms a9356fd377 Skip paste test when not Chromium 2020-02-15 04:43:38 -08:00
Daniel Imms f3eb9f79a1 Movee api test timeout into CLI args 2020-02-15 04:29:04 -08:00
Daniel Imms 93026bcae0 Only enable webgl tests on chromium and non-headless firefox
See microsoft/playwright#1032
2020-02-15 04:23:31 -08:00
Daniel Imms 6082c00b1d Fix serialize tests on firefox
Firefox takes longer to start up
2020-02-15 03:58:33 -08:00
Daniel Imms e6e181e6bc Fix firefox tests FitAddon tests 2020-02-15 03:54:30 -08:00
Daniel Imms 502452f58e Enable tests on other browsers 2020-02-15 03:35:08 -08:00
Daniel Imms 1d0cceb2d3 Merge branch 'master' into playwright 2020-02-15 03:26:37 -08:00
Daniel Imms 5f0217cdb0 Merge pull request #2710 from jmbockhorst/linkFix
Fix Link Provider end positions
2020-02-13 08:49:45 -08:00
Daniel Imms 7b40aa89b4 Merge branch 'master' into linkFix 2020-02-13 08:36:43 -08:00
Jon Bockhorst e817a7b6c6 Add Linkifier2 tests 2020-02-13 00:43:24 -06:00
Jon Bockhorst b53c2e1a26 Fix tests 2020-02-13 00:15:12 -06:00
Jon Bockhorst 383e2ed46f Remove duplicate code and clean up after merge 2020-02-13 00:07:07 -06:00
Jon Bockhorst 258b084a62 Merge branch 'master' of https://github.com/xtermjs/xterm.js into playwright 2020-02-12 23:53:53 -06:00
Daniel Imms c5593bde08 Merge pull request #2711 from jmbockhorst/testing
Move openTerminal() to common util file
2020-02-12 16:12:47 -08:00
Daniel Imms 2cd4580154 Merge pull request #2715 from kumaran-14/2586_dom_renderer
Remove multiple usance of background-color in dom renderer
2020-02-12 16:05:38 -08:00
Jon Bockhorst 2442dc09b0 Clean up some stuff based on feedback 2020-02-12 16:00:21 -06:00
Jon Bockhorst d482ca5d68 Merge branch 'master' into testing 2020-02-12 14:34:30 -06:00
Jon Bockhorst 3616b697ec Remove unnecessary composite setting 2020-02-12 14:29:38 -06:00
Kumaran a19dfe608d Merge branch 'master' into 2586_dom_renderer 2020-02-12 23:12:28 +05:30
kumaran-14 0c1e9b6678 Remove multiple usance of background-color in dom renderer 2020-02-12 23:07:12 +05:30
Jon Bockhorst 391313cd39 Revert some formatting and remove puppeteer 2020-02-11 13:32:55 -06:00
Jon Bockhorst 8fb6ded506 Fix mouse tests for chromium 2020-02-11 13:13:03 -06:00
Jon Bockhorst eb7b2f4683 Make addons use common test functions 2020-02-11 11:39:36 -06:00
Jon Bockhorst 520918348e Fix some mouse tests 2020-02-10 18:18:23 -06:00
Jon Bockhorst e7b3910f3d Update CI to node 10 2020-02-10 17:05:18 -06:00
Jon Bockhorst 7515ed6146 Use playwright instead of puppeteer 2020-02-10 16:56:37 -06:00
Jon Bockhorst 901adb38ae Fix linkProvider end positions 2020-02-10 12:39:16 -06:00
Jon Bockhorst 522378a507 Move openTerminal() to common util file 2020-02-10 11:18:23 -06:00
Daniel Imms 1bb2fc912b Merge pull request #2709 from Tyriar/search_bump
Release search
2020-02-10 05:51:49 -08:00
Daniel Imms 423d112dfc Release search
0.4.0 isn't compatible with xterm@4.4
2020-02-10 05:51:02 -08:00
Daniel Imms 63fde03552 Merge pull request #2530 from jmbockhorst/linkParser
Basic link parser for better link detection
2020-02-08 11:12:10 -08:00
Daniel Imms d1d27b5367 Remove logs 2020-02-08 11:04:04 -08:00
Daniel Imms 0e302baa43 Mark link matcher API as deprecated 2020-02-08 10:58:58 -08:00
Daniel Imms a7c2d54e53 Document link provider argument in web links addon 2020-02-08 10:54:29 -08:00
Daniel Imms 54743eda16 Make link matcher API the default for web links addon 2020-02-08 10:52:36 -08:00
Daniel Imms 660cf26e49 Merge branch 'master' into linkParser 2020-02-07 12:47:25 -08:00
Daniel Imms e4875f26ff Add test for absense of hover/leave 2020-02-07 11:54:07 -08:00
Daniel Imms 7f1f22146d Add test for activate 2020-02-07 11:52:58 -08:00
Daniel Imms 0103c689f7 Start registerLinkProvider tests
Bugs fixed:

- Not breaking on first link match for multiple providers
- Hover event wasn't fired when on last character of link
2020-02-07 11:16:41 -08:00
Daniel Imms f4a31251cb handle -> activate 2020-02-07 09:59:25 -08:00
Daniel Imms b18ded0e2d Rename tooltip to hover/leave 2020-02-07 09:55:06 -08:00
Daniel Imms c327116e7f url -> text 2020-02-07 09:39:26 -08:00
Daniel Imms ef071ac7ac Polish API 2020-02-07 09:30:12 -08:00
Daniel Imms 56990f2a29 Merge pull request #2698 from xtermjs/Tyriar-patch-1
Fix grammar in demo
2020-02-07 09:13:58 -08:00
Daniel Imms 4475a82776 Fix grammar in demo 2020-02-07 09:13:03 -08:00
Daniel Imms 560a30662b Merge pull request #2696 from mofux/fix/serialize-addon-peer-dependency
Fix serialize addon peer dependency to xterm
2020-02-07 09:09:13 -08:00
Daniel Imms a066bbe32f 💄 2020-02-07 09:08:07 -08:00
Daniel Imms e252b4b674 Merge branch 'master' into linkParser 2020-02-07 09:04:24 -08:00
Thomas Zilz 3283ffb4e1 Fix serialize addon peer dependency to xterm 2020-02-06 12:27:02 +01:00
Jon Bockhorst a95b1b4881 Merge branch 'master' of https://github.com/xtermjs/xterm.js into linkParser 2020-02-04 11:40:48 -06:00
Jon Bockhorst a1906347b0 Only clear link if it was in the range of onRender and fix tests 2019-11-08 12:10:00 -06:00
Jon Bockhorst 8924aa736e Use onRender() instead of onData() and onScroll() 2019-11-08 11:08:50 -06:00
Daniel f93939ef20 Merge remote-tracking branch 'ups/master' into pr/jmbockhorst/2530 2019-11-08 08:46:09 -08:00
Jon Bockhorst 700c4acf75 Rework link parser to use a single cached link and make other requested changes 2019-11-08 02:14:53 -06:00
Jon Bockhorst f18a6fe1eb Fixed bug in link detection for wrapped links 2019-11-06 22:52:09 -06:00
Jon Bockhorst b07a6d10f1 Add test for multiline links 2019-11-05 23:44:30 -06:00
Jon Bockhorst ac6c8a5bd3 Support multiline links in the WebLinksAddon 2019-11-05 23:35:20 -06:00
Jon Bockhorst b3d95d20dc Merge branch 'master' of https://github.com/xtermjs/xterm.js into linkParser 2019-11-05 16:35:33 -06:00
Jon Bockhorst 28c43ac2bf Remove link caching 2019-11-05 13:01:53 -06:00
Jon Bockhorst fdfadfefbc Fixed link caching errors 2019-11-04 23:37:05 -06:00
Jon Bockhorst e57cb8bf7e Use regex parser for web links 2019-11-01 17:38:08 -05:00
Jon Bockhorst ec78fc7e7c Remove usage of ! operator 2019-11-01 13:42:02 -05:00
Jon Bockhorst cf082cca04 Fix links when viewport is scrolled 2019-11-01 11:24:21 -05:00
Jon Bockhorst 1e877a0f96 Fix lint errors 2019-11-01 09:18:45 -05:00
Jon Bockhorst 3e2e4ace48 Removed file detection from WebLinkProvider 2019-11-01 01:05:18 -05:00
Jon Bockhorst 0c5962c538 Added support for a basic link parser 2019-11-01 00:54:31 -05:00
93 changed files with 4762 additions and 3399 deletions
+2 -2
View File
@@ -3,7 +3,7 @@
"dockerFile": "Dockerfile",
"appPort": 3000,
"extensions": [
"editorconfig.editorconfig",
"ms-vscode.vscode-typescript-tslint-plugin"
"dbaeumer.vscode-eslint",
"editorconfig.editorconfig"
]
}
+150
View File
@@ -0,0 +1,150 @@
{
"env": {
"browser": true,
"es6": true,
"node": true
},
"parser": "@typescript-eslint/parser",
"parserOptions": {
"project": [
"src/browser/tsconfig.json",
"src/common/tsconfig.json",
"src/tsconfig.json",
"test/api/tsconfig.json",
"test/benchmark/tsconfig.json",
"addons/xterm-addon-attach/src/tsconfig.json",
"addons/xterm-addon-fit/src/tsconfig.json",
"addons/xterm-addon-search/src/tsconfig.json",
"addons/xterm-addon-unicode11/src/tsconfig.json",
"addons/xterm-addon-web-links/src/tsconfig.json",
"addons/xterm-addon-webgl/src/tsconfig.json",
"addons/xterm-addon-serialize/src/tsconfig.json",
"addons/xterm-addon-serialize/benchmark/tsconfig.json"
],
"sourceType": "module"
},
"ignorePatterns": [
"**/typings/*.d.ts"
],
"plugins": [
"@typescript-eslint"
],
"rules": {
"@typescript-eslint/array-type": [
"error",
{
"default": "array-simple",
"readonly": "generic"
}
],
"@typescript-eslint/class-name-casing": "error",
"@typescript-eslint/consistent-type-definitions": "error",
"@typescript-eslint/explicit-function-return-type": [
"error",
{
"allowExpressions": true
}
],
"@typescript-eslint/indent": [
"error",
2
],
"@typescript-eslint/interface-name-prefix": [
"error",
"always"
],
"@typescript-eslint/member-delimiter-style": [
"error",
{
"multiline": {
"delimiter": "semi",
"requireLast": true
},
"singleline": {
"delimiter": "comma",
"requireLast": false
}
}
],
"@typescript-eslint/naming-convention": [
"error",
{ "selector": "default", "format": ["camelCase"] },
// variableLike
{ "selector": "variable", "format": ["camelCase", "UPPER_CASE"] },
{ "selector": "variable", "filter": "^I.+Service$", "format": ["PascalCase"], "prefix": ["I"] },
// memberLike
{ "selector": "memberLike", "modifiers": ["private"], "format": ["camelCase"], "leadingUnderscore": "require" },
{ "selector": "memberLike", "modifiers": ["protected"], "format": ["camelCase"], "leadingUnderscore": "require" },
{ "selector": "enumMember", "format": ["UPPER_CASE"] },
// memberLike - Allow enum-like objects to use UPPER_CASE
{ "selector": "property", "modifiers": ["public"], "format": ["camelCase", "UPPER_CASE"] },
{ "selector": "method", "modifiers": ["public"], "format": ["camelCase", "UPPER_CASE"] },
// typeLike
{ "selector": "typeLike", "format": ["PascalCase"] },
{ "selector": "interface", "format": ["PascalCase"], "prefix": ["I"] }
],
"@typescript-eslint/prefer-namespace-keyword": "error",
"@typescript-eslint/type-annotation-spacing": "error",
"@typescript-eslint/quotes": [
"error",
"single",
{ "allowTemplateLiterals": true }
],
"@typescript-eslint/semi": [
"error",
"always"
],
"comma-dangle": [
"error",
{
"objects": "never",
"arrays": "never",
"functions": "never"
}
],
"curly": [
"error",
"multi-line"
],
"eol-last": "error",
"eqeqeq": [
"error",
"always"
],
"keyword-spacing": "error",
"new-parens": "error",
"no-duplicate-imports": "error",
"no-else-return": [
"error",
{
"allowElseIf": false
}
],
"no-eval": "error",
"no-irregular-whitespace": "error",
"no-restricted-imports": [
"error",
{
"patterns": [
".*\\/out\\/.*"
]
}
],
"no-trailing-spaces": "error",
"no-unsafe-finally": "error",
"no-var": "error",
"one-var": [
"error",
"never"
],
"prefer-const": "error",
"spaced-comment": [
"error",
"always",
{
"markers": ["/"],
"exceptions": ["-"]
}
]
}
}
-1
View File
@@ -42,7 +42,6 @@
*.test.js
*.test.js.map
lib/test/
tslint.json
webpack.config.js
docs/
+2 -2
View File
@@ -1,4 +1,4 @@
{
"typescript.preferences.quoteStyle": "single",
"typescript.preferences.importModuleSpecifier": "non-relative"
"typescript.preferences.importModuleSpecifier": "non-relative",
"typescript.preferences.quoteStyle": "single"
}
+2
View File
@@ -159,6 +159,8 @@ Xterm.js is used in several world-class applications to provide great terminal e
- [**Gus**](https://gus.jp): A shared coding pad where you can run Python with xterm.js
- [**Linode**](https://linode.com): Linode uses xterm.js to provide users a web console for their Linode instances.
- [**FluffOS**](https://www.fluffos.info): Active maintained LPMUD driver with websocket support.
- [**x-terminal**](https://atom.io/packages/x-terminal): Atom plugin for providing terminals inside your Atom workspace.
- [**CoCalc**](https://cocalc.com/): Lots of free software pre-installed, to chat, collaborate, develop, program, publish, research, share, teach, in C++, HTML, Julia, Jupyter, LaTeX, Markdown, Python, R, SageMath, Scala, ...
[And much more...](https://github.com/xtermjs/xterm.js/network/dependents)
@@ -3,77 +3,51 @@
* @license MIT
*/
import * as puppeteer from 'puppeteer';
import { assert } from 'chai';
import { ITerminalOptions } from 'xterm';
import WebSocket = require('ws');
import { openTerminal, pollFor, getBrowserType } from '../../../out-test/api/TestUtils';
import { Browser, Page } from 'playwright-core';
const APP = 'http://127.0.0.1:3000/test';
let browser: puppeteer.Browser;
let page: puppeteer.Page;
let browser: Browser;
let page: Page;
const width = 800;
const height = 600;
describe('AttachAddon', () => {
before(async function(): Promise<any> {
this.timeout(20000);
browser = await puppeteer.launch({
headless: process.argv.indexOf('--headless') !== -1,
args: [`--window-size=${width},${height}`, `--no-sandbox`]
const browserType = getBrowserType();
browser = await browserType.launch({ dumpio: true,
headless: process.argv.indexOf('--headless') !== -1
});
page = (await browser.pages())[0];
await page.setViewport({ width, height });
page = await (await browser.newContext()).newPage();
await page.setViewportSize({ width, height });
});
after(async () => {
await browser.close();
});
beforeEach(async function(): Promise<any> {
this.timeout(20000);
await page.goto(APP);
});
beforeEach(async () => await page.goto(APP));
it('string', async function(): Promise<any> {
this.timeout(20000);
await openTerminal({ rendererType: 'dom' });
await openTerminal(page, { rendererType: 'dom' });
const port = 8080;
const server = new WebSocket.Server({ port });
server.on('connection', socket => socket.send('foo'));
await page.evaluate(`window.term.loadAddon(new window.AttachAddon(new WebSocket('ws://localhost:${port}')))`);
await pollFor(page, `window.term.buffer.getLine(0).translateToString(true)`, 'foo');
await pollFor(page, `window.term.buffer.active.getLine(0).translateToString(true)`, 'foo');
server.close();
});
it('utf8', async function(): Promise<any> {
this.timeout(20000);
await openTerminal({ rendererType: 'dom' });
await openTerminal(page, { rendererType: 'dom' });
const port = 8080;
const server = new WebSocket.Server({ port });
const data = new Uint8Array([102, 111, 111]);
server.on('connection', socket => socket.send(data));
await page.evaluate(`window.term.loadAddon(new window.AttachAddon(new WebSocket('ws://localhost:${port}')))`);
await pollFor(page, `window.term.buffer.getLine(0).translateToString(true)`, 'foo');
await pollFor(page, `window.term.buffer.active.getLine(0).translateToString(true)`, 'foo');
server.close();
});
});
async function openTerminal(options: ITerminalOptions = {}): Promise<void> {
await page.evaluate(`window.term = new Terminal(${JSON.stringify(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');
}
}
async function pollFor(page: puppeteer.Page, fn: string, val: any): Promise<void> {
const result = await page.evaluate(fn);
if (result !== val) {
return new Promise<void>(r => {
setTimeout(() => r(pollFor(page, fn, val)), 10);
});
}
}
+5 -1
View File
@@ -10,7 +10,11 @@
"outDir": "../out",
"sourceMap": true,
"removeComments": true,
"strict": true
"strict": true,
"types": [
"../../../node_modules/@types/mocha",
"../../../out-test/api/TestUtils"
]
},
"include": [
"./**/*",
+28 -33
View File
@@ -3,28 +3,27 @@
* @license MIT
*/
import * as puppeteer from 'puppeteer';
import { assert } from 'chai';
import { ITerminalOptions } from 'xterm';
import { openTerminal, getBrowserType } from '../../../out-test/api/TestUtils';
import { Browser, Page } from 'playwright-core';
const APP = 'http://127.0.0.1:3000/test';
let browser: puppeteer.Browser;
let page: puppeteer.Page;
let browser: Browser;
let page: Page;
const width = 1024;
const height = 768;
describe('FitAddon', () => {
before(async function(): Promise<any> {
this.timeout(20000);
browser = await puppeteer.launch({
headless: process.argv.indexOf('--headless') !== -1,
args: [`--window-size=${width},${height}`, `--no-sandbox`]
const browserType = getBrowserType();
browser = await browserType.launch({ dumpio: true,
headless: process.argv.indexOf('--headless') !== -1
});
page = (await browser.pages())[0];
await page.setViewport({ width, height });
page = await (await browser.newContext()).newPage();
await page.setViewportSize({ width, height });
await page.goto(APP);
await openTerminal();
await openTerminal(page);
});
after(async () => {
@@ -43,18 +42,18 @@ describe('FitAddon', () => {
it('default', async function(): Promise<any> {
await loadFit();
assert.deepEqual(await page.evaluate(`window.fit.proposeDimensions()`), {
cols: 87,
rows: 26
});
const dimensions: {cols: number, rows: number} = await page.evaluate(`window.fit.proposeDimensions()`);
assert.equal(dimensions.cols, 87);
assert.isAbove(dimensions.rows, 24);
assert.isBelow(dimensions.rows, 29);
});
it('width', async function(): Promise<any> {
await loadFit(1008);
assert.deepEqual(await page.evaluate(`window.fit.proposeDimensions()`), {
cols: 110,
rows: 26
});
const dimensions: {cols: number, rows: number} = await page.evaluate(`window.fit.proposeDimensions()`);
assert.equal(dimensions.cols, 110);
assert.isAbove(dimensions.rows, 24);
assert.isBelow(dimensions.rows, 29);
});
it('small', async function(): Promise<any> {
@@ -74,15 +73,21 @@ describe('FitAddon', () => {
it('default', async function(): Promise<any> {
await loadFit();
await page.evaluate(`window.fit.fit()`);
assert.equal(await page.evaluate(`window.term.cols`), 87);
assert.equal(await page.evaluate(`window.term.rows`), 26);
const cols: number = await page.evaluate(`window.term.cols`);
const rows: number = await page.evaluate(`window.term.rows`);
assert.equal(cols, 87);
assert.isAbove(rows, 24);
assert.isBelow(rows, 29);
});
it('width', async function(): Promise<any> {
await loadFit(1008);
await page.evaluate(`window.fit.fit()`);
assert.equal(await page.evaluate(`window.term.cols`), 110);
assert.equal(await page.evaluate(`window.term.rows`), 26);
const cols: number = await page.evaluate(`window.term.cols`);
const rows: number = await page.evaluate(`window.term.rows`);
assert.equal(cols, 110);
assert.isAbove(rows, 24);
assert.isBelow(rows, 29);
});
it('small', async function(): Promise<any> {
@@ -106,13 +111,3 @@ async function loadFit(width: number = 800, height: number = 450): Promise<void>
async function unloadFit(): Promise<void> {
await page.evaluate(`window.fit.dispose();`);
}
async function openTerminal(options: ITerminalOptions = {}): Promise<void> {
await page.evaluate(`window.term = new Terminal(${JSON.stringify(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');
}
}
+2 -2
View File
@@ -38,7 +38,7 @@ export class FitAddon implements ITerminalAddon {
}
// TODO: Remove reliance on private API
const core = (<any>this._terminal)._core;
const core = (this._terminal as any)._core;
// Force a full render
if (this._terminal.rows !== dims.rows || this._terminal.cols !== dims.cols) {
@@ -57,7 +57,7 @@ export class FitAddon implements ITerminalAddon {
}
// TODO: Remove reliance on private API
const core = (<any>this._terminal)._core;
const core = (this._terminal as any)._core;
const parentElementStyle = window.getComputedStyle(this._terminal.element.parentElement);
const parentElementHeight = parseInt(parentElementStyle.getPropertyValue('height'));
+5 -1
View File
@@ -10,7 +10,11 @@
"outDir": "../out",
"sourceMap": true,
"removeComments": true,
"strict": true
"strict": true,
"types": [
"../../../node_modules/@types/mocha",
"../../../out-test/api/TestUtils"
]
},
"include": [
"./**/*",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "xterm-addon-search",
"version": "0.4.0",
"version": "0.6.0",
"author": {
"name": "The xterm.js authors",
"url": "https://xtermjs.org/"
@@ -3,31 +3,29 @@
* @license MIT
*/
import * as puppeteer from 'puppeteer';
import { assert } from 'chai';
import { ITerminalOptions } from 'xterm';
import { readFile } from 'fs';
import { resolve } from 'path';
import { openTerminal, writeSync, getBrowserType } from '../../../out-test/api/TestUtils';
import { Browser, Page } from 'playwright-core';
const APP = 'http://127.0.0.1:3000/test';
let browser: puppeteer.Browser;
let page: puppeteer.Page;
let browser: Browser;
let page: Page;
const width = 800;
const height = 600;
describe('Search Tests', function (): void {
this.timeout(20000);
before(async function (): Promise<any> {
browser = await puppeteer.launch({
headless: process.argv.indexOf('--headless') !== -1,
args: [`--window-size=${width},${height}`, `--no-sandbox`]
describe('Search Tests', function(): void {
before(async function(): Promise<any> {
const browserType = getBrowserType();
browser = await browserType.launch({ dumpio: true,
headless: process.argv.indexOf('--headless') !== -1
});
page = (await browser.pages())[0];
await page.setViewport({ width, height });
page = await (await browser.newContext()).newPage();
await page.setViewportSize({ width, height });
await page.goto(APP);
await openTerminal();
await openTerminal(page);
await page.evaluate(`window.search = new SearchAddon();`);
await page.evaluate(`window.term.loadAddon(window.search);`);
});
@@ -41,7 +39,7 @@ describe('Search Tests', function (): void {
});
it('Simple Search', async () => {
await writeSync('dafhdjfldshafhldsahfkjhldhjkftestlhfdsakjfhdjhlfdsjkafhjdlk');
await writeSync(page, 'dafhdjfldshafhldsahfkjhldhjkftestlhfdsakjfhdjhlfdsjkafhjdlk');
assert.deepEqual(await page.evaluate(`window.search.findNext('test')`), true);
assert.deepEqual(await page.evaluate(`window.term.getSelection()`), 'test');
});
@@ -54,16 +52,16 @@ describe('Search Tests', function (): void {
}
dataString += makeData(50);
}
await writeSync(dataString);
await writeSync(page, dataString);
assert.deepEqual(await page.evaluate(`window.search.findNext('$^1_3{}test$#')`), true);
assert.deepEqual(await page.evaluate(`window.term.getSelection()`), '$^1_3{}test$#');
});
it ('Incremental Find Previous', async () => {
it('Incremental Find Previous', async () => {
await page.evaluate(`window.term.writeln('package.jsonc\\n')`);
await writeSync('package.json pack package.lock');
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.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().startRow).translateToString()`);
let selectionPosition: { startColumn: number, startRow: number, endColumn: number, endRow: 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');
await page.evaluate(`window.search.findPrevious('package.j', {incremental: true})`);
@@ -71,16 +69,16 @@ describe('Search Tests', function (): void {
assert.deepEqual(line.substring(selectionPosition.startColumn, selectionPosition.endColumn + 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.getLine(window.term.getSelectionPosition().startRow).translateToString()`);
line = await page.evaluate(`window.term.buffer.active.getLine(window.term.getSelectionPosition().startRow).translateToString()`);
selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`);
assert.deepEqual(line.substring(selectionPosition.startColumn, selectionPosition.endColumn), 'package.jsonc');
});
it ('Incremental Find Next', async () => {
it('Incremental Find Next', async () => {
await page.evaluate(`window.term.writeln('package.lock pack package.json package.ups\\n')`);
await writeSync('package.jsonc');
await writeSync(page, 'package.jsonc');
await page.evaluate(`window.search.findNext('pack', {incremental: true})`);
let line: string = await page.evaluate(`window.term.buffer.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().startRow).translateToString()`);
let selectionPosition: { startColumn: number, startRow: number, endColumn: number, endRow: 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');
await page.evaluate(`window.search.findNext('package.j', {incremental: true})`);
@@ -88,12 +86,12 @@ describe('Search Tests', function (): void {
assert.deepEqual(line.substring(selectionPosition.startColumn, selectionPosition.endColumn + 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.getLine(window.term.getSelectionPosition().startRow).translateToString()`);
line = await page.evaluate(`window.term.buffer.active.getLine(window.term.getSelectionPosition().startRow).translateToString()`);
selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`);
assert.deepEqual(line.substring(selectionPosition.startColumn, selectionPosition.endColumn), 'package.jsonc');
});
it('Simple Regex', async () => {
await writeSync('abc123defABCD');
await writeSync(page, 'abc123defABCD');
await page.evaluate(`window.search.findNext('[a-z]+', {regex: true})`);
assert.deepEqual(await page.evaluate(`window.term.getSelection()`), 'abc');
await page.evaluate(`window.search.findNext('[A-Z]+', {regex: true, caseSensitive: true})`);
@@ -101,7 +99,7 @@ describe('Search Tests', function (): void {
});
it('Search for single result twice should not unselect it', async () => {
await writeSync('abc def');
await writeSync(page, 'abc def');
assert.deepEqual(await page.evaluate(`window.search.findNext('abc')`), true);
assert.deepEqual(await page.evaluate(`window.term.getSelection()`), 'abc');
assert.deepEqual(await page.evaluate(`window.search.findNext('abc')`), true);
@@ -118,7 +116,7 @@ describe('Search Tests', function (): void {
.replace(/'/g, '\\\'');
});
it('should find all occurrences using findNext', async () => {
await writeSync(fixture);
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 });
@@ -152,7 +150,7 @@ describe('Search Tests', function (): void {
assert.deepEqual(selectionPosition, { startColumn: 24, startRow: 53, endColumn: 30, endRow: 53 });
});
it('should find all occurrences using findPrevious', async () => {
await writeSync(fixture);
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 });
@@ -189,20 +187,6 @@ describe('Search Tests', function (): void {
});
});
async function openTerminal(options: ITerminalOptions = {}): Promise<void> {
await page.evaluate(`window.term = new Terminal(${JSON.stringify(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');
}
}
async function writeSync(data: string): Promise<void> {
return page.evaluate(`new Promise(resolve => window.term.write('${data}', resolve))`);
}
function makeData(length: number): string {
let result = '';
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
+11 -11
View File
@@ -87,7 +87,7 @@ export class SearchAddon implements ITerminalAddon {
// Search from startRow + 1 to end
if (!result) {
for (let y = startRow + 1; y < this._terminal.buffer.baseY + this._terminal.rows; y++) {
for (let y = startRow + 1; y < this._terminal.buffer.active.baseY + this._terminal.rows; y++) {
searchPosition.startRow = y;
searchPosition.startCol = 0;
// If the current line is wrapped line, increase index of column to ignore the previous scan
@@ -135,7 +135,7 @@ export class SearchAddon implements ITerminalAddon {
}
const isReverseSearch = true;
let startRow = this._terminal.buffer.baseY + this._terminal.rows;
let startRow = this._terminal.buffer.active.baseY + this._terminal.rows;
let startCol = this._terminal.cols;
let result: ISearchResult | undefined;
const incremental = searchOptions ? searchOptions.incremental : false;
@@ -174,8 +174,8 @@ export class SearchAddon implements ITerminalAddon {
}
}
// If we hit the top and didn't search from the very bottom wrap back down
if (!result && startRow !== (this._terminal.buffer.baseY + this._terminal.rows)) {
for (let y = (this._terminal.buffer.baseY + this._terminal.rows); y > startRow; y--) {
if (!result && startRow !== (this._terminal.buffer.active.baseY + this._terminal.rows)) {
for (let y = (this._terminal.buffer.active.baseY + this._terminal.rows); y > startRow; y--) {
searchPosition.startRow = y;
result = this._findInLine(term, searchPosition, searchOptions, isReverseSearch);
if (result) {
@@ -197,7 +197,7 @@ export class SearchAddon implements ITerminalAddon {
private _initLinesCache(): void {
const terminal = this._terminal!;
if (!this._linesCache) {
this._linesCache = new Array(terminal.buffer.length);
this._linesCache = new Array(terminal.buffer.active.length);
this._cursorMoveListener = terminal.onCursorMove(() => this._destroyLinesCache());
this._resizeListener = terminal.onResize(() => this._destroyLinesCache());
}
@@ -249,7 +249,7 @@ export class SearchAddon implements ITerminalAddon {
const col = searchPosition.startCol;
// Ignore wrapped lines, only consider on unwrapped line (first row of command string).
const firstLine = terminal.buffer.getLine(row);
const firstLine = terminal.buffer.active.getLine(row);
if (firstLine && firstLine.isWrapped) {
if (isReverseSearch) {
searchPosition.startCol += terminal.cols;
@@ -311,7 +311,7 @@ export class SearchAddon implements ITerminalAddon {
return;
}
const line = terminal.buffer.getLine(row);
const line = terminal.buffer.active.getLine(row);
if (line) {
for (let i = 0; i < resultIndex; i++) {
@@ -354,9 +354,9 @@ export class SearchAddon implements ITerminalAddon {
let lineWrapsToNext: boolean;
do {
const nextLine = terminal.buffer.getLine(lineIndex + 1);
const nextLine = terminal.buffer.active.getLine(lineIndex + 1);
lineWrapsToNext = nextLine ? nextLine.isWrapped : false;
const line = terminal.buffer.getLine(lineIndex);
const line = terminal.buffer.active.getLine(lineIndex);
if (!line) {
break;
}
@@ -380,8 +380,8 @@ export class SearchAddon implements ITerminalAddon {
}
terminal.select(result.col, result.row, result.term.length);
// If it is not in the viewport then we scroll else it just gets selected
if (result.row >= (terminal.buffer.viewportY + terminal.rows) || result.row < terminal.buffer.viewportY) {
let scroll = result.row - terminal.buffer.viewportY;
if (result.row >= (terminal.buffer.active.viewportY + terminal.rows) || result.row < terminal.buffer.active.viewportY) {
let scroll = result.row - terminal.buffer.active.viewportY;
scroll = scroll - Math.floor(terminal.rows / 2);
terminal.scrollLines(scroll);
}
+5 -1
View File
@@ -10,7 +10,11 @@
"outDir": "../out",
"sourceMap": true,
"removeComments": true,
"strict": true
"strict": true,
"types": [
"../../../node_modules/@types/mocha",
"../../../out-test/api/TestUtils"
]
},
"include": [
"./**/*",
@@ -12,7 +12,7 @@ import { SerializeAddon } from 'SerializeAddon';
class TestTerminal extends Terminal {
writeSync(data: string): void {
(<any>this)._core.writeSync(data);
(this as any)._core.writeSync(data);
}
}
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "xterm-addon-serialize",
"version": "0.1.1",
"version": "0.2.0",
"author": {
"name": "The xterm.js authors",
"url": "https://xtermjs.org/"
@@ -18,6 +18,6 @@
"benchmark-eval": "NODE_PATH=../../out:./out:./out-benchmark/ ../../node_modules/.bin/xterm-benchmark -r 5 -c benchmark/benchmark.json --eval out-benchmark/addons/xterm-addon-serialize/benchmark/*benchmark.js"
},
"peerDependencies": {
"xterm": "^3.14.0"
"xterm": "^4.0.0"
}
}
@@ -3,27 +3,27 @@
* @license MIT
*/
import * as puppeteer from 'puppeteer';
import { assert } from 'chai';
import { ITerminalOptions } from 'xterm';
import { openTerminal, writeSync, getBrowserType } from '../../../out-test/api/TestUtils';
import { Browser, Page } from 'playwright-core';
const APP = 'http://127.0.0.1:3000/test';
let browser: puppeteer.Browser;
let page: puppeteer.Page;
let browser: Browser;
let page: Page;
const width = 800;
const height = 600;
describe('SerializeAddon', () => {
before(async function (): Promise<any> {
browser = await puppeteer.launch({
headless: process.argv.indexOf('--headless') !== -1,
args: [`--window-size=${width},${height}`]
before(async function(): Promise<any> {
const browserType = getBrowserType();
browser = await browserType.launch({ dumpio: true,
headless: process.argv.indexOf('--headless') !== -1
});
page = (await browser.pages())[0];
await page.setViewport({ width, height });
page = await (await browser.newContext()).newPage();
await page.setViewportSize({ width, height });
await page.goto(APP);
await openTerminal({ rows: 10, cols: 10, rendererType: 'dom' });
await openTerminal(page, { rows: 10, cols: 10, rendererType: 'dom' });
await page.evaluate(`
window.serializeAddon = new SerializeAddon();
window.term.loadAddon(window.serializeAddon);
@@ -33,13 +33,13 @@ describe('SerializeAddon', () => {
after(async () => await browser.close());
beforeEach(async () => await page.evaluate(`window.term.reset()`));
it('empty content', async function (): Promise<any> {
it('empty content', async function(): Promise<any> {
const rows = 10;
const cols = 10;
assert.equal(await page.evaluate(`serializeAddon.serialize();`), '');
});
it('trim last empty lines', async function (): Promise<any> {
it('trim last empty lines', async function(): Promise<any> {
const cols = 10;
const lines = [
'',
@@ -58,7 +58,7 @@ describe('SerializeAddon', () => {
assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.slice(0, 8).join('\r\n'));
});
it('digits content', async function (): Promise<any> {
it('digits content', async function(): Promise<any> {
const rows = 10;
const cols = 10;
const digitsLine = digitsString(cols);
@@ -67,7 +67,7 @@ describe('SerializeAddon', () => {
assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n'));
});
it('serialize half rows of content', async function (): Promise<any> {
it('serialize half rows of content', async function(): Promise<any> {
const rows = 10;
const halfRows = rows >> 1;
const cols = 10;
@@ -76,7 +76,7 @@ describe('SerializeAddon', () => {
assert.equal(await page.evaluate(`serializeAddon.serialize(${halfRows});`), lines.slice(halfRows, 2 * halfRows).join('\r\n'));
});
it('serialize 0 rows of content', async function (): Promise<any> {
it('serialize 0 rows of content', async function(): Promise<any> {
const rows = 10;
const cols = 10;
const lines = newArray<string>((index: number) => digitsString(cols, index), rows);
@@ -84,7 +84,7 @@ describe('SerializeAddon', () => {
assert.equal(await page.evaluate(`serializeAddon.serialize(0);`), '');
});
it('serialize all rows of content with color16', async function (): Promise<any> {
it('serialize all rows of content with color16', async function(): Promise<any> {
const cols = 10;
const color16 = [
30, 31, 32, 33, 34, 35, 36, 37, // Set foreground color
@@ -101,7 +101,7 @@ describe('SerializeAddon', () => {
assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n'));
});
it('serialize all rows of content with fg/bg flags', async function (): Promise<any> {
it('serialize all rows of content with fg/bg flags', async function(): Promise<any> {
const cols = 10;
const line = '+'.repeat(cols);
const lines: string[] = [
@@ -122,7 +122,7 @@ describe('SerializeAddon', () => {
assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n'));
});
it('serialize all rows of content with color256', async function (): Promise<any> {
it('serialize all rows of content with color256', async function(): Promise<any> {
const rows = 32;
const cols = 10;
const lines = newArray<string>(
@@ -133,7 +133,7 @@ describe('SerializeAddon', () => {
assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n'));
});
it('serialize all rows of content with color16 and style separately', async function (): Promise<any> {
it('serialize all rows of content with color16 and style separately', async function(): Promise<any> {
const cols = 10;
const line = '+'.repeat(cols);
const lines: string[] = [
@@ -152,7 +152,7 @@ describe('SerializeAddon', () => {
assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n'));
});
it('serialize all rows of content with color16 and style together', async function (): Promise<any> {
it('serialize all rows of content with color16 and style together', async function(): Promise<any> {
const cols = 10;
const line = '+'.repeat(cols);
const lines: string[] = [
@@ -174,7 +174,7 @@ describe('SerializeAddon', () => {
assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n'));
});
it('serialize all rows of content with color256 and style separately', async function (): Promise<any> {
it('serialize all rows of content with color256 and style separately', async function(): Promise<any> {
const cols = 10;
const line = '+'.repeat(cols);
const lines: string[] = [
@@ -193,7 +193,7 @@ describe('SerializeAddon', () => {
assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n'));
});
it('serialize all rows of content with color256 and style together', async function (): Promise<any> {
it('serialize all rows of content with color256 and style together', async function(): Promise<any> {
const cols = 10;
const line = '+'.repeat(cols);
const lines: string[] = [
@@ -215,7 +215,7 @@ describe('SerializeAddon', () => {
assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n'));
});
it('serialize all rows of content with colorRGB and style separately', async function (): Promise<any> {
it('serialize all rows of content with colorRGB and style separately', async function(): Promise<any> {
const cols = 10;
const line = '+'.repeat(cols);
const lines: string[] = [
@@ -234,7 +234,7 @@ describe('SerializeAddon', () => {
assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n'));
});
it('serialize all rows of content with colorRGB and style together', async function (): Promise<any> {
it('serialize all rows of content with colorRGB and style together', async function(): Promise<any> {
const cols = 10;
const line = '+'.repeat(cols);
const lines: string[] = [
@@ -272,16 +272,6 @@ describe('SerializeAddon', () => {
});
});
async function openTerminal(options: ITerminalOptions = {}): Promise<void> {
await page.evaluate(`window.term = new Terminal(${JSON.stringify(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');
}
}
function newArray<T>(initial: T | ((index: number) => T), count: number): T[] {
const array: T[] = new Array<T>(count);
for (let i = 0; i < array.length; i++) {
@@ -348,23 +338,3 @@ const DIM = '2';
const NO_ITALIC = '23';
const NO_DIM = '22';
async function writeSync(page: puppeteer.Page, data: string): Promise<void> {
await page.evaluate(`
window.ready = false;
window.term.write('${data}', () => window.ready = true);
`);
await pollFor(page, 'window.ready', true);
}
async function pollFor(page: puppeteer.Page, fn: string, val: any, preFn?: () => Promise<void>): Promise<void> {
if (preFn) {
await preFn();
}
const result = await page.evaluate(fn);
if (result !== val) {
return new Promise<void>(r => {
setTimeout(() => r(pollFor(page, fn, val, preFn)), 10);
});
}
}
@@ -175,8 +175,8 @@ export class SerializeAddon implements ITerminalAddon {
throw new Error('Cannot use addon until it has been loaded');
}
const maxRows = this._terminal.buffer.length;
const handler = new StringSerializeHandler(this._terminal.buffer);
const maxRows = this._terminal.buffer.active.length;
const handler = new StringSerializeHandler(this._terminal.buffer.active);
rows = (rows === undefined) ? maxRows : constrain(rows, 0, maxRows);
+11 -3
View File
@@ -12,15 +12,23 @@
"removeComments": true,
"baseUrl": ".",
"paths": {
"common/*": [ "../../../src/common/*" ]
"common/*": [
"../../../src/common/*"
]
},
"strict": true
"strict": true,
"types": [
"../../../node_modules/@types/mocha",
"../../../out-test/api/TestUtils"
]
},
"include": [
"./**/*",
"../../../typings/xterm.d.ts"
],
"references": [
{ "path": "../../../src/common" }
{
"path": "../../../src/common"
}
]
}
@@ -3,26 +3,25 @@
* @license MIT
*/
import * as puppeteer from 'puppeteer';
import { ITerminalOptions } from 'xterm';
import { assert } from 'chai';
import { openTerminal, getBrowserType } from '../../../out-test/api/TestUtils';
import { Browser, Page } from 'playwright-core';
const APP = 'http://127.0.0.1:3000/test';
let browser: puppeteer.Browser;
let page: puppeteer.Page;
let browser: Browser;
let page: Page;
const width = 800;
const height = 600;
describe('Unicode11Addon', () => {
before(async function(): Promise<any> {
this.timeout(20000);
browser = await puppeteer.launch({
headless: process.argv.indexOf('--headless') !== -1,
args: [`--window-size=${width},${height}`, `--no-sandbox`]
const browserType = getBrowserType();
browser = await browserType.launch({ dumpio: true,
headless: process.argv.indexOf('--headless') !== -1
});
page = (await browser.pages())[0];
await page.setViewport({ width, height });
page = await (await browser.newContext()).newPage();
await page.setViewportSize({ width, height });
});
after(async () => {
@@ -30,9 +29,8 @@ describe('Unicode11Addon', () => {
});
beforeEach(async function(): Promise<any> {
this.timeout(20000);
await page.goto(APP);
await openTerminal();
await openTerminal(page);
});
it('wcwidth V11 emoji test', async () => {
@@ -49,13 +47,3 @@ describe('Unicode11Addon', () => {
assert.deepEqual(await page.evaluate(`window.term._core.unicodeService.getStringCellWidth('🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣')`), 20);
});
});
async function openTerminal(options: ITerminalOptions = {}): Promise<void> {
await page.evaluate(`window.term = new Terminal(${JSON.stringify(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');
}
}
+10 -2
View File
@@ -13,14 +13,22 @@
"strict": true,
"baseUrl": ".",
"paths": {
"common/*": [ "../../../src/common/*" ]
"common/*": [
"../../../src/common/*"
]
},
"types": [
"../../../node_modules/@types/mocha",
"../../../out-test/api/TestUtils"
]
},
"include": [
"./**/*",
"../../../typings/xterm.d.ts"
],
"references": [
{ "path": "../../../src/common" }
{
"path": "../../../src/common"
}
]
}
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "xterm-addon-web-links",
"version": "0.2.1",
"version": "0.3.0",
"author": {
"name": "The xterm.js authors",
"url": "https://xtermjs.org/"
@@ -0,0 +1,114 @@
/**
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
* @license MIT
*/
import { ILinkProvider, IBufferCellPosition, ILink, Terminal, IBuffer } from 'xterm';
export class WebLinkProvider implements ILinkProvider {
constructor(
private readonly _terminal: Terminal,
private readonly _regex: RegExp,
private readonly _handler: (event: MouseEvent, uri: string) => void
) {
}
provideLink(position: IBufferCellPosition, callback: (link: ILink | undefined) => void): void {
callback(LinkComputer.computeLink(position, this._regex, this._terminal, this._handler));
}
}
export class LinkComputer {
public static computeLink(position: IBufferCellPosition, regex: RegExp, terminal: Terminal, handler: (event: MouseEvent, uri: string) => void): ILink | undefined {
const rex = new RegExp(regex.source, (regex.flags || '') + 'g');
const [line, startLineIndex] = LinkComputer._translateBufferLineToStringWithWrap(position.y - 1, false, terminal);
let match;
let stringIndex = -1;
while ((match = rex.exec(line)) !== null) {
const text = match[1];
if (!text) {
// 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
console.log('match found without corresponding matchIndex');
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 = line.indexOf(text, stringIndex + 1);
rex.lastIndex = stringIndex + text.length;
if (stringIndex < 0) {
// invalid stringIndex (should not have happened)
break;
}
let endX = stringIndex + text.length;
let endY = startLineIndex + 1;
while (endX > terminal.cols) {
endX -= terminal.cols;
endY++;
}
const range = {
start: {
x: stringIndex + 1,
y: startLineIndex + 1
},
end: {
x: endX,
y: endY
}
};
return { range, text, activate: handler };
}
}
/**
* Gets the entire line for the buffer line
* @param line The line being translated.
* @param trimRight Whether to trim whitespace to the right.
* @param terminal The terminal
*/
private static _translateBufferLineToStringWithWrap(lineIndex: number, trimRight: boolean, terminal: Terminal): [string, number] {
let lineString = '';
let lineWrapsToNext: boolean;
let prevLinesToWrap: boolean;
do {
const line = terminal.buffer.active.getLine(lineIndex);
if (!line) {
break;
}
if (line.isWrapped) {
lineIndex--;
}
prevLinesToWrap = line.isWrapped;
} while (prevLinesToWrap);
const startLineIndex = lineIndex;
do {
const nextLine = terminal.buffer.active.getLine(lineIndex + 1);
lineWrapsToNext = nextLine ? nextLine.isWrapped : false;
const line = terminal.buffer.active.getLine(lineIndex);
if (!line) {
break;
}
lineString += line.translateToString(!lineWrapsToNext && trimRight).substring(0, terminal.cols);
lineIndex++;
} while (lineWrapsToNext);
return [lineString, startLineIndex];
}
}
@@ -3,55 +3,45 @@
* @license MIT
*/
import * as puppeteer from 'puppeteer';
import { assert } from 'chai';
import { ITerminalOptions } from 'xterm';
import { openTerminal, pollFor, writeSync, getBrowserType } from '../../../out-test/api/TestUtils';
import { Browser, Page } from 'playwright-core';
const APP = 'http://127.0.0.1:3000/test';
let browser: puppeteer.Browser;
let page: puppeteer.Page;
let browser: Browser;
let page: Page;
const width = 800;
const height = 600;
describe('WebLinksAddon', () => {
before(async function(): Promise<any> {
this.timeout(10000);
browser = await puppeteer.launch({
headless: process.argv.indexOf('--headless') !== -1,
args: [`--window-size=${width},${height}`, `--no-sandbox`]
const browserType = getBrowserType();
browser = await browserType.launch({ dumpio: true,
headless: process.argv.indexOf('--headless') !== -1
});
page = (await browser.pages())[0];
await page.setViewport({ width, height });
page = await (await browser.newContext()).newPage();
await page.setViewportSize({ width, height });
});
after(async () => {
await browser.close();
});
beforeEach(async function(): Promise<any> {
this.timeout(5000);
await page.goto(APP);
});
after(async () => await browser.close());
beforeEach(async () => await page.goto(APP));
it('.com', async function(): Promise<any> {
this.timeout(20000);
await testHostName('foo.com');
});
it('.com.au', async function(): Promise<any> {
this.timeout(20000);
await testHostName('foo.com.au');
});
it('.io', async function(): Promise<any> {
this.timeout(20000);
await testHostName('foo.io');
});
});
async function testHostName(hostname: string): Promise<void> {
await openTerminal({ rendererType: 'dom' });
await openTerminal(page, { rendererType: 'dom', 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` +
@@ -70,16 +60,6 @@ async function testHostName(hostname: string): Promise<void> {
await pollForLinkAtCell(1, 7, `http://${hostname}/subpath/+/id`);
}
async function openTerminal(options: ITerminalOptions = {}): Promise<void> {
await page.evaluate(`window.term = new Terminal(${JSON.stringify(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');
}
}
async function pollForLinkAtCell(col: number, row: number, value: string): Promise<void> {
const rowSelector = `.xterm-rows > :nth-child(${row})`;
// Ensure the hover element exists before trying to hover it
@@ -87,23 +67,3 @@ async function pollForLinkAtCell(col: number, row: number, value: string): Promi
await pollFor(page, `document.querySelectorAll('${rowSelector} > span[style]').length >= ${value.length}`, true, async () => page.hover(`${rowSelector} > :nth-child(${col})`));
assert.equal(await page.evaluate(`Array.prototype.reduce.call(document.querySelectorAll('${rowSelector} > span[style]'), (a, b) => a + b.textContent, '');`), value);
}
async function pollFor(page: puppeteer.Page, fn: string, val: any, preFn?: () => Promise<void>): Promise<void> {
if (preFn) {
await preFn();
}
const result = await page.evaluate(fn);
if (result !== val) {
return new Promise<void>(r => {
setTimeout(() => r(pollFor(page, fn, val, preFn)), 10);
});
}
}
async function writeSync(page: puppeteer.Page, data: string): Promise<void> {
await page.evaluate(`
window.ready = false;
window.term.write('${data}', () => window.ready = true);
`);
await pollFor(page, 'window.ready', true);
}
@@ -1,9 +1,10 @@
/**
* Copyright (c) 2017 The xterm.js authors. All rights reserved.
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
* @license MIT
*/
import { Terminal, ILinkMatcherOptions, ITerminalAddon } from 'xterm';
import { Terminal, ILinkMatcherOptions, ITerminalAddon, ILinkProvider, IDisposable } from 'xterm';
import { WebLinkProvider } from './WebLinkProvider';
const protocolClause = '(https?:\\/\\/)';
const domainCharacterSet = '[\\da-z\\.-]+';
@@ -38,22 +39,32 @@ 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 = {}
private _options: ILinkMatcherOptions = {},
private _useLinkProvider: boolean = false
) {
this._options.matchIndex = 1;
}
public activate(terminal: Terminal): void {
this._terminal = terminal;
this._linkMatcherId = this._terminal.registerLinkMatcher(strictUrlRegex, this._handler, this._options);
if (this._useLinkProvider && 'registerLinkProvider' in this._terminal) {
this._linkProvider = this._terminal.registerLinkProvider(new WebLinkProvider(this._terminal, strictUrlRegex, this._handler));
} else {
// TODO: This should be removed eventually
this._linkMatcherId = (this._terminal as Terminal).registerLinkMatcher(strictUrlRegex, this._handler, this._options);
}
}
public dispose(): void {
if (this._linkMatcherId !== undefined && this._terminal !== undefined) {
this._terminal.deregisterLinkMatcher(this._linkMatcherId);
}
this._linkProvider?.dispose();
}
}
@@ -10,7 +10,11 @@
"outDir": "../out",
"sourceMap": true,
"removeComments": true,
"strict": true
"strict": true,
"types": [
"../../../node_modules/@types/mocha",
"../../../out-test/api/TestUtils"
]
},
"include": [
"./**/*",
@@ -15,8 +15,12 @@ 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.
*/
constructor(handler?: (event: MouseEvent, uri: string) => void, options?: ILinkMatcherOptions);
constructor(handler?: (event: MouseEvent, uri: string) => void, options?: ILinkMatcherOptions, useLinkProvider?: boolean);
/**
* Activates the addon
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "xterm-addon-webgl",
"version": "0.5.0",
"version": "0.6.0",
"author": {
"name": "The xterm.js authors",
"url": "https://xtermjs.org/"
@@ -250,7 +250,7 @@ export class GlyphRenderer {
private _updateSelectionRange(startCol: number, endCol: number, y: number, model: IRenderModel, bg: number): void {
const terminal = this._terminal;
const row = y + terminal.buffer.viewportY;
const row = y + terminal.buffer.active.viewportY;
let line: IBufferLine | undefined;
for (let x = startCol; x < endCol; x++) {
const offset = (y * this._terminal.cols + x) * RENDER_MODEL_INDICIES_PER_CELL;
@@ -281,7 +281,7 @@ export class GlyphRenderer {
}
if (code & COMBINED_CHAR_BIT_MASK) {
if (!line) {
line = terminal.buffer.getLine(row);
line = terminal.buffer.active.getLine(row);
}
const chars = line!.getCell(x)!.getChars();
this._updateCell(this._vertices.selectionAttributes, x, y, model.cells[offset], bg, fg, chars);
+13 -15
View File
@@ -5,9 +5,7 @@
import { assert } from 'chai';
import { sliceFallback } from './TypedArray';
type TypedArray = Uint8Array | Uint16Array | Uint32Array | Uint8ClampedArray
| Int8Array | Int16Array | Int32Array
| Float32Array | Float64Array;
type TypedArray = Uint8Array | Uint16Array | Uint32Array | Uint8ClampedArray | Int8Array | Int16Array | Int32Array | Float32Array | Float64Array;
function deepEquals(a: TypedArray, b: TypedArray): void {
assert.equal(a.length, b.length);
@@ -40,19 +38,19 @@ describe('polyfill conformance tests', function(): void {
deepEquals(sliceFallback(u321, -1), u322.slice(-1));
});
it('Int8Array', () => {
const i81 = new Int8Array(5);
const i82 = new Int8Array(5);
deepEquals(sliceFallback(i81, 2), i82.slice(2));
deepEquals(sliceFallback(i81, 65537), i82.slice(65537));
deepEquals(sliceFallback(i81, -1), i82.slice(-1));
});
const i81 = new Int8Array(5);
const i82 = new Int8Array(5);
deepEquals(sliceFallback(i81, 2), i82.slice(2));
deepEquals(sliceFallback(i81, 65537), i82.slice(65537));
deepEquals(sliceFallback(i81, -1), i82.slice(-1));
});
it('Int16Array', () => {
const i161 = new Int16Array(5);
const i162 = new Int16Array(5);
deepEquals(sliceFallback(i161, 2), i162.slice(2));
deepEquals(sliceFallback(i161, 65535), i162.slice(65535));
deepEquals(sliceFallback(i161, -1), i162.slice(-1));
});
const i161 = new Int16Array(5);
const i162 = new Int16Array(5);
deepEquals(sliceFallback(i161, 2), i162.slice(2));
deepEquals(sliceFallback(i161, 65535), i162.slice(65535));
deepEquals(sliceFallback(i161, -1), i162.slice(-1));
});
it('Int32Array', () => {
const i321 = new Int32Array(5);
const i322 = new Int32Array(5);
+1 -3
View File
@@ -3,9 +3,7 @@
* @license MIT
*/
export type TypedArray = Uint8Array | Uint16Array | Uint32Array | Uint8ClampedArray
| Int8Array | Int16Array | Int32Array
| Float32Array | Float64Array;
export type TypedArray = Uint8Array | Uint16Array | Uint32Array | Uint8ClampedArray | Int8Array | Int16Array | Int32Array | Float32Array | Float64Array;
export function slice<T extends TypedArray>(array: T, start?: number, end?: number): T {
// all modern engines that support .slice
+3 -3
View File
@@ -21,7 +21,7 @@ export class WebglAddon implements ITerminalAddon {
throw new Error('Cannot activate WebglAddon before Terminal.open');
}
this._terminal = terminal;
const renderService: IRenderService = (<any>terminal)._core._renderService;
const renderService: IRenderService = (<any>terminal)._core._renderService;
const colors: IColorSet = (<any>terminal)._core._colorManager.colors;
this._renderer = new WebglRenderer(terminal, colors, this._preserveDrawingBuffer);
renderService.setRenderer(this._renderer);
@@ -31,8 +31,8 @@ export class WebglAddon implements ITerminalAddon {
if (!this._terminal) {
throw new Error('Cannot dispose WebglAddon because it is activated');
}
const renderService: IRenderService = (<any>this._terminal)._core._renderService;
renderService.setRenderer((<any>this._terminal)._core._createRenderer());
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 = undefined;
}
+115 -124
View File
@@ -3,21 +3,29 @@
* @license MIT
*/
import * as puppeteer from 'puppeteer';
import { ITerminalOptions } from '../../../src/Types';
import { ITheme } from 'xterm';
import { assert } from 'chai';
import deepEqual = require('deep-equal');
import { openTerminal, pollFor, writeSync, getBrowserType } from '../../../out-test/api/TestUtils';
import { Browser, Page } from 'playwright-core';
const APP = 'http://127.0.0.1:3000/test';
let browser: puppeteer.Browser;
let page: puppeteer.Page;
let browser: Browser;
let page: Page;
const width = 800;
const height = 600;
describe('WebGL Renderer Integration Tests', function(): void {
it('dispose removes renderer canvases', async () => {
let itWebgl: (expectation: string, callback?: (this: Mocha.ITestCallbackContext, done: MochaDone) => any) => Mocha.ITest | void;
describe('WebGL Renderer Integration Tests', async () => {
const browserType = getBrowserType();
const isHeadless = process.argv.indexOf('--headless') !== -1;
// Firefox works only in non-headless mode https://github.com/microsoft/playwright/issues/1032
const areTestsEnabled = browserType.name() === 'chromium' || (browserType.name() === 'firefox' && !isHeadless);
itWebgl = areTestsEnabled ? it : it.skip;
itWebgl('dispose removes renderer canvases', async function(): Promise<void> {
await setupBrowser();
assert.equal(await page.evaluate(`document.querySelectorAll('.xterm canvas').length`), 3);
await page.evaluate(`addon.dispose()`);
@@ -26,11 +34,13 @@ describe('WebGL Renderer Integration Tests', function(): void {
});
describe('colors', () => {
before(async () => setupBrowser());
after(async () => browser.close());
beforeEach(async () => page.evaluate(`window.term.reset()`));
if (areTestsEnabled) {
before(async () => setupBrowser());
after(async () => browser.close());
beforeEach(async () => page.evaluate(`window.term.reset()`));
}
it('foreground 0-15', async () => {
itWebgl('foreground 0-15', async () => {
const theme: ITheme = {
black: '#010203',
red: '#040506',
@@ -42,7 +52,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
white: '#161718'
};
await page.evaluate(`window.term.setOption('theme', ${JSON.stringify(theme)});`);
await writeSync(`\\x1b[30m█\\x1b[31m█\\x1b[32m█\\x1b[33m█\\x1b[34m█\\x1b[35m█\\x1b[36m█\\x1b[37m█`);
await writeSync(page, `\\x1b[30m█\\x1b[31m█\\x1b[32m█\\x1b[33m█\\x1b[34m█\\x1b[35m█\\x1b[36m█\\x1b[37m█`);
await pollFor(page, () => getCellColor(1, 1), [1, 2, 3, 255]);
await pollFor(page, () => getCellColor(2, 1), [4, 5, 6, 255]);
await pollFor(page, () => getCellColor(3, 1), [7, 8, 9, 255]);
@@ -53,7 +63,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
await pollFor(page, () => getCellColor(8, 1), [22, 23, 24, 255]);
});
it('foreground 0-7 drawBoldTextInBrightColors', async () => {
itWebgl('foreground 0-7 drawBoldTextInBrightColors', async () => {
const theme: ITheme = {
brightBlack: '#010203',
brightRed: '#040506',
@@ -68,7 +78,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
window.term.setOption('theme', ${JSON.stringify(theme)});
window.term.setOption('drawBoldTextInBrightColors', true);
`);
await writeSync(`\\x1b[1;30m█\\x1b[1;31m█\\x1b[1;32m█\\x1b[1;33m█\\x1b[1;34m█\\x1b[1;35m█\\x1b[1;36m█\\x1b[1;37m█`);
await writeSync(page, `\\x1b[1;30m█\\x1b[1;31m█\\x1b[1;32m█\\x1b[1;33m█\\x1b[1;34m█\\x1b[1;35m█\\x1b[1;36m█\\x1b[1;37m█`);
await pollFor(page, () => getCellColor(1, 1), [1, 2, 3, 255]);
await pollFor(page, () => getCellColor(2, 1), [4, 5, 6, 255]);
await pollFor(page, () => getCellColor(3, 1), [7, 8, 9, 255]);
@@ -79,7 +89,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
await pollFor(page, () => getCellColor(8, 1), [22, 23, 24, 255]);
});
it('background 0-15', async () => {
itWebgl('background 0-15', async () => {
const theme: ITheme = {
black: '#010203',
red: '#040506',
@@ -91,7 +101,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
white: '#161718'
};
await page.evaluate(`window.term.setOption('theme', ${JSON.stringify(theme)});`);
await writeSync(`\\x1b[40m \\x1b[41m \\x1b[42m \\x1b[43m \\x1b[44m \\x1b[45m \\x1b[46m \\x1b[47m `);
await writeSync(page, `\\x1b[40m \\x1b[41m \\x1b[42m \\x1b[43m \\x1b[44m \\x1b[45m \\x1b[46m \\x1b[47m `);
await pollFor(page, () => getCellColor(1, 1), [1, 2, 3, 255]);
await pollFor(page, () => getCellColor(2, 1), [4, 5, 6, 255]);
await pollFor(page, () => getCellColor(3, 1), [7, 8, 9, 255]);
@@ -102,7 +112,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
await pollFor(page, () => getCellColor(8, 1), [22, 23, 24, 255]);
});
it('foreground 0-15 inverse', async () => {
itWebgl('foreground 0-15 inverse', async () => {
const theme: ITheme = {
black: '#010203',
red: '#040506',
@@ -114,7 +124,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
white: '#161718'
};
await page.evaluate(`window.term.setOption('theme', ${JSON.stringify(theme)});`);
await writeSync(`\\x1b[7;30m \\x1b[7;31m \\x1b[7;32m \\x1b[7;33m \\x1b[7;34m \\x1b[7;35m \\x1b[7;36m \\x1b[7;37m `);
await writeSync(page, `\\x1b[7;30m \\x1b[7;31m \\x1b[7;32m \\x1b[7;33m \\x1b[7;34m \\x1b[7;35m \\x1b[7;36m \\x1b[7;37m `);
await pollFor(page, () => getCellColor(1, 1), [1, 2, 3, 255]);
await pollFor(page, () => getCellColor(2, 1), [4, 5, 6, 255]);
await pollFor(page, () => getCellColor(3, 1), [7, 8, 9, 255]);
@@ -125,7 +135,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
await pollFor(page, () => getCellColor(8, 1), [22, 23, 24, 255]);
});
it('background 0-15 inverse', async () => {
itWebgl('background 0-15 inverse', async () => {
const theme: ITheme = {
black: '#010203',
red: '#040506',
@@ -137,7 +147,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
white: '#161718'
};
await page.evaluate(`window.term.setOption('theme', ${JSON.stringify(theme)});`);
await writeSync(`\\x1b[7;40m█\\x1b[7;41m█\\x1b[7;42m█\\x1b[7;43m█\\x1b[7;44m█\\x1b[7;45m█\\x1b[7;46m█\\x1b[7;47m█`);
await writeSync(page, `\\x1b[7;40m█\\x1b[7;41m█\\x1b[7;42m█\\x1b[7;43m█\\x1b[7;44m█\\x1b[7;45m█\\x1b[7;46m█\\x1b[7;47m█`);
await pollFor(page, () => getCellColor(1, 1), [1, 2, 3, 255]);
await pollFor(page, () => getCellColor(2, 1), [4, 5, 6, 255]);
await pollFor(page, () => getCellColor(3, 1), [7, 8, 9, 255]);
@@ -148,7 +158,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
await pollFor(page, () => getCellColor(8, 1), [22, 23, 24, 255]);
});
it('foreground 0-15 inivisible', async () => {
itWebgl('foreground 0-15 inivisible', async () => {
const theme: ITheme = {
black: '#010203',
red: '#040506',
@@ -160,7 +170,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
white: '#161718'
};
await page.evaluate(`window.term.setOption('theme', ${JSON.stringify(theme)});`);
await writeSync(`\\x1b[8;30m \\x1b[8;31m \\x1b[8;32m \\x1b[8;33m \\x1b[8;34m \\x1b[8;35m \\x1b[8;36m \\x1b[8;37m `);
await writeSync(page, `\\x1b[8;30m \\x1b[8;31m \\x1b[8;32m \\x1b[8;33m \\x1b[8;34m \\x1b[8;35m \\x1b[8;36m \\x1b[8;37m `);
await pollFor(page, () => getCellColor(1, 1), [0, 0, 0, 255]);
await pollFor(page, () => getCellColor(2, 1), [0, 0, 0, 255]);
await pollFor(page, () => getCellColor(3, 1), [0, 0, 0, 255]);
@@ -171,7 +181,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
await pollFor(page, () => getCellColor(8, 1), [0, 0, 0, 255]);
});
it('background 0-15 inivisible', async () => {
itWebgl('background 0-15 inivisible', async () => {
const theme: ITheme = {
black: '#010203',
red: '#040506',
@@ -183,7 +193,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
white: '#161718'
};
await page.evaluate(`window.term.setOption('theme', ${JSON.stringify(theme)});`);
await writeSync(`\\x1b[8;40m█\\x1b[8;41m█\\x1b[8;42m█\\x1b[8;43m█\\x1b[8;44m█\\x1b[8;45m█\\x1b[8;46m█\\x1b[8;47m█`);
await writeSync(page, `\\x1b[8;40m█\\x1b[8;41m█\\x1b[8;42m█\\x1b[8;43m█\\x1b[8;44m█\\x1b[8;45m█\\x1b[8;46m█\\x1b[8;47m█`);
await pollFor(page, () => getCellColor(1, 1), [1, 2, 3, 255]);
await pollFor(page, () => getCellColor(2, 1), [4, 5, 6, 255]);
await pollFor(page, () => getCellColor(3, 1), [7, 8, 9, 255]);
@@ -194,7 +204,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
await pollFor(page, () => getCellColor(8, 1), [22, 23, 24, 255]);
});
it('foreground 0-15 bright', async () => {
itWebgl('foreground 0-15 bright', async () => {
const theme: ITheme = {
brightBlack: '#010203',
brightRed: '#040506',
@@ -206,7 +216,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
brightWhite: '#161718'
};
await page.evaluate(`window.term.setOption('theme', ${JSON.stringify(theme)});`);
await writeSync(`\\x1b[90m█\\x1b[91m█\\x1b[92m█\\x1b[93m█\\x1b[94m█\\x1b[95m█\\x1b[96m█\\x1b[97m█`);
await writeSync(page, `\\x1b[90m█\\x1b[91m█\\x1b[92m█\\x1b[93m█\\x1b[94m█\\x1b[95m█\\x1b[96m█\\x1b[97m█`);
await pollFor(page, () => getCellColor(1, 1), [1, 2, 3, 255]);
await pollFor(page, () => getCellColor(2, 1), [4, 5, 6, 255]);
await pollFor(page, () => getCellColor(3, 1), [7, 8, 9, 255]);
@@ -217,7 +227,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
await pollFor(page, () => getCellColor(8, 1), [22, 23, 24, 255]);
});
it('background 0-15 bright', async () => {
itWebgl('background 0-15 bright', async () => {
const theme: ITheme = {
brightBlack: '#010203',
brightRed: '#040506',
@@ -229,7 +239,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
brightWhite: '#161718'
};
await page.evaluate(`window.term.setOption('theme', ${JSON.stringify(theme)});`);
await writeSync(`\\x1b[100m \\x1b[101m \\x1b[102m \\x1b[103m \\x1b[104m \\x1b[105m \\x1b[106m \\x1b[107m `);
await writeSync(page, `\\x1b[100m \\x1b[101m \\x1b[102m \\x1b[103m \\x1b[104m \\x1b[105m \\x1b[106m \\x1b[107m `);
await pollFor(page, () => getCellColor(1, 1), [1, 2, 3, 255]);
await pollFor(page, () => getCellColor(2, 1), [4, 5, 6, 255]);
await pollFor(page, () => getCellColor(3, 1), [7, 8, 9, 255]);
@@ -240,7 +250,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
await pollFor(page, () => getCellColor(8, 1), [22, 23, 24, 255]);
});
it('foreground 16-255', async () => {
itWebgl('foreground 16-255', async () => {
let data = '';
for (let y = 0; y < 240 / 16; y++) {
for (let x = 0; x < 16; x++) {
@@ -248,7 +258,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
}
data += '\\r\\n';
}
await writeSync(data);
await writeSync(page, data);
for (let y = 0; y < 240 / 16; y++) {
for (let x = 0; x < 16; x++) {
const cssColor = COLORS_16_TO_255[y * 16 + x];
@@ -260,7 +270,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
}
});
it('background 16-255', async () => {
itWebgl('background 16-255', async () => {
let data = '';
for (let y = 0; y < 240 / 16; y++) {
for (let x = 0; x < 16; x++) {
@@ -268,7 +278,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
}
data += '\\r\\n';
}
await writeSync(data);
await writeSync(page, data);
for (let y = 0; y < 240 / 16; y++) {
for (let x = 0; x < 16; x++) {
const cssColor = COLORS_16_TO_255[y * 16 + x];
@@ -280,7 +290,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
}
});
it('foreground 16-255 inverse', async () => {
itWebgl('foreground 16-255 inverse', async () => {
let data = '';
for (let y = 0; y < 240 / 16; y++) {
for (let x = 0; x < 16; x++) {
@@ -288,7 +298,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
}
data += '\\r\\n';
}
await writeSync(data);
await writeSync(page, data);
for (let y = 0; y < 240 / 16; y++) {
for (let x = 0; x < 16; x++) {
const cssColor = COLORS_16_TO_255[y * 16 + x];
@@ -300,7 +310,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
}
});
it('background 16-255 inverse', async () => {
itWebgl('background 16-255 inverse', async () => {
let data = '';
for (let y = 0; y < 240 / 16; y++) {
for (let x = 0; x < 16; x++) {
@@ -308,7 +318,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
}
data += '\\r\\n';
}
await writeSync(data);
await writeSync(page, data);
for (let y = 0; y < 240 / 16; y++) {
for (let x = 0; x < 16; x++) {
const cssColor = COLORS_16_TO_255[y * 16 + x];
@@ -320,7 +330,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
}
});
it('foreground 16-255 invisible', async () => {
itWebgl('foreground 16-255 invisible', async () => {
let data = '';
for (let y = 0; y < 240 / 16; y++) {
for (let x = 0; x < 16; x++) {
@@ -328,7 +338,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
}
data += '\\r\\n';
}
await writeSync(data);
await writeSync(page, data);
for (let y = 0; y < 240 / 16; y++) {
for (let x = 0; x < 16; x++) {
const cssColor = COLORS_16_TO_255[y * 16 + x];
@@ -340,7 +350,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
}
});
it('background 16-255 invisible', async () => {
itWebgl('background 16-255 invisible', async () => {
let data = '';
for (let y = 0; y < 240 / 16; y++) {
for (let x = 0; x < 16; x++) {
@@ -348,7 +358,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
}
data += '\\r\\n';
}
await writeSync(data);
await writeSync(page, data);
for (let y = 0; y < 240 / 16; y++) {
for (let x = 0; x < 16; x++) {
const cssColor = COLORS_16_TO_255[y * 16 + x];
@@ -360,7 +370,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
}
});
it('foreground true color red', async () => {
itWebgl('foreground true color red', async () => {
let data = '';
for (let y = 0; y < 16; y++) {
for (let x = 0; x < 16; x++) {
@@ -369,7 +379,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
}
data += '\\r\\n';
}
await writeSync(data);
await writeSync(page, data);
for (let y = 0; y < 16; y++) {
for (let x = 0; x < 16; x++) {
const i = y * 16 + x;
@@ -378,7 +388,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
}
});
it('background true color red', async () => {
itWebgl('background true color red', async () => {
let data = '';
for (let y = 0; y < 16; y++) {
for (let x = 0; x < 16; x++) {
@@ -387,7 +397,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
}
data += '\\r\\n';
}
await writeSync(data);
await writeSync(page, data);
for (let y = 0; y < 16; y++) {
for (let x = 0; x < 16; x++) {
const i = y * 16 + x;
@@ -396,7 +406,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
}
});
it('foreground true color green', async () => {
itWebgl('foreground true color green', async () => {
let data = '';
for (let y = 0; y < 16; y++) {
for (let x = 0; x < 16; x++) {
@@ -405,7 +415,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
}
data += '\\r\\n';
}
await writeSync(data);
await writeSync(page, data);
for (let y = 0; y < 16; y++) {
for (let x = 0; x < 16; x++) {
const i = y * 16 + x;
@@ -414,7 +424,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
}
});
it('background true color green', async () => {
itWebgl('background true color green', async () => {
let data = '';
for (let y = 0; y < 16; y++) {
for (let x = 0; x < 16; x++) {
@@ -423,7 +433,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
}
data += '\\r\\n';
}
await writeSync(data);
await writeSync(page, data);
for (let y = 0; y < 16; y++) {
for (let x = 0; x < 16; x++) {
const i = y * 16 + x;
@@ -432,7 +442,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
}
});
it('foreground true color blue', async () => {
itWebgl('foreground true color blue', async () => {
let data = '';
for (let y = 0; y < 16; y++) {
for (let x = 0; x < 16; x++) {
@@ -441,7 +451,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
}
data += '\\r\\n';
}
await writeSync(data);
await writeSync(page, data);
for (let y = 0; y < 16; y++) {
for (let x = 0; x < 16; x++) {
const i = y * 16 + x;
@@ -450,7 +460,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
}
});
it('background true color blue', async () => {
itWebgl('background true color blue', async () => {
let data = '';
for (let y = 0; y < 16; y++) {
for (let x = 0; x < 16; x++) {
@@ -459,7 +469,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
}
data += '\\r\\n';
}
await writeSync(data);
await writeSync(page, data);
for (let y = 0; y < 16; y++) {
for (let x = 0; x < 16; x++) {
const i = y * 16 + x;
@@ -468,7 +478,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
}
});
it('foreground true color grey', async () => {
itWebgl('foreground true color grey', async () => {
let data = '';
for (let y = 0; y < 16; y++) {
for (let x = 0; x < 16; x++) {
@@ -477,7 +487,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
}
data += '\\r\\n';
}
await writeSync(data);
await writeSync(page, data);
for (let y = 0; y < 16; y++) {
for (let x = 0; x < 16; x++) {
const i = y * 16 + x;
@@ -486,7 +496,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
}
});
it('background true color grey', async () => {
itWebgl('background true color grey', async () => {
let data = '';
for (let y = 0; y < 16; y++) {
for (let x = 0; x < 16; x++) {
@@ -495,7 +505,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
}
data += '\\r\\n';
}
await writeSync(data);
await writeSync(page, data);
for (let y = 0; y < 16; y++) {
for (let x = 0; x < 16; x++) {
const i = y * 16 + x;
@@ -504,7 +514,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
}
});
it('foreground true color red inverse', async function(): Promise<void> {
itWebgl('foreground true color red inverse', async function(): Promise<void> {
let data = '';
for (let y = 0; y < 16; y++) {
for (let x = 0; x < 16; x++) {
@@ -513,7 +523,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
}
data += '\\r\\n';
}
await writeSync(data);
await writeSync(page, data);
for (let y = 0; y < 16; y++) {
for (let x = 0; x < 16; x++) {
const i = y * 16 + x;
@@ -522,7 +532,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
}
});
it('background true color red inverse', async function(): Promise<void> {
itWebgl('background true color red inverse', async function(): Promise<void> {
let data = '';
for (let y = 0; y < 16; y++) {
for (let x = 0; x < 16; x++) {
@@ -531,7 +541,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
}
data += '\\r\\n';
}
await writeSync(data);
await writeSync(page, data);
for (let y = 0; y < 16; y++) {
for (let x = 0; x < 16; x++) {
const i = y * 16 + x;
@@ -540,7 +550,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
}
});
it('foreground true color green inverse', async () => {
itWebgl('foreground true color green inverse', async () => {
let data = '';
for (let y = 0; y < 16; y++) {
for (let x = 0; x < 16; x++) {
@@ -549,7 +559,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
}
data += '\\r\\n';
}
await writeSync(data);
await writeSync(page, data);
for (let y = 0; y < 16; y++) {
for (let x = 0; x < 16; x++) {
const i = y * 16 + x;
@@ -558,7 +568,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
}
});
it('background true color green inverse', async () => {
itWebgl('background true color green inverse', async () => {
let data = '';
for (let y = 0; y < 16; y++) {
for (let x = 0; x < 16; x++) {
@@ -567,7 +577,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
}
data += '\\r\\n';
}
await writeSync(data);
await writeSync(page, data);
for (let y = 0; y < 16; y++) {
for (let x = 0; x < 16; x++) {
const i = y * 16 + x;
@@ -576,7 +586,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
}
});
it('foreground true color blue inverse', async () => {
itWebgl('foreground true color blue inverse', async () => {
let data = '';
for (let y = 0; y < 16; y++) {
for (let x = 0; x < 16; x++) {
@@ -585,7 +595,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
}
data += '\\r\\n';
}
await writeSync(data);
await writeSync(page, data);
for (let y = 0; y < 16; y++) {
for (let x = 0; x < 16; x++) {
const i = y * 16 + x;
@@ -594,7 +604,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
}
});
it('background true color blue inverse', async () => {
itWebgl('background true color blue inverse', async () => {
let data = '';
for (let y = 0; y < 16; y++) {
for (let x = 0; x < 16; x++) {
@@ -603,7 +613,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
}
data += '\\r\\n';
}
await writeSync(data);
await writeSync(page, data);
for (let y = 0; y < 16; y++) {
for (let x = 0; x < 16; x++) {
const i = y * 16 + x;
@@ -612,7 +622,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
}
});
it('foreground true color grey inverse', async () => {
itWebgl('foreground true color grey inverse', async () => {
let data = '';
for (let y = 0; y < 16; y++) {
for (let x = 0; x < 16; x++) {
@@ -621,7 +631,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
}
data += '\\r\\n';
}
await writeSync(data);
await writeSync(page, data);
for (let y = 0; y < 16; y++) {
for (let x = 0; x < 16; x++) {
const i = y * 16 + x;
@@ -630,7 +640,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
}
});
it('background true color grey inverse', async () => {
itWebgl('background true color grey inverse', async () => {
let data = '';
for (let y = 0; y < 16; y++) {
for (let x = 0; x < 16; x++) {
@@ -639,7 +649,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
}
data += '\\r\\n';
}
await writeSync(data);
await writeSync(page, data);
for (let y = 0; y < 16; y++) {
for (let x = 0; x < 16; x++) {
const i = y * 16 + x;
@@ -648,7 +658,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
}
});
it('foreground true color grey invisible', async () => {
itWebgl('foreground true color grey invisible', async () => {
let data = '';
for (let y = 0; y < 16; y++) {
for (let x = 0; x < 16; x++) {
@@ -657,7 +667,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
}
data += '\\r\\n';
}
await writeSync(data);
await writeSync(page, data);
for (let y = 0; y < 16; y++) {
for (let x = 0; x < 16; x++) {
const i = y * 16 + x;
@@ -666,7 +676,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
}
});
it('background true color grey invisible', async () => {
itWebgl('background true color grey invisible', async () => {
let data = '';
for (let y = 0; y < 16; y++) {
for (let x = 0; x < 16; x++) {
@@ -675,7 +685,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
}
data += '\\r\\n';
}
await writeSync(data);
await writeSync(page, data);
for (let y = 0; y < 16; y++) {
for (let x = 0; x < 16; x++) {
const i = y * 16 + x;
@@ -686,11 +696,13 @@ describe('WebGL Renderer Integration Tests', function(): void {
});
describe('minimumContrastRatio', async () => {
before(async () => setupBrowser());
after(async () => browser.close());
beforeEach(async () => page.evaluate(`window.term.reset()`));
if (areTestsEnabled) {
before(async () => setupBrowser());
after(async () => browser.close());
beforeEach(async () => page.evaluate(`window.term.reset()`));
}
it('should adjust 0-15 colors on black background', async () => {
itWebgl('should adjust 0-15 colors on black background', async () => {
const theme: ITheme = {
black: '#2e3436',
red: '#cc0000',
@@ -713,7 +725,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
window.term.setOption('theme', ${JSON.stringify(theme)});
window.term.setOption('minimumContrastRatio', 1);
`);
await writeSync(
await writeSync(page,
`\\x1b[30m█\\x1b[31m█\\x1b[32m█\\x1b[33m█\\x1b[34m█\\x1b[35m█\\x1b[36m█\\x1b[37m█\\r\\n` +
`\\x1b[90m█\\x1b[91m█\\x1b[92m█\\x1b[93m█\\x1b[94m█\\x1b[95m█\\x1b[96m█\\x1b[97m█`
);
@@ -757,7 +769,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
await pollFor(page, () => getCellColor(8, 2), [0xee, 0xee, 0xec, 255]);
});
it('should adjust 0-15 colors on white background', async () => {
itWebgl('should adjust 0-15 colors on white background', async () => {
const theme: ITheme = {
background: '#ffffff',
black: '#2e3436',
@@ -781,7 +793,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
window.term.setOption('theme', ${JSON.stringify(theme)});
window.term.setOption('minimumContrastRatio', 1);
`);
await writeSync(
await writeSync(page,
`\\x1b[30m█\\x1b[31m█\\x1b[32m█\\x1b[33m█\\x1b[34m█\\x1b[35m█\\x1b[36m█\\x1b[37m█\\r\\n` +
`\\x1b[90m█\\x1b[91m█\\x1b[92m█\\x1b[93m█\\x1b[94m█\\x1b[95m█\\x1b[96m█\\x1b[97m█`
);
@@ -826,18 +838,20 @@ describe('WebGL Renderer Integration Tests', function(): void {
});
describe('selection', async () => {
before(async () => setupBrowser());
after(async () => browser.close());
beforeEach(async () => page.evaluate(`window.term.reset()`));
if (areTestsEnabled) {
before(async () => setupBrowser());
after(async () => browser.close());
beforeEach(async () => page.evaluate(`window.term.reset()`));
}
it('should resolve the inverse foreground color based on the original background color, not the selection', async () => {
itWebgl('should resolve the inverse foreground color based on the original background color, not the selection', async () => {
const theme: ITheme = {
foreground: '#FF0000',
background: '#00FF00',
selection: '#0000FF'
};
await page.evaluate(`window.term.setOption('theme', ${JSON.stringify(theme)});`);
await writeSync(` █\\x1b[7m█\\x1b[0m`);
await writeSync(page, ` █\\x1b[7m█\\x1b[0m`);
await pollFor(page, () => getCellColor(1, 1), [0, 255, 0, 255]);
await pollFor(page, () => getCellColor(2, 1), [255, 0, 0, 255]);
await pollFor(page, () => getCellColor(3, 1), [0, 255, 0, 255]);
@@ -850,36 +864,25 @@ describe('WebGL Renderer Integration Tests', function(): void {
});
describe('allowTransparency', async () => {
before(async () => setupBrowser({ rendererType: 'dom', allowTransparency: true}));
after(async () => browser.close());
beforeEach(async () => page.evaluate(`window.term.reset()`));
it('transparent background inverse', async () => {
if (areTestsEnabled) {
before(async () => setupBrowser({ rendererType: 'dom', allowTransparency: true }));
after(async () => browser.close());
beforeEach(async () => page.evaluate(`window.term.reset()`));
}
itWebgl('transparent background inverse', async () => {
const theme: ITheme = {
background: '#ff000080'
};
await page.evaluate(`window.term.setOption('theme', ${JSON.stringify(theme)});`);
const data = `\\x1b[7m█\x1b[0m`;
await writeSync(data);
await writeSync(page, data);
// Inverse background should be opaque
await pollFor(page, () => getCellColor(1, 1), [255, 0, 0, 255]);
});
});
});
async function openTerminal(options: ITerminalOptions = {}): Promise<void> {
await page.evaluate(`window.term = new Terminal(${JSON.stringify(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');
}
}
async function writeSync(data: string): Promise<void> {
return page.evaluate(`new Promise(resolve => window.term.write('${data}', resolve))`);
}
async function getCellColor(col: number, row: number): Promise<number[]> {
await page.evaluate(`
window.gl = window.term._core._renderService._renderer._gl;
@@ -895,32 +898,20 @@ async function getCellColor(col: number, row: number): Promise<number[]> {
}
async function setupBrowser(options: ITerminalOptions = { rendererType: 'dom' }): Promise<void> {
browser = await puppeteer.launch({
headless: process.argv.indexOf('--headless') !== -1,
args: [`--window-size=${width},${height}`, `--no-sandbox`]
const browserType = getBrowserType();
browser = await browserType.launch({ dumpio: true,
headless: process.argv.indexOf('--headless') !== -1
});
page = (await browser.pages())[0];
await page.setViewport({ width, height });
page = await (await browser.newContext()).newPage();
await page.setViewportSize({ width, height });
await page.goto(APP);
await openTerminal(options);
await openTerminal(page, options);
await page.evaluate(`
window.addon = new WebglAddon(true);
window.term.loadAddon(window.addon);
`);
}
export async function pollFor<T>(page: puppeteer.Page, evalOrFn: string | (() => Promise<T>), val: T, preFn?: () => Promise<void>): Promise<void> {
if (preFn) {
await preFn();
}
const result = typeof evalOrFn === 'string' ? await page.evaluate(evalOrFn) : await evalOrFn();
if (!deepEqual(result, val)) {
return new Promise<void>(r => {
setTimeout(() => r(pollFor(page, evalOrFn, val, preFn)), 1);
});
}
}
const COLORS_16_TO_255 = [
'#000000', '#00005f', '#000087', '#0000af', '#0000d7', '#0000ff', '#005f00', '#005f5f', '#005f87', '#005faf', '#005fd7', '#005fff', '#008700', '#00875f', '#008787', '#0087af',
'#0087d7', '#0087ff', '#00af00', '#00af5f', '#00af87', '#00afaf', '#00afd7', '#00afff', '#00d700', '#00d75f', '#00d787', '#00d7af', '#00d7d7', '#00d7ff', '#00ff00', '#00ff5f',
@@ -49,7 +49,7 @@ export class WebglRenderer extends Disposable implements IRenderer {
) {
super();
this._core = (<any>this._terminal)._core;
this._core = (this._terminal as any)._core;
this._renderLayers = [
new LinkRenderLayer(this._core.screenElement, 2, this._colors, this._core),
@@ -81,7 +81,7 @@ export class WebglRenderer extends Disposable implements IRenderer {
};
this._gl = this._canvas.getContext('webgl2', contextAttributes) as IWebGL2RenderingContext;
if (!this._gl) {
throw new Error('WebGL2 not supported');
throw new Error('WebGL2 not supported ' + this._gl);
}
this._core.screenElement.appendChild(this._canvas);
@@ -226,7 +226,7 @@ export class WebglRenderer extends Disposable implements IRenderer {
public renderRows(start: number, end: number): void {
if (!this._isAttached) {
if (document.body.contains(this._core.screenElement) && (<any>this._core)._charSizeService.width && (<any>this._core)._charSizeService.height) {
if (document.body.contains(this._core.screenElement) && (this._core as any)._charSizeService.width && (this._core as any)._charSizeService.height) {
this._updateDimensions();
this._refreshCharAtlas();
this._isAttached = true;
@@ -302,8 +302,8 @@ export class WebglRenderer extends Disposable implements IRenderer {
}
// Translate from buffer position to viewport position
const viewportStartRow = start[1] - terminal.buffer.viewportY;
const viewportEndRow = end[1] - terminal.buffer.viewportY;
const viewportStartRow = start[1] - terminal.buffer.active.viewportY;
const viewportEndRow = end[1] - terminal.buffer.active.viewportY;
const viewportCappedStartRow = Math.max(viewportStartRow, 0);
const viewportCappedEndRow = Math.min(viewportEndRow, terminal.rows - 1);
@@ -329,7 +329,7 @@ export class WebglRenderer extends Disposable implements IRenderer {
// TODO: Acquire CharSizeService properly
// Perform a new measure if the CharMeasure dimensions are not yet available
if (!(<any>this._core)._charSizeService.width || !(<any>this._core)._charSizeService.height) {
if (!(this._core as any)._charSizeService.width || !(this._core as any)._charSizeService.height) {
return;
}
@@ -340,12 +340,12 @@ export class WebglRenderer extends Disposable implements IRenderer {
// NOTE: ceil fixes sometime, floor does others :s
this.dimensions.scaledCharWidth = Math.floor((<any>this._core)._charSizeService.width * this._devicePixelRatio);
this.dimensions.scaledCharWidth = Math.floor((this._core as any)._charSizeService.width * this._devicePixelRatio);
// Calculate the scaled character height. Height is ceiled in case
// devicePixelRatio is a floating point number in order to ensure there is
// enough space to draw the character to the cell.
this.dimensions.scaledCharHeight = Math.ceil((<any>this._core)._charSizeService.height * this._devicePixelRatio);
this.dimensions.scaledCharHeight = Math.ceil((this._core as any)._charSizeService.height * this._devicePixelRatio);
// Calculate the scaled cell height, if lineHeight is not 1 then the value
// will be floored because since lineHeight can never be lower then 1, there
@@ -74,7 +74,7 @@ export abstract class BaseRenderLayer implements IRenderLayer {
const oldCanvas = this._canvas;
this._alpha = alpha;
// Cloning preserves properties
this._canvas = <HTMLCanvasElement>this._canvas.cloneNode();
this._canvas = this._canvas.cloneNode() as HTMLCanvasElement;
this._initCanvas();
this._container.replaceChild(this._canvas, oldCanvas);
@@ -127,10 +127,10 @@ export abstract class BaseRenderLayer implements IRenderLayer {
*/
protected _fillCells(x: number, y: number, width: number, height: number): void {
this._ctx.fillRect(
x * this._scaledCellWidth,
y * this._scaledCellHeight,
width * this._scaledCellWidth,
height * this._scaledCellHeight);
x * this._scaledCellWidth,
y * this._scaledCellHeight,
width * this._scaledCellWidth,
height * this._scaledCellHeight);
}
/**
@@ -141,10 +141,10 @@ export abstract class BaseRenderLayer implements IRenderLayer {
*/
protected _fillBottomLineAtCells(x: number, y: number, width: number = 1): void {
this._ctx.fillRect(
x * this._scaledCellWidth,
(y + 1) * this._scaledCellHeight - window.devicePixelRatio - 1 /* Ensure it's drawn within the cell */,
width * this._scaledCellWidth,
window.devicePixelRatio);
x * this._scaledCellWidth,
(y + 1) * this._scaledCellHeight - window.devicePixelRatio - 1 /* Ensure it's drawn within the cell */,
width * this._scaledCellWidth,
window.devicePixelRatio);
}
/**
@@ -155,10 +155,10 @@ export abstract class BaseRenderLayer implements IRenderLayer {
*/
protected _fillLeftLineAtCell(x: number, y: number, width: number): void {
this._ctx.fillRect(
x * this._scaledCellWidth,
y * this._scaledCellHeight,
window.devicePixelRatio * width,
this._scaledCellHeight);
x * this._scaledCellWidth,
y * this._scaledCellHeight,
window.devicePixelRatio * width,
this._scaledCellHeight);
}
/**
@@ -170,10 +170,10 @@ export abstract class BaseRenderLayer implements IRenderLayer {
protected _strokeRectAtCell(x: number, y: number, width: number, height: number): void {
this._ctx.lineWidth = window.devicePixelRatio;
this._ctx.strokeRect(
x * this._scaledCellWidth + window.devicePixelRatio / 2,
y * this._scaledCellHeight + (window.devicePixelRatio / 2),
width * this._scaledCellWidth - window.devicePixelRatio,
(height * this._scaledCellHeight) - window.devicePixelRatio);
x * this._scaledCellWidth + window.devicePixelRatio / 2,
y * this._scaledCellHeight + (window.devicePixelRatio / 2),
width * this._scaledCellWidth - window.devicePixelRatio,
(height * this._scaledCellHeight) - window.devicePixelRatio);
}
/**
@@ -198,17 +198,17 @@ export abstract class BaseRenderLayer implements IRenderLayer {
protected _clearCells(x: number, y: number, width: number, height: number): void {
if (this._alpha) {
this._ctx.clearRect(
x * this._scaledCellWidth,
y * this._scaledCellHeight,
width * this._scaledCellWidth,
height * this._scaledCellHeight);
x * this._scaledCellWidth,
y * this._scaledCellHeight,
width * this._scaledCellWidth,
height * this._scaledCellHeight);
} else {
this._ctx.fillStyle = this._colors.background.css;
this._ctx.fillRect(
x * this._scaledCellWidth,
y * this._scaledCellHeight,
width * this._scaledCellWidth,
height * this._scaledCellHeight);
x * this._scaledCellWidth,
y * this._scaledCellHeight,
width * this._scaledCellWidth,
height * this._scaledCellHeight);
}
}
@@ -227,9 +227,9 @@ export abstract class BaseRenderLayer implements IRenderLayer {
this._ctx.textBaseline = 'middle';
this._clipRow(terminal, y);
this._ctx.fillText(
cell.getChars(),
x * this._scaledCellWidth + this._scaledCharLeft,
y * this._scaledCellHeight + this._scaledCharTop + this._scaledCharHeight / 2);
cell.getChars(),
x * this._scaledCellWidth + this._scaledCharLeft,
y * this._scaledCellHeight + this._scaledCharTop + this._scaledCharHeight / 2);
}
/**
@@ -240,10 +240,10 @@ export abstract class BaseRenderLayer implements IRenderLayer {
private _clipRow(terminal: Terminal, y: number): void {
this._ctx.beginPath();
this._ctx.rect(
0,
y * this._scaledCellHeight,
terminal.cols * this._scaledCellWidth,
this._scaledCellHeight);
0,
y * this._scaledCellHeight,
terminal.cols * this._scaledCellWidth,
this._scaledCellHeight);
this._ctx.clip();
}
@@ -76,14 +76,14 @@ export class CursorRenderLayer extends BaseRenderLayer {
if (this._cursorBlinkStateManager) {
this._cursorBlinkStateManager.pause();
}
this._onRequestRefreshRowsEvent.fire({ start: terminal.buffer.cursorY, end: terminal.buffer.cursorY });
this._onRequestRefreshRowsEvent.fire({ start: terminal.buffer.active.cursorY, end: terminal.buffer.active.cursorY });
}
public onFocus(terminal: Terminal): void {
if (this._cursorBlinkStateManager) {
this._cursorBlinkStateManager.resume(terminal);
} else {
this._onRequestRefreshRowsEvent.fire({ start: terminal.buffer.cursorY, end: terminal.buffer.cursorY });
this._onRequestRefreshRowsEvent.fire({ start: terminal.buffer.active.cursorY, end: terminal.buffer.active.cursorY });
}
}
@@ -100,7 +100,7 @@ export class CursorRenderLayer extends BaseRenderLayer {
}
// Request a refresh from the terminal as management of rendering is being
// moved back to the terminal
this._onRequestRefreshRowsEvent.fire({ start: terminal.buffer.cursorY, end: terminal.buffer.cursorY });
this._onRequestRefreshRowsEvent.fire({ start: terminal.buffer.active.cursorY, end: terminal.buffer.active.cursorY });
}
public onCursorMove(terminal: Terminal): void {
@@ -125,8 +125,11 @@ export class CursorRenderLayer extends BaseRenderLayer {
return;
}
const cursorY = terminal.buffer.baseY + terminal.buffer.cursorY;
const viewportRelativeCursorY = cursorY - terminal.buffer.viewportY;
const cursorY = terminal.buffer.active.baseY + terminal.buffer.active.cursorY;
const viewportRelativeCursorY = cursorY - terminal.buffer.active.viewportY;
// in case cursor.x == cols adjust visual cursor to cols - 1
const cursorX = Math.min(terminal.buffer.active.cursorX, terminal.cols - 1);
// Don't draw the cursor if it's off-screen
if (viewportRelativeCursorY < 0 || viewportRelativeCursorY >= terminal.rows) {
@@ -135,7 +138,7 @@ export class CursorRenderLayer extends BaseRenderLayer {
}
// TODO: Need fast buffer API for loading cell
(terminal as any)._core.buffer.lines.get(cursorY).loadCell(terminal.buffer.cursorX, this._cell);
(terminal as any)._core.buffer.lines.get(cursorY).loadCell(cursorX, this._cell);
if (this._cell.content === undefined) {
return;
}
@@ -146,12 +149,12 @@ export class CursorRenderLayer extends BaseRenderLayer {
this._ctx.fillStyle = this._colors.cursor.css;
const cursorStyle = terminal.getOption('cursorStyle');
if (cursorStyle && cursorStyle !== 'block') {
this._cursorRenderers[cursorStyle](terminal, terminal.buffer.cursorX, viewportRelativeCursorY, this._cell);
this._cursorRenderers[cursorStyle](terminal, cursorX, viewportRelativeCursorY, this._cell);
} else {
this._renderBlurCursor(terminal, terminal.buffer.cursorX, viewportRelativeCursorY, this._cell);
this._renderBlurCursor(terminal, cursorX, viewportRelativeCursorY, this._cell);
}
this._ctx.restore();
this._state.x = terminal.buffer.cursorX;
this._state.x = cursorX;
this._state.y = viewportRelativeCursorY;
this._state.isFocused = false;
this._state.style = cursorStyle;
@@ -167,7 +170,7 @@ export class CursorRenderLayer extends BaseRenderLayer {
if (this._state) {
// The cursor is already in the correct spot, don't redraw
if (this._state.x === terminal.buffer.cursorX &&
if (this._state.x === cursorX &&
this._state.y === viewportRelativeCursorY &&
this._state.isFocused === isTerminalFocused(terminal) &&
this._state.style === terminal.getOption('cursorStyle') &&
@@ -178,10 +181,10 @@ export class CursorRenderLayer extends BaseRenderLayer {
}
this._ctx.save();
this._cursorRenderers[terminal.getOption('cursorStyle') || 'block'](terminal, terminal.buffer.cursorX, viewportRelativeCursorY, this._cell);
this._cursorRenderers[terminal.getOption('cursorStyle') || 'block'](terminal, cursorX, viewportRelativeCursorY, this._cell);
this._ctx.restore();
this._state.x = terminal.buffer.cursorX;
this._state.x = cursorX;
this._state.y = viewportRelativeCursorY;
this._state.isFocused = false;
this._state.style = terminal.getOption('cursorStyle');
@@ -299,7 +302,7 @@ class CursorBlinkStateManager {
// the regular interval is setup in order to support restarting the blink
// animation in a lightweight way (without thrashing clearInterval and
// setInterval).
this._blinkStartTimeout = <number><any>setTimeout(() => {
this._blinkStartTimeout = window.setTimeout(() => {
// Check if another animation restart was requested while this was being
// started
if (this._animationTimeRestarted) {
@@ -319,7 +322,7 @@ class CursorBlinkStateManager {
});
// Setup the blink interval
this._blinkInterval = <number><any>setInterval(() => {
this._blinkInterval = window.setInterval(() => {
// Adjust the animation time if it was restarted
if (this._animationTimeRestarted) {
// calc time diff
@@ -357,6 +360,9 @@ class CursorBlinkStateManager {
}
public resume(terminal: Terminal): void {
// Clear out any existing timers just in case
this.pause();
this._animationTimeRestarted = undefined;
this._restartInterval();
this.restartBlinkAnimation(terminal);
@@ -18,6 +18,9 @@ export class LinkRenderLayer extends BaseRenderLayer {
super(container, 'link', zIndex, true, colors);
terminal.linkifier.onLinkHover(e => this._onLinkHover(e));
terminal.linkifier.onLinkLeave(e => this._onLinkLeave(e));
terminal.linkifier2.onLinkHover(e => this._onLinkHover(e));
terminal.linkifier2.onLinkLeave(e => this._onLinkLeave(e));
}
public resize(terminal: Terminal, dim: IRenderDimensions): void {
+17 -5
View File
@@ -12,17 +12,29 @@
"removeComments": true,
"baseUrl": ".",
"paths": {
"common/*": [ "../../../src/common/*" ],
"browser/*": [ "../../../src/browser/*" ]
"common/*": [
"../../../src/common/*"
],
"browser/*": [
"../../../src/browser/*"
]
},
"strict": true
"strict": true,
"types": [
"../../../node_modules/@types/mocha",
"../../../out-test/api/TestUtils"
]
},
"include": [
"./**/*",
"../../../typings/xterm.d.ts"
],
"references": [
{ "path": "../../../src/common" },
{ "path": "../../../src/browser" }
{
"path": "../../../src/common"
},
{
"path": "../../../src/browser"
}
]
}
+41 -22
View File
@@ -6,13 +6,11 @@
jobs:
- job: Linux
pool:
vmImage: 'ubuntu-16.04'
variables:
PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: 1
vmImage: 'ubuntu-18.04'
steps:
- task: NodeTool@0
inputs:
versionSpec: '8.x'
versionSpec: '10.x'
displayName: 'Install Node.js'
- task: YarnInstaller@3
inputs:
@@ -41,13 +39,11 @@ jobs:
- job: macOS
pool:
vmImage: 'xcode9-macos10.13'
variables:
PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: 1
vmImage: 'macOS-10.15'
steps:
- task: NodeTool@0
inputs:
versionSpec: '8.x'
versionSpec: '10.x'
displayName: 'Install Node.js'
- task: CacheBeta@1
inputs:
@@ -64,12 +60,10 @@ jobs:
- job: Windows
pool:
vmImage: 'vs2017-win2016'
variables:
PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: 1
steps:
- task: NodeTool@0
inputs:
versionSpec: '8.x'
versionSpec: '10.x'
displayName: 'Install Node.js'
- task: CacheBeta@1
inputs:
@@ -85,39 +79,66 @@ jobs:
- job: Linux_IntegrationTests
pool:
vmImage: 'ubuntu-16.04'
vmImage: 'ubuntu-18.04'
steps:
- script: |
# source: https://github.com/microsoft/playwright/issues/1041
sudo apt update
sudo apt install libwoff1 libopus0 libwebp6 libwebpdemux2 libenchant1c2a libgudev-1.0-0 libsecret-1-0 libhyphen0 libgdk-pixbuf2.0-0 libegl1 libnotify4 libxslt1.1 libevent-2.1-6 libgles2 libgl1 libegl1 libvpx5
# for chromium
sudo apt install libnss3 libxss1 libasound2
# for firefox
sudo apt install libdbus-glib-1-2 libxt6
displayName: Install required packages
- task: NodeTool@0
inputs:
versionSpec: '8.x'
versionSpec: '10.x'
displayName: 'Install Node.js'
- task: YarnInstaller@3
inputs:
versionSpec: '1.x'
displayName: 'Install Yarn'
- task: CacheBeta@1
inputs:
key: yarn2 | $(Agent.OS) | yarn.lock
path: node_modules
displayName: Cache node modules
- script: yarn --frozen-lockfile
displayName: 'Install dependencies and build'
- script: |
yarn start &
sleep 10
yarn test-api --headless --forbid-only
displayName: 'Linux Integration tests'
displayName: 'Start test server'
- script: yarn test-api-chromium --headless --forbid-only
displayName: 'Integration tests (Chromium)'
- script: xvfb-run --auto-servernum -- bash -c "yarn test-api-firefox --headless --forbid-only"
displayName: 'Integration tests (Firefox)'
- job: macOS_IntegrationTests
pool:
vmImage: 'xcode9-macos10.13'
vmImage: 'macOS-10.15'
steps:
- task: NodeTool@0
inputs:
versionSpec: '8.x'
versionSpec: '10.x'
displayName: 'Install Node.js'
- task: CacheBeta@1
inputs:
key: yarn2 | $(Agent.OS) | yarn.lock
path: node_modules
displayName: Cache node modules
- script: yarn --frozen-lockfile
displayName: 'Install dependencies and build'
- script: |
yarn start &
sleep 10
yarn test-api --headless --forbid-only
displayName: 'MacOS Integration tests'
displayName: 'Start test server'
- script: yarn test-api-chromium --headless --forbid-only
displayName: 'Integration tests (Chromium)'
- script: yarn test-api-firefox --headless --forbid-only
displayName: 'Integration tests (Firefox)'
- script: yarn test-api-webkit --headless --forbid-only
displayName: 'Integration tests (Webkit)'
- job: Release
dependsOn:
@@ -129,12 +150,10 @@ jobs:
condition: and(succeeded(), or(eq(variables['Build.SourceBranch'], 'refs/heads/master'), eq(variables['FORCE_RELEASE'], 'true')))
pool:
vmImage: 'ubuntu-16.04'
variables:
PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: 1
steps:
- task: NodeTool@0
inputs:
versionSpec: '8.x'
versionSpec: '10.x'
displayName: 'Install Node.js'
- task: YarnInstaller@3
inputs:
+23
View File
@@ -0,0 +1,23 @@
/**
* Copyright (c) 2020 The xterm.js authors. All rights reserved.
* @license MIT
*/
const playwright = require('playwright-core');
const fs = require('fs');
// Default to chromium
let browserType = playwright['chromium'];
const index = process.argv.indexOf('--browser');
if (index !== -1 && process.argv.length > index + 1 && typeof process.argv[index + 1] === 'string') {
const string = process.argv[index + 1];
if (string === 'firefox' || string === 'webkit') {
browserType = playwright[string];
}
}
const exists = fs.existsSync(browserType.executablePath());
if (!exists) {
console.log(`Downloading ${browserType.name()}`);
browserType.downloadBrowserIfNeeded().then(() => process.exit(0));
}
+7 -1
View File
@@ -32,9 +32,15 @@ const TYPES = [
'SOS'
];
const MARKDOWN_TMPL = `
const MARKDOWN_TMPL = `---
title: Supported Terminal Sequences
category: API
---
{::options parse_block_html="true" /}
# Supported Terminal Sequences
xterm.js version: {{version}}
## Table of Contents
+2 -1
View File
@@ -152,7 +152,8 @@ function createTerminal(): void {
addons.serialize.instance = new SerializeAddon();
addons.fit.instance = new FitAddon();
addons.unicode11.instance = new Unicode11Addon();
addons['web-links'].instance = new WebLinksAddon();
// TODO: Remove arguments when link provider API is the default
addons['web-links'].instance = new WebLinksAddon(undefined, undefined, true);
typedTerm.loadAddon(addons.fit.instance);
typedTerm.loadAddon(addons.search.instance);
typedTerm.loadAddon(addons.serialize.instance);
+6 -2
View File
@@ -2,6 +2,11 @@
<html>
<head>
<title>xterm.js demo</title>
<!--
WARNING: This demo is a barebones implementation designed for development and evaluation
purposes only. It is definitely NOT production ready and does not aim to be so. Exposing the
demo to the public as is would introduce security risks for the host.
-->
<link rel="shortcut icon" type="image/png" href="/logo.png">
<link rel="stylesheet" href="/xterm.css" />
<link rel="stylesheet" href="/style.css" />
@@ -13,7 +18,7 @@
<div id="terminal-container"></div>
<div>
<h3>Options</h3>
<p>These options can be set in the <code>Terminal</code> constructor or using the <code>Terminal.setOption</code> function.</p>
<p>These options can be set in the <code>Terminal</code> constructor or by using the <code>Terminal.setOption</code> function.</p>
<div id="options-container"></div>
</div>
<div>
@@ -44,7 +49,6 @@
</div>
</div>
<hr/>
<p><strong>Attention:</strong> The demo is a barebones implementation and is designed for the development and evaluation of xterm.js only. Exposing the demo to the public as is would introduce security risks for the host.</p>
<button id="dispose" title="This is used to testing memory leaks">Dispose terminal</button>
<script src="dist/client-bundle.js" defer ></script>
</body>
+6
View File
@@ -1,3 +1,9 @@
/**
* WARNING: This demo is a barebones implementation designed for development and evaluation
* purposes only. It is definitely NOT production ready and does not aim to be so. Exposing the
* demo to the public as is would introduce security risks for the host.
**/
var express = require('express');
var expressWs = require('express-ws');
var os = require('os');
+29 -22
View File
@@ -1,7 +1,7 @@
{
"name": "xterm",
"description": "Full xterm terminal, in your browser",
"version": "4.4.0",
"version": "4.5.0",
"main": "lib/xterm.js",
"style": "css/xterm.css",
"types": "typings/xterm.d.ts",
@@ -11,10 +11,16 @@
"prepackage": "npm run build",
"package": "webpack",
"start": "node demo/start",
"lint": "tslint 'src/**/*.ts' 'addons/*/src/**/*.ts'",
"lint": "eslint -c .eslintrc.json --ext .ts src/ addons/",
"test": "npm run test-unit",
"posttest": "npm run lint",
"test-api": "mocha \"**/*.api.js\"",
"test-api": "npm run test-api-chromium",
"test-api-chromium": "mocha \"**/*.api.js\" --browser chromium --timeout 20000",
"test-api-firefox": "mocha \"**/*.api.js\" --browser firefox --timeout 20000",
"test-api-webkit": "mocha \"**/*.api.js\" --browser webkit --timeout 20000",
"pretest-api-chromium": "node ./bin/download_browser.js --browser chromium",
"pretest-api-firefox": "node ./bin/download_browser.js --browser firefox",
"pretest-api-webkit": "node ./bin/download_browser.js --browser webkit",
"test-unit": "node ./bin/test.js",
"test-unit-coverage": "node ./bin/test.js --coverage",
"build": "tsc -b ./tsconfig.all.json",
@@ -30,35 +36,36 @@
"vtfeatures": "node bin/extract_vtfeatures.js src/**/*.ts src/*.ts"
},
"devDependencies": {
"@types/chai": "^3.4.34",
"@types/chai": "^4.2.11",
"@types/debug": "^4.1.5",
"@types/deep-equal": "^1.0.1",
"@types/glob": "^5.0.35",
"@types/jsdom": "11.0.1",
"@types/mocha": "^2.2.33",
"@types/node": "6.0.108",
"@types/puppeteer": "^1.12.4",
"@types/glob": "^7.1.1",
"@types/jsdom": "^16.2.0",
"@types/mocha": "^7.0.2",
"@types/node": "^10.17.17",
"@types/utf8": "^2.1.6",
"@types/webpack": "^4.4.11",
"@types/ws": "^6.0.1",
"chai": "3.5.0",
"deep-equal": "^1.1.0",
"@types/webpack": "^4.41.10",
"@types/ws": "^7.2.3",
"@typescript-eslint/eslint-plugin": "^2.27.0",
"@typescript-eslint/parser": "^2.27.0",
"chai": "^4.2.0",
"deep-equal": "^2.0.2",
"eslint": "^6.8.0",
"express": "^4.17.1",
"express-ws": "^4.0.0",
"glob": "^7.0.5",
"jsdom": "^11.11.0",
"mocha": "^6.1.4",
"mustache": "^3.0.1",
"jsdom": "^16.2.2",
"mocha": "^7.1.1",
"mustache": "^4.0.1",
"node-pty": "^0.9.0",
"nyc": "13",
"puppeteer": "^1.15.0",
"nyc": "^15.0.1",
"playwright-core": "^0.11.1",
"source-map-loader": "^0.2.4",
"ts-loader": "^6.0.4",
"tslint": "^5.18.0",
"tslint-consistent-codestyle": "^1.13.0",
"typescript": "3.7",
"typescript": "3.8",
"utf8": "^3.0.0",
"webpack": "^4.35.3",
"webpack-cli": "^3.1.0",
"webpack-cli": "^3.3.11",
"ws": "^7.0.0",
"xterm-benchmark": "^0.1.3"
}
+1
View File
@@ -56,6 +56,7 @@ export class AccessibilityManager extends Disposable {
this._rowContainer = document.createElement('div');
this._rowContainer.classList.add('xterm-accessibility-tree');
this._rowContainer.setAttribute('role', 'list');
this._rowElements = [];
for (let i = 0; i < this._terminal.rows; i++) {
this._rowElements[i] = this._createAccessibilityTreeNode();
+49
View File
@@ -1445,4 +1445,53 @@ describe('InputHandler', () => {
assert.deepEqual(getLines(term), ['¥¥ ¥¥', '¥¥ ¥', '¥¥ ¥', '¥¥¥¥¥', '']);
});
});
describe('DECSTR', () => {
let term: TestTerminal;
beforeEach(() => {
term = new TestTerminal({cols: 10, rows: 5, scrollback: 1});
term.writeSync('01234567890123');
});
it('should reset IRM', () => {
term.writeSync('\x1b[4h');
assert.equal(term.insertMode, true);
term.writeSync('\x1b[!p');
assert.equal(term.insertMode, false);
});
it('should reset cursor visibility', () => {
term.writeSync('\x1b[?25l');
assert.equal((term as any)._coreService.isCursorHidden, true);
term.writeSync('\x1b[!p');
assert.equal((term as any)._coreService.isCursorHidden, false);
});
it('should reset scroll margins', () => {
term.writeSync('\x1b[2;4r');
assert.equal((term as any)._bufferService.buffer.scrollTop, 1);
assert.equal((term as any)._bufferService.buffer.scrollBottom, 3);
term.writeSync('\x1b[!p');
assert.equal((term as any)._bufferService.buffer.scrollTop, 0);
assert.equal((term as any)._bufferService.buffer.scrollBottom, term.rows - 1);
});
it('should reset text attributes', () => {
term.writeSync('\x1b[1;2;32;43m');
assert.equal(!!term.curAttrData.isBold(), true);
term.writeSync('\x1b[!p');
assert.equal(!!term.curAttrData.isBold(), false);
assert.equal(term.curAttrData.fg, 0);
assert.equal(term.curAttrData.bg, 0);
});
it('should reset DECSC data', () => {
term.writeSync('\x1b7');
assert.equal((term as any)._bufferService.buffer.savedX, 4);
assert.equal((term as any)._bufferService.buffer.savedY, 1);
term.writeSync('\x1b[!p');
assert.equal((term as any)._bufferService.buffer.savedX, 0);
assert.equal((term as any)._bufferService.buffer.savedY, 0);
});
it('should reset DECOM', () => {
term.writeSync('\x1b[?6h');
assert.equal((term as any)._coreService.decPrivateModes.origin, true);
term.writeSync('\x1b[!p');
assert.equal((term as any)._coreService.decPrivateModes.origin, false);
});
});
});
+14 -4
View File
@@ -1998,7 +1998,7 @@ export class InputHandler extends Disposable implements IInputHandler {
this._coreMouseService.activeEncoding = 'DEFAULT';
break;
case 1015: // urxvt ext mode mouse - removed in #2507
this._logService.debug('DECRST 1015 not supported (see #2507)');
this._logService.debug('DECRST 1015 not supported (see #2507)');
break;
case 25: // hide cursor
this._coreService.isCursorHidden = true;
@@ -2007,7 +2007,7 @@ export class InputHandler extends Disposable implements IInputHandler {
this.restoreCursor();
break;
case 1049: // alt screen buffer cursor
// FALL-THROUGH
// FALL-THROUGH
case 47: // normal screen buffer
case 1047: // normal screen buffer - clearing it first
// Ensure the selection manager has the correct buffer
@@ -2344,11 +2344,12 @@ export class InputHandler extends Disposable implements IInputHandler {
* DECSTR only resets certain attributes. For most needs DECSTR should be sufficient.
*
* The following terminal attributes are reset to default values:
* - cursor is reset (default = visible, home position)
* - IRM is reset (dafault = false)
* - scroll margins are reset (default = viewport size)
* - erase attributes are reset to default
* - charsets are reset
* - DECSC data is reset to initial values
* - DECOM is reset to absolute mode
*
*
* FIXME: there are several more attributes missing (see VT520 manual)
@@ -2360,9 +2361,18 @@ export class InputHandler extends Disposable implements IInputHandler {
this._bufferService.buffer.scrollTop = 0;
this._bufferService.buffer.scrollBottom = this._bufferService.rows - 1;
this._curAttrData = DEFAULT_ATTR_DATA.clone();
this._bufferService.buffer.x = this._bufferService.buffer.y = 0; // ?
this._coreService.reset();
this._charsetService.reset();
// reset DECSC data
this._bufferService.buffer.savedX = 0;
this._bufferService.buffer.savedY = this._bufferService.buffer.ybase;
this._bufferService.buffer.savedCurAttrData.fg = this._curAttrData.fg;
this._bufferService.buffer.savedCurAttrData.bg = this._curAttrData.bg;
this._bufferService.buffer.savedCharset = this._charsetService.charset;
// reset DECOM
this._coreService.decPrivateModes.origin = false;
}
/**
+26 -18
View File
@@ -37,7 +37,7 @@ 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 } from 'xterm';
import { ITheme, IMarker, IDisposable, ISelectionPosition, ILinkProvider } from 'xterm';
import { DomRenderer } from 'browser/renderer/dom/DomRenderer';
import { IKeyboardEvent, KeyboardResultType, IBufferLine, IAttributeData, CoreMouseEventType, CoreMouseButton, CoreMouseAction } from 'common/Types';
import { evaluateKeyboardEvent } from 'common/input/Keyboard';
@@ -57,11 +57,12 @@ import { MouseService } from 'browser/services/MouseService';
import { IParams, IFunctionIdentifier } from 'common/parser/Types';
import { CoreService } from 'common/services/CoreService';
import { LogService } from 'common/services/LogService';
import { ILinkifier, IMouseZoneManager, LinkMatcherHandler, ILinkMatcherOptions, IViewport } from 'browser/Types';
import { ILinkifier, IMouseZoneManager, LinkMatcherHandler, ILinkMatcherOptions, IViewport, ILinkifier2 } from 'browser/Types';
import { DirtyRowService } from 'common/services/DirtyRowService';
import { InstantiationService } from 'common/services/InstantiationService';
import { CoreMouseService } from 'common/services/CoreMouseService';
import { WriteBuffer } from 'common/input/WriteBuffer';
import { Linkifier2 } from 'browser/Linkifier2';
import { CoreBrowserService } from 'browser/services/CoreBrowserService';
import { UnicodeService } from 'common/services/UnicodeService';
import { CharsetService } from 'common/services/CharsetService';
@@ -131,6 +132,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
private _inputHandler: InputHandler;
public linkifier: ILinkifier;
public linkifier2: ILinkifier2;
public viewport: IViewport;
private _compositionHelper: ICompositionHelper;
private _mouseZoneManager: IMouseZoneManager;
@@ -228,7 +230,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
this._windowsMode = undefined;
this._renderService?.dispose();
this._customKeyEventHandler = null;
this.write = () => {};
this.write = () => { };
this.element?.parentNode?.removeChild(this.element);
}
@@ -257,6 +259,9 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
if (!this.linkifier) {
this.linkifier = new Linkifier(this._bufferService, this._logService, this.optionsService, this.unicodeService);
}
if (!this.linkifier2) {
this.linkifier2 = new Linkifier2(this._bufferService);
}
if (this.options.windowsMode) {
this._enableWindowsMode();
@@ -414,7 +419,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
}
copyHandler(event, this._selectionService);
}));
const pasteHandlerWrapper = (event: ClipboardEvent) => handlePasteEvent(event, this.textarea, this.bracketedPasteMode, this._coreService);
const pasteHandlerWrapper = (event: ClipboardEvent): void => handlePasteEvent(event, this.textarea, this.bracketedPasteMode, this._coreService);
this.register(addDisposableDomListener(this.textarea, 'paste', pasteHandlerWrapper));
this.register(addDisposableDomListener(this.element, 'paste', pasteHandlerWrapper));
@@ -471,7 +476,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
}
if (!document.body.contains(parent)) {
this._logService.warn('Terminal.open was called on an element that was not attached to the DOM');
this._logService.debug('Terminal.open was called on an element that was not attached to the DOM');
}
this._document = parent.ownerDocument;
@@ -585,6 +590,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
this.register(this._mouseZoneManager);
this.register(this.onScroll(() => this._mouseZoneManager.clearAll()));
this.linkifier.attachToDom(this.element, this._mouseZoneManager);
this.linkifier2.attachToDom(this.element, this._mouseService, this._renderService);
// This event listener must be registered aftre MouseZoneManager is created
this.register(addDisposableDomListener(this.element, 'mousedown', (e: MouseEvent) => this._selectionService.onMouseDown(e)));
@@ -619,8 +625,8 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
private _createRenderer(): IRenderer {
switch (this.options.rendererType) {
case 'canvas': return this._instantiationService.createInstance(Renderer, this._colorManager.colors, this.screenElement, this.linkifier);
case 'dom': return this._instantiationService.createInstance(DomRenderer, this._colorManager.colors, this.element, this.screenElement, this._viewportElement, this.linkifier);
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);
default: throw new Error(`Unrecognized rendererType "${this.options.rendererType}"`);
}
}
@@ -657,10 +663,8 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
// send event to CoreMouseService
function sendEvent(ev: MouseEvent | WheelEvent): boolean {
let pos;
// get mouse coordinates
pos = self._mouseService.getRawByteCoords(ev, self.screenElement, self.cols, self.rows);
const pos = self._mouseService.getRawByteCoords(ev, self.screenElement, self.cols, self.rows);
if (!pos) {
return false;
}
@@ -679,8 +683,8 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
} else {
// according to MDN buttons only reports up to button 5 (AUX2)
but = ev.buttons & 1 ? CoreMouseButton.LEFT :
ev.buttons & 4 ? CoreMouseButton.MIDDLE :
ev.buttons & 2 ? CoreMouseButton.RIGHT :
ev.buttons & 4 ? CoreMouseButton.MIDDLE :
ev.buttons & 2 ? CoreMouseButton.RIGHT :
CoreMouseButton.NONE; // fallback to NONE
}
break;
@@ -729,13 +733,13 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
* Note: 'mousedown' currently is "always on" and not managed
* by onProtocolChange.
*/
const requestedEvents: {[key: string]: ((ev: Event) => void) | null} = {
const requestedEvents: { [key: string]: ((ev: Event) => void) | null } = {
mouseup: null,
wheel: null,
mousedrag: null,
mousemove: null
};
const eventListeners: {[key: string]: (ev: Event) => void} = {
const eventListeners: { [key: string]: (ev: Event) => void } = {
mouseup: (ev: MouseEvent) => {
sendEvent(ev);
if (!ev.buttons) {
@@ -858,7 +862,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
}
// Construct and send sequences
const sequence = C0.ESC + (this._coreService.decPrivateModes.applicationCursorKeys ? 'O' : '[') + ( ev.deltaY < 0 ? 'A' : 'B');
const sequence = C0.ESC + (this._coreService.decPrivateModes.applicationCursorKeys ? 'O' : '[') + (ev.deltaY < 0 ? 'A' : 'B');
let data = '';
for (let i = 0; i < Math.abs(amount); i++) {
data += sequence;
@@ -981,7 +985,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
} else {
// scrollTop is non-zero which means no line will be going to the
// scrollback, instead we can just shift them in-place.
const scrollRegionHeight = bottomRow - topRow + 1/*as it's zero-based*/;
const scrollRegionHeight = bottomRow - topRow + 1 /* as it's zero-based */;
this.buffer.lines.shiftElements(topRow + 1, scrollRegionHeight - 1, -1);
this.buffer.lines.set(bottomRow, newLine.clone());
}
@@ -1121,6 +1125,10 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
}
}
public registerLinkProvider(linkProvider: ILinkProvider): IDisposable {
return this.linkifier2.registerLinkProvider(linkProvider);
}
public registerCharacterJoiner(handler: CharacterJoinerHandler): number {
const joinerId = this._renderService.registerCharacterJoiner(handler);
this.refresh(0, this.rows - 1);
@@ -1273,8 +1281,8 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
private _isThirdLevelShift(browser: IBrowser, ev: IKeyboardEvent): boolean {
const thirdLevelKey =
(browser.isMac && !this.options.macOptionIsMeta && ev.altKey && !ev.ctrlKey && !ev.metaKey) ||
(browser.isWindows && ev.altKey && ev.ctrlKey && !ev.metaKey);
(browser.isMac && !this.options.macOptionIsMeta && ev.altKey && !ev.ctrlKey && !ev.metaKey) ||
(browser.isWindows && ev.altKey && ev.ctrlKey && !ev.metaKey);
if (ev.type === 'keypress') {
return thirdLevelKey;
+22 -18
View File
@@ -9,10 +9,10 @@ import { IBuffer, IBufferStringIterator, IBufferSet } from 'common/buffer/Types'
import { IBufferLine, ICellData, IAttributeData, ICircularList, XtermListener, ICharset } from 'common/Types';
import { Buffer } from 'common/buffer/Buffer';
import * as Browser from 'common/Platform';
import { IDisposable, IMarker, IEvent, ISelectionPosition } from 'xterm';
import { IDisposable, IMarker, IEvent, ISelectionPosition, ILinkProvider } from 'xterm';
import { Terminal } from './Terminal';
import { AttributeData } from 'common/buffer/AttributeData';
import { IColorManager, IColorSet, ILinkMatcherOptions, ILinkifier, IViewport } from 'browser/Types';
import { IColorManager, IColorSet, ILinkMatcherOptions, ILinkifier, IViewport, ILinkifier2 } from 'browser/Types';
import { IOptionsService, IUnicodeService } from 'common/services/Services';
import { EventEmitter } from 'common/EventEmitter';
import { IParams, IFunctionIdentifier } from 'common/parser/Types';
@@ -36,9 +36,9 @@ export class MockTerminal implements ITerminal {
onBinary: IEvent<string>;
onTitleChange: IEvent<string>;
onScroll: IEvent<number>;
onKey: IEvent<{ key: string; domEvent: KeyboardEvent; }>;
onRender: IEvent<{ start: number; end: number; }>;
onResize: IEvent<{ cols: number; rows: number; }>;
onKey: IEvent<{ key: string, domEvent: KeyboardEvent }>;
onRender: IEvent<{ start: number, end: number }>;
onResize: IEvent<{ cols: number, rows: number }>;
markers: IMarker[];
optionsService: IOptionsService;
unicodeService: IUnicodeService;
@@ -94,6 +94,9 @@ export class MockTerminal implements ITerminal {
deregisterLinkMatcher(matcherId: number): void {
throw new Error('Method not implemented.');
}
registerLinkProvider(linkProvider: ILinkProvider): IDisposable {
throw new Error('Method not implemented.');
}
hasSelection(): boolean {
throw new Error('Method not implemented.');
}
@@ -136,6 +139,7 @@ export class MockTerminal implements ITerminal {
bracketedPasteMode: boolean;
renderer: IRenderer;
linkifier: ILinkifier;
linkifier2: ILinkifier2;
isFocused: boolean;
options: ITerminalOptions = {};
element: HTMLElement;
@@ -247,7 +251,7 @@ export class MockBuffer implements IBuffer {
translateBufferLineToString(lineIndex: number, trimRight: boolean, startCol?: number, endCol?: number): string {
return Buffer.prototype.translateBufferLineToString.apply(this, arguments);
}
getWrappedRangeForLine(y: number): { first: number; last: number; } {
getWrappedRangeForLine(y: number): { first: number, last: number } {
return Buffer.prototype.getWrappedRangeForLine.apply(this, arguments);
}
nextStop(x?: number): number {
@@ -278,8 +282,8 @@ export class MockBuffer implements IBuffer {
export class MockRenderer implements IRenderer {
onRequestRefreshRows: IEvent<IRequestRefreshRowsEvent>;
onCanvasResize: IEvent<{ width: number; height: number; }>;
onRender: IEvent<{ start: number; end: number; }>;
onCanvasResize: IEvent<{ width: number, height: number }>;
onRender: IEvent<{ start: number, end: number }>;
dispose(): void {
throw new Error('Method not implemented.');
}
@@ -300,16 +304,16 @@ export class MockRenderer implements IRenderer {
setColors(colors: IColorSet): void {
throw new Error('Method not implemented.');
}
onResize(cols: number, rows: number): void {}
onCharSizeChanged(): void {}
onBlur(): void {}
onFocus(): void {}
onSelectionChanged(start: [number, number], end: [number, number]): void {}
onCursorMove(): void {}
onOptionsChanged(): void {}
onDevicePixelRatioChange(): void {}
clear(): void {}
renderRows(start: number, end: number): void {}
onResize(cols: number, rows: number): void { }
onCharSizeChanged(): void { }
onBlur(): void { }
onFocus(): void { }
onSelectionChanged(start: [number, number], end: [number, number]): void { }
onCursorMove(): void { }
onOptionsChanged(): void { }
onDevicePixelRatioChange(): void { }
clear(): void { }
renderRows(start: number, end: number): void { }
registerCharacterJoiner(handler: CharacterJoinerHandler): number { return 0; }
deregisterCharacterJoiner(): boolean { return true; }
}
+4 -2
View File
@@ -3,10 +3,10 @@
* @license MIT
*/
import { ITerminalOptions as IPublicTerminalOptions, IDisposable, IMarker, ISelectionPosition } from 'xterm';
import { ITerminalOptions as IPublicTerminalOptions, IDisposable, IMarker, ISelectionPosition, ILinkProvider } from 'xterm';
import { ICharset, IAttributeData, CharData, CoreMouseEventType } from 'common/Types';
import { IEvent, IEventEmitter } from 'common/EventEmitter';
import { IColorSet, ILinkifier, ILinkMatcherOptions, IViewport } from 'browser/Types';
import { IColorSet, ILinkifier, ILinkMatcherOptions, IViewport, ILinkifier2 } from 'browser/Types';
import { IOptionsService, IUnicodeService } from 'common/services/Services';
import { IBuffer, IBufferSet } from 'common/buffer/Types';
import { IParams, IFunctionIdentifier } from 'common/parser/Types';
@@ -179,6 +179,7 @@ export interface IPublicTerminal extends IDisposable {
addOscHandler(ident: number, callback: (data: string) => boolean): 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;
addMarker(cursorYOffset: number): IMarker;
@@ -212,6 +213,7 @@ export interface IElementAccessor {
export interface ILinkifierAccessor {
linkifier: ILinkifier;
linkifier2: ILinkifier2;
}
// TODO: The options that are not in the public API should be reviewed
+1 -1
View File
@@ -11,7 +11,7 @@ describe('ColorManager', () => {
let cm: ColorManager;
let dom: jsdom.JSDOM;
let document: Document;
let window: Window;
let window: jsdom.DOMWindow;
beforeEach(() => {
dom = new jsdom.JSDOM('');
+1 -1
View File
@@ -192,7 +192,7 @@ export class ColorManager implements IColorManager {
const rgba: number = channels.toRgba(r, g, b, alpha);
return {
rgba,
css: channels.toCss(r, g, b, alpha)
css
};
}
+40
View File
@@ -0,0 +1,40 @@
/**
* Copyright (c) 2020 The xterm.js authors. All rights reserved.
* @license MIT
*/
import { assert } from 'chai';
import { addDisposableDomListener } from './Lifecycle';
import jsdom = require('jsdom');
describe('addDisposableDomListener', () => {
const dom = new jsdom.JSDOM();
const document = dom.window.document;
function createEvent(type: string): Event {
const event = document.createEvent('Event');
event.initEvent(type);
return event;
}
it('dispose', () => {
let calledTimes = 0;
const div = document.createElement('div');
const disposable = addDisposableDomListener(div, 'test', () => { calledTimes++; });
assert.equal(calledTimes, 0);
div.dispatchEvent(createEvent('test'));
assert.equal(calledTimes, 1);
disposable.dispose();
div.dispatchEvent(createEvent('test'));
assert.equal(calledTimes, 1);
disposable.dispose(); // double disposing
div.dispatchEvent(createEvent('test'));
assert.equal(calledTimes, 1);
});
});
+1 -1
View File
@@ -20,7 +20,7 @@ export function addDisposableDomListener(
let disposed = false;
return {
dispose: () => {
if (!disposed) {
if (disposed) {
return;
}
disposed = true;
+76
View File
@@ -0,0 +1,76 @@
/**
* Copyright (c) 2020 The xterm.js authors. All rights reserved.
* @license MIT
*/
import { assert } from 'chai';
import { IBufferService } from 'common/services/Services';
import { Linkifier2 } from 'browser/Linkifier2';
import { MockBufferService } from 'common/TestUtils.test';
import { ILink } from 'browser/Types';
class TestLinkifier2 extends Linkifier2 {
constructor(bufferService: IBufferService) {
super(bufferService);
}
public linkHover(element: HTMLElement, link: ILink, event: MouseEvent): void {
this._linkHover(element, link, event);
}
public linkLeave(element: HTMLElement, link: ILink, event: MouseEvent): void {
this._linkLeave(element, link, event);
}
}
describe('Linkifier2', () => {
let bufferService: IBufferService;
let linkifier: TestLinkifier2;
beforeEach(() => {
bufferService = new MockBufferService(100, 10);
linkifier = new TestLinkifier2(bufferService);
});
const link: ILink = {
text: 'foo',
range: {
start: {
x: 5,
y: 1
},
end: {
x: 7,
y: 1
}
},
activate: () => { }
};
it('onLinkHover event range is correct', done => {
linkifier.onLinkHover(e => {
assert.equal(link.range.start.x - 1, e.x1);
assert.equal(link.range.start.y - 1, e.y1);
assert.equal(link.range.end.x, e.x2);
assert.equal(link.range.end.y - 1, e.y2);
done();
});
linkifier.linkHover({ classList: { add: () => { } } } as any, link, {} as any);
});
it('onLinkLeave event range is correct', done => {
linkifier.onLinkLeave(e => {
assert.equal(link.range.start.x - 1, e.x1);
assert.equal(link.range.start.y - 1, e.y1);
assert.equal(link.range.end.x, e.x2);
assert.equal(link.range.end.y - 1, e.y2);
done();
});
linkifier.linkLeave({ classList: { add: () => { } } } as any, link, {} as any);
});
});
+243
View File
@@ -0,0 +1,243 @@
/**
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
* @license MIT
*/
import { ILinkifier2, ILinkProvider, IBufferCellPosition, ILink, ILinkifierEvent } from './Types';
import { IDisposable } from 'common/Types';
import { IMouseService, IRenderService } from './services/Services';
import { IBufferService } from 'common/services/Services';
import { EventEmitter, IEvent } from 'common/EventEmitter';
export class Linkifier2 implements ILinkifier2 {
private _element: HTMLElement | undefined;
private _mouseService: IMouseService | undefined;
private _renderService: IRenderService | undefined;
private _linkProviders: ILinkProvider[] = [];
private _currentLink: ILink | undefined;
private _lastMouseEvent: MouseEvent | undefined;
private _linkCacheDisposables: IDisposable[] = [];
private _lastBufferCell: IBufferCellPosition | undefined;
private _onLinkHover = new EventEmitter<ILinkifierEvent>();
public get onLinkHover(): IEvent<ILinkifierEvent> { return this._onLinkHover.event; }
private _onLinkLeave = new EventEmitter<ILinkifierEvent>();
public get onLinkLeave(): IEvent<ILinkifierEvent> { return this._onLinkLeave.event; }
constructor(
private readonly _bufferService: IBufferService
) {
}
public registerLinkProvider(linkProvider: ILinkProvider): IDisposable {
this._linkProviders.push(linkProvider);
return {
dispose: () => {
// Remove the link provider from the list
const providerIndex = this._linkProviders.indexOf(linkProvider);
if (providerIndex !== -1) {
this._linkProviders.splice(providerIndex, 1);
}
}
};
}
public attachToDom(element: HTMLElement, mouseService: IMouseService, renderService: IRenderService): void {
this._element = element;
this._mouseService = mouseService;
this._renderService = renderService;
this._element.addEventListener('mousemove', this._onMouseMove.bind(this));
this._element.addEventListener('click', this._onMouseDown.bind(this));
}
private _onMouseMove(event: MouseEvent): void {
this._lastMouseEvent = event;
if (!this._element || !this._mouseService) {
return;
}
const position = this._positionFromMouseEvent(event, this._element, this._mouseService);
if (!position) {
return;
}
if (!this._lastBufferCell || (position.x !== this._lastBufferCell.x || position.y !== this._lastBufferCell.y)) {
this._onHover(position);
this._lastBufferCell = position;
}
}
private _onHover(position: IBufferCellPosition): void {
if (this._currentLink) {
// Check the if the link is in the mouse position
const isInPosition = this._linkAtPosition(this._currentLink, position);
// Check if we need to clear the link
if (!isInPosition) {
this._clearCurrentLink();
this._askForLink(position);
}
} else {
this._askForLink(position);
}
}
private _askForLink(position: IBufferCellPosition): void {
const providerReplies: Map<Number, ILink | undefined> = new Map();
let linkProvided = false;
// There is no link cached, so ask for one
this._linkProviders.forEach((linkProvider, i) => {
linkProvider.provideLink(position, (link: ILink | undefined) => {
providerReplies.set(i, link);
// Check if every provider before this one has come back undefined
let hasLinkBefore = false;
for (let j = 0; j < i; j++) {
if (!providerReplies.has(j) || providerReplies.get(j)) {
hasLinkBefore = true;
}
}
// If all providers with higher priority came back undefined, then this link should be used
if (!hasLinkBefore && link) {
linkProvided = true;
this._handleNewLink(link);
}
// Check if all the providers have responded
if (providerReplies.size === this._linkProviders.length && !linkProvided) {
// Respect the order of the link providers
for (let j = 0; j < providerReplies.size; j++) {
const currentLink = providerReplies.get(j);
if (currentLink) {
this._handleNewLink(currentLink);
break;
}
}
}
});
});
}
private _onMouseDown(event: MouseEvent): void {
if (!this._element || !this._mouseService || !this._currentLink) {
return;
}
const position = this._positionFromMouseEvent(event, this._element, this._mouseService);
if (!position) {
return;
}
if (this._linkAtPosition(this._currentLink, position)) {
this._currentLink.activate(event, this._currentLink.text);
}
}
private _clearCurrentLink(startRow?: number, endRow?: number): void {
if (!this._element || !this._currentLink || !this._lastMouseEvent) {
return;
}
// If we have a start and end row, check that the link is within it
if (!startRow || !endRow || (this._currentLink.range.start.y >= startRow && this._currentLink.range.end.y <= endRow)) {
this._linkLeave(this._element, this._currentLink, this._lastMouseEvent);
this._currentLink = undefined;
this._linkCacheDisposables.forEach(l => l.dispose());
this._linkCacheDisposables = [];
}
}
private _handleNewLink(link: ILink): void {
if (!this._element || !this._lastMouseEvent || !this._mouseService) {
return;
}
const position = this._positionFromMouseEvent(this._lastMouseEvent, this._element, this._mouseService);
if (!position) {
return;
}
// Trigger hover if the we have a link at the position
if (this._linkAtPosition(link, position)) {
this._currentLink = link;
this._linkHover(this._element, link, this._lastMouseEvent);
// Add listener for rerendering
if (this._renderService) {
this._linkCacheDisposables.push(this._renderService.onRender(e => {
this._clearCurrentLink(e.start + 1 + this._bufferService.buffer.ydisp, e.end + 1 + this._bufferService.buffer.ydisp);
}));
}
}
}
protected _linkHover(element: HTMLElement, link: ILink, event: MouseEvent): void {
const range = link.range;
const scrollOffset = this._bufferService.buffer.ydisp;
this._onLinkHover.fire(this._createLinkHoverEvent(range.start.x - 1, range.start.y - scrollOffset - 1, range.end.x, range.end.y - scrollOffset - 1, undefined));
element.classList.add('xterm-cursor-pointer');
if (link.hover) {
link.hover(event, link.text);
}
}
protected _linkLeave(element: HTMLElement, link: ILink, event: MouseEvent): void {
const range = link.range;
const scrollOffset = this._bufferService.buffer.ydisp;
this._onLinkLeave.fire(this._createLinkHoverEvent(range.start.x - 1, range.start.y - scrollOffset - 1, range.end.x, range.end.y - scrollOffset - 1, undefined));
element.classList.remove('xterm-cursor-pointer');
if (link.leave) {
link.leave(event, link.text);
}
}
/**
* Check if the buffer position is within the link
* @param link
* @param position
*/
private _linkAtPosition(link: ILink, position: IBufferCellPosition): boolean {
const sameLine = link.range.start.y === link.range.end.y;
const wrappedFromLeft = link.range.start.y < position.y;
const wrappedToRight = link.range.end.y > position.y;
// If the start and end have the same y, then the position must be between start and end x
// If not, then handle each case seperately, depending on which way it wraps
return ((sameLine && link.range.start.x <= position.x && link.range.end.x >= position.x) ||
(wrappedFromLeft && link.range.end.x >= position.x) ||
(wrappedToRight && link.range.start.x <= position.x) ||
(wrappedFromLeft && wrappedToRight)) &&
link.range.start.y <= position.y &&
link.range.end.y >= position.y;
}
/**
* Get the buffer position from a mouse event
* @param event
*/
private _positionFromMouseEvent(event: MouseEvent, element: HTMLElement, mouseService: IMouseService): IBufferCellPosition | undefined {
const coords = mouseService.getCoords(event, element, this._bufferService.cols, this._bufferService.rows);
if (!coords) {
return;
}
return { x: coords[0], y: coords[1] + this._bufferService.buffer.ydisp };
}
private _createLinkHoverEvent(x1: number, y1: number, x2: number, y2: number, fg: number | undefined): ILinkifierEvent {
return { x1, y1, x2, y2, cols: this._bufferService.cols, fg };
}
}
+3
View File
@@ -3,5 +3,8 @@
* @license MIT
*/
// eslint-disable-next-line prefer-const
export let promptLabel = 'Terminal input';
// eslint-disable-next-line prefer-const
export let tooMuchOutput = 'Too much output to announce, navigate to rows manually to read';
+31
View File
@@ -5,6 +5,7 @@
import { IEvent } from 'common/EventEmitter';
import { IDisposable } from 'common/Types';
import { IMouseService, IRenderService } from './services/Services';
export interface IColorManager {
colors: IColorSet;
@@ -105,6 +106,14 @@ export interface ILinkifier {
deregisterLinkMatcher(matcherId: number): boolean;
}
export interface ILinkifier2 {
onLinkHover: IEvent<ILinkifierEvent>;
onLinkLeave: IEvent<ILinkifierEvent>;
attachToDom(element: HTMLElement, mouseService: IMouseService, renderService: IRenderService): void;
registerLinkProvider(linkProvider: ILinkProvider): IDisposable;
}
export interface ILinkMatcherOptions {
/**
* The index of the link from the regex.match(text) call. This defaults to 0
@@ -155,3 +164,25 @@ export interface IMouseZone {
leaveCallback: () => any | undefined;
willLinkActivate: (e: MouseEvent) => boolean;
}
interface ILinkProvider {
provideLink(position: IBufferCellPosition, callback: (link: ILink | undefined) => void): void;
}
interface ILink {
range: IBufferRange;
text: string;
activate(event: MouseEvent, text: string): void;
hover?(event: MouseEvent, text: string): void;
leave?(event: MouseEvent, text: string): void;
}
interface IBufferRange {
start: IBufferCellPosition;
end: IBufferCellPosition;
}
interface IBufferCellPosition {
x: number;
y: number;
}
+3 -3
View File
@@ -167,7 +167,7 @@ export class Viewport extends Disposable implements IViewport {
private _bubbleScroll(ev: Event, amount: number): boolean {
const scrollPosFromTop = this._viewportElement.scrollTop + this._lastRecordedViewportHeight;
if ((amount < 0 && this._viewportElement.scrollTop !== 0) ||
(amount > 0 && scrollPosFromTop < this._lastRecordedBufferHeight)) {
(amount > 0 && scrollPosFromTop < this._lastRecordedBufferHeight)) {
if (ev.cancelable) {
ev.preventDefault();
}
@@ -235,8 +235,8 @@ export class Viewport extends Disposable implements IViewport {
const modifier = this._optionsService.options.fastScrollModifier;
// Multiply the scroll speed when the modifier is down
if ((modifier === 'alt' && ev.altKey) ||
(modifier === 'ctrl' && ev.ctrlKey) ||
(modifier === 'shift' && ev.shiftKey)) {
(modifier === 'ctrl' && ev.ctrlKey) ||
(modifier === 'shift' && ev.shiftKey)) {
return amount * this._optionsService.options.fastScrollSensitivity * this._optionsService.options.scrollSensitivity;
}
+2 -1
View File
@@ -87,7 +87,8 @@ export class CompositionHelper {
if (ev.keyCode === 229) {
// Continue composing if the keyCode is the "composition character"
return false;
} else if (ev.keyCode === 16 || ev.keyCode === 17 || ev.keyCode === 18) {
}
if (ev.keyCode === 16 || ev.keyCode === 17 || ev.keyCode === 18) {
// Continue composing if the keyCode is a modifier key
return false;
}
+1 -1
View File
@@ -38,7 +38,7 @@ export function moveToCellSequence(targetX: number, targetY: number, bufferServi
direction = startY > targetY ? Direction.LEFT : Direction.RIGHT;
const rowDifference = Math.abs(startY - targetY);
const cellsToMove = colsFromRowEnd(startY > targetY ? targetX : startX, bufferService) +
(rowDifference - 1) * bufferService.cols + 1 /*wrap around 1 row*/ +
(rowDifference - 1) * bufferService.cols + 1 /* wrap around 1 row */ +
colsFromRowBeginning(startY > targetY ? startX : targetX, bufferService);
return repeat(cellsToMove, sequence(direction, applicationCursor));
}
+35 -35
View File
@@ -93,7 +93,7 @@ export abstract class BaseRenderLayer implements IRenderLayer {
const oldCanvas = this._canvas;
this._alpha = alpha;
// Cloning preserves properties
this._canvas = <HTMLCanvasElement>this._canvas.cloneNode();
this._canvas = this._canvas.cloneNode() as HTMLCanvasElement;
this._initCanvas();
this._container.replaceChild(this._canvas, oldCanvas);
@@ -145,10 +145,10 @@ export abstract class BaseRenderLayer implements IRenderLayer {
*/
protected _fillCells(x: number, y: number, width: number, height: number): void {
this._ctx.fillRect(
x * this._scaledCellWidth,
y * this._scaledCellHeight,
width * this._scaledCellWidth,
height * this._scaledCellHeight);
x * this._scaledCellWidth,
y * this._scaledCellHeight,
width * this._scaledCellWidth,
height * this._scaledCellHeight);
}
/**
@@ -159,10 +159,10 @@ export abstract class BaseRenderLayer implements IRenderLayer {
*/
protected _fillBottomLineAtCells(x: number, y: number, width: number = 1): void {
this._ctx.fillRect(
x * this._scaledCellWidth,
(y + 1) * this._scaledCellHeight - window.devicePixelRatio - 1 /* Ensure it's drawn within the cell */,
width * this._scaledCellWidth,
window.devicePixelRatio);
x * this._scaledCellWidth,
(y + 1) * this._scaledCellHeight - window.devicePixelRatio - 1 /* Ensure it's drawn within the cell */,
width * this._scaledCellWidth,
window.devicePixelRatio);
}
/**
@@ -173,10 +173,10 @@ export abstract class BaseRenderLayer implements IRenderLayer {
*/
protected _fillLeftLineAtCell(x: number, y: number, width: number): void {
this._ctx.fillRect(
x * this._scaledCellWidth,
y * this._scaledCellHeight,
window.devicePixelRatio * width,
this._scaledCellHeight);
x * this._scaledCellWidth,
y * this._scaledCellHeight,
window.devicePixelRatio * width,
this._scaledCellHeight);
}
/**
@@ -188,10 +188,10 @@ export abstract class BaseRenderLayer implements IRenderLayer {
protected _strokeRectAtCell(x: number, y: number, width: number, height: number): void {
this._ctx.lineWidth = window.devicePixelRatio;
this._ctx.strokeRect(
x * this._scaledCellWidth + window.devicePixelRatio / 2,
y * this._scaledCellHeight + (window.devicePixelRatio / 2),
width * this._scaledCellWidth - window.devicePixelRatio,
(height * this._scaledCellHeight) - window.devicePixelRatio);
x * this._scaledCellWidth + window.devicePixelRatio / 2,
y * this._scaledCellHeight + (window.devicePixelRatio / 2),
width * this._scaledCellWidth - window.devicePixelRatio,
(height * this._scaledCellHeight) - window.devicePixelRatio);
}
/**
@@ -216,17 +216,17 @@ export abstract class BaseRenderLayer implements IRenderLayer {
protected _clearCells(x: number, y: number, width: number, height: number): void {
if (this._alpha) {
this._ctx.clearRect(
x * this._scaledCellWidth,
y * this._scaledCellHeight,
width * this._scaledCellWidth,
height * this._scaledCellHeight);
x * this._scaledCellWidth,
y * this._scaledCellHeight,
width * this._scaledCellWidth,
height * this._scaledCellHeight);
} else {
this._ctx.fillStyle = this._colors.background.css;
this._ctx.fillRect(
x * this._scaledCellWidth,
y * this._scaledCellHeight,
width * this._scaledCellWidth,
height * this._scaledCellHeight);
x * this._scaledCellWidth,
y * this._scaledCellHeight,
width * this._scaledCellWidth,
height * this._scaledCellHeight);
}
}
@@ -244,9 +244,9 @@ export abstract class BaseRenderLayer implements IRenderLayer {
this._ctx.textBaseline = 'middle';
this._clipRow(y);
this._ctx.fillText(
cell.getChars(),
x * this._scaledCellWidth + this._scaledCharLeft,
y * this._scaledCellHeight + this._scaledCharTop + this._scaledCharHeight / 2);
cell.getChars(),
x * this._scaledCellWidth + this._scaledCharLeft,
y * this._scaledCellHeight + this._scaledCharTop + this._scaledCharHeight / 2);
}
/**
@@ -359,9 +359,9 @@ export abstract class BaseRenderLayer implements IRenderLayer {
}
// Draw the character
this._ctx.fillText(
cell.getChars(),
x * this._scaledCellWidth + this._scaledCharLeft,
y * this._scaledCellHeight + this._scaledCharTop + this._scaledCharHeight / 2);
cell.getChars(),
x * this._scaledCellWidth + this._scaledCharLeft,
y * this._scaledCellHeight + this._scaledCharTop + this._scaledCharHeight / 2);
this._ctx.restore();
}
@@ -372,10 +372,10 @@ export abstract class BaseRenderLayer implements IRenderLayer {
private _clipRow(y: number): void {
this._ctx.beginPath();
this._ctx.rect(
0,
y * this._scaledCellHeight,
this._bufferService.cols * this._scaledCellWidth,
this._scaledCellHeight);
0,
y * this._scaledCellHeight,
this._bufferService.cols * this._scaledCellWidth,
this._scaledCellHeight);
this._ctx.clip();
}
+14 -9
View File
@@ -140,7 +140,9 @@ export class CursorRenderLayer extends BaseRenderLayer {
return;
}
this._bufferService.buffer.lines.get(cursorY)!.loadCell(this._bufferService.buffer.x, this._cell);
// in case cursor.x == cols adjust visual cursor to cols - 1
const cursorX = Math.min(this._bufferService.buffer.x, this._bufferService.cols - 1);
this._bufferService.buffer.lines.get(cursorY)!.loadCell(cursorX, this._cell);
if (this._cell.content === undefined) {
return;
}
@@ -151,12 +153,12 @@ export class CursorRenderLayer extends BaseRenderLayer {
this._ctx.fillStyle = this._colors.cursor.css;
const cursorStyle = this._optionsService.options.cursorStyle;
if (cursorStyle && cursorStyle !== 'block') {
this._cursorRenderers[cursorStyle](this._bufferService.buffer.x, viewportRelativeCursorY, this._cell);
this._cursorRenderers[cursorStyle](cursorX, viewportRelativeCursorY, this._cell);
} else {
this._renderBlurCursor(this._bufferService.buffer.x, viewportRelativeCursorY, this._cell);
this._renderBlurCursor(cursorX, viewportRelativeCursorY, this._cell);
}
this._ctx.restore();
this._state.x = this._bufferService.buffer.x;
this._state.x = cursorX;
this._state.y = viewportRelativeCursorY;
this._state.isFocused = false;
this._state.style = cursorStyle;
@@ -172,7 +174,7 @@ export class CursorRenderLayer extends BaseRenderLayer {
if (this._state) {
// The cursor is already in the correct spot, don't redraw
if (this._state.x === this._bufferService.buffer.x &&
if (this._state.x === cursorX &&
this._state.y === viewportRelativeCursorY &&
this._state.isFocused === this._coreBrowserService.isFocused &&
this._state.style === this._optionsService.options.cursorStyle &&
@@ -183,10 +185,10 @@ export class CursorRenderLayer extends BaseRenderLayer {
}
this._ctx.save();
this._cursorRenderers[this._optionsService.options.cursorStyle || 'block'](this._bufferService.buffer.x, viewportRelativeCursorY, this._cell);
this._cursorRenderers[this._optionsService.options.cursorStyle || 'block'](cursorX, viewportRelativeCursorY, this._cell);
this._ctx.restore();
this._state.x = this._bufferService.buffer.x;
this._state.x = cursorX;
this._state.y = viewportRelativeCursorY;
this._state.isFocused = false;
this._state.style = this._optionsService.options.cursorStyle;
@@ -304,7 +306,7 @@ class CursorBlinkStateManager {
// the regular interval is setup in order to support restarting the blink
// animation in a lightweight way (without thrashing clearInterval and
// setInterval).
this._blinkStartTimeout = <number><any>setTimeout(() => {
this._blinkStartTimeout = window.setTimeout(() => {
// Check if another animation restart was requested while this was being
// started
if (this._animationTimeRestarted) {
@@ -324,7 +326,7 @@ class CursorBlinkStateManager {
});
// Setup the blink interval
this._blinkInterval = <number><any>setInterval(() => {
this._blinkInterval = window.setInterval(() => {
// Adjust the animation time if it was restarted
if (this._animationTimeRestarted) {
// calc time diff
@@ -362,6 +364,9 @@ class CursorBlinkStateManager {
}
public resume(): void {
// Clear out any existing timers just in case
this.pause();
this._animationTimeRestarted = undefined;
this._restartInterval();
this.restartBlinkAnimation();
+5 -1
View File
@@ -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 } from 'browser/Types';
import { IColorSet, ILinkifierEvent, ILinkifier, ILinkifier2 } from 'browser/Types';
import { IBufferService, IOptionsService } from 'common/services/Services';
export class LinkRenderLayer extends BaseRenderLayer {
@@ -19,12 +19,16 @@ export class LinkRenderLayer extends BaseRenderLayer {
colors: IColorSet,
rendererId: number,
linkifier: ILinkifier,
linkifier2: ILinkifier2,
readonly bufferService: IBufferService,
readonly optionsService: IOptionsService
) {
super(container, 'link', zIndex, true, colors, rendererId, bufferService, optionsService);
linkifier.onLinkHover(e => this._onLinkHover(e));
linkifier.onLinkLeave(e => this._onLinkLeave(e));
linkifier2.onLinkHover(e => this._onLinkHover(e));
linkifier2.onLinkLeave(e => this._onLinkLeave(e));
}
public resize(dim: IRenderDimensions): void {
+4 -3
View File
@@ -10,7 +10,7 @@ import { IRenderLayer, IRenderer, IRenderDimensions, CharacterJoinerHandler, ICh
import { LinkRenderLayer } from 'browser/renderer/LinkRenderLayer';
import { CharacterJoinerRegistry } from 'browser/renderer/CharacterJoinerRegistry';
import { Disposable } from 'common/Lifecycle';
import { IColorSet, ILinkifier } from 'browser/Types';
import { IColorSet, ILinkifier, ILinkifier2 } from 'browser/Types';
import { ICharSizeService, ICoreBrowserService } from 'browser/services/Services';
import { IBufferService, IOptionsService, ICoreService } from 'common/services/Services';
import { removeTerminalFromCache } from 'browser/renderer/atlas/CharAtlasCache';
@@ -33,7 +33,8 @@ export class Renderer extends Disposable implements IRenderer {
constructor(
private _colors: IColorSet,
private readonly _screenElement: HTMLElement,
private readonly _linkifier: ILinkifier,
readonly linkifier: ILinkifier,
readonly linkifier2: ILinkifier2,
@IBufferService private readonly _bufferService: IBufferService,
@ICharSizeService private readonly _charSizeService: ICharSizeService,
@IOptionsService private readonly _optionsService: IOptionsService,
@@ -47,7 +48,7 @@ export class Renderer extends Disposable implements IRenderer {
this._renderLayers = [
new TextRenderLayer(this._screenElement, 0, this._colors, this._characterJoinerRegistry, allowTransparency, this._id, this._bufferService, _optionsService),
new SelectionRenderLayer(this._screenElement, 1, this._colors, this._id, this._bufferService, _optionsService),
new LinkRenderLayer(this._screenElement, 2, this._colors, this._id, this._linkifier, this._bufferService, _optionsService),
new LinkRenderLayer(this._screenElement, 2, this._colors, this._id, linkifier, linkifier2, this._bufferService, _optionsService),
new CursorRenderLayer(this._screenElement, 3, this._colors, this._id, this._onRequestRefreshRows, this._bufferService, _optionsService, coreService, coreBrowserService)
];
this.dimensions = {
@@ -140,7 +140,8 @@ export class DynamicCharAtlas extends BaseCharAtlas {
if (cacheValue !== null && cacheValue !== undefined) {
this._drawFromCache(ctx, cacheValue, x, y);
return true;
} else if (this._drawToCacheCount < FRAME_CACHE_DRAW_LIMIT) {
}
if (this._drawToCacheCount < FRAME_CACHE_DRAW_LIMIT) {
let index;
if (this._cacheMap.size < this._cacheMap.capacity) {
index = this._cacheMap.size;
@@ -213,9 +214,11 @@ export class DynamicCharAtlas extends BaseCharAtlas {
// transparent in the atlas. Otherwise we'd end up drawing the transparent background twice
// around the anti-aliased edges of the glyph, and it would look too dark.
return TRANSPARENT_COLOR;
} else if (glyph.bg === INVERTED_DEFAULT_COLOR) {
}
if (glyph.bg === INVERTED_DEFAULT_COLOR) {
return this._config.colors.foreground;
} else if (glyph.bg < 256) {
}
if (glyph.bg < 256) {
return this._getColorFromAnsiIndex(glyph.bg);
}
return this._config.colors.background;
@@ -224,7 +227,8 @@ export class DynamicCharAtlas extends BaseCharAtlas {
private _getForegroundColor(glyph: IGlyphIdentifier): IColor {
if (glyph.fg === INVERTED_DEFAULT_COLOR) {
return color.opaque(this._config.colors.background);
} else if (glyph.fg < 256) {
}
if (glyph.fg < 256) {
// 256 color support
return this._getColorFromAnsiIndex(glyph.fg);
}
+76 -73
View File
@@ -7,7 +7,7 @@ import { IRenderer, IRenderDimensions, CharacterJoinerHandler, IRequestRefreshRo
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 } from 'browser/Types';
import { IColorSet, ILinkifierEvent, ILinkifier, ILinkifier2 } from 'browser/Types';
import { ICharSizeService } from 'browser/services/Services';
import { IOptionsService, IBufferService } from 'common/services/Services';
import { EventEmitter, IEvent } from 'common/EventEmitter';
@@ -48,6 +48,7 @@ export class DomRenderer extends Disposable implements IRenderer {
private readonly _screenElement: HTMLElement,
private readonly _viewportElement: HTMLElement,
private readonly _linkifier: ILinkifier,
private readonly _linkifier2: ILinkifier2,
@ICharSizeService private readonly _charSizeService: ICharSizeService,
@IOptionsService private readonly _optionsService: IOptionsService,
@IBufferService private readonly _bufferService: IBufferService
@@ -88,6 +89,9 @@ export class DomRenderer extends Disposable implements IRenderer {
this._linkifier.onLinkHover(e => this._onLinkHover(e));
this._linkifier.onLinkLeave(e => this._onLinkLeave(e));
this._linkifier2.onLinkHover(e => this._onLinkHover(e));
this._linkifier2.onLinkLeave(e => this._onLinkLeave(e));
}
public dispose(): void {
@@ -127,12 +131,12 @@ export class DomRenderer extends Disposable implements IRenderer {
}
const styles =
`${this._terminalSelector} .${ROW_CONTAINER_CLASS} span {` +
` display: inline-block;` +
` height: 100%;` +
` vertical-align: top;` +
` width: ${this.dimensions.actualCellWidth}px` +
`}`;
`${this._terminalSelector} .${ROW_CONTAINER_CLASS} span {` +
` display: inline-block;` +
` height: 100%;` +
` vertical-align: top;` +
` width: ${this.dimensions.actualCellWidth}px` +
`}`;
this._dimensionsStyleElement.innerHTML = styles;
@@ -154,85 +158,84 @@ export class DomRenderer extends Disposable implements IRenderer {
// Base CSS
let styles =
`${this._terminalSelector} .${ROW_CONTAINER_CLASS} {` +
` color: ${this._colors.foreground.css};` +
` background-color: ${this._colors.background.css};` +
` font-family: ${this._optionsService.options.fontFamily};` +
` font-size: ${this._optionsService.options.fontSize}px;` +
`}`;
`${this._terminalSelector} .${ROW_CONTAINER_CLASS} {` +
` color: ${this._colors.foreground.css};` +
` font-family: ${this._optionsService.options.fontFamily};` +
` font-size: ${this._optionsService.options.fontSize}px;` +
`}`;
// Text styles
styles +=
`${this._terminalSelector} span:not(.${BOLD_CLASS}) {` +
` font-weight: ${this._optionsService.options.fontWeight};` +
`}` +
`${this._terminalSelector} span.${BOLD_CLASS} {` +
` font-weight: ${this._optionsService.options.fontWeightBold};` +
`}` +
`${this._terminalSelector} span.${ITALIC_CLASS} {` +
` font-style: italic;` +
`}`;
`${this._terminalSelector} span:not(.${BOLD_CLASS}) {` +
` font-weight: ${this._optionsService.options.fontWeight};` +
`}` +
`${this._terminalSelector} span.${BOLD_CLASS} {` +
` font-weight: ${this._optionsService.options.fontWeightBold};` +
`}` +
`${this._terminalSelector} span.${ITALIC_CLASS} {` +
` font-style: italic;` +
`}`;
// Blink animation
styles +=
`@keyframes blink_box_shadow` + `_` + this._terminalClass + ` {` +
` 50% {` +
` box-shadow: none;` +
` }` +
`}`;
`@keyframes blink_box_shadow` + `_` + this._terminalClass + ` {` +
` 50% {` +
` box-shadow: none;` +
` }` +
`}`;
styles +=
`@keyframes blink_block` + `_` + this._terminalClass + ` {` +
` 0% {` +
` background-color: ${this._colors.cursor.css};` +
` color: ${this._colors.cursorAccent.css};` +
` }` +
` 50% {` +
` background-color: ${this._colors.cursorAccent.css};` +
` color: ${this._colors.cursor.css};` +
` }` +
`}`;
`@keyframes blink_block` + `_` + this._terminalClass + ` {` +
` 0% {` +
` background-color: ${this._colors.cursor.css};` +
` color: ${this._colors.cursorAccent.css};` +
` }` +
` 50% {` +
` background-color: ${this._colors.cursorAccent.css};` +
` color: ${this._colors.cursor.css};` +
` }` +
`}`;
// Cursor
styles +=
`${this._terminalSelector} .${ROW_CONTAINER_CLASS}:not(.${FOCUS_CLASS}) .${CURSOR_CLASS}.${CURSOR_STYLE_BLOCK_CLASS} {` +
` outline: 1px solid ${this._colors.cursor.css};` +
` outline-offset: -1px;` +
`}` +
`${this._terminalSelector} .${ROW_CONTAINER_CLASS}.${FOCUS_CLASS} .${CURSOR_CLASS}.${CURSOR_BLINK_CLASS}:not(.${CURSOR_STYLE_BLOCK_CLASS}) {` +
` animation: blink_box_shadow` + `_` + this._terminalClass + ` 1s step-end infinite;` +
`}` +
`${this._terminalSelector} .${ROW_CONTAINER_CLASS}.${FOCUS_CLASS} .${CURSOR_CLASS}.${CURSOR_BLINK_CLASS}.${CURSOR_STYLE_BLOCK_CLASS} {` +
` animation: blink_block` + `_` + this._terminalClass + ` 1s step-end infinite;` +
`}` +
`${this._terminalSelector} .${ROW_CONTAINER_CLASS}.${FOCUS_CLASS} .${CURSOR_CLASS}.${CURSOR_STYLE_BLOCK_CLASS} {` +
` background-color: ${this._colors.cursor.css};` +
` color: ${this._colors.cursorAccent.css};` +
`}` +
`${this._terminalSelector} .${ROW_CONTAINER_CLASS} .${CURSOR_CLASS}.${CURSOR_STYLE_BAR_CLASS} {` +
` box-shadow: ${this._optionsService.options.cursorWidth}px 0 0 ${this._colors.cursor.css} inset;` +
`}` +
`${this._terminalSelector} .${ROW_CONTAINER_CLASS} .${CURSOR_CLASS}.${CURSOR_STYLE_UNDERLINE_CLASS} {` +
` box-shadow: 0 -1px 0 ${this._colors.cursor.css} inset;` +
`}`;
`${this._terminalSelector} .${ROW_CONTAINER_CLASS}:not(.${FOCUS_CLASS}) .${CURSOR_CLASS}.${CURSOR_STYLE_BLOCK_CLASS} {` +
` outline: 1px solid ${this._colors.cursor.css};` +
` outline-offset: -1px;` +
`}` +
`${this._terminalSelector} .${ROW_CONTAINER_CLASS}.${FOCUS_CLASS} .${CURSOR_CLASS}.${CURSOR_BLINK_CLASS}:not(.${CURSOR_STYLE_BLOCK_CLASS}) {` +
` animation: blink_box_shadow` + `_` + this._terminalClass + ` 1s step-end infinite;` +
`}` +
`${this._terminalSelector} .${ROW_CONTAINER_CLASS}.${FOCUS_CLASS} .${CURSOR_CLASS}.${CURSOR_BLINK_CLASS}.${CURSOR_STYLE_BLOCK_CLASS} {` +
` animation: blink_block` + `_` + this._terminalClass + ` 1s step-end infinite;` +
`}` +
`${this._terminalSelector} .${ROW_CONTAINER_CLASS}.${FOCUS_CLASS} .${CURSOR_CLASS}.${CURSOR_STYLE_BLOCK_CLASS} {` +
` background-color: ${this._colors.cursor.css};` +
` color: ${this._colors.cursorAccent.css};` +
`}` +
`${this._terminalSelector} .${ROW_CONTAINER_CLASS} .${CURSOR_CLASS}.${CURSOR_STYLE_BAR_CLASS} {` +
` box-shadow: ${this._optionsService.options.cursorWidth}px 0 0 ${this._colors.cursor.css} inset;` +
`}` +
`${this._terminalSelector} .${ROW_CONTAINER_CLASS} .${CURSOR_CLASS}.${CURSOR_STYLE_UNDERLINE_CLASS} {` +
` box-shadow: 0 -1px 0 ${this._colors.cursor.css} inset;` +
`}`;
// Selection
styles +=
`${this._terminalSelector} .${SELECTION_CLASS} {` +
` position: absolute;` +
` top: 0;` +
` left: 0;` +
` z-index: 1;` +
` pointer-events: none;` +
`}` +
`${this._terminalSelector} .${SELECTION_CLASS} div {` +
` position: absolute;` +
` background-color: ${this._colors.selection.css};` +
`}`;
`${this._terminalSelector} .${SELECTION_CLASS} {` +
` position: absolute;` +
` top: 0;` +
` left: 0;` +
` z-index: 1;` +
` pointer-events: none;` +
`}` +
`${this._terminalSelector} .${SELECTION_CLASS} div {` +
` position: absolute;` +
` background-color: ${this._colors.selection.css};` +
`}`;
// Colors
this._colors.ansi.forEach((c, i) => {
styles +=
`${this._terminalSelector} .${FG_CLASS_PREFIX}${i} { color: ${c.css}; }` +
`${this._terminalSelector} .${BG_CLASS_PREFIX}${i} { background-color: ${c.css}; }`;
`${this._terminalSelector} .${FG_CLASS_PREFIX}${i} { color: ${c.css}; }` +
`${this._terminalSelector} .${BG_CLASS_PREFIX}${i} { background-color: ${c.css}; }`;
});
styles +=
`${this._terminalSelector} .${FG_CLASS_PREFIX}${INVERTED_DEFAULT_COLOR} { color: ${color.opaque(this._colors.background).css}; }` +
`${this._terminalSelector} .${BG_CLASS_PREFIX}${INVERTED_DEFAULT_COLOR} { background-color: ${this._colors.foreground.css}; }`;
`${this._terminalSelector} .${FG_CLASS_PREFIX}${INVERTED_DEFAULT_COLOR} { color: ${color.opaque(this._colors.background).css}; }` +
`${this._terminalSelector} .${BG_CLASS_PREFIX}${INVERTED_DEFAULT_COLOR} { background-color: ${this._colors.foreground.css}; }`;
this._themeStyleElement.innerHTML = styles;
}
@@ -349,7 +352,7 @@ export class DomRenderer extends Disposable implements IRenderer {
public renderRows(start: number, end: number): void {
const cursorAbsoluteY = this._bufferService.buffer.ybase + this._bufferService.buffer.y;
const cursorX = this._bufferService.buffer.x;
const cursorX = Math.min(this._bufferService.buffer.x, this._bufferService.cols - 1);
const cursorBlink = this._optionsService.options.cursorBlink;
for (let y = start; y <= end; y++) {
+10 -10
View File
@@ -835,12 +835,12 @@ export class SelectionService implements ISelectionService {
// Calculate the length in _columns_, converting the the string indexes back
// to column coordinates.
let length = Math.min(this._bufferService.cols, // Disallow lengths larger than the terminal cols
endIndex // The index of the selection's end char in the line string
- startIndex // The index of the selection's start char in the line string
+ leftWideCharCount // The number of wide chars left of the initial char
+ rightWideCharCount // The number of wide chars right of the initial char (inclusive)
- leftLongCharOffset // The number of additional chars left of the initial char added by columns with strings longer than 1 (emojis)
- rightLongCharOffset); // The number of additional chars right of the initial char (inclusive) added by columns with strings longer than 1 (emojis)
endIndex // The index of the selection's end char in the line string
- startIndex // The index of the selection's start char in the line string
+ leftWideCharCount // The number of wide chars left of the initial char
+ rightWideCharCount // The number of wide chars right of the initial char (inclusive)
- leftLongCharOffset // The number of additional chars left of the initial char added by columns with strings longer than 1 (emojis)
- rightLongCharOffset); // The number of additional chars right of the initial char (inclusive) added by columns with strings longer than 1 (emojis)
if (!allowWhitespaceOnlySelection && line.slice(startIndex, endIndex).trim() === '') {
return undefined;
@@ -848,9 +848,9 @@ export class SelectionService implements ISelectionService {
// Recurse upwards if the line is wrapped and the word wraps to the above line
if (followWrappedLinesAbove) {
if (start === 0 && bufferLine.getCodePoint(0) !== 32 /*' '*/) {
if (start === 0 && bufferLine.getCodePoint(0) !== 32 /* ' ' */) {
const previousBufferLine = buffer.lines.get(coords[1] - 1);
if (previousBufferLine && bufferLine.isWrapped && previousBufferLine.getCodePoint(this._bufferService.cols - 1) !== 32 /*' '*/) {
if (previousBufferLine && bufferLine.isWrapped && previousBufferLine.getCodePoint(this._bufferService.cols - 1) !== 32 /* ' ' */) {
const previousLineWordPosition = this._getWordAt([this._bufferService.cols - 1, coords[1] - 1], false, true, false);
if (previousLineWordPosition) {
const offset = this._bufferService.cols - previousLineWordPosition.start;
@@ -863,9 +863,9 @@ export class SelectionService implements ISelectionService {
// Recurse downwards if the line is wrapped and the word wraps to the next line
if (followWrappedLinesBelow) {
if (start + length === this._bufferService.cols && bufferLine.getCodePoint(this._bufferService.cols - 1) !== 32 /*' '*/) {
if (start + length === this._bufferService.cols && bufferLine.getCodePoint(this._bufferService.cols - 1) !== 32 /* ' ' */) {
const nextBufferLine = buffer.lines.get(coords[1] + 1);
if (nextBufferLine && nextBufferLine.isWrapped && nextBufferLine.getCodePoint(0) !== 32 /*' '*/) {
if (nextBufferLine && nextBufferLine.isWrapped && nextBufferLine.getCodePoint(0) !== 32 /* ' ' */) {
const nextLineWordPosition = this._getWordAt([0, coords[1] + 1], false, false, true);
if (nextLineWordPosition) {
length += nextLineWordPosition.length;
+2 -2
View File
@@ -47,7 +47,7 @@ export class MockCharsetService implements ICharsetService {
serviceBrand: any;
charset: ICharset | undefined;
glevel: number = 0;
charsets: readonly ICharset[] = [];
charsets: ReadonlyArray<ICharset> = [];
reset(): void {}
setgLevel(g: number): void {}
setgCharset(g: number, charset: ICharset): void {}
@@ -116,7 +116,7 @@ export class MockUnicodeService implements IUnicodeService {
versions: string[] = [];
activeVersion: string = '';
onChange: IEvent<string> = new EventEmitter<string>().event;
wcwidth = (codepoint: number) => this._provider.wcwidth(codepoint);
wcwidth = (codepoint: number): number => this._provider.wcwidth(codepoint);
getStringCellWidth(s: string): number {
throw new Error('Method not implemented.');
}
+1 -3
View File
@@ -5,9 +5,7 @@
import { assert } from 'chai';
import { fillFallback, concat } from 'common/TypedArrayUtils';
type TypedArray = Uint8Array | Uint16Array | Uint32Array | Uint8ClampedArray
| Int8Array | Int16Array | Int32Array
| Float32Array | Float64Array;
type TypedArray = Uint8Array | Uint16Array | Uint32Array | Uint8ClampedArray | Int8Array | Int16Array | Int32Array | Float32Array | Float64Array;
function deepEquals(a: TypedArray, b: TypedArray): void {
assert.equal(a.length, b.length);
+1 -3
View File
@@ -3,9 +3,7 @@
* @license MIT
*/
export type TypedArray = Uint8Array | Uint16Array | Uint32Array | Uint8ClampedArray
| Int8Array | Int16Array | Int32Array
| Float32Array | Float64Array;
export type TypedArray = Uint8Array | Uint16Array | Uint32Array | Uint8ClampedArray | Int8Array | Int16Array | Int32Array | Float32Array | Float64Array;
/**
+1
View File
@@ -246,6 +246,7 @@ CHARSETS['='] = {
'\\': 'ç',
']': 'ê',
'^': 'î',
// eslint-disable-next-line @typescript-eslint/naming-convention
'_': 'è',
'`': 'ô',
'{': 'ä',
+10 -10
View File
@@ -107,16 +107,16 @@ it('wcwidth should match all values from the old implementation', function(): vo
function isWideBMP(ucs: number): boolean {
return (
ucs >= 0x1100 && (
ucs <= 0x115f || // Hangul Jamo init. consonants
ucs === 0x2329 ||
ucs === 0x232a ||
(ucs >= 0x2e80 && ucs <= 0xa4cf && ucs !== 0x303f) || // CJK..Yi
(ucs >= 0xac00 && ucs <= 0xd7a3) || // Hangul Syllables
(ucs >= 0xf900 && ucs <= 0xfaff) || // CJK Compat Ideographs
(ucs >= 0xfe10 && ucs <= 0xfe19) || // Vertical forms
(ucs >= 0xfe30 && ucs <= 0xfe6f) || // CJK Compat Forms
(ucs >= 0xff00 && ucs <= 0xff60) || // Fullwidth Forms
(ucs >= 0xffe0 && ucs <= 0xffe6)));
ucs <= 0x115f || // Hangul Jamo init. consonants
ucs === 0x2329 ||
ucs === 0x232a ||
(ucs >= 0x2e80 && ucs <= 0xa4cf && ucs !== 0x303f) || // CJK..Yi
(ucs >= 0xac00 && ucs <= 0xd7a3) || // Hangul Syllables
(ucs >= 0xf900 && ucs <= 0xfaff) || // CJK Compat Ideographs
(ucs >= 0xfe10 && ucs <= 0xfe19) || // Vertical forms
(ucs >= 0xfe30 && ucs <= 0xfe6f) || // CJK Compat Forms
(ucs >= 0xff00 && ucs <= 0xff60) || // Fullwidth Forms
(ucs >= 0xffe0 && ucs <= 0xffe6)));
}
function wcwidthHigh(ucs: number): 0 | 1 | 2 {
if (bisearch(ucs, COMBINING_HIGH)) {
+1
View File
@@ -6,6 +6,7 @@
import { assert } from 'chai';
import { WriteBuffer } from './WriteBuffer';
// eslint-disable-next-line @typescript-eslint/naming-convention
declare let Buffer: any;
function toBytes(s: string): Uint8Array {
+10 -7
View File
@@ -58,6 +58,9 @@ const oscPutParser = new MockOscPutParser();
// derived parser with access to internal states
class TestEscapeSequenceParser extends EscapeSequenceParser {
public get transitions(): TransitionTable {
return this._transitions;
}
public get osc(): string {
return (this._oscParser as MockOscPutParser).data;
}
@@ -192,13 +195,13 @@ describe('EscapeSequenceParser', function (): void {
const parser = testParser;
describe('Parser init and methods', function (): void {
it('constructor', function (): void {
let p: EscapeSequenceParser = new EscapeSequenceParser();
chai.expect(p.TRANSITIONS).equal(VT500_TRANSITION_TABLE);
p = new EscapeSequenceParser(VT500_TRANSITION_TABLE);
chai.expect(p.TRANSITIONS).equal(VT500_TRANSITION_TABLE);
let p = new TestEscapeSequenceParser();
chai.expect(p.transitions).equal(VT500_TRANSITION_TABLE);
p = new TestEscapeSequenceParser(VT500_TRANSITION_TABLE);
chai.expect(p.transitions).equal(VT500_TRANSITION_TABLE);
const tansitions: TransitionTable = new TransitionTable(10);
p = new EscapeSequenceParser(tansitions);
chai.expect(p.TRANSITIONS).equal(tansitions);
p = new TestEscapeSequenceParser(tansitions);
chai.expect(p.transitions).equal(tansitions);
});
it('inital states', function (): void {
chai.expect(parser.initialState).equal(ParserState.GROUND);
@@ -735,7 +738,7 @@ describe('EscapeSequenceParser', function (): void {
it('state OSC_STRING ignore rules', function (): void {
parser.reset();
const ignored = [
'\x00', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', /*'\x07',*/ '\x08',
'\x00', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', /* '\x07', */ '\x08',
'\x09', '\x0a', '\x0b', '\x0c', '\x0d', '\x0e', '\x0f', '\x10', '\x11',
'\x12', '\x13', '\x14', '\x15', '\x16', '\x17', '\x19', '\x1c', '\x1d', '\x1e', '\x1f'];
for (let i = 0; i < ignored.length; ++i) {
+5 -3
View File
@@ -82,7 +82,7 @@ export const VT500_TRANSITION_TABLE = (function (): TransitionTable {
// range macro for byte
const BYTE_VALUES = 256;
const blueprint = Array.apply(null, Array(BYTE_VALUES)).map((unused: any, i: number) => i);
const r = (start: number, end: number) => blueprint.slice(start, end);
const r = (start: number, end: number): number[] => blueprint.slice(start, end);
// Default definitions.
const PRINTABLES = r(0x20, 0x7f); // 0x20 (SP) included, 0x7F (DEL) excluded
@@ -253,7 +253,9 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
protected _escHandlerFb: EscFallbackHandlerType;
protected _errorHandlerFb: (state: IParsingState) => IParsingState;
constructor(readonly TRANSITIONS: TransitionTable = VT500_TRANSITION_TABLE) {
constructor(
protected readonly _transitions: TransitionTable = VT500_TRANSITION_TABLE
) {
super();
this.initialState = ParserState.GROUND;
@@ -471,7 +473,7 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
const dcs = this._dcsParser;
let collect = this._collect;
const params = this._params;
const table: Uint8Array = this.TRANSITIONS.table;
const table: Uint8Array = this._transitions.table;
// process input string
for (let i = 0; i < length; ++i) {
+5 -5
View File
@@ -284,11 +284,11 @@ export class CoreMouseService implements ICoreMouseService {
public explainEvents(events: CoreMouseEventType): {[event: string]: boolean} {
return {
DOWN: !!(events & CoreMouseEventType.DOWN),
UP: !!(events & CoreMouseEventType.UP),
DRAG: !!(events & CoreMouseEventType.DRAG),
MOVE: !!(events & CoreMouseEventType.MOVE),
WHEEL: !!(events & CoreMouseEventType.WHEEL)
down: !!(events & CoreMouseEventType.DOWN),
up: !!(events & CoreMouseEventType.UP),
drag: !!(events & CoreMouseEventType.DRAG),
move: !!(events & CoreMouseEventType.MOVE),
wheel: !!(events & CoreMouseEventType.WHEEL)
};
}
+2 -2
View File
@@ -56,7 +56,7 @@ export class InstantiationService implements IInstantiationService {
return this._services.get(id);
}
public createInstance<T>(ctor: any, ...args: any[]): any {
public createInstance<T>(ctor: any, ...args: any[]): T {
const serviceDependencies = getServiceDependencies(ctor).sort((a, b) => a.index - b.index);
const serviceArgs: any[] = [];
@@ -76,6 +76,6 @@ export class InstantiationService implements IInstantiationService {
}
// now create the instance
return <T>new ctor(...[...args, ...serviceArgs]);
return new ctor(...[...args, ...serviceArgs]);
}
}
+1 -1
View File
@@ -25,7 +25,7 @@ export function createDecorator<T>(id: string): IServiceIdentifier<T> {
return serviceRegistry.get(id)!;
}
const decorator = <any>function (target: Function, key: string, index: number): any {
const decorator: any = function (target: Function, key: string, index: number): any {
if (arguments.length !== 3) {
throw new Error('@IServiceName-decorator can only be used to decorate a parameter');
}
+13 -13
View File
@@ -87,7 +87,7 @@ export interface ICoreService {
* Triggers the onBinary event in the public API.
* @param data The data that is being emitted.
*/
triggerBinaryEvent(data: string): void;
triggerBinaryEvent(data: string): void;
}
export const ICharsetService = createDecorator<ICharsetService>('CharsetService');
@@ -104,7 +104,7 @@ export interface ICharsetService {
* Set the G level of the terminal.
* @param g
*/
setgLevel(g: number): void;
setgLevel(g: number): void;
/**
* Set the charset for the given G level of the terminal.
@@ -133,39 +133,39 @@ export interface IServiceIdentifier<T> {
}
export interface IConstructorSignature0<T> {
new(...services: { serviceBrand: any; }[]): T;
new(...services: { serviceBrand: any }[]): T;
}
export interface IConstructorSignature1<A1, T> {
new(first: A1, ...services: { serviceBrand: any; }[]): T;
new(first: A1, ...services: { serviceBrand: any }[]): T;
}
export interface IConstructorSignature2<A1, A2, T> {
new(first: A1, second: A2, ...services: { serviceBrand: any; }[]): T;
new(first: A1, second: A2, ...services: { serviceBrand: any }[]): T;
}
export interface IConstructorSignature3<A1, A2, A3, T> {
new(first: A1, second: A2, third: A3, ...services: { serviceBrand: any; }[]): T;
new(first: A1, second: A2, third: A3, ...services: { serviceBrand: any }[]): T;
}
export interface IConstructorSignature4<A1, A2, A3, A4, T> {
new(first: A1, second: A2, third: A3, fourth: A4, ...services: { serviceBrand: any; }[]): T;
new(first: A1, second: A2, third: A3, fourth: A4, ...services: { serviceBrand: any }[]): T;
}
export interface IConstructorSignature5<A1, A2, A3, A4, A5, T> {
new(first: A1, second: A2, third: A3, fourth: A4, fifth: A5, ...services: { serviceBrand: any; }[]): T;
new(first: A1, second: A2, third: A3, fourth: A4, fifth: A5, ...services: { serviceBrand: any }[]): T;
}
export interface IConstructorSignature6<A1, A2, A3, A4, A5, A6, T> {
new(first: A1, second: A2, third: A3, fourth: A4, fifth: A5, sixth: A6, ...services: { serviceBrand: any; }[]): T;
new(first: A1, second: A2, third: A3, fourth: A4, fifth: A5, sixth: A6, ...services: { serviceBrand: any }[]): T;
}
export interface IConstructorSignature7<A1, A2, A3, A4, A5, A6, A7, T> {
new(first: A1, second: A2, third: A3, fourth: A4, fifth: A5, sixth: A6, seventh: A7, ...services: { serviceBrand: any; }[]): T;
new(first: A1, second: A2, third: A3, fourth: A4, fifth: A5, sixth: A6, seventh: A7, ...services: { serviceBrand: any }[]): T;
}
export interface IConstructorSignature8<A1, A2, A3, A4, A5, A6, A7, A8, T> {
new(first: A1, second: A2, third: A3, fourth: A4, fifth: A5, sixth: A6, seventh: A7, eigth: A8, ...services: { serviceBrand: any; }[]): T;
new(first: A1, second: A2, third: A3, fourth: A4, fifth: A5, sixth: A6, seventh: A7, eigth: A8, ...services: { serviceBrand: any }[]): T;
}
export const IInstantiationService = createDecorator<IInstantiationService>('InstantiationService');
@@ -213,7 +213,7 @@ export type RendererType = 'dom' | 'canvas';
export interface IPartialTerminalOptions {
allowTransparency?: boolean;
bellSound?: string;
bellStyle?: 'none' /*| 'visual'*/ | 'sound' /*| 'both'*/;
bellStyle?: 'none' | 'sound' /* | 'visual' | 'both' */;
cols?: number;
cursorBlink?: boolean;
cursorStyle?: 'block' | 'underline' | 'bar';
@@ -247,7 +247,7 @@ export interface IPartialTerminalOptions {
export interface ITerminalOptions {
allowTransparency: boolean;
bellSound: string;
bellStyle: 'none' /*| 'visual'*/ | 'sound' /*| 'both'*/;
bellStyle: 'none' | 'sound' /* | 'visual' | 'both' */;
cols: number;
cursorBlink: boolean;
cursorStyle: 'block' | 'underline' | 'bar';
+42 -7
View File
@@ -3,14 +3,14 @@
* @license MIT
*/
import { Terminal as ITerminalApi, ITerminalOptions, IMarker, IDisposable, ILinkMatcherOptions, ITheme, ILocalizableStrings, ITerminalAddon, ISelectionPosition, IBuffer as IBufferApi, IBufferLine as IBufferLineApi, IBufferCell as IBufferCellApi, IParser, IFunctionIdentifier, IUnicodeHandling, IUnicodeVersionProvider } from 'xterm';
import { Terminal as ITerminalApi, ITerminalOptions, IMarker, IDisposable, ILinkMatcherOptions, ITheme, ILocalizableStrings, ITerminalAddon, ISelectionPosition, IBuffer as IBufferApi, IBufferNamespace as IBufferNamespaceApi, IBufferLine as IBufferLineApi, IBufferCell as IBufferCellApi, IParser, IFunctionIdentifier, ILinkProvider, IUnicodeHandling, IUnicodeVersionProvider } from 'xterm';
import { ITerminal } from '../Types';
import { IBufferLine, ICellData } from 'common/Types';
import { IBuffer } from 'common/buffer/Types';
import { IBuffer, IBufferSet } from 'common/buffer/Types';
import { CellData } from 'common/buffer/CellData';
import { Terminal as TerminalCore } from '../Terminal';
import * as Strings from '../browser/LocalizableStrings';
import { IEvent } from 'common/EventEmitter';
import { IEvent, EventEmitter } from 'common/EventEmitter';
import { AddonManager } from './AddonManager';
import { IParams } from 'common/parser/Types';
@@ -48,7 +48,7 @@ export class Terminal implements ITerminalApi {
public get textarea(): HTMLTextAreaElement | undefined { return this._core.textarea; }
public get rows(): number { return this._core.rows; }
public get cols(): number { return this._core.cols; }
public get buffer(): IBufferApi { return new BufferApiView(this._core.buffer); }
public get buffer(): IBufferNamespaceApi { return new BufferNamespaceApi(this._core.buffers); }
public get markers(): ReadonlyArray<IMarker> { return this._core.markers; }
public blur(): void {
this._core.blur();
@@ -72,6 +72,9 @@ export class Terminal implements ITerminalApi {
public deregisterLinkMatcher(matcherId: number): void {
this._core.deregisterLinkMatcher(matcherId);
}
public registerLinkProvider(linkProvider: ILinkProvider): IDisposable {
return this._core.registerLinkProvider(linkProvider);
}
public registerCharacterJoiner(handler: (text: string) => [number, number][]): number {
return this._core.registerCharacterJoiner(handler);
}
@@ -190,7 +193,15 @@ export class Terminal implements ITerminalApi {
}
class BufferApiView implements IBufferApi {
constructor(private _buffer: IBuffer) { }
constructor(
private _buffer: IBuffer,
public readonly type: 'normal' | 'alternate'
) { }
public init(buffer: IBuffer): BufferApiView {
this._buffer = buffer;
return this;
}
public get cursorY(): number { return this._buffer.y; }
public get cursorX(): number { return this._buffer.x; }
@@ -207,6 +218,30 @@ class BufferApiView implements IBufferApi {
public getNullCell(): IBufferCellApi { return new CellData(); }
}
class BufferNamespaceApi implements IBufferNamespaceApi {
private _normal: BufferApiView;
private _alternate: BufferApiView;
private _onBufferChange = new EventEmitter<IBufferApi>();
public get onBufferChange(): IEvent<IBufferApi> { return this._onBufferChange.event; }
constructor(private _buffers: IBufferSet) {
this._normal = new BufferApiView(this._buffers.normal, 'normal');
this._alternate = new BufferApiView(this._buffers.alt, 'alternate');
this._buffers.onBufferActivate(() => this._onBufferChange.fire(this.active));
}
public get active(): IBufferApi {
if (this._buffers.active === this._buffers.normal) { return this.normal; }
if (this._buffers.active === this._buffers.alt) { return this.alternate; }
throw new Error('Active buffer is neither normal nor alternate');
}
public get normal(): IBufferApi {
return this._normal.init(this._buffers.normal);
}
public get alternate(): IBufferApi {
return this._alternate.init(this._buffers.alt);
}
}
class BufferLineApiView implements IBufferLineApi {
constructor(private _line: IBufferLine) { }
@@ -229,7 +264,7 @@ class BufferLineApiView implements IBufferLineApi {
}
class ParserApi implements IParser {
constructor(private _core: ITerminal) {}
constructor(private _core: ITerminal) { }
public registerCsiHandler(id: IFunctionIdentifier, callback: (params: (number | number[])[]) => boolean): IDisposable {
return this._core.addCsiHandler(id, (params: IParams) => callback(params.toArray()));
@@ -258,7 +293,7 @@ class ParserApi implements IParser {
}
class UnicodeApi implements IUnicodeHandling {
constructor(private _core: ITerminal) {}
constructor(private _core: ITerminal) { }
public register(provider: IUnicodeVersionProvider): void {
this._core.unicodeService.register(provider);
+11 -22
View File
@@ -3,27 +3,26 @@
* @license MIT
*/
import * as puppeteer from 'puppeteer';
import { ITerminalOptions } from 'xterm';
import { pollFor } from './TestUtils';
import { pollFor, openTerminal, getBrowserType } from './TestUtils';
import { Browser, Page } from 'playwright-core';
const APP = 'http://127.0.0.1:3000/test';
let browser: puppeteer.Browser;
let page: puppeteer.Page;
let browser: Browser;
let page: Page;
const width = 800;
const height = 600;
describe('CharWidth Integration Tests', function(): void {
before(async function(): Promise<any> {
browser = await puppeteer.launch({
headless: process.argv.indexOf('--headless') !== -1,
args: [`--window-size=${width},${height}`, `--no-sandbox`]
const browserType = getBrowserType();
browser = await browserType.launch({ dumpio: true,
headless: process.argv.indexOf('--headless') !== -1
});
page = (await browser.pages())[0];
await page.setViewport({ width, height });
page = await (await browser.newContext()).newPage();
await page.setViewportSize({ width, height });
await page.goto(APP);
await openTerminal({ rows: 5, cols: 30 });
await openTerminal(page, { rows: 5, cols: 30 });
});
after(() => {
@@ -75,21 +74,11 @@ describe('CharWidth Integration Tests', function(): void {
});
});
async function openTerminal(options: ITerminalOptions = {}): Promise<void> {
await page.evaluate(`window.term = new Terminal(${JSON.stringify(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');
}
}
async function sumWidths(start: number, end: number, sentinel: string): Promise<number> {
await page.evaluate(`
(function() {
window.result = 0;
const buffer = window.term.buffer;
const buffer = window.term.buffer.active;
for (let i = ${start}; i < ${end}; i++) {
const line = buffer.getLine(i);
let j = 0;
+95 -102
View File
@@ -3,28 +3,31 @@
* @license MIT
*/
import * as puppeteer from 'puppeteer';
import { assert } from 'chai';
import { ITerminalOptions } from 'xterm';
import { pollFor } from './TestUtils';
import { pollFor, openTerminal, getBrowserType } from './TestUtils';
import { Browser, Page } from 'playwright-core';
import { IRenderDimensions } from 'browser/renderer/Types';
const APP = 'http://127.0.0.1:3000/test';
let browser: puppeteer.Browser;
let page: puppeteer.Page;
let browser: Browser;
let page: Page;
const width = 800;
const height = 600;
let isChromium = false;
describe('InputHandler Integration Tests', function(): void {
before(async function(): Promise<any> {
browser = await puppeteer.launch({
headless: process.argv.indexOf('--headless') !== -1,
args: [`--window-size=${width},${height}`, `--no-sandbox`]
const browserType = getBrowserType();
isChromium = browserType.name() === 'chromium';
browser = await browserType.launch({ dumpio: true,
headless: process.argv.indexOf('--headless') !== -1
});
page = (await browser.pages())[0];
await page.setViewport({ width, height });
page = await (await browser.newContext()).newPage();
await page.setViewportSize({ width, height });
await page.goto(APP);
await openTerminal();
await openTerminal(page);
});
after(() => {
@@ -161,7 +164,7 @@ describe('InputHandler Integration Tests', function(): void {
window.term.reset()
window.term.write('1\\n2\\n3\\n4\\n5${fixture}\x1b[3J')
`);
await pollFor(page, () => page.evaluate(`window.term.buffer.length`), 5);
await pollFor(page, () => page.evaluate(`window.term.buffer.active.length`), 5);
await pollFor(page, () => getLinesAsArray(5), [' 4', ' 5', 'abc', 'def', 'ghi']);
});
@@ -190,7 +193,7 @@ describe('InputHandler Integration Tests', function(): void {
window.term.reset()
window.term.write('1\\n2\\n3\\n4\\n5${fixture}\x1b[?3J')
`);
await pollFor(page, () => page.evaluate(`window.term.buffer.length`), 5);
await pollFor(page, () => page.evaluate(`window.term.buffer.active.length`), 5);
await pollFor(page, () => getLinesAsArray(5), [' 4', ' 5', 'abc', 'def', 'ghi']);
});
@@ -236,7 +239,7 @@ describe('InputHandler Integration Tests', function(): void {
it('Report Cursor Position (CPR) - CSI 6 n', async function(): Promise<any> {
await page.evaluate(`window.term.write('\\n\\nfoo')`);
await pollFor(page, () => page.evaluate(`
[window.term.buffer.cursorY, window.term.buffer.cursorX]
[window.term.buffer.active.cursorY, window.term.buffer.active.cursorX]
`), [2, 3]);
await page.evaluate(`
window.term.onData(e => window.result = e);
@@ -248,7 +251,7 @@ describe('InputHandler Integration Tests', function(): void {
it('Report Cursor Position (DECXCPR) - CSI ? 6 n', async function(): Promise<any> {
await page.evaluate(`window.term.write('\\n\\nfoo')`);
await pollFor(page, () => page.evaluate(`
[window.term.buffer.cursorY, window.term.buffer.cursorX]
[window.term.buffer.active.cursorY, window.term.buffer.active.cursorX]
`), [2, 3]);
await page.evaluate(`
window.term.onData(e => window.result = e);
@@ -260,18 +263,18 @@ describe('InputHandler Integration Tests', function(): void {
describe('SM: Set Mode', () => {
describe('CSI ? Pm h', () => {
it('Pm = 1003, Set Use All Motion (any event) Mouse Tracking', async() => {
const coords = await page.evaluate(`
(function() {
const rect = window.term.element.getBoundingClientRect();
return {left: rect.left, top: rect.top, bottom: rect.bottom, right: rect.right};
})();
it('Pm = 1003, Set Use All Motion (any event) Mouse Tracking', async () => {
const coords: { left: number, top: number, bottom: number, right: number } = await page.evaluate(`
(function() {
const rect = window.term.element.getBoundingClientRect();
return { left: rect.left, top: rect.top, bottom: rect.bottom, right: rect.right };
})();
`);
// Click and drag and ensure there is a selection
await page.mouse.click((coords.left + coords.right) / 2, (coords.top + coords.bottom) / 2);
await page.mouse.down();
await page.mouse.move((coords.left + coords.right) / 2, (coords.top + coords.bottom) / 4);
assert.ok(await page.evaluate(`window.term.getSelection().length`) > 0, 'mouse events are off so there should be a selection');
assert.ok(await page.evaluate(`window.term.getSelection().length`) as number > 0, 'mouse events are off so there should be a selection');
await page.mouse.up();
// Clear selection
await page.mouse.click((coords.left + coords.right) / 2, (coords.top + coords.bottom) / 2);
@@ -286,7 +289,7 @@ describe('InputHandler Integration Tests', function(): void {
await pollFor(page, () => page.evaluate(`window.term.getSelection().length`), 0);
await page.mouse.up();
});
it('Pm = 2004, Set bracketed paste mode', async function(): Promise<any> {
(isChromium ? it : it.skip)('Pm = 2004, Set bracketed paste mode', async function(): Promise<any> {
await pollFor(page, () => simulatePaste('foo'), 'foo');
await page.evaluate(`window.term.write('\x1b[?2004h')`);
await pollFor(page, () => simulatePaste('bar'), '\x1b[200~bar\x1b[201~');
@@ -299,86 +302,86 @@ describe('InputHandler Integration Tests', function(): void {
it('REP: Repeat preceding character, ECMA48 - CSI Ps b', async function(): Promise<any> {
// default to 1
await page.evaluate(`
window.term.resize(10, 10);
window.term.write('#\x1b[b');
window.term.writeln('');
window.term.write('#\x1b[0b');
window.term.writeln('');
window.term.write('#\x1b[1b');
window.term.writeln('');
window.term.write('#\x1b[5b');
`);
window.term.resize(10, 10);
window.term.write('#\x1b[b');
window.term.writeln('');
window.term.write('#\x1b[0b');
window.term.writeln('');
window.term.write('#\x1b[1b');
window.term.writeln('');
window.term.write('#\x1b[5b');
`);
await pollFor(page, () => getLinesAsArray(4), ['##', '##', '##', '######']);
await pollFor(page, () => getCursor(), {col: 6, row: 3});
await pollFor(page, () => getCursor(), { col: 6, row: 3 });
// should not repeat on fullwidth chars
await page.evaluate(`
window.term.reset();
window.term.write('¥\x1b[10b');
`);
window.term.reset();
window.term.write('¥\x1b[10b');
`);
await pollFor(page, () => getLinesAsArray(1), ['¥']);
// should repeat only base char of combining
await page.evaluate(`
window.term.reset();
window.term.write('e\u0301\x1b[5b');
`);
window.term.reset();
window.term.write('e\u0301\x1b[5b');
`);
await pollFor(page, () => getLinesAsArray(1), ['e\u0301eeeee']);
// should wrap correctly
await page.evaluate(`
window.term.reset();
window.term.write('#\x1b[15b');
`);
window.term.reset();
window.term.write('#\x1b[15b');
`);
await pollFor(page, () => getLinesAsArray(2), ['##########', '######']);
await page.evaluate(`
window.term.reset();
window.term.write('\x1b[?7l'); // disable wrap around
window.term.write('#\x1b[15b');
`);
window.term.reset();
window.term.write('\x1b[?7l'); // disable wrap around
window.term.write('#\x1b[15b');
`);
await pollFor(page, () => getLinesAsArray(2), ['##########', '']);
// any successful sequence should reset REP
await page.evaluate(`
window.term.reset();
window.term.write('\x1b[?7h'); // re-enable wrap around
window.term.write('#\\n\x1b[3b');
window.term.write('#\\r\x1b[3b');
window.term.writeln('');
window.term.write('abcdefg\x1b[3D\x1b[10b#\x1b[3b');
`);
window.term.reset();
window.term.write('\x1b[?7h'); // re-enable wrap around
window.term.write('#\\n\x1b[3b');
window.term.write('#\\r\x1b[3b');
window.term.writeln('');
window.term.write('abcdefg\x1b[3D\x1b[10b#\x1b[3b');
`);
await pollFor(page, () => getLinesAsArray(3), ['#', ' #', 'abcd####']);
});
describe('Window Options - CSI Ps ; Ps ; Ps t', () => {
it('should be disabled by default', async function() {
it('should be disabled by default', async function(): Promise<any> {
await page.evaluate(`(() => {
window._stack = [];
const _h = window.term.onData(data => window._stack.push(data));
window.term.write('\x1b[14t');
window.term.write('\x1b[16t');
window.term.write('\x1b[18t');
window.term.write('\x1b[20t');
window.term.write('\x1b[21t');
return new Promise((r) => window.term.write('', () => { _h.dispose(); r(); }));
})()`);
window._stack = [];
const _h = window.term.onData(data => window._stack.push(data));
window.term.write('\x1b[14t');
window.term.write('\x1b[16t');
window.term.write('\x1b[18t');
window.term.write('\x1b[20t');
window.term.write('\x1b[21t');
return new Promise((r) => window.term.write('', () => { _h.dispose(); r(); }));
})()`);
await pollFor(page, async () => await page.evaluate(`(() => _stack)()`), []);
});
it('14 - GetWinSizePixels', async function() {
await page.evaluate(`window.term.setOption('windowOptions', {getWinSizePixels: true});`);
it('14 - GetWinSizePixels', async function(): Promise<any> {
await page.evaluate(`window.term.setOption('windowOptions', { getWinSizePixels: true }); `);
await page.evaluate(`(() => {
window._stack = [];
const _h = window.term.onData(data => window._stack.push(data));
window.term.write('\x1b[14t');
return new Promise((r) => window.term.write('', () => { _h.dispose(); r(); }));
})()`);
window._stack = [];
const _h = window.term.onData(data => window._stack.push(data));
window.term.write('\x1b[14t');
return new Promise((r) => window.term.write('', () => { _h.dispose(); r(); }));
})()`);
const d = await getDimensions();
await pollFor(page, async () => await page.evaluate(`(() => _stack)()`), [`\x1b[4;${d.height};${d.width}t`]);
});
it('16 - GetCellSizePixels', async function() {
await page.evaluate(`window.term.setOption('windowOptions', {getCellSizePixels: true});`);
it('16 - GetCellSizePixels', async function(): Promise<any> {
await page.evaluate(`window.term.setOption('windowOptions', { getCellSizePixels: true }); `);
await page.evaluate(`(() => {
window._stack = [];
const _h = window.term.onData(data => window._stack.push(data));
window.term.write('\x1b[16t');
return new Promise((r) => window.term.write('', () => { _h.dispose(); r(); }));
})()`);
window._stack = [];
const _h = window.term.onData(data => window._stack.push(data));
window.term.write('\x1b[16t');
return new Promise((r) => window.term.write('', () => { _h.dispose(); r(); }));
})()`);
const d = await getDimensions();
await pollFor(page, async () => await page.evaluate(`(() => _stack)()`), [`\x1b[6;${d.cellHeight};${d.cellWidth}t`]);
});
@@ -392,31 +395,21 @@ describe('InputHandler Integration Tests', function(): void {
window.term.resize(10, 2);
window.term.write('1\\n\\r2\\n\\r3\\n\\r4\\n\\r5');
window.term.write('\\x1b7\\x1b[?47h');
`);
`);
await page.evaluate(`
window.term.resize(10, 4);
window.term.write('\\x1b[?47l\\x1b8');
`);
await pollFor(page, () => getCursor(), {col: 1, row: 3});
`);
await pollFor(page, () => getCursor(), { col: 1, row: 3 });
});
});
});
});
async function openTerminal(options: ITerminalOptions = {}): Promise<void> {
await page.evaluate(`window.term = new Terminal(${JSON.stringify(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');
}
}
async function getLinesAsArray(count: number, start: number = 0): Promise<string[]> {
let text = '';
for (let i = start; i < start + count; i++) {
text += `window.term.buffer.getLine(${i}).translateToString(true),`;
text += `window.term.buffer.active.getLine(${i}).translateToString(true),`;
}
return await page.evaluate(`[${text}]`);
}
@@ -424,26 +417,26 @@ async function getLinesAsArray(count: number, start: number = 0): Promise<string
async function simulatePaste(text: string): Promise<string> {
const id = Math.floor(Math.random() * 1000000);
await page.evaluate(`
(function () {
window.term.onData(e => window.result_${id} = e);
const clipboardData = new DataTransfer();
clipboardData.setData('text/plain', '${text}');
window.term.textarea.dispatchEvent(new ClipboardEvent('paste', { clipboardData }));
})();
`);
return await page.evaluate(`window.result_${id}`);
(function() {
window.term.onData(e => window.result_${id} = e);
const clipboardData = new DataTransfer();
clipboardData.setData('text/plain', '${text}');
window.term.textarea.dispatchEvent(new ClipboardEvent('paste', { clipboardData }));
})();
`);
return await page.evaluate(`window.result_${id} `);
}
async function getCursor(): Promise<{col: number, row: number}> {
async function getCursor(): Promise<{ col: number, row: number }> {
return page.evaluate(`
(function() {
return {col: term.buffer.cursorX, row: term.buffer.cursorY};
return {col: term.buffer.active.cursorX, row: term.buffer.active.cursorY};
})();
`);
}
async function getDimensions(): Promise<any> {
const dim = await page.evaluate(`term._core._renderService.dimensions`);
const dim: IRenderDimensions = await page.evaluate(`term._core._renderService.dimensions`);
return {
cellWidth: dim.actualCellWidth.toFixed(0),
cellHeight: dim.actualCellHeight.toFixed(0),
File diff suppressed because it is too large Load Diff
+10 -21
View File
@@ -3,28 +3,27 @@
* @license MIT
*/
import * as puppeteer from 'puppeteer';
import { assert } from 'chai';
import { ITerminalOptions } from 'xterm';
import { writeSync } from './TestUtils';
import { writeSync, openTerminal, getBrowserType } from './TestUtils';
import { Browser, Page } from 'playwright-core';
const APP = 'http://127.0.0.1:3000/test';
let browser: puppeteer.Browser;
let page: puppeteer.Page;
let browser: Browser;
let page: Page;
const width = 800;
const height = 600;
describe('Parser Integration Tests', function(): void {
before(async function(): Promise<any> {
browser = await puppeteer.launch({
headless: process.argv.indexOf('--headless') !== -1,
args: [`--window-size=${width},${height}`, `--no-sandbox`]
const browserType = getBrowserType();
browser = await browserType.launch({ dumpio: true,
headless: process.argv.indexOf('--headless') !== -1
});
page = (await browser.pages())[0];
await page.setViewport({ width, height });
page = await (await browser.newContext()).newPage();
await page.setViewportSize({ width, height });
await page.goto(APP);
await openTerminal();
await openTerminal(page);
});
after(async () => browser.close());
@@ -110,13 +109,3 @@ describe('Parser Integration Tests', function(): void {
});
});
});
async function openTerminal(options: ITerminalOptions = {}): Promise<void> {
await page.evaluate(`window.term = new Terminal(${JSON.stringify(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');
}
}
+329 -126
View File
@@ -3,60 +3,59 @@
* @license MIT
*/
import * as puppeteer from 'puppeteer';
import { assert } from 'chai';
import { ITerminalOptions } from 'xterm';
import { pollFor, timeout, writeSync } from './TestUtils';
import { pollFor, timeout, writeSync, openTerminal, getBrowserType } from './TestUtils';
import { Browser, Page } from 'playwright-core';
const APP = 'http://127.0.0.1:3000/test';
let browser: puppeteer.Browser;
let page: puppeteer.Page;
let browser: Browser;
let page: Page;
const width = 800;
const height = 600;
describe('API Integration Tests', function(): void {
before(async () => {
browser = await puppeteer.launch({
headless: process.argv.indexOf('--headless') !== -1,
args: [`--window-size=${width},${height}`, `--no-sandbox`]
const browserType = getBrowserType();
browser = await browserType.launch({ dumpio: true,
headless: process.argv.indexOf('--headless') !== -1
});
page = (await browser.pages())[0];
await page.setViewport({ width, height });
page = await (await browser.newContext()).newPage();
await page.setViewportSize({ width, height });
});
after(async () => browser.close());
beforeEach(async () => page.goto(APP));
it('Default options', async () => {
await openTerminal();
await openTerminal(page);
assert.equal(await page.evaluate(`window.term.cols`), 80);
assert.equal(await page.evaluate(`window.term.rows`), 24);
});
it('write', async () => {
await openTerminal();
await openTerminal(page);
await page.evaluate(`
window.term.write('foo');
window.term.write('bar');
window.term.write('文');
`);
await pollFor(page, `window.term.buffer.getLine(0).translateToString(true)`, 'foobar文');
await pollFor(page, `window.term.buffer.active.getLine(0).translateToString(true)`, 'foobar文');
});
it('write with callback', async () => {
await openTerminal();
await openTerminal(page);
await page.evaluate(`
window.term.write('foo', () => { window.__x = 'a'; });
window.term.write('bar', () => { window.__x += 'b'; });
window.term.write('文', () => { window.__x += 'c'; });
`);
await pollFor(page, `window.term.buffer.getLine(0).translateToString(true)`, 'foobar文');
await pollFor(page, `window.term.buffer.active.getLine(0).translateToString(true)`, 'foobar文');
await pollFor(page, `window.__x`, 'abc');
});
it('write - bytes (UTF8)', async () => {
await openTerminal();
await openTerminal(page);
await page.evaluate(`
// foo
window.term.write(new Uint8Array([102, 111, 111]));
@@ -65,11 +64,11 @@ describe('API Integration Tests', function(): void {
// 文
window.term.write(new Uint8Array([230, 150, 135]));
`);
await pollFor(page, `window.term.buffer.getLine(0).translateToString(true)`, 'foobar文');
await pollFor(page, `window.term.buffer.active.getLine(0).translateToString(true)`, 'foobar文');
});
it('write - bytes (UTF8) with callback', async () => {
await openTerminal();
await openTerminal(page);
await page.evaluate(`
// foo
window.term.write(new Uint8Array([102, 111, 111]), () => { window.__x = 'A'; });
@@ -78,49 +77,49 @@ describe('API Integration Tests', function(): void {
// 文
window.term.write(new Uint8Array([230, 150, 135]), () => { window.__x += 'C'; });
`);
await pollFor(page, `window.term.buffer.getLine(0).translateToString(true)`, 'foobar文');
await pollFor(page, `window.term.buffer.active.getLine(0).translateToString(true)`, 'foobar文');
await pollFor(page, `window.__x`, 'ABC');
});
it('writeln', async () => {
await openTerminal();
await openTerminal(page);
await page.evaluate(`
window.term.writeln('foo');
window.term.writeln('bar');
window.term.writeln('文');
`);
await pollFor(page, `window.term.buffer.getLine(0).translateToString(true)`, 'foo');
await pollFor(page, `window.term.buffer.getLine(1).translateToString(true)`, 'bar');
await pollFor(page, `window.term.buffer.getLine(2).translateToString(true)`, '文');
await pollFor(page, `window.term.buffer.active.getLine(0).translateToString(true)`, 'foo');
await pollFor(page, `window.term.buffer.active.getLine(1).translateToString(true)`, 'bar');
await pollFor(page, `window.term.buffer.active.getLine(2).translateToString(true)`, '文');
});
it('writeln with callback', async () => {
await openTerminal();
await openTerminal(page);
await page.evaluate(`
window.term.writeln('foo', () => { window.__x = '1'; });
window.term.writeln('bar', () => { window.__x += '2'; });
window.term.writeln('文', () => { window.__x += '3'; });
`);
await pollFor(page, `window.term.buffer.getLine(0).translateToString(true)`, 'foo');
await pollFor(page, `window.term.buffer.getLine(1).translateToString(true)`, 'bar');
await pollFor(page, `window.term.buffer.getLine(2).translateToString(true)`, '文');
await pollFor(page, `window.term.buffer.active.getLine(0).translateToString(true)`, 'foo');
await pollFor(page, `window.term.buffer.active.getLine(1).translateToString(true)`, 'bar');
await pollFor(page, `window.term.buffer.active.getLine(2).translateToString(true)`, '文');
await pollFor(page, `window.__x`, '123');
});
it('writeln - bytes (UTF8)', async () => {
await openTerminal();
await openTerminal(page);
await page.evaluate(`
window.term.writeln(new Uint8Array([102, 111, 111]));
window.term.writeln(new Uint8Array([98, 97, 114]));
window.term.writeln(new Uint8Array([230, 150, 135]));
`);
await pollFor(page, `window.term.buffer.getLine(0).translateToString(true)`, 'foo');
await pollFor(page, `window.term.buffer.getLine(1).translateToString(true)`, 'bar');
await pollFor(page, `window.term.buffer.getLine(2).translateToString(true)`, '文');
await pollFor(page, `window.term.buffer.active.getLine(0).translateToString(true)`, 'foo');
await pollFor(page, `window.term.buffer.active.getLine(1).translateToString(true)`, 'bar');
await pollFor(page, `window.term.buffer.active.getLine(2).translateToString(true)`, '文');
});
it('paste', async () => {
await openTerminal();
await openTerminal(page);
await page.evaluate(`
window.calls = [];
window.term.onData(e => calls.push(e));
@@ -134,7 +133,7 @@ describe('API Integration Tests', function(): void {
});
it('clear', async () => {
await openTerminal({ rows: 5 });
await openTerminal(page, { rows: 5 });
await page.evaluate(`
window.term.write('test0');
window.parsed = 0;
@@ -144,15 +143,15 @@ describe('API Integration Tests', function(): void {
`);
await pollFor(page, `window.parsed`, 9);
await page.evaluate(`window.term.clear()`);
await pollFor(page, `window.term.buffer.length`, 5);
await pollFor(page, `window.term.buffer.getLine(0).translateToString(true)`, 'test9');
await pollFor(page, `window.term.buffer.active.length`, 5);
await pollFor(page, `window.term.buffer.active.getLine(0).translateToString(true)`, 'test9');
for (let i = 1; i < 5; i++) {
await pollFor(page, `window.term.buffer.getLine(${i}).translateToString(true)`, '');
await pollFor(page, `window.term.buffer.active.getLine(${i}).translateToString(true)`, '');
}
});
it('getOption, setOption', async () => {
await openTerminal();
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');
@@ -160,7 +159,7 @@ describe('API Integration Tests', function(): void {
describe('renderer', () => {
it('foreground', async () => {
await openTerminal({ rendererType: 'dom' });
await openTerminal(page, { rendererType: 'dom' });
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(`
@@ -185,7 +184,7 @@ describe('API Integration Tests', function(): void {
});
it('background', async () => {
await openTerminal({ rendererType: 'dom' });
await openTerminal(page, { rendererType: 'dom' });
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(`
@@ -211,7 +210,7 @@ describe('API Integration Tests', function(): void {
});
it('selection', async () => {
await openTerminal({ rows: 5, cols: 5 });
await openTerminal(page, { rows: 5, cols: 5 });
await page.evaluate(`window.term.write('\\n\\nfoo\\n\\n\\rbar\\n\\n\\rbaz')`);
assert.equal(await page.evaluate(`window.term.hasSelection()`), false);
assert.equal(await page.evaluate(`window.term.getSelection()`), '');
@@ -231,7 +230,7 @@ describe('API Integration Tests', function(): void {
});
it('focus, blur', async () => {
await openTerminal();
await openTerminal(page);
assert.equal(await page.evaluate(`document.activeElement.className`), '');
await page.evaluate(`window.term.focus()`);
assert.equal(await page.evaluate(`document.activeElement.className`), 'xterm-helper-textarea');
@@ -241,7 +240,7 @@ describe('API Integration Tests', function(): void {
describe('loadAddon', () => {
it('constructor', async () => {
await openTerminal({ cols: 5 });
await openTerminal(page, { cols: 5 });
await page.evaluate(`
window.cols = 0;
window.term.loadAddon({
@@ -253,7 +252,7 @@ describe('API Integration Tests', function(): void {
});
it('dispose (addon)', async () => {
await openTerminal();
await openTerminal(page);
await page.evaluate(`
window.disposeCalled = false
window.addon = {
@@ -268,7 +267,7 @@ describe('API Integration Tests', function(): void {
});
it('dispose (terminal)', async () => {
await openTerminal();
await openTerminal(page);
await page.evaluate(`
window.disposeCalled = false
window.term.loadAddon({
@@ -284,7 +283,7 @@ describe('API Integration Tests', function(): void {
describe('Events', () => {
it('onCursorMove', async () => {
await openTerminal();
await openTerminal(page);
await page.evaluate(`
window.callCount = 0;
window.term.onCursorMove(e => window.callCount++);
@@ -296,7 +295,7 @@ describe('API Integration Tests', function(): void {
});
it('onData', async () => {
await openTerminal();
await openTerminal(page);
await page.evaluate(`
window.calls = [];
window.term.onData(e => calls.push(e));
@@ -306,7 +305,7 @@ describe('API Integration Tests', function(): void {
});
it('onKey', async () => {
await openTerminal();
await openTerminal(page);
await page.evaluate(`
window.calls = [];
window.term.onKey(e => calls.push(e.key));
@@ -316,7 +315,7 @@ describe('API Integration Tests', function(): void {
});
it('onLineFeed', async () => {
await openTerminal();
await openTerminal(page);
await page.evaluate(`
window.callCount = 0;
window.term.onLineFeed(() => callCount++);
@@ -328,7 +327,7 @@ describe('API Integration Tests', function(): void {
});
it('onScroll', async () => {
await openTerminal({ rows: 5 });
await openTerminal(page, { rows: 5 });
await page.evaluate(`
window.calls = [];
window.term.onScroll(e => window.calls.push(e));
@@ -344,7 +343,7 @@ describe('API Integration Tests', function(): void {
});
it('onSelectionChange', async () => {
await openTerminal();
await openTerminal(page);
await page.evaluate(`
window.callCount = 0;
window.term.onSelectionChange(() => window.callCount++);
@@ -356,9 +355,9 @@ describe('API Integration Tests', function(): void {
await pollFor(page, `window.callCount`, 2);
});
it('onRender', async function (): Promise<void> {
it('onRender', async function(): Promise<void> {
this.retries(3);
await openTerminal();
await openTerminal(page);
await timeout(20); // Ensure all init events are fired
await page.evaluate(`
window.calls = [];
@@ -372,7 +371,7 @@ describe('API Integration Tests', function(): void {
});
it('onResize', async () => {
await openTerminal();
await openTerminal(page);
await timeout(20); // Ensure all init events are fired
await page.evaluate(`
window.calls = [];
@@ -386,7 +385,7 @@ describe('API Integration Tests', function(): void {
});
it('onTitleChange', async () => {
await openTerminal();
await openTerminal(page);
await page.evaluate(`
window.calls = [];
window.term.onTitleChange(e => window.calls.push(e));
@@ -399,116 +398,148 @@ describe('API Integration Tests', function(): void {
describe('buffer', () => {
it('cursorX, cursorY', async () => {
await openTerminal({ rows: 5, cols: 5 });
assert.equal(await page.evaluate(`window.term.buffer.cursorX`), 0);
assert.equal(await page.evaluate(`window.term.buffer.cursorY`), 0);
await openTerminal(page, { rows: 5, cols: 5 });
assert.equal(await page.evaluate(`window.term.buffer.active.cursorX`), 0);
assert.equal(await page.evaluate(`window.term.buffer.active.cursorY`), 0);
await writeSync(page, 'foo');
assert.equal(await page.evaluate(`window.term.buffer.cursorX`), 3);
assert.equal(await page.evaluate(`window.term.buffer.cursorY`), 0);
assert.equal(await page.evaluate(`window.term.buffer.active.cursorX`), 3);
assert.equal(await page.evaluate(`window.term.buffer.active.cursorY`), 0);
await writeSync(page, '\\n');
assert.equal(await page.evaluate(`window.term.buffer.cursorX`), 3);
assert.equal(await page.evaluate(`window.term.buffer.cursorY`), 1);
assert.equal(await page.evaluate(`window.term.buffer.active.cursorX`), 3);
assert.equal(await page.evaluate(`window.term.buffer.active.cursorY`), 1);
await writeSync(page, '\\r');
assert.equal(await page.evaluate(`window.term.buffer.cursorX`), 0);
assert.equal(await page.evaluate(`window.term.buffer.cursorY`), 1);
assert.equal(await page.evaluate(`window.term.buffer.active.cursorX`), 0);
assert.equal(await page.evaluate(`window.term.buffer.active.cursorY`), 1);
await writeSync(page, 'abcde');
assert.equal(await page.evaluate(`window.term.buffer.cursorX`), 5);
assert.equal(await page.evaluate(`window.term.buffer.cursorY`), 1);
assert.equal(await page.evaluate(`window.term.buffer.active.cursorX`), 5);
assert.equal(await page.evaluate(`window.term.buffer.active.cursorY`), 1);
await writeSync(page, '\\n\\r\\n\\n\\n\\n\\n');
assert.equal(await page.evaluate(`window.term.buffer.cursorX`), 0);
assert.equal(await page.evaluate(`window.term.buffer.cursorY`), 4);
assert.equal(await page.evaluate(`window.term.buffer.active.cursorX`), 0);
assert.equal(await page.evaluate(`window.term.buffer.active.cursorY`), 4);
});
it('viewportY', async () => {
await openTerminal({ rows: 5 });
assert.equal(await page.evaluate(`window.term.buffer.viewportY`), 0);
await openTerminal(page, { rows: 5 });
assert.equal(await page.evaluate(`window.term.buffer.active.viewportY`), 0);
await writeSync(page, '\\n\\n\\n\\n');
assert.equal(await page.evaluate(`window.term.buffer.viewportY`), 0);
assert.equal(await page.evaluate(`window.term.buffer.active.viewportY`), 0);
await writeSync(page, '\\n');
assert.equal(await page.evaluate(`window.term.buffer.viewportY`), 1);
assert.equal(await page.evaluate(`window.term.buffer.active.viewportY`), 1);
await writeSync(page, '\\n\\n\\n\\n');
assert.equal(await page.evaluate(`window.term.buffer.viewportY`), 5);
assert.equal(await page.evaluate(`window.term.buffer.active.viewportY`), 5);
await page.evaluate(`window.term.scrollLines(-1)`);
assert.equal(await page.evaluate(`window.term.buffer.viewportY`), 4);
assert.equal(await page.evaluate(`window.term.buffer.active.viewportY`), 4);
await page.evaluate(`window.term.scrollToTop()`);
assert.equal(await page.evaluate(`window.term.buffer.viewportY`), 0);
assert.equal(await page.evaluate(`window.term.buffer.active.viewportY`), 0);
});
it('baseY', async () => {
await openTerminal({ rows: 5 });
assert.equal(await page.evaluate(`window.term.buffer.baseY`), 0);
await openTerminal(page, { rows: 5 });
assert.equal(await page.evaluate(`window.term.buffer.active.baseY`), 0);
await writeSync(page, '\\n\\n\\n\\n');
assert.equal(await page.evaluate(`window.term.buffer.baseY`), 0);
assert.equal(await page.evaluate(`window.term.buffer.active.baseY`), 0);
await writeSync(page, '\\n');
assert.equal(await page.evaluate(`window.term.buffer.baseY`), 1);
assert.equal(await page.evaluate(`window.term.buffer.active.baseY`), 1);
await writeSync(page, '\\n\\n\\n\\n');
assert.equal(await page.evaluate(`window.term.buffer.baseY`), 5);
assert.equal(await page.evaluate(`window.term.buffer.active.baseY`), 5);
await page.evaluate(`window.term.scrollLines(-1)`);
assert.equal(await page.evaluate(`window.term.buffer.baseY`), 5);
assert.equal(await page.evaluate(`window.term.buffer.active.baseY`), 5);
await page.evaluate(`window.term.scrollToTop()`);
assert.equal(await page.evaluate(`window.term.buffer.baseY`), 5);
assert.equal(await page.evaluate(`window.term.buffer.active.baseY`), 5);
});
it('length', async () => {
await openTerminal({ rows: 5 });
assert.equal(await page.evaluate(`window.term.buffer.length`), 5);
await openTerminal(page, { rows: 5 });
assert.equal(await page.evaluate(`window.term.buffer.active.length`), 5);
await writeSync(page, '\\n\\n\\n\\n');
assert.equal(await page.evaluate(`window.term.buffer.length`), 5);
assert.equal(await page.evaluate(`window.term.buffer.active.length`), 5);
await writeSync(page, '\\n');
assert.equal(await page.evaluate(`window.term.buffer.length`), 6);
assert.equal(await page.evaluate(`window.term.buffer.active.length`), 6);
await writeSync(page, '\\n\\n\\n\\n');
assert.equal(await page.evaluate(`window.term.buffer.length`), 10);
assert.equal(await page.evaluate(`window.term.buffer.active.length`), 10);
});
describe('getLine', () => {
it('invalid index', async () => {
await openTerminal({ rows: 5 });
assert.equal(await page.evaluate(`window.term.buffer.getLine(-1)`), undefined);
assert.equal(await page.evaluate(`window.term.buffer.getLine(5)`), undefined);
await openTerminal(page, { rows: 5 });
assert.equal(await page.evaluate(`window.term.buffer.active.getLine(-1)`), undefined);
assert.equal(await page.evaluate(`window.term.buffer.active.getLine(5)`), undefined);
});
it('isWrapped', async () => {
await openTerminal({ cols: 5 });
assert.equal(await page.evaluate(`window.term.buffer.getLine(0).isWrapped`), false);
assert.equal(await page.evaluate(`window.term.buffer.getLine(1).isWrapped`), false);
await openTerminal(page, { cols: 5 });
assert.equal(await page.evaluate(`window.term.buffer.active.getLine(0).isWrapped`), false);
assert.equal(await page.evaluate(`window.term.buffer.active.getLine(1).isWrapped`), false);
await writeSync(page, 'abcde');
assert.equal(await page.evaluate(`window.term.buffer.getLine(0).isWrapped`), false);
assert.equal(await page.evaluate(`window.term.buffer.getLine(1).isWrapped`), false);
assert.equal(await page.evaluate(`window.term.buffer.active.getLine(0).isWrapped`), false);
assert.equal(await page.evaluate(`window.term.buffer.active.getLine(1).isWrapped`), false);
await writeSync(page, 'f');
assert.equal(await page.evaluate(`window.term.buffer.getLine(0).isWrapped`), false);
assert.equal(await page.evaluate(`window.term.buffer.getLine(1).isWrapped`), true);
assert.equal(await page.evaluate(`window.term.buffer.active.getLine(0).isWrapped`), false);
assert.equal(await page.evaluate(`window.term.buffer.active.getLine(1).isWrapped`), true);
});
it('translateToString', async () => {
await openTerminal({ cols: 5 });
assert.equal(await page.evaluate(`window.term.buffer.getLine(0).translateToString()`), ' ');
assert.equal(await page.evaluate(`window.term.buffer.getLine(0).translateToString(true)`), '');
await openTerminal(page, { cols: 5 });
assert.equal(await page.evaluate(`window.term.buffer.active.getLine(0).translateToString()`), ' ');
assert.equal(await page.evaluate(`window.term.buffer.active.getLine(0).translateToString(true)`), '');
await writeSync(page, 'foo');
assert.equal(await page.evaluate(`window.term.buffer.getLine(0).translateToString()`), 'foo ');
assert.equal(await page.evaluate(`window.term.buffer.getLine(0).translateToString(true)`), 'foo');
assert.equal(await page.evaluate(`window.term.buffer.active.getLine(0).translateToString()`), 'foo ');
assert.equal(await page.evaluate(`window.term.buffer.active.getLine(0).translateToString(true)`), 'foo');
await writeSync(page, 'bar');
assert.equal(await page.evaluate(`window.term.buffer.getLine(0).translateToString()`), 'fooba');
assert.equal(await page.evaluate(`window.term.buffer.getLine(0).translateToString(true)`), 'fooba');
assert.equal(await page.evaluate(`window.term.buffer.getLine(1).translateToString(true)`), 'r');
assert.equal(await page.evaluate(`window.term.buffer.getLine(0).translateToString(false, 1)`), 'ooba');
assert.equal(await page.evaluate(`window.term.buffer.getLine(0).translateToString(false, 1, 3)`), 'oo');
assert.equal(await page.evaluate(`window.term.buffer.active.getLine(0).translateToString()`), 'fooba');
assert.equal(await page.evaluate(`window.term.buffer.active.getLine(0).translateToString(true)`), 'fooba');
assert.equal(await page.evaluate(`window.term.buffer.active.getLine(1).translateToString(true)`), 'r');
assert.equal(await page.evaluate(`window.term.buffer.active.getLine(0).translateToString(false, 1)`), 'ooba');
assert.equal(await page.evaluate(`window.term.buffer.active.getLine(0).translateToString(false, 1, 3)`), 'oo');
});
it('getCell', async () => {
await openTerminal({ cols: 5 });
assert.equal(await page.evaluate(`window.term.buffer.getLine(0).getCell(-1)`), undefined);
assert.equal(await page.evaluate(`window.term.buffer.getLine(0).getCell(5)`), undefined);
assert.equal(await page.evaluate(`window.term.buffer.getLine(0).getCell(0).getChars()`), '');
assert.equal(await page.evaluate(`window.term.buffer.getLine(0).getCell(0).getWidth()`), 1);
await openTerminal(page, { cols: 5 });
assert.equal(await page.evaluate(`window.term.buffer.active.getLine(0).getCell(-1)`), undefined);
assert.equal(await page.evaluate(`window.term.buffer.active.getLine(0).getCell(5)`), undefined);
assert.equal(await page.evaluate(`window.term.buffer.active.getLine(0).getCell(0).getChars()`), '');
assert.equal(await page.evaluate(`window.term.buffer.active.getLine(0).getCell(0).getWidth()`), 1);
await writeSync(page, 'a文');
assert.equal(await page.evaluate(`window.term.buffer.getLine(0).getCell(0).getChars()`), 'a');
assert.equal(await page.evaluate(`window.term.buffer.getLine(0).getCell(0).getWidth()`), 1);
assert.equal(await page.evaluate(`window.term.buffer.getLine(0).getCell(1).getChars()`), '文');
assert.equal(await page.evaluate(`window.term.buffer.getLine(0).getCell(1).getWidth()`), 2);
assert.equal(await page.evaluate(`window.term.buffer.getLine(0).getCell(2).getChars()`), '');
assert.equal(await page.evaluate(`window.term.buffer.getLine(0).getCell(2).getWidth()`), 0);
assert.equal(await page.evaluate(`window.term.buffer.active.getLine(0).getCell(0).getChars()`), 'a');
assert.equal(await page.evaluate(`window.term.buffer.active.getLine(0).getCell(0).getWidth()`), 1);
assert.equal(await page.evaluate(`window.term.buffer.active.getLine(0).getCell(1).getChars()`), '文');
assert.equal(await page.evaluate(`window.term.buffer.active.getLine(0).getCell(1).getWidth()`), 2);
assert.equal(await page.evaluate(`window.term.buffer.active.getLine(0).getCell(2).getChars()`), '');
assert.equal(await page.evaluate(`window.term.buffer.active.getLine(0).getCell(2).getWidth()`), 0);
});
});
it('active, normal, alternate', async () => {
await openTerminal(page, { cols: 5 });
assert.equal(await page.evaluate(`window.term.buffer.active.type`), 'normal');
assert.equal(await page.evaluate(`window.term.buffer.normal.type`), 'normal');
assert.equal(await page.evaluate(`window.term.buffer.alternate.type`), 'alternate');
await writeSync(page, 'norm ');
assert.equal(await page.evaluate(`window.term.buffer.active.getLine(0).translateToString()`), 'norm ');
assert.equal(await page.evaluate(`window.term.buffer.normal.getLine(0).translateToString()`), 'norm ');
assert.equal(await page.evaluate(`window.term.buffer.alternate.getLine(0)`), undefined);
await writeSync(page, '\\x1b[?47h\\r'); // use alternate screen buffer
assert.equal(await page.evaluate(`window.term.buffer.active.type`), 'alternate');
assert.equal(await page.evaluate(`window.term.buffer.normal.type`), 'normal');
assert.equal(await page.evaluate(`window.term.buffer.alternate.type`), 'alternate');
assert.equal(await page.evaluate(`window.term.buffer.active.getLine(0).translateToString()`), ' ');
await writeSync(page, 'alt ');
assert.equal(await page.evaluate(`window.term.buffer.active.getLine(0).translateToString()`), 'alt ');
assert.equal(await page.evaluate(`window.term.buffer.normal.getLine(0).translateToString()`), 'norm ');
assert.equal(await page.evaluate(`window.term.buffer.alternate.getLine(0).translateToString()`), 'alt ');
await writeSync(page, '\\x1b[?47l\\r'); // use normal screen buffer
assert.equal(await page.evaluate(`window.term.buffer.active.type`), 'normal');
assert.equal(await page.evaluate(`window.term.buffer.normal.type`), 'normal');
assert.equal(await page.evaluate(`window.term.buffer.alternate.type`), 'alternate');
assert.equal(await page.evaluate(`window.term.buffer.active.getLine(0).translateToString()`), 'norm ');
assert.equal(await page.evaluate(`window.term.buffer.normal.getLine(0).translateToString()`), 'norm ');
assert.equal(await page.evaluate(`window.term.buffer.alternate.getLine(0)`), undefined);
})
});
it('dispose', async () => {
@@ -520,18 +551,190 @@ describe('API Integration Tests', function(): void {
});
it('dispose (opened)', async () => {
await openTerminal();
await openTerminal(page);
await page.evaluate(`window.term.dispose()`);
assert.equal(await page.evaluate(`window.term._core._isDisposed`), true);
});
describe('registerLinkProvider', () => {
it('should fire provideLink when hovering cells', async () => {
await openTerminal(page, { rendererType: 'dom' });
await page.evaluate(`
window.calls = [];
window.disposable = window.term.registerLinkProvider({
provideLink: (position, cb) => {
calls.push(position);
cb(undefined);
}
});
`);
const dims = await getDimensions();
await moveMouseCell(page, dims, 1, 1);
await moveMouseCell(page, dims, 2, 2);
await moveMouseCell(page, dims, 10, 4);
await pollFor(page, `window.calls`, [{ x: 1, y: 1 }, { x: 2, y: 2 }, { x: 10, y: 4 }]);
await page.evaluate(`window.disposable.dispose()`);
});
it('should fire hover and leave events on the link', async () => {
await openTerminal(page, { rendererType: 'dom' });
await writeSync(page, 'foo bar baz');
// Wait for renderer to catch up as links are cleared on render
await pollFor(page, `document.querySelector('.xterm-rows').textContent`, 'foo bar baz ');
await page.evaluate(`
window.calls = [];
window.disposable = window.term.registerLinkProvider({
provideLink: (position, cb) => {
window.calls.push('provide ' + position.x + ',' + position.y);
if (position.x >= 5 && position.x <= 7 && position.y === 1) {
window.calls.push('match');
cb({
range: { start: { x: 5, y: 1 }, end: { x: 7, y: 1 } },
text: 'bar',
activate: () => window.calls.push('activate'),
hover: () => window.calls.push('hover'),
leave: () => window.calls.push('leave')
});
}
}
});
`);
const dims = await getDimensions();
await moveMouseCell(page, dims, 5, 1);
await pollFor(page, `window.calls`, ['provide 5,1', 'match', 'hover']);
await moveMouseCell(page, dims, 4, 1);
await pollFor(page, `window.calls`, ['provide 5,1', 'match', 'hover', 'leave', 'provide 4,1']);
await moveMouseCell(page, dims, 7, 1);
await pollFor(page, `window.calls`, ['provide 5,1', 'match', 'hover', 'leave', 'provide 4,1', 'provide 7,1', 'match', 'hover']);
await moveMouseCell(page, dims, 8, 1);
await pollFor(page, `window.calls`, ['provide 5,1', 'match', 'hover', 'leave', 'provide 4,1', 'provide 7,1', 'match', 'hover', 'leave', 'provide 8,1']);
await page.evaluate(`window.disposable.dispose()`);
});
it('should work fine when hover and leave callbacks are not provided', async () => {
await openTerminal(page, { rendererType: 'dom' });
await writeSync(page, 'foo bar baz');
// Wait for renderer to catch up as links are cleared on render
await pollFor(page, `document.querySelector('.xterm-rows').textContent`, 'foo bar baz ');
await page.evaluate(`
window.calls = [];
window.disposable = window.term.registerLinkProvider({
provideLink: (position, cb) => {
window.calls.push('provide ' + position.x + ',' + position.y);
if (position.x >= 5 && position.x <= 7 && position.y === 1) {
window.calls.push('match');
cb({
range: { start: { x: 5, y: 1 }, end: { x: 7, y: 1 } },
text: 'bar',
activate: () => window.calls.push('activate')
});
}
}
});
`);
const dims = await getDimensions();
await moveMouseCell(page, dims, 5, 1);
await pollFor(page, `window.calls`, ['provide 5,1', 'match']);
await moveMouseCell(page, dims, 4, 1);
await pollFor(page, `window.calls`, ['provide 5,1', 'match', 'provide 4,1']);
await moveMouseCell(page, dims, 7, 1);
await pollFor(page, `window.calls`, ['provide 5,1', 'match', 'provide 4,1', 'provide 7,1', 'match']);
await moveMouseCell(page, dims, 8, 1);
await pollFor(page, `window.calls`, ['provide 5,1', 'match', 'provide 4,1', 'provide 7,1', 'match', 'provide 8,1']);
await page.evaluate(`window.disposable.dispose()`);
});
it('should fire activate events when clicking the link', async () => {
await openTerminal(page, { rendererType: 'dom' });
await writeSync(page, 'a b c');
// Wait for renderer to catch up as links are cleared on render
await pollFor(page, `document.querySelector('.xterm-rows').textContent`, 'a b c ');
// Focus terminal to avoid a render event clearing the active link
const dims = await getDimensions();
await moveMouseCell(page, dims, 5, 5);
await page.mouse.down();
await page.mouse.up();
await timeout(50); // Not sure how to avoid this timeout, checking for xterm-focus doesn't help
await page.evaluate(`
window.calls = [];
window.disposable = window.term.registerLinkProvider({
provideLink: (position, cb) => {
window.calls.push('provide ' + position.x + ',' + position.y);
cb({
range: { start: position, end: position },
text: window.term.buffer.active.getLine(position.y - 1).getCell(position.x - 1).getChars(),
activate: (_, text) => window.calls.push('activate ' + text),
hover: () => window.calls.push('hover'),
leave: () => window.calls.push('leave')
});
}
});
`);
await moveMouseCell(page, dims, 3, 1);
await pollFor(page, `window.calls`, ['provide 3,1', 'hover']);
await page.mouse.down();
await page.mouse.up();
await pollFor(page, `window.calls`, ['provide 3,1', 'hover', 'activate b']);
await moveMouseCell(page, dims, 1, 1);
await pollFor(page, `window.calls`, ['provide 3,1', 'hover', 'activate b', 'leave', 'provide 1,1', 'hover']);
await page.mouse.down();
await page.mouse.up();
await pollFor(page, `window.calls`, ['provide 3,1', 'hover', 'activate b', 'leave', 'provide 1,1', 'hover', 'activate a']);
await moveMouseCell(page, dims, 5, 1);
await pollFor(page, `window.calls`, ['provide 3,1', 'hover', 'activate b', 'leave', 'provide 1,1', 'hover', 'activate a', 'leave', 'provide 5,1', 'hover']);
await page.mouse.down();
await page.mouse.up();
await pollFor(page, `window.calls`, ['provide 3,1', 'hover', 'activate b', 'leave', 'provide 1,1', 'hover', 'activate a', 'leave', 'provide 5,1', 'hover', 'activate c']);
await page.evaluate(`window.disposable.dispose()`);
});
});
});
async function openTerminal(options: ITerminalOptions = {}): Promise<void> {
await page.evaluate(`window.term = new Terminal(${JSON.stringify(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');
}
interface IDimensions {
top: number;
left: number;
renderDimensions: IRenderDimensions;
}
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;
}
async function getDimensions(): Promise<IDimensions> {
return await page.evaluate(`
(function() {
const rect = document.querySelector('.xterm-rows').getBoundingClientRect();
return {
top: rect.top,
left: rect.left,
renderDimensions: window.term._core._renderService.dimensions
};
})();
`);
}
async function getCellCoordinates(dimensions: IDimensions, col: number, row: number): Promise<{ x: number, y: number }> {
return {
x: dimensions.left + dimensions.renderDimensions.scaledCellWidth * (col - 0.5),
y: dimensions.top + dimensions.renderDimensions.scaledCellHeight * (row - 0.5)
};
}
async function moveMouseCell(page: Page, dimensions: IDimensions, col: number, row: number): Promise<void> {
const coords = await getCellCoordinates(dimensions, col, row);
await page.mouse.move(coords.x, coords.y);
}
+29 -3
View File
@@ -3,10 +3,11 @@
* @license MIT
*/
import * as puppeteer from 'puppeteer';
import * as playwright from 'playwright-core';
import deepEqual = require('deep-equal');
import { ITerminalOptions } from 'xterm';
export async function pollFor<T>(page: puppeteer.Page, evalOrFn: string | (() => Promise<T>), val: T, preFn?: () => Promise<void>): Promise<void> {
export async function pollFor<T>(page: playwright.Page, evalOrFn: string | (() => Promise<T>), val: T, preFn?: () => Promise<void>): Promise<void> {
if (preFn) {
await preFn();
}
@@ -18,7 +19,7 @@ export async function pollFor<T>(page: puppeteer.Page, evalOrFn: string | (() =>
}
}
export async function writeSync(page: puppeteer.Page, data: string): Promise<void> {
export async function writeSync(page: playwright.Page, data: string): Promise<void> {
await page.evaluate(`
window.ready = false;
window.term.write('${data}', () => window.ready = true);
@@ -29,3 +30,28 @@ export async function writeSync(page: puppeteer.Page, data: string): Promise<voi
export async function timeout(ms: number): Promise<void> {
return new Promise<void>(r => setTimeout(r, ms));
}
export async function openTerminal(page: playwright.Page, options: ITerminalOptions = {}): Promise<void> {
await page.evaluate(`window.term = new Terminal(${JSON.stringify(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');
}
}
export function getBrowserType(): playwright.BrowserType {
// Default to chromium
let browserType: playwright.BrowserType = playwright['chromium'];
const index = process.argv.indexOf('--browser');
if (index !== -1 && process.argv.length > index + 1 && typeof process.argv[index + 1] === 'string') {
const string = process.argv[index + 1];
if (string === 'firefox' || string === 'webkit') {
browserType = playwright[string];
}
}
return browserType;
}
+14 -2
View File
@@ -2,7 +2,7 @@
"compilerOptions": {
"lib": [
"dom",
"es6",
"es6"
],
"rootDir": ".",
"outDir": "../../out-test/api",
@@ -12,10 +12,22 @@
"sourceMap": true,
"removeComments": true,
"pretty": true,
"strict": true
"strict": true,
"declaration": true,
"baseUrl": ".",
"paths": {
"browser/*": [
"../../src/browser/*"
]
}
},
"include": [
"./**/*",
"../../typings/xterm.d.ts"
],
"references": [
{
"path": "../../src/browser"
}
]
}
@@ -19,16 +19,16 @@ function toUtf32(s: string): Uint32Array {
}
class DcsHandler implements IDcsHandler {
hook(params: IParams) : void {}
put(data: Uint32Array, start: number, end: number) : void {}
unhook() :void {}
hook(params: IParams): void {}
put(data: Uint32Array, start: number, end: number): void {}
unhook(): void {}
}
perfContext('Parser throughput - 50MB data', () => {
let parsed: Uint32Array;
let parser: EscapeSequenceParser;
beforeEach(() => {
parser = new EscapeSequenceParser();
parser.setPrintHandler((data, start, end) => {});
-110
View File
@@ -1,110 +0,0 @@
{
"rulesDirectory": [
"tslint-consistent-codestyle"
],
"rules": {
"array-type": [
true,
"array"
],
"class-name": true,
"comment-format": [
true,
"check-space"
],
"curly": [
true,
"ignore-same-line"
],
"indent": [
true,
"spaces"
],
"import-blacklist": [
true,
[".*\\/out\\/.*"]
],
"interface-name": [
true,
"always-prefix"
],
"interface-over-type-literal": true,
"typedef": [
true,
"call-signature",
"parameter"
],
"eofline": true,
"new-parens": true,
"no-duplicate-imports": true,
"no-eval": true,
"no-internal-module": true,
"no-trailing-whitespace": true,
"one-variable-per-declaration": true,
"no-unsafe-finally": true,
"no-var-keyword": true,
"prefer-const": true,
"quotemark": [
true,
"single"
],
"semicolon": [
true,
"always"
],
"trailing-comma": [
true,
{
"multiline": {
"objects": "never",
"arrays": "never",
"functions": "never",
"typeLiterals": "ignore"
},
"esSpecCompliant": true
}
],
"triple-equals": true,
"typedef-whitespace": [
true,
{
"call-signature": "nospace",
"index-signature": "nospace",
"parameter": "nospace",
"property-declaration": "nospace",
"variable-declaration": "nospace"
}
],
"whitespace": [
true,
"check-branch",
"check-decl",
"check-module",
"check-operator",
"check-rest-spread",
"check-separator",
"check-type",
"check-type-operator",
"check-preblock"
],
"naming-convention": [
true,
{"type": "default", "format": "camelCase", "leadingUnderscore": "forbid"},
{"type": "type", "format": "PascalCase"},
{"type": "class", "format": "PascalCase"},
{"type": "property", "modifiers": ["const"], "format": ["camelCase", "UPPER_CASE"]},
{"type": "member", "modifiers": ["protected"], "format": "camelCase", "leadingUnderscore": "require"},
{"type": "member", "modifiers": ["private"], "format": "camelCase", "leadingUnderscore": "require"},
{"type": "variable", "modifiers": ["const"], "format": ["camelCase", "UPPER_CASE"]},
{"type": "variable", "modifiers": ["const", "export"], "filter": "^I.+Service$", "format": "PascalCase", "prefix": "I"},
{"type": "interface", "prefix": "I"}
],
"no-else-after-return": {
"options": "allow-else-if"
},
"prefer-const-enum": [
true
]
}
}
+151 -28
View File
@@ -45,7 +45,7 @@ declare module 'xterm' {
/**
* The type of the bell notification the terminal will use.
*/
bellStyle?: 'none' /*| 'visual'*/ | 'sound' /*| 'both'*/;
bellStyle?: 'none' | 'sound';
/**
* When enabled the cursor will be set to the beginning of the next line
@@ -387,10 +387,10 @@ declare module 'xterm' {
/**
* Enable various window manipulation and report features (CSI Ps ; Ps ; Ps t).
*
*
* Most settings have no default implementation, as they heavily rely on
* the embedding environment.
*
*
* To implement a feature, create a custom CSI hook like this:
* ```ts
* term.parser.addCsiHandler({final: 't'}, params => {
@@ -403,8 +403,8 @@ declare module 'xterm' {
* return false; // any Ps that was not handled
* });
* ```
*
* Note on security:
*
* Note on security:
* Most features are meant to deal with some information of the host machine
* where the terminal runs on. This is seen as a security risk possibly leaking
* sensitive data of the host to the program in the terminal. Therefore all options
@@ -413,18 +413,18 @@ declare module 'xterm' {
*/
export interface IWindowOptions {
/**
* Ps=1 De-iconify window.
* Ps=1 De-iconify window.
* No default implementation.
*/
restoreWin?: boolean;
/**
* Ps=2 Iconify window.
* Ps=2 Iconify window.
* No default implementation.
*/
minimizeWin?: boolean;
/**
* Ps=3 ; x ; y
* Move window to [x, y].
* Move window to [x, y].
* No default implementation.
*/
setWinPosition?: boolean;
@@ -432,17 +432,17 @@ declare module 'xterm' {
* Ps = 4 ; height ; width
* Resize the window to given `height` and `width` in pixels.
* Omitted parameters should reuse the current height or width.
* Zero parameters should use the display's height or width.
* Zero parameters should use the display's height or width.
* No default implementation.
*/
setWinSizePixels?: boolean;
/**
* Ps=5 Raise the window to the front of the stacking order.
* Ps=5 Raise the window to the front of the stacking order.
* No default implementation.
*/
raiseWin?: boolean;
/**
* Ps=6 Lower the xterm window to the bottom of the stacking order.
* Ps=6 Lower the xterm window to the bottom of the stacking order.
* No default implementation.
*/
lowerWin?: boolean;
@@ -452,7 +452,7 @@ declare module 'xterm' {
* Ps = 8 ; height ; width
* Resize the text area to given height and width in characters.
* Omitted parameters should reuse the current height or width.
* Zero parameters use the display's height or width.
* Zero parameters use the display's height or width.
* No default implementation.
*/
setWinSizeChars?: boolean;
@@ -460,81 +460,81 @@ declare module 'xterm' {
* Ps=9 ; 0 Restore maximized window.
* Ps=9 ; 1 Maximize window (i.e., resize to screen size).
* Ps=9 ; 2 Maximize window vertically.
* Ps=9 ; 3 Maximize window horizontally.
* Ps=9 ; 3 Maximize window horizontally.
* No default implementation.
*/
maximizeWin?: boolean;
/**
* Ps=10 ; 0 Undo full-screen mode.
* Ps=10 ; 1 Change to full-screen.
* Ps=10 ; 2 Toggle full-screen.
* Ps=10 ; 2 Toggle full-screen.
* No default implementation.
*/
fullscreenWin?: boolean;
/** Ps=11 Report xterm window state.
* If the xterm window is non-iconified, it returns "CSI 1 t".
* If the xterm window is iconified, it returns "CSI 2 t".
* If the xterm window is iconified, it returns "CSI 2 t".
* No default implementation.
*/
getWinState?: boolean;
/**
* Ps=13 Report xterm window position. Result is "CSI 3 ; x ; y t".
* Ps=13 ; 2 Report xterm text-area position. Result is "CSI 3 ; x ; y t".
* Ps=13 ; 2 Report xterm text-area position. Result is "CSI 3 ; x ; y t".
* No default implementation.
*/
getWinPosition?: boolean;
/**
* Ps=14 Report xterm text area size in pixels. Result is "CSI 4 ; height ; width t".
* Ps=14 ; 2 Report xterm window size in pixels. Result is "CSI 4 ; height ; width t".
* Ps=14 ; 2 Report xterm window size in pixels. Result is "CSI 4 ; height ; width t".
* Has a default implementation.
*/
getWinSizePixels?: boolean;
/**
* Ps=15 Report size of the screen in pixels. Result is "CSI 5 ; height ; width t".
* Ps=15 Report size of the screen in pixels. Result is "CSI 5 ; height ; width t".
* No default implementation.
*/
getScreenSizePixels?: boolean;
/**
* Ps=16 Report xterm character cell size in pixels. Result is "CSI 6 ; height ; width t".
* Ps=16 Report xterm character cell size in pixels. Result is "CSI 6 ; height ; width t".
* Has a default implementation.
*/
getCellSizePixels?: boolean;
/**
* Ps=18 Report the size of the text area in characters. Result is "CSI 8 ; height ; width t".
* Ps=18 Report the size of the text area in characters. Result is "CSI 8 ; height ; width t".
* Has a default implementation.
*/
getWinSizeChars?: boolean;
/**
* Ps=19 Report the size of the screen in characters. Result is "CSI 9 ; height ; width t".
* Ps=19 Report the size of the screen in characters. Result is "CSI 9 ; height ; width t".
* No default implementation.
*/
getScreenSizeChars?: boolean;
/**
* Ps=20 Report xterm window's icon label. Result is "OSC L label ST".
* Ps=20 Report xterm window's icon label. Result is "OSC L label ST".
* No default implementation.
*/
getIconTitle?: boolean;
/**
* Ps=21 Report xterm window's title. Result is "OSC l label ST".
* Ps=21 Report xterm window's title. Result is "OSC l label ST".
* No default implementation.
*/
getWinTitle?: boolean;
/**
* Ps=22 ; 0 Save xterm icon and window title on stack.
* Ps=22 ; 1 Save xterm icon title on stack.
* Ps=22 ; 2 Save xterm window title on stack.
* Ps=22 ; 2 Save xterm window title on stack.
* All variants have a default implementation.
*/
pushTitle?: boolean;
/**
* Ps=23 ; 0 Restore xterm icon and window title from stack.
* Ps=23 ; 1 Restore xterm icon title from stack.
* Ps=23 ; 2 Restore xterm window title from stack.
* Ps=23 ; 2 Restore xterm window title from stack.
* All variants have a default implementation.
*/
popTitle?: boolean;
/**
* Ps>=24 Resize to Ps lines (DECSLPP).
* Ps>=24 Resize to Ps lines (DECSLPP).
* DECSLPP is not implemented. This settings is also used to
* enable / disable DECCOLM (earlier variant of DECSLPP).
*/
@@ -574,7 +574,7 @@ declare module 'xterm' {
* normal buffer or the alt buffer depending on what's running in the
* terminal.
*/
readonly buffer: IBuffer;
readonly buffer: IBufferNamespace;
/**
* (EXPERIMENTAL) Get all markers registered against the buffer. If the alt
@@ -722,6 +722,8 @@ declare module 'xterm' {
/**
* (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.
@@ -733,10 +735,20 @@ declare module 'xterm' {
/**
* (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;
/**
* (EXPERIMENTAL) 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 in the order in which they are registered.
* @param linkProvider The link provider to use to detect links.
*/
registerLinkProvider(linkProvider: ILinkProvider): IDisposable;
/**
* (EXPERIMENTAL) Registers a character joiner, allowing custom sequences of
* characters to be rendered as a single unit. This is useful in particular
@@ -1073,10 +1085,94 @@ declare module 'xterm' {
y: number;
}
/**
* A custom link provider.
*/
interface ILinkProvider {
/**
* Provides a link a buffer position
* @param position The position of the buffer that is currently active.
* @param callback The callback to be fired with the resulting link or
* `undefined` when ready.
*/
provideLink(position: IBufferCellPosition, callback: (link: ILink | undefined) => void): void;
}
/**
* A link within the terminal.
*/
interface ILink {
/**
* The buffer range of the link.
*/
range: IBufferRange;
/**
* The text of the link.
*/
text: string;
/**
* Calls when the link is activated.
* @param event The mouse event triggering the callback.
* @param text The text of the link.
*/
activate(event: MouseEvent, text: string): void;
/**
* Called when the mouse hovers the link.
* @param event The mouse event triggering the callback.
* @param text The text of the link.
*/
hover?(event: MouseEvent, text: string): void;
/**
* Called when the mouse leaves the link.
* @param event The mouse event triggering the callback.
* @param text The text of the link.
*/
leave?(event: MouseEvent, text: string): void;
}
/**
* A range within a buffer.
*/
interface IBufferRange {
/**
* The start position of the range.
*/
start: IBufferCellPosition;
/**
* The end position of the range.
*/
end: IBufferCellPosition;
}
/**
* A position within a buffer.
*/
interface IBufferCellPosition {
/**
* The x position within the buffer.
*/
x: number;
/**
* The y position within the buffer.
*/
y: number;
}
/**
* Represents a terminal buffer.
*/
interface IBuffer {
/**
* The type of the buffer.
*/
readonly type: 'normal' | 'alternate';
/**
* The y position of the cursor. This ranges between `0` (when the
* cursor is at baseY) and `Terminal.rows - 1` (when the cursor is on the
@@ -1086,7 +1182,7 @@ declare module 'xterm' {
/**
* The x position of the cursor. This ranges between `0` (left side) and
* `Terminal.cols - 1` (right side).
* `Terminal.cols` (after last cell of the row).
*/
readonly cursorX: number;
@@ -1126,6 +1222,33 @@ declare module 'xterm' {
getNullCell(): IBufferCell;
}
/**
* Represents the terminal's set of buffers.
*/
interface IBufferNamespace {
/**
* The active buffer, this will either be the normal or alternate buffers.
*/
readonly active: IBuffer;
/**
* The normal buffer.
*/
readonly normal: IBuffer;
/**
* The alternate buffer, this becomes the active buffer when an application
* enters this mode via DECSET (`CSI ? 4 7 h`)
*/
readonly alternate: IBuffer;
/**
* Adds an event listener for when the active buffer changes.
* @returns an `IDisposable` to stop listening.
*/
onBufferChange: IEvent<IBuffer>;
}
/**
* Represents a line in the terminal's buffer.
*/
+2274 -1928
View File
File diff suppressed because it is too large Load Diff