Compare commits

...
244 Commits
Author SHA1 Message Date
Daniel Imms 3ec926750a Merge pull request #2929 from Tyriar/4_6_0
v4.6.0 and bump all addons
2020-05-13 07:29:15 -07:00
Daniel Imms 6d8e9c43c9 v4.6.0 and bump all addons 2020-05-13 07:05:58 -07:00
Daniel Imms 69f3f302d6 Merge pull request #2928 from xtermjs/dependabot/npm_and_yarn/typescript-eslint/eslint-plugin-2.33.0
Bump @typescript-eslint/eslint-plugin from 2.32.0 to 2.33.0
2020-05-13 06:37:16 -07:00
dependabot-preview[bot] 1282900140 Bump @typescript-eslint/eslint-plugin from 2.32.0 to 2.33.0
Bumps [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) from 2.32.0 to 2.33.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.33.0/packages/eslint-plugin)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-05-13 13:23:37 +00:00
Daniel Imms 768710b2d7 Merge pull request #2927 from xtermjs/dependabot/npm_and_yarn/typescript-eslint/parser-2.33.0
Bump @typescript-eslint/parser from 2.32.0 to 2.33.0
2020-05-13 05:43:51 -07:00
Daniel Imms 8750ad4ec2 Merge pull request #2926 from xtermjs/dependabot/npm_and_yarn/typescript-3.9.2
Bump typescript from 3.8.3 to 3.9.2
2020-05-13 05:42:33 -07:00
dependabot-preview[bot] 409d995080 Bump @typescript-eslint/parser from 2.32.0 to 2.33.0
Bumps [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) from 2.32.0 to 2.33.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.33.0/packages/parser)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-05-13 08:09:36 +00:00
dependabot-preview[bot] a151b72fc5 Bump typescript from 3.8.3 to 3.9.2
Bumps [typescript](https://github.com/Microsoft/TypeScript) from 3.8.3 to 3.9.2.
- [Release notes](https://github.com/Microsoft/TypeScript/releases)
- [Commits](https://github.com/Microsoft/TypeScript/compare/v3.8.3...v3.9.2)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-05-13 08:09:16 +00:00
Daniel Imms 0626b7ec22 Merge pull request #2923 from xtermjs/dependabot/npm_and_yarn/typescript-eslint/parser-2.32.0
Bump @typescript-eslint/parser from 2.31.0 to 2.32.0
2020-05-12 08:27:10 -07:00
dependabot-preview[bot] 91205710ef Bump @typescript-eslint/parser from 2.31.0 to 2.32.0
Bumps [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) from 2.31.0 to 2.32.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.32.0/packages/parser)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-05-12 14:53:11 +00:00
Daniel Imms 5a4f352bb0 Merge pull request #2924 from xtermjs/dependabot/npm_and_yarn/typescript-eslint/eslint-plugin-2.32.0
Bump @typescript-eslint/eslint-plugin from 2.31.0 to 2.32.0
2020-05-12 07:51:31 -07:00
dependabot-preview[bot] f62a14e976 Bump @typescript-eslint/eslint-plugin from 2.31.0 to 2.32.0
Bumps [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) from 2.31.0 to 2.32.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.32.0/packages/eslint-plugin)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-05-12 12:08:11 +00:00
Daniel Imms 5f3fb410f7 Merge pull request #2925 from xtermjs/dependabot/npm_and_yarn/ts-loader-7.0.4
Bump ts-loader from 7.0.3 to 7.0.4
2020-05-12 05:06:27 -07:00
dependabot-preview[bot] 2d8dfde660 Bump ts-loader from 7.0.3 to 7.0.4
Bumps [ts-loader](https://github.com/TypeStrong/ts-loader) from 7.0.3 to 7.0.4.
- [Release notes](https://github.com/TypeStrong/ts-loader/releases)
- [Changelog](https://github.com/TypeStrong/ts-loader/blob/master/CHANGELOG.md)
- [Commits](https://github.com/TypeStrong/ts-loader/compare/v7.0.3...v7.0.4)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-05-12 07:54:10 +00:00
Daniel Imms 7fb885ce7d Merge pull request #2921 from Tyriar/2826_api_check
Add a proposed API check option
2020-05-11 04:46:45 -07:00
Daniel Imms 4aa3097cd1 Merge pull request #2922 from xtermjs/dependabot/npm_and_yarn/ws-7.3.0
Bump ws from 7.2.5 to 7.3.0
2020-05-11 04:35:48 -07:00
dependabot-preview[bot] 0b87d9a39c Bump ws from 7.2.5 to 7.3.0
Bumps [ws](https://github.com/websockets/ws) from 7.2.5 to 7.3.0.
- [Release notes](https://github.com/websockets/ws/releases)
- [Commits](https://github.com/websockets/ws/compare/7.2.5...7.3.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-05-11 08:29:28 +00:00
Daniel Imms 92fdf0ce58 Fix lint issue 2020-05-08 12:41:26 -07:00
Daniel Imms aa59f34aab Add a proposed API check option
Part of #2826
2020-05-08 12:41:10 -07:00
Daniel Imms 05c149783f Merge pull request #2914 from drauggres/bug/demo_addons_list
Fix demo. Clear addons list on terminal recreation
2020-05-08 11:27:51 -07:00
Daniel Imms c3bc436e49 Merge branch 'master' into bug/demo_addons_list 2020-05-08 11:26:25 -07:00
Daniel Imms 64328ee39c Merge pull request #2920 from xtermjs/dependabot/npm_and_yarn/ts-loader-7.0.3
Bump ts-loader from 7.0.2 to 7.0.3
2020-05-07 04:53:58 -07:00
dependabot-preview[bot] 61667c1c23 Bump ts-loader from 7.0.2 to 7.0.3
Bumps [ts-loader](https://github.com/TypeStrong/ts-loader) from 7.0.2 to 7.0.3.
- [Release notes](https://github.com/TypeStrong/ts-loader/releases)
- [Changelog](https://github.com/TypeStrong/ts-loader/blob/master/CHANGELOG.md)
- [Commits](https://github.com/TypeStrong/ts-loader/compare/v7.0.2...v7.0.3)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-05-07 07:52:11 +00:00
Daniel Imms 3a9cdbab0d Merge pull request #2916 from xtermjs/tyriar/intersecting
Remove intersecting lower priority links
2020-05-06 06:55:51 -07:00
Daniel Imms 27af770068 Remove intersecting lower priority links
Fixes #2848
2020-05-06 06:16:36 -07:00
Sergey Volkov 71e45cdd25 Fix demo. Clear addons list on terminal recreation 2020-05-06 15:28:23 +03:00
Daniel Imms f9adf8ad83 Merge pull request #2912 from xtermjs/tyriar/2911
Workaround for hover firing multiple times
2020-05-06 04:59:33 -07:00
Daniel Imms f886698dc3 Workaround for hover firing multiple times
Fixes #2911
2020-05-06 04:53:25 -07:00
Daniel Imms 7ab0a42b24 Merge pull request #2908 from Tyriar/line_links
Change link provider API to provide all links for a line
2020-05-06 03:05:32 -07:00
Daniel Imms 99e2a3db1c Add test for multiple links on one line 2020-05-06 02:56:52 -07:00
Daniel Imms 9ca6733f68 Fix api tests 2020-05-06 02:50:35 -07:00
Daniel Imms 6d5ae1416a Correct api usage in tests
assertions should still fail
2020-05-05 18:35:37 -07:00
Daniel Imms 0707398a5b Remove logs 2020-05-05 18:27:11 -07:00
Daniel Imms 75e4e2089e Improve api 2020-05-05 18:26:07 -07:00
Daniel Imms 484883f442 Fix Linkifier2 tests 2020-05-05 18:24:45 -07:00
Daniel Imms d488b49fc4 Change link provider API to return all links for the line
Fixes #2848
2020-05-05 18:19:57 -07:00
Daniel Imms 1168c6c52f Merge pull request #2903 from theflyingape/patch-1
Update README.md
2020-05-05 05:50:14 -07:00
Daniel Imms f4e87cdbb3 Merge pull request #2905 from xtermjs/dependabot/npm_and_yarn/typescript-eslint/eslint-plugin-2.31.0
Bump @typescript-eslint/eslint-plugin from 2.30.0 to 2.31.0
2020-05-05 05:33:03 -07:00
dependabot-preview[bot] 24c64bfc7f Bump @typescript-eslint/eslint-plugin from 2.30.0 to 2.31.0
Bumps [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) from 2.30.0 to 2.31.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.31.0/packages/eslint-plugin)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-05-05 12:06:10 +00:00
Daniel Imms ed5aeb7472 Merge pull request #2906 from xtermjs/dependabot/npm_and_yarn/typescript-eslint/parser-2.31.0
Bump @typescript-eslint/parser from 2.30.0 to 2.31.0
2020-05-05 05:04:34 -07:00
dependabot-preview[bot] eb3f933290 Bump @typescript-eslint/parser from 2.30.0 to 2.31.0
Bumps [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) from 2.30.0 to 2.31.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.31.0/packages/parser)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-05-05 08:06:57 +00:00
Robert Hurst 832c43a509 Update README.md
A TypeScript port (app & client) of the venerable Hack & Slash game.
2020-05-04 09:57:35 -04:00
Daniel Imms 31d3ae451c Merge pull request #2899 from Tyriar/memoryleak
Fix listener leaks causing Terminal memory from not getting freed
2020-05-03 15:30:29 -07:00
Daniel Imms e413133602 Remove unused helper 2020-05-03 15:26:02 -07:00
Daniel Imms 06b8d36cf0 Fix other leaking listeners 2020-05-03 14:37:20 -07:00
Daniel Imms b75e42a56c Fix terminal object leaking 2020-05-03 12:46:37 -07:00
Daniel Imms 66b2c01549 Merge pull request #2898 from Tyriar/eslint_js
Disable eslint in all js files
2020-05-03 07:39:11 -07:00
Daniel Imms 065eb13a9d Merge pull request #2751 from jerch/underline_core
Underline styles and colors - core part
2020-05-03 07:32:32 -07:00
Daniel Imms 15bb9aa137 Disable eslint in all js files
This was causing distracting editor diagnostics
2020-05-03 07:31:46 -07:00
Daniel Imms 07862c2e52 Fixing minor comments 2020-05-03 07:26:27 -07:00
Daniel Imms a8b6252c3c Fix compile after merge 2020-05-03 07:20:58 -07:00
Daniel Imms bbbf128b7c Fix tests 2020-05-03 07:19:10 -07:00
Daniel Imms 62b8398719 Merge branch 'master' into pr/jerch/2751 2020-05-03 07:14:02 -07:00
Daniel Imms 3ea9a7511e Merge pull request #2724 from jerch/reverse_wraparound
Reverse wraparound
2020-05-03 07:11:25 -07:00
Daniel Imms 076d51fc35 Merge branch 'master' into reverse_wraparound 2020-05-03 07:05:14 -07:00
Daniel Imms 210fa7f655 Move commented test into Terminal.test 2020-05-03 07:04:01 -07:00
Daniel Imms fbdb01d43c Merge pull request #2847 from LabhanshAgrawal/ligatures-addon
Merge ligatures addon into core repo
2020-05-03 06:54:46 -07:00
Daniel Imms 36e241cd8d Alphabetize tsconfig addons 2020-05-03 06:49:05 -07:00
Daniel Imms c5b0068c9b Remove coveralls 2020-05-03 06:48:00 -07:00
Daniel Imms ed7bab7f02 Correct package.json repo links 2020-05-03 06:46:38 -07:00
Daniel Imms 001e16e081 Copyright 2020-05-03 06:45:12 -07:00
Daniel Imms 41670dc181 Remove test dependence on applyAddon 2020-05-03 06:44:17 -07:00
Daniel Imms 6c6900ede8 Bring ligatures readme install/usage up to date 2020-05-03 06:35:36 -07:00
Daniel Imms b450470142 Remove old addon api 'apply' call 2020-05-03 06:32:32 -07:00
Daniel Imms d98fcf8d1f Update copyright 2020-05-03 06:29:02 -07:00
Daniel Imms 5b90b8a3ad Clean up ligatures readme 2020-05-03 06:26:23 -07:00
Daniel Imms e343e690f2 Merge branch 'master' into ligatures-addon 2020-05-03 06:22:14 -07:00
Daniel Imms 6f1f4ba98f Merge pull request #2867 from rishab-pdx/#2840
Different behavior when search two same words in only one row
2020-05-03 06:21:14 -07:00
Daniel Imms 7a34b4de73 Merge pull request #2895 from Tyriar/eslint_warn
Make all eslint rules warnings
2020-05-03 06:15:15 -07:00
Daniel Imms b84831f148 Make all eslint rules warnings
eslint warnings normally don't exit with non-zero when warnings fail unless
'--max-warnings 0' is used. This commit adds that and changes all rules to
be warnings in order to improve feedback in the editor. After this commit
all warning diagnostics (yellow) are stylistic/lint-related ones that will
still fail lint, which error diagnostics (red) are actual errors that will
fail compilation (from tsc).

Fixes #2886
2020-05-03 06:07:53 -07:00
Daniel Imms d9ecafeb2a Merge pull request #2892 from Eugeny/fixed-dpr-listener
fixed ScreenDprListener never being fired
2020-05-02 15:54:52 -07:00
EugeneandDaniel Imms 930de9aeb7 Update src/browser/ScreenDprMonitor.ts
Co-authored-by: Daniel Imms <tyriar@tyriar.com>
2020-05-02 22:48:30 +02:00
Eugene Pankov 40a09d7412 fixed ScreenDprListener never being fired 2020-05-02 16:20:46 +02:00
Labhansh Agrawal ad9aabbb7e fix ci errors 2020-05-01 14:26:03 +05:30
Labhansh Agrawal 195bce0485 add dependencies as externals in webpack 2020-05-01 14:26:03 +05:30
Labhansh Agrawal 42b88f34f4 remove azure devops config 2020-05-01 14:26:03 +05:30
Labhansh Agrawal 7bde7039bb add webpack config 2020-05-01 14:26:03 +05:30
Labhansh Agrawal 4702a0af9d lint 2020-05-01 14:26:03 +05:30
Labhansh Agrawal c818381ef1 add ligatures addon config 2020-05-01 14:26:03 +05:30
Labhansh Agrawal ef4ecf7c8e clean up dev dependencies 2020-05-01 14:26:03 +05:30
Labhansh Agrawal fb65cea115 set up similar to other addons 2020-05-01 14:26:03 +05:30
Labhansh Agrawal dcca79baef delete non-required files 2020-05-01 14:26:03 +05:30
Labhansh Agrawal 15df03ba09 clone ligatures addon repo 2020-05-01 14:26:03 +05:30
rishab 5eb33e9d8a Merge branch 'master' of https://github.com/xtermjs/xterm.js into #2840 2020-04-30 14:05:01 -07:00
Daniel Imms 939de5d151 Merge pull request #2888 from lunaroyster/master
Added @ as a character in URL paths for the weblinks addon
2020-04-30 06:30:59 -07:00
Daniel Imms 4c440b8552 Merge pull request #2890 from xtermjs/dependabot/npm_and_yarn/ts-loader-7.0.2
Bump ts-loader from 7.0.1 to 7.0.2
2020-04-30 05:10:20 -07:00
dependabot-preview[bot] 1c73fd5454 Bump ts-loader from 7.0.1 to 7.0.2
Bumps [ts-loader](https://github.com/TypeStrong/ts-loader) from 7.0.1 to 7.0.2.
- [Release notes](https://github.com/TypeStrong/ts-loader/releases)
- [Changelog](https://github.com/TypeStrong/ts-loader/blob/master/CHANGELOG.md)
- [Commits](https://github.com/TypeStrong/ts-loader/compare/v7.0.1...v7.0.2)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-04-30 08:01:12 +00:00
lunaroysterandEricWangUTM 9567ae4bfe Added @ as a character to pathCharacterSet for xterm-addons-web-links
Co-authored-by: lunaroyster <oneinaquadrillion@live.com>
Co-authored-by: EricWangUTM <yifeng.wang@mail.utoronto.ca>
2020-04-30 04:35:34 +00:00
rishab 853b454eae Merge branch 'master' of https://github.com/xtermjs/xterm.js into #2840 2020-04-29 19:56:45 -07:00
Daniel Imms 94b1fec91e Merge pull request #2883 from xtermjs/dependabot/npm_and_yarn/typescript-eslint/eslint-plugin-2.30.0
Bump @typescript-eslint/eslint-plugin from 2.29.0 to 2.30.0
2020-04-28 05:57:46 -07:00
dependabot-preview[bot] a6a5c010fe Bump @typescript-eslint/eslint-plugin from 2.29.0 to 2.30.0
Bumps [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) from 2.29.0 to 2.30.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.30.0/packages/eslint-plugin)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-04-28 12:46:07 +00:00
Daniel Imms 52bcbaed08 Merge pull request #2882 from xtermjs/dependabot/npm_and_yarn/typescript-eslint/parser-2.30.0
Bump @typescript-eslint/parser from 2.29.0 to 2.30.0
2020-04-28 05:44:20 -07:00
dependabot-preview[bot] 74ac45730f Bump @typescript-eslint/parser from 2.29.0 to 2.30.0
Bumps [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) from 2.29.0 to 2.30.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.30.0/packages/parser)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-04-28 08:06:37 +00:00
Jörg Breitbart 0d0f04de5c resolve master conflicts 2020-04-27 16:16:31 +02:00
Jörg Breitbart e1be294147 Merge branch 'master' into reverse_wraparound 2020-04-27 16:11:39 +02:00
Daniel Imms 45077d7106 Merge pull request #2880 from xtermjs/dependabot/npm_and_yarn/mocha-7.1.2
Bump mocha from 7.1.1 to 7.1.2
2020-04-27 06:16:14 -07:00
dependabot-preview[bot] e65ead3f20 Bump mocha from 7.1.1 to 7.1.2
Bumps [mocha](https://github.com/mochajs/mocha) from 7.1.1 to 7.1.2.
- [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/v7.1.1...v7.1.2)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-04-27 13:01:17 +00:00
Daniel Imms 5ed6d0fedb Merge pull request #2879 from xtermjs/dependabot/npm_and_yarn/ws-7.2.5
Bump ws from 7.2.3 to 7.2.5
2020-04-27 05:59:41 -07:00
dependabot-preview[bot] 673138c5af Bump ws from 7.2.3 to 7.2.5
Bumps [ws](https://github.com/websockets/ws) from 7.2.3 to 7.2.5.
- [Release notes](https://github.com/websockets/ws/releases)
- [Commits](https://github.com/websockets/ws/compare/7.2.3...7.2.5)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-04-27 08:26:57 +00:00
Rishab 0e145a1a57 Merge branch 'master' into #2840 2020-04-26 14:42:43 -07:00
Daniel Imms a8bcfc62d0 Merge pull request #2876 from Tyriar/layers3
Move more parts of Terminal into CoreTerminal and move everything else into browser/
2020-04-26 11:19:05 -07:00
Daniel Imms 394bb38913 Remove src/tsconfig.json from eslintrc 2020-04-26 11:11:04 -07:00
Daniel Imms 80c5c94e7e Remove src project 2020-04-26 11:06:30 -07:00
Daniel Imms 03bfd2c842 Move Terminal*.test into browser 2020-04-26 10:59:32 -07:00
Daniel Imms bdb438972f Fix errors in Terminal and elsewhere
This was mostly ! to compel TS, we should actually fix this down the road
but that can come later.
2020-04-26 10:44:44 -07:00
Daniel Imms f256206fd4 Move Terminal and AccessibilityManager into browser 2020-04-26 10:18:05 -07:00
Daniel Imms 4d01929503 Introduce ICoreTerminal 2020-04-26 10:11:24 -07:00
Daniel Imms 2a9b76246b Move scroll into CoreTerminal 2020-04-26 10:02:59 -07:00
Daniel Imms 21bcef5008 Move a bunch of scroll logic into CoreTerminal
This change will now reset userScrolling on RIS
2020-04-26 09:54:44 -07:00
Daniel Imms 46e7fc61cc Move WriteBuffer into CoreTerminal 2020-04-26 09:38:34 -07:00
Daniel Imms b9dee59900 Move InputHandler init into CoreTerminal 2020-04-26 09:36:56 -07:00
Daniel Imms 2fd4c149fd Merge pull request #2874 from Tyriar/layers2
Move InputHandler into common
2020-04-26 09:08:50 -07:00
Daniel Imms d085768e8b Fix some lint issues 2020-04-26 08:23:38 -07:00
Daniel Imms 6f9730c652 Merge branch 'master' into layers2 2020-04-26 08:14:47 -07:00
Daniel Imms b79a0a9832 Remove unwanted vscode setting 2020-04-26 08:12:53 -07:00
Daniel Imms 212061be50 Move InputHandler.test into common, fix strict errors 2020-04-26 08:11:53 -07:00
Daniel Imms c931341259 Remove Terminal dep in InputHandler.test 2020-04-26 08:08:49 -07:00
Daniel Imms 51dc03a730 Merge pull request #2873 from jerch/fix_2872
Fix missing ybase calc in print
2020-04-26 08:08:26 -07:00
Jörg Breitbart 31670da633 fix sloppy coding in InputHandler 2020-04-26 16:53:15 +02:00
Jörg Breitbart 51a808c094 some refactoring 2020-04-26 16:34:15 +02:00
Daniel Imms d756086b8e Move InputHandler into common
Part of #2749
Part of #1507
2020-04-26 07:24:30 -07:00
Daniel Imms 81d289b266 Remove RenderService dep in InputHandler 2020-04-26 07:03:11 -07:00
Jörg Breitbart 71c2551a2e Merge branch 'reverse_wraparound' of github.com:jerch/xterm.js into reverse_wraparound 2020-04-26 15:59:27 +02:00
Jörg Breitbart ab2c8c7382 Merge branch 'master' into reverse_wraparound 2020-04-26 15:59:10 +02:00
Daniel Imms ba40fe4ff4 Remove Terminal dependency from InputHandler 🎉 2020-04-26 06:46:37 -07:00
Daniel Imms dbced5c1a4 Move Terminal.is into InputHandler 2020-04-26 06:38:10 -07:00
Daniel Imms 3a1a0d6590 Remove input handler Terminal.resize dep 2020-04-26 06:34:36 -07:00
Daniel Imms 451d9aab96 Register forwardEvent calls 2020-04-26 06:21:16 -07:00
Daniel Imms a6949d522e Move resize event into bufferservice 2020-04-26 06:16:13 -07:00
Daniel Imms eba372e2cc More some of Terminal.resize into bufferservice 2020-04-25 20:52:59 -07:00
Daniel Imms a3ac0d542d Remove unused imports 2020-04-25 20:39:58 -07:00
Daniel Imms 0709b0dc3b Break input handler dep on Terminal.scroll 2020-04-25 19:28:00 -07:00
rishab 5bb710e921 Merge branch 'master' of https://github.com/xtermjs/xterm.js 2020-04-25 17:39:30 -07:00
Rishab a5c1e594b3 Merge pull request #1 from rishab-pdx/#2840
handle wrap back with find previous
2020-04-25 17:37:12 -07:00
rishab 08f8f51ade handle wrap back with find previous
wrap back search selection to last selected result when we hit top
2020-04-25 17:33:48 -07:00
Daniel Imms 3897950e22 Remove a11y event dep, introduce forwardEvent helper 2020-04-25 16:45:46 -07:00
Daniel Imms e9db1ecd1f Remove Terminal.handleTitle dep 2020-04-25 16:37:31 -07:00
Daniel Imms 9346eba2f2 Remove Terminal.showCursor dep from input handler 2020-04-25 16:18:43 -07:00
Daniel Imms c5ba1d62e7 Remove mouseEvents from Terminal
Depend on CoreMouseService directly
2020-04-25 13:31:45 -07:00
Daniel Imms 37996bcd15 Move sendFocus to core service 2020-04-25 13:26:06 -07:00
Daniel Imms d5729f9216 Move insertMode into core service 2020-04-25 11:26:20 -07:00
Daniel Imms 4cec11be9c Move bracketed paste mode to coreservice 2020-04-25 11:11:05 -07:00
Daniel Imms b80c14639e Move option listeners to core 2020-04-25 10:34:37 -07:00
Daniel Imms 0d3bd2052b Remove remaining common service inits to common 2020-04-25 10:26:29 -07:00
Daniel Imms 353a8c5388 Create CoreTerminal, fix createInstance in strict mode 2020-04-25 10:21:56 -07:00
Daniel Imms dac3037ee4 Merge pull request #2871 from Tyriar/layers
Support dependency injection in UnicodeService
2020-04-25 08:44:25 -07:00
Daniel Imms e3c81048af Use DI with Linkifier2 2020-04-25 07:29:07 -07:00
Daniel Imms c752ca84df Merge pull request #2870 from Tyriar/selection
Make sure selection gets redrawn when switching renderers
2020-04-25 07:27:17 -07:00
Daniel Imms a5d50960a7 Support DI in UnicodeService 2020-04-25 07:27:09 -07:00
Daniel Imms af0845b4a3 Merge branch 'master' into selection 2020-04-25 07:18:27 -07:00
Daniel Imms 8d77b1ae16 Merge pull request #2869 from Tyriar/vs95556
Invalidate all links above when the terminal scrolls
2020-04-25 07:17:20 -07:00
Daniel Imms c185529ad5 Make sure selection gets redrawn when switching renderers
Fixes #2555
2020-04-25 07:11:43 -07:00
Daniel Imms 3a5def98e5 Correct selection types being optional 2020-04-25 06:56:00 -07:00
Daniel Imms 603c68bd69 Merge branch 'master' into vs95556 2020-04-25 06:46:02 -07:00
Daniel Imms 2f984964da Merge pull request #2868 from Tyriar/2595_selection_colors
Refresh model colors after changing theme
2020-04-25 06:42:01 -07:00
Daniel Imms 22aaf0471a Invalidate all links above when the terminal scrolls
See microsoft/vscode#95556
2020-04-25 06:41:25 -07:00
Daniel Imms 1134d0d7d4 Refresh model colors after changing theme
Fixes #2595
2020-04-25 06:22:08 -07:00
rishab 45ff3ee637 Update SearchAddon.ts 2020-04-25 03:13:32 -07:00
rishab 50ba6bfa1b Wrap search back if there exists a result in search
Search must wrap back as it does when there are multiple lines.
2020-04-25 03:13:19 -07:00
Daniel Imms cdcbb1d6da Merge pull request #2866 from xtermjs/dependabot/npm_and_yarn/deep-equal-2.0.3
Bump deep-equal from 2.0.2 to 2.0.3
2020-04-24 05:09:50 -07:00
dependabot-preview[bot] 49bf22ace4 Bump deep-equal from 2.0.2 to 2.0.3
Bumps [deep-equal](https://github.com/inspect-js/node-deep-equal) from 2.0.2 to 2.0.3.
- [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.2...v2.0.3)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-04-24 08:02:20 +00:00
Daniel Imms 22fa95cf87 Merge pull request #2865 from xtermjs/dependabot/npm_and_yarn/webpack-4.43.0
Bump webpack from 4.42.1 to 4.43.0
2020-04-22 05:30:31 -07:00
dependabot-preview[bot] a76a889924 Bump webpack from 4.42.1 to 4.43.0
Bumps [webpack](https://github.com/webpack/webpack) from 4.42.1 to 4.43.0.
- [Release notes](https://github.com/webpack/webpack/releases)
- [Commits](https://github.com/webpack/webpack/compare/v4.42.1...v4.43.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-04-22 08:27:07 +00:00
Daniel Imms 8f7c6a635f Merge pull request #2864 from Tyriar/center_text
Center webgl text vertically
2020-04-21 18:23:14 -07:00
Daniel Imms 9ece6b1fbc Center webgl text vertically
Fixes #2613
2020-04-21 18:20:10 -07:00
Daniel Imms 9d7a45f459 Merge pull request #2862 from xtermjs/dependabot/npm_and_yarn/typescript-eslint/parser-2.29.0
Bump @typescript-eslint/parser from 2.28.0 to 2.29.0
2020-04-21 03:33:20 -07:00
dependabot-preview[bot] 0465ba24a2 Bump @typescript-eslint/parser from 2.28.0 to 2.29.0
Bumps [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) from 2.28.0 to 2.29.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.29.0/packages/parser)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-04-21 10:22:18 +00:00
Daniel Imms b622c89967 Merge pull request #2861 from xtermjs/dependabot/npm_and_yarn/typescript-eslint/eslint-plugin-2.29.0
Bump @typescript-eslint/eslint-plugin from 2.28.0 to 2.29.0
2020-04-21 03:20:43 -07:00
dependabot-preview[bot] 7efb141f88 Bump @typescript-eslint/eslint-plugin from 2.28.0 to 2.29.0
Bumps [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) from 2.28.0 to 2.29.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.29.0/packages/eslint-plugin)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-04-21 08:13:30 +00:00
Daniel Imms d093912f9d Merge pull request #2859 from xtermjs/dependabot/npm_and_yarn/ts-loader-7.0.1
Bump ts-loader from 7.0.0 to 7.0.1
2020-04-20 07:20:17 -07:00
dependabot-preview[bot] 92e5c8d228 Bump ts-loader from 7.0.0 to 7.0.1
Bumps [ts-loader](https://github.com/TypeStrong/ts-loader) from 7.0.0 to 7.0.1.
- [Release notes](https://github.com/TypeStrong/ts-loader/releases)
- [Changelog](https://github.com/TypeStrong/ts-loader/blob/master/CHANGELOG.md)
- [Commits](https://github.com/TypeStrong/ts-loader/compare/v7.0.0...v7.0.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-04-20 13:06:59 +00:00
Daniel Imms 68995e6ed8 Merge pull request #2858 from xtermjs/dependabot/npm_and_yarn/types/webpack-4.41.12
Bump @types/webpack from 4.41.11 to 4.41.12
2020-04-20 06:05:01 -07:00
dependabot-preview[bot] edac29fe51 Bump @types/webpack from 4.41.11 to 4.41.12
Bumps [@types/webpack](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/webpack) from 4.41.11 to 4.41.12.
- [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-04-20 08:35:06 +00:00
Daniel Imms e7f72a1ba8 Merge pull request #2857 from Tyriar/decoration_customizations
Allow embedders to customize link decorations independently
2020-04-19 04:24:50 -07:00
Daniel Imms d0519d6c25 Fix tests 2020-04-19 04:20:10 -07:00
Daniel Imms 2ce435f98d Allow embedders to customize link decorations independently
See microsoft/vscode#95631
2020-04-19 04:07:22 -07:00
Daniel Imms 67fde3e941 Merge pull request #2856 from Tyriar/link_underline
Prevent hover callbacks firing if the mouse is outside the viewport
2020-04-18 18:27:12 -07:00
Daniel Imms 1282c060fc Prevent hover callbacks firing if the mouse is outside the viewport
Fixes #2849
2020-04-18 18:02:51 -07:00
Daniel Imms 52f1fc943d Merge pull request #2855 from Tyriar/link_underline
Polish link decorations and better support embedder hovers
2020-04-18 10:21:57 -07:00
Daniel Imms 88ff19383b Allow embedders to designate link hover elements 2020-04-18 09:13:09 -07:00
Daniel Imms 832312e095 Change hideUnderline to hideDecorations
Also track cursor style
2020-04-18 06:23:46 -07:00
Daniel Imms f2ed01993d Track changes to hideUnderline 2020-04-18 06:18:01 -07:00
Daniel Imms ccf869e671 Merge pull request #2854 from Tyriar/hide_underline
Allow hiding of link underlines
2020-04-17 19:01:22 -07:00
Daniel Imms 48f579977e Allow hiding of link underlines
See microsoft/vscode#95080
2020-04-17 18:46:01 -07:00
Daniel Imms ab595659c2 Merge pull request #2852 from Tyriar/mouseleave
Clear link when leaving xterm link
2020-04-17 17:38:45 -07:00
Daniel Imms 0cd2b78425 Clear link when leaving xterm link
Fixes #2851
2020-04-17 17:30:53 -07:00
Daniel Imms c1e618fc3b Merge pull request #2830 from kumaran-14/2702_api_test_debug
Add api-test script to log poll result on passing --debug flag
2020-04-15 08:04:12 -07:00
Daniel Imms 934b9faf36 Merge branch 'master' into 2702_api_test_debug 2020-04-15 06:52:14 -07:00
Daniel Imms 2836dcb71f Merge pull request #2845 from xtermjs/dependabot/npm_and_yarn/ts-loader-7.0.0
Bump ts-loader from 6.2.2 to 7.0.0
2020-04-15 06:09:39 -07:00
dependabot-preview[bot] 2b874c5817 Bump ts-loader from 6.2.2 to 7.0.0
Bumps [ts-loader](https://github.com/TypeStrong/ts-loader) from 6.2.2 to 7.0.0.
- [Release notes](https://github.com/TypeStrong/ts-loader/releases)
- [Changelog](https://github.com/TypeStrong/ts-loader/blob/master/CHANGELOG.md)
- [Commits](https://github.com/TypeStrong/ts-loader/compare/v6.2.2...v7.0.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-04-15 08:12:32 +00:00
Daniel Imms 19b73d2ba7 Merge pull request #2842 from xtermjs/dependabot/npm_and_yarn/typescript-eslint/parser-2.28.0
Bump @typescript-eslint/parser from 2.27.0 to 2.28.0
2020-04-14 06:36:51 -07:00
Daniel Imms 548a17d94c Merge pull request #2844 from Tyriar/2824
Enforce explicit access modifiers
2020-04-14 06:36:34 -07:00
Daniel Imms de4005d470 Merge branch 'master' into 2824 2020-04-14 06:31:48 -07:00
dependabot-preview[bot] 46e9b9499c Bump @typescript-eslint/parser from 2.27.0 to 2.28.0
Bumps [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) from 2.27.0 to 2.28.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.28.0/packages/parser)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-04-14 13:26:53 +00:00
Daniel Imms 580b06ada5 Merge pull request #2843 from xtermjs/dependabot/npm_and_yarn/typescript-eslint/eslint-plugin-2.28.0
Bump @typescript-eslint/eslint-plugin from 2.27.0 to 2.28.0
2020-04-14 06:25:15 -07:00
Daniel Imms 1472e7b051 Merge branch 'master' into 2824 2020-04-14 06:25:06 -07:00
Daniel Imms 9817754f92 Fix explicit access 2020-04-14 06:23:11 -07:00
dependabot-preview[bot] 05f1959c83 Bump @typescript-eslint/eslint-plugin from 2.27.0 to 2.28.0
Bumps [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) from 2.27.0 to 2.28.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.28.0/packages/eslint-plugin)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-04-14 13:08:53 +00:00
Daniel Imms 80a692a751 Merge pull request #2841 from xtermjs/dependabot/npm_and_yarn/types/ws-7.2.4
Bump @types/ws from 7.2.3 to 7.2.4
2020-04-14 06:07:11 -07:00
Daniel Imms a5ebb6d98c Add member access eslint rule 2020-04-14 06:03:47 -07:00
Daniel Imms 04c1e4b57d Remove readonly from ctors 2020-04-14 06:03:31 -07:00
dependabot-preview[bot] f8f9c06ed8 Bump @types/ws from 7.2.3 to 7.2.4
Bumps [@types/ws](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/ws) from 7.2.3 to 7.2.4.
- [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-04-14 08:14:38 +00:00
kumaran-14 1f4b2607cc Replace flatMap method with reduce and map 2020-04-13 22:36:33 +05:30
Daniel Imms f0c518a134 Merge pull request #2838 from xtermjs/dependabot/npm_and_yarn/types/jsdom-16.2.1
Bump @types/jsdom from 16.2.0 to 16.2.1
2020-04-13 06:33:50 -07:00
dependabot-preview[bot] c396518736 Bump @types/jsdom from 16.2.0 to 16.2.1
Bumps [@types/jsdom](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/jsdom) from 16.2.0 to 16.2.1.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/jsdom)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-04-13 12:34:17 +00:00
Daniel Imms d6a45b511e Merge pull request #2839 from xtermjs/dependabot/npm_and_yarn/types/webpack-4.41.11
Bump @types/webpack from 4.41.10 to 4.41.11
2020-04-13 05:32:38 -07:00
dependabot-preview[bot] f5c4c3e450 Bump @types/webpack from 4.41.10 to 4.41.11
Bumps [@types/webpack](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/webpack) from 4.41.10 to 4.41.11.
- [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-04-13 08:19:05 +00:00
Daniel Imms aecfb5772d Merge pull request #2835 from Tyriar/2822
Add linkTooltipHoverDuration setting
2020-04-11 10:08:30 -07:00
Daniel Imms 3b7b936582 Merge pull request #2837 from Tyriar/2836
Only fire IRenderService.onRender when buffer changes occurred
2020-04-11 09:59:34 -07:00
Daniel Imms 309bdf3d70 Rename onRender 2020-04-11 09:53:32 -07:00
Daniel Imms 55f9203f60 Only fire IRenderService.onRender when buffer changes occurred
Fixes #2836
2020-04-11 09:51:28 -07:00
kumaran-14 df7ba37d57 Update args parser to allow flag values 2020-04-11 21:15:24 +05:30
kumaran-14 96cb4e9a52 Merge remote-tracking branch 'upstream/master' into 2702_api_test_debug 2020-04-11 19:55:36 +05:30
Daniel Imms f070be8dd3 Add deprecated with explanation 2020-04-11 07:23:53 -07:00
Daniel Imms 3408a0537f Add linkTooltipHoverDuration setting
Fixes #2822
2020-04-11 07:22:20 -07:00
Daniel Imms 5669cf832f Merge branch 'master' into reverse_wraparound 2020-04-11 06:41:28 -07:00
Daniel Imms e2ac4b45d0 Merge pull request #2834 from Tyriar/2833
Fix selection wrapping to end when not beyond viewport
2020-04-11 06:38:50 -07:00
Daniel Imms 714daba393 Fix selection wrapping to end when not beyond viewport
This was happening as the devicePixelRatio wasn't being taken into account,
the fix was to rely on IRenderService to get the right canvas height.

Fixes #2833
2020-04-11 06:31:58 -07:00
Daniel Imms 1913e9512c Merge pull request #2718 from kumaran-14/2498_alt_enter
Support alt+enter key signal by default
2020-04-11 05:49:33 -07:00
kumaran-14 ae72994cfb Undo changes on ./bin/test.js 2020-04-11 12:02:27 +05:30
kumaran-14 f9b2f3e87d Add api-test script to log poll result on passing --debug flag 2020-04-11 11:54:58 +05:30
Daniel Imms e05bf87528 Merge pull request #2829 from Tyriar/lgtm
Fix most lgtm warnings
2020-04-10 14:22:24 -07:00
Daniel Imms 7609a5456e Fix most lgtm warnings 2020-04-10 14:08:28 -07:00
Daniel Imms e61c34b85f Merge branch 'master' into 2498_alt_enter 2020-04-10 12:53:16 -07:00
Daniel Imms ba0dc60b09 Combine into ternary 2020-04-10 12:53:02 -07:00
kumaran-14 3ef41f2b1d Remove decset modes from coreservices 2020-04-10 23:19:31 +05:30
Daniel Imms 2e6f547bfc Merge branch 'master' into reverse_wraparound 2020-04-10 10:30:30 -07:00
Daniel Imms 9460e915cd Merge branch 'master' into underline_core 2020-04-10 09:22:07 -07:00
Jörg Breitbart 120527cb41 underline color support, test cases 2020-03-08 22:56:58 +01:00
Jörg Breitbart 058f33cd7b update vt docs 2020-03-08 20:25:26 +01:00
Jörg Breitbart 88582726b7 implement SGR 21, sequence tests 2020-03-08 20:08:22 +01:00
Jörg Breitbart fd5f5e3204 extend AttributeData with eAttrs, implement SGR 4 subparams, tests 2020-03-08 18:57:05 +01:00
jerch 23cacbf81a Merge branch 'master' into reverse_wraparound 2020-02-22 23:49:25 +01:00
Jörg Breitbart d35d12f671 Merge branch 'reverse_wraparound' of github.com:jerch/xterm.js into reverse_wraparound 2020-02-21 18:24:55 +01:00
Jörg Breitbart cb31179274 respect scroll margins, tests 2020-02-21 18:23:00 +01:00
kumaran-14 c9277c3c72 Remove altEscape DECSET mode and add support for alt+esc key 2020-02-21 13:13:46 +05:30
kumaran-14 5896a05ca6 Change decset flag to altEscMode 2020-02-20 00:21:33 +05:30
jerch 5cf0aadd84 Merge branch 'master' into reverse_wraparound 2020-02-18 20:12:55 +01:00
Jörg Breitbart 62ea2c691e wide char tests 2020-02-17 12:39:36 +01:00
Jörg Breitbart 7de600cc64 test case for clearing cells to the right on early wrap-around 2020-02-17 12:30:35 +01:00
Jörg Breitbart 0c703276b1 fix behavior, first BS tests 2020-02-17 12:22:53 +01:00
Jörg Breitbart f736b6c299 comment missing empty cell handling 2020-02-16 20:53:53 +01:00
Jörg Breitbart 6f886fe167 working draft 2020-02-16 20:41:55 +01:00
kumaran-14 b75ae0f7b1 Support alt+Enter key signal by default 2020-02-14 20:00:03 +05:30
131 changed files with 6342 additions and 3763 deletions
+43 -32
View File
@@ -9,7 +9,6 @@
"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",
@@ -19,42 +18,54 @@
"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"
"addons/xterm-addon-serialize/benchmark/tsconfig.json",
"addons/xterm-addon-ligatures/src/tsconfig.json"
],
"sourceType": "module"
},
"ignorePatterns": [
"**/typings/*.d.ts"
"**/typings/*.d.ts",
"**/node_modules",
"**/*.js"
],
"plugins": [
"@typescript-eslint"
],
"rules": {
"@typescript-eslint/array-type": [
"error",
"warn",
{
"default": "array-simple",
"readonly": "generic"
}
],
"@typescript-eslint/class-name-casing": "error",
"@typescript-eslint/consistent-type-definitions": "error",
"@typescript-eslint/class-name-casing": "warn",
"@typescript-eslint/consistent-type-definitions": "warn",
"@typescript-eslint/explicit-function-return-type": [
"error",
"warn",
{
"allowExpressions": true
}
],
"@typescript-eslint/explicit-member-accessibility": [
"warn",
{
"accessibility": "explicit",
"overrides": {
"constructors": "off"
}
}
],
"@typescript-eslint/indent": [
"error",
"warn",
2
],
"@typescript-eslint/interface-name-prefix": [
"error",
"warn",
"always"
],
"@typescript-eslint/member-delimiter-style": [
"error",
"warn",
{
"multiline": {
"delimiter": "semi",
@@ -67,7 +78,7 @@
}
],
"@typescript-eslint/naming-convention": [
"error",
"warn",
{ "selector": "default", "format": ["camelCase"] },
// variableLike
{ "selector": "variable", "format": ["camelCase", "UPPER_CASE"] },
@@ -83,19 +94,19 @@
{ "selector": "typeLike", "format": ["PascalCase"] },
{ "selector": "interface", "format": ["PascalCase"], "prefix": ["I"] }
],
"@typescript-eslint/prefer-namespace-keyword": "error",
"@typescript-eslint/type-annotation-spacing": "error",
"@typescript-eslint/prefer-namespace-keyword": "warn",
"@typescript-eslint/type-annotation-spacing": "warn",
"@typescript-eslint/quotes": [
"error",
"warn",
"single",
{ "allowTemplateLiterals": true }
],
"@typescript-eslint/semi": [
"error",
"warn",
"always"
],
"comma-dangle": [
"error",
"warn",
{
"objects": "never",
"arrays": "never",
@@ -103,43 +114,43 @@
}
],
"curly": [
"error",
"warn",
"multi-line"
],
"eol-last": "error",
"eol-last": "warn",
"eqeqeq": [
"error",
"warn",
"always"
],
"keyword-spacing": "error",
"new-parens": "error",
"no-duplicate-imports": "error",
"keyword-spacing": "warn",
"new-parens": "warn",
"no-duplicate-imports": "warn",
"no-else-return": [
"error",
"warn",
{
"allowElseIf": false
}
],
"no-eval": "error",
"no-irregular-whitespace": "error",
"no-eval": "warn",
"no-irregular-whitespace": "warn",
"no-restricted-imports": [
"error",
"warn",
{
"patterns": [
".*\\/out\\/.*"
]
}
],
"no-trailing-spaces": "error",
"no-unsafe-finally": "error",
"no-var": "error",
"no-trailing-spaces": "warn",
"no-unsafe-finally": "warn",
"no-var": "warn",
"one-var": [
"error",
"warn",
"never"
],
"prefer-const": "error",
"prefer-const": "warn",
"spaced-comment": [
"error",
"warn",
"always",
{
"markers": ["/"],
+1
View File
@@ -161,6 +161,7 @@ Xterm.js is used in several world-class applications to provide great terminal e
- [**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, ...
- [**Dank Domain**](https://www.DDgame.us/): Open source multiuser medieval game supporting old & new terminal emulation.
[And much more...](https://github.com/xtermjs/xterm.js/network/dependents)
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "xterm-addon-attach",
"version": "0.5.0",
"version": "0.6.0",
"author": {
"name": "The xterm.js authors",
"url": "https://xtermjs.org/"
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "xterm-addon-fit",
"version": "0.3.0",
"version": "0.4.0",
"author": {
"name": "The xterm.js authors",
"url": "https://xtermjs.org/"
+13
View File
@@ -0,0 +1,13 @@
node_modules/
.nyc_output/
coverage/
lib/
fonts/
.env
.vscode/
*.swp
*.tgz
npm-debug.log*
yarn-error.log*
+48
View File
@@ -0,0 +1,48 @@
# Blacklist - exclude everything except npm defaults such as LICENSE, etc
*
!*/
# Whitelist - entries to be included must be negated with "!"
!*.js
!*.json
# Whitelist - lib/
!lib/**/*.d.ts
!lib/**/*.js
!lib/**/*.js.map
# Whitelist - out/
!out/**/*.d.ts
!out/**/*.js
!out/**/*.js.map
# Whitelist - src/
!src/**/*.ts
!src/**/*.d.ts
!src/**/*.js
!src/**/*.js.map
# Whitelist - typings/
!typings/**/*.d.ts
# Blacklist - (normal behavior) these will override any whitelist
*.test.ts
*.test.d.ts
*.test.js
*.test.js.map
docs/
/.idea/
.vscode/
coverage/
.nyc_output/
fonts/
**/*.api.js
**/*.api.ts
tsconfig.json
.yarnrc
webpack.config.js
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2018
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+53
View File
@@ -0,0 +1,53 @@
## xterm-addon-ligatures
Add support for programming ligatures to [xterm.js] when running in environments with access to [Node.js] APIs (such as [Electron]).
### Requirements
* [Node.js] 8.x or higher (present in [Electron] 1.8.3 or higher)
* [xterm.js] 4.0.0 or higher using the default canvas renderer
### Install
```bash
npm install --save xterm-addon-ligatures
```
### Usage
```ts
import { Terminal } from 'xterm';
import { LigaturesAddon } from 'xterm-addon-ligatures';
const terminal = new Terminal();
const ligaturesAddon = new LigaturesAddon();
terminal.open(containerElement);
terminal.loadAddon(ligaturesAddon);
```
### How It Works
In a browser environment, font ligature information is read directly by the web browser and used to render text correctly without any intervention from the developer. As of version 3, xterm.js uses the canvas to render characters individually, resulting in a significant performance boost. However, this means that it can no longer lean on the browser to determine when to draw font ligatures.
This package locates the font file on disk for the font currently in use by the terminal and parses the ligature information out of it (via the [font-ligatures] package). As text is rendered in xterm.js, this package annotates it with the locations of ligatures, allowing xterm.js to render it correctly.
Since this package depends on being able to find and resolve a system font from disk, it has to have system access that isn't available in the web browser. As a result, this package is mainly useful in environments that combine browser and Node.js runtimes (such as [Electron]).
### Fonts
This package makes use of the following fonts for testing:
* [Fira Code][Fira Code] - [Licensed under the OFL][Fira Code License] by Nikita
Prokopov, Mozilla Foundation with reserved names Fira Code, Fira Mono, and
Fira Sans
* [Iosevka] - [Licensed under the OFL][Iosevka License] by Belleve Invis with
reserved name Iosevka
[xterm.js]: https://github.com/xtermjs/xterm.js
[Electron]: https://electronjs.org/
[Node.js]: https://nodejs.org/
[font-ligatures]: https://github.com/princjef/font-ligatures
[Fira Code]: https://github.com/tonsky/FiraCode
[Fira Code License]: https://github.com/tonsky/FiraCode/blob/master/LICENSE
[Iosevka]: https://github.com/be5invis/Iosevka
[Iosevka License]: https://github.com/be5invis/Iosevka/blob/master/LICENSE.md
@@ -0,0 +1,75 @@
/**
* Copyright (c) 2018 The xterm.js authors. All rights reserved.
* @license MIT
*/
const fs = require('fs');
const path = require('path');
const util = require('util');
const axios = require('axios').default;
const mkdirp = require('mkdirp');
const yauzl = require('yauzl');
const urls = {
fira: 'https://github.com/tonsky/FiraCode/raw/master/distr/otf/FiraCode-Regular.otf',
iosevka: 'https://github.com/be5invis/Iosevka/releases/download/v1.14.3/01-iosevka-1.14.3.zip'
};
const writeFile = util.promisify(fs.writeFile);
const fontsFolder = path.join(__dirname, '../fonts');
async function download() {
await mkdirp(fontsFolder);
await downloadFiraCode();
await downloadIosevka();
console.log('Loaded all fonts for testing')
}
async function downloadFiraCode() {
const file = path.join(fontsFolder, 'firaCode.otf');
if (await util.promisify(fs.exists)(file)) {
console.log('Fira Code already loaded');
} else {
console.log('Downloading Fira Code...');
await writeFile(
file,
(await axios.get(urls.fira, { responseType: 'arraybuffer' })).data
);
}
}
async function downloadIosevka() {
const file = path.join(fontsFolder, 'iosevka.ttf');
if (await util.promisify(fs.exists)(file)) {
console.log('Iosevka already loaded');
} else {
console.log('Downloading Iosevka...');
const iosevkaContents = (await axios.get(urls.iosevka, { responseType: 'arraybuffer' })).data;
const iosevkaZipfile = await util.promisify(yauzl.fromBuffer)(iosevkaContents);
await new Promise((resolve, reject) => {
iosevkaZipfile.on('entry', entry => {
if (entry.fileName === 'ttf/iosevka-regular.ttf') {
iosevkaZipfile.openReadStream(entry, (err, stream) => {
if (err) {
return reject(err);
}
const writeStream = fs.createWriteStream(file);
stream.pipe(writeStream);
writeStream.on('close', () => resolve());
});
}
});
});
}
}
download();
process.on('unhandledRejection', e => {
console.error(e);
process.exit(1);
});
+47
View File
@@ -0,0 +1,47 @@
{
"name": "xterm-addon-ligatures",
"version": "0.3.0",
"description": "Add support for programming ligatures to xterm.js",
"author": {
"name": "The xterm.js authors",
"url": "https://xtermjs.org/"
},
"main": "lib/xterm-addon-ligatures.js",
"types": "typings/xterm-addon-ligatures.d.ts",
"repository": "https://github.com/xtermjs/xterm.js",
"engines": {
"node": ">8.0.0"
},
"scripts": {
"prepare": "node bin/download-fonts.js",
"build": "tsc -p src",
"watch": "tsc -w -p src",
"prepackage": "npm run build",
"package": "webpack",
"pretest": "npm run build",
"test": "nyc mocha out/**/*.test.js",
"prepublish": "npm run package"
},
"keywords": [
"font",
"ligature",
"xterm",
"xterm.js",
"terminal"
],
"license": "MIT",
"dependencies": {
"font-finder": "^1.0.4",
"font-ligatures": "^1.3.2"
},
"devDependencies": {
"@types/sinon": "^5.0.1",
"axios": "^0.18.0",
"mkdirp": "^0.5.1",
"sinon": "^6.1.3",
"yauzl": "^2.10.0"
},
"peerDependencies": {
"xterm": "^4.0.0"
}
}
@@ -0,0 +1,23 @@
/**
* Copyright (c) 2018 The xterm.js authors. All rights reserved.
* @license MIT
*/
import { Terminal } from 'xterm';
import { enableLigatures } from '.';
export interface ITerminalAddon {
activate(terminal: Terminal): void;
dispose(): void;
}
export class LigaturesAddon implements ITerminalAddon {
constructor() {}
public activate(terminal: Terminal): void {
enableLigatures(terminal);
}
public dispose(): void {}
}
+53
View File
@@ -0,0 +1,53 @@
/**
* Copyright (c) 2018 The xterm.js authors. All rights reserved.
* @license MIT
*/
import * as fontFinder from 'font-finder';
import * as fontLigatures from 'font-ligatures';
import parse from './parse';
let fontsPromise: Promise<fontFinder.FontList> | undefined = undefined;
/**
* Loads the font ligature wrapper for the specified font family if it could be
* resolved, throwing if it is unable to find a suitable match.
* @param fontFamily The CSS font family definition to resolve
* @param cacheSize The size of the ligature cache to maintain if the font is resolved
*/
export default async function load(fontFamily: string, cacheSize: number): Promise<fontLigatures.Font | undefined> {
if (!fontsPromise) {
fontsPromise = fontFinder.list();
}
const fonts = await fontsPromise;
for (const family of parse(fontFamily)) {
// If we reach one of the generic font families, the font resolution
// will end for the browser and we can't determine the specific font
// used. Throw.
if (genericFontFamilies.includes(family)) {
return undefined;
}
if (fonts.hasOwnProperty(family) && fonts[family].length > 0) {
return await fontLigatures.loadFile(fonts[family][0].path, { cacheSize });
}
}
// If none of the fonts could resolve, throw an error
return undefined;
}
// https://drafts.csswg.org/css-fonts-4/#generic-font-families
const genericFontFamilies = [
'serif',
'sans-serif',
'cursive',
'fantasy',
'monospace',
'system-ui',
'emoji',
'math',
'fangsong'
];
@@ -0,0 +1,222 @@
/**
* Copyright (c) 2018 The xterm.js authors. All rights reserved.
* @license MIT
*/
import * as path from 'path';
import * as sinon from 'sinon';
import { assert } from 'chai';
import * as fontFinder from 'font-finder';
import * as fontLigatures from 'font-ligatures';
import * as ligatureSupport from '.';
describe('xterm-addon-ligatures', () => {
let onRefresh: sinon.SinonStub;
let term: MockTerminal;
// -> forms a ligature in Fira Code and Iosevka, but www only forms a ligature
// in Fira Code
const input = 'a -> b www c';
before(() => {
sinon.stub(fontFinder, 'list').returns(Promise.resolve({
// eslint-disable-next-line @typescript-eslint/naming-convention
'Fira Code': [{
path: path.join(__dirname, '../fonts/firaCode.otf'),
style: fontFinder.Style.Regular,
type: fontFinder.Type.Monospace,
weight: 400
}],
// eslint-disable-next-line @typescript-eslint/naming-convention
'Iosevka': [{
path: path.join(__dirname, '../fonts/iosevka.ttf'),
style: fontFinder.Style.Regular,
type: fontFinder.Type.Monospace,
weight: 400
}],
// eslint-disable-next-line @typescript-eslint/naming-convention
'Nonexistant Font': [{
path: path.join(__dirname, '../fonts/nonexistant.ttf'),
style: fontFinder.Style.Regular,
type: fontFinder.Type.Monospace,
weight: 400
}]
} as fontFinder.FontList));
});
beforeEach(() => {
onRefresh = sinon.stub();
term = new MockTerminal(onRefresh);
ligatureSupport.enableLigatures(term as any);
});
it('registers itself correctly', () => {
const term = new MockTerminal(sinon.spy());
assert.isUndefined(term.joiner);
ligatureSupport.enableLigatures(term as any);
assert.isFunction(term.joiner);
});
it('registers itself correctly when called directly', () => {
const term = new MockTerminal(sinon.spy());
assert.isUndefined(term.joiner);
ligatureSupport.enableLigatures(term as any);
assert.isFunction(term.joiner);
});
it('returns an empty set of ranges on the first call while the font is loading', () => {
assert.deepEqual(term.joiner!(input), []);
});
it('returns the correct set of ranges once the font has loaded', done => {
assert.deepEqual(term.joiner!(input), []);
onRefresh.callsFake(() => {
assert.deepEqual(term.joiner!(input), [[2, 4], [7, 10]]);
done();
});
});
it('handles quoted font names', done => {
term.setOption('fontFamily', '"Fira Code", monospace');
assert.deepEqual(term.joiner!(input), []);
onRefresh.callsFake(() => {
assert.deepEqual(term.joiner!(input), [[2, 4], [7, 10]]);
done();
});
});
it('falls back to later fonts if earlier ones are not present', done => {
term.setOption('fontFamily', 'notinstalled, Fira Code, monospace');
assert.deepEqual(term.joiner!(input), []);
onRefresh.callsFake(() => {
assert.deepEqual(term.joiner!(input), [[2, 4], [7, 10]]);
done();
});
});
it('uses the current font value', done => {
// The first three calls are all synchronous so that we don't allow time for
// any fonts to load while we're switching things around
term.setOption('fontFamily', 'Fira Code');
assert.deepEqual(term.joiner!(input), []);
term.setOption('fontFamily', 'notinstalled');
assert.deepEqual(term.joiner!(input), []);
term.setOption('fontFamily', 'Iosevka');
assert.deepEqual(term.joiner!(input), []);
onRefresh.callsFake(() => {
assert.deepEqual(term.joiner!(input), [[2, 4]]);
// And switch it back to Fira Code for good measure
term.setOption('fontFamily', 'Fira Code');
// At this point, we haven't loaded the new font, so the result reverts
// back to empty until that happens
assert.deepEqual(term.joiner!(input), []);
onRefresh.callsFake(() => {
assert.deepEqual(term.joiner!(input), [[2, 4], [7, 10]]);
done();
});
});
});
it('allows multiple terminal instances that use different fonts', done => {
const onRefresh2 = sinon.stub();
const term2 = new MockTerminal(onRefresh2);
term2.setOption('fontFamily', 'Iosevka');
ligatureSupport.enableLigatures(term2 as any);
assert.deepEqual(term.joiner!(input), []);
onRefresh.callsFake(() => {
assert.deepEqual(term.joiner!(input), [[2, 4], [7, 10]]);
assert.deepEqual(term2.joiner!(input), []);
onRefresh2.callsFake(() => {
assert.deepEqual(term2.joiner!(input), [[2, 4]]);
assert.deepEqual(term.joiner!(input), [[2, 4], [7, 10]]);
done();
});
});
});
it('fails if it finds but cannot load the font', async () => {
term.setOption('fontFamily', 'Nonexistant Font, monospace');
assert.deepEqual(term.joiner!(input), []);
await delay(500);
assert.isTrue(onRefresh.notCalled);
assert.throws(() => term.joiner!(input));
});
it('returns nothing if the font is not present on the system', async () => {
term.setOption('fontFamily', 'notinstalled');
assert.deepEqual(term.joiner!(input), []);
await delay(500);
assert.isTrue(onRefresh.notCalled);
assert.deepEqual(term.joiner!(input), []);
});
it('returns nothing if no specific font is specified', async () => {
term.setOption('fontFamily', 'monospace');
assert.deepEqual(term.joiner!(input), []);
await delay(500);
assert.isTrue(onRefresh.notCalled);
assert.deepEqual(term.joiner!(input), []);
});
it('returns nothing if no fonts are provided', async () => {
term.setOption('fontFamily', '');
assert.deepEqual(term.joiner!(input), []);
await delay(500);
assert.isTrue(onRefresh.notCalled);
assert.deepEqual(term.joiner!(input), []);
});
it('fails when given malformed inputs', async () => {
term.setOption('fontFamily', {} as any);
assert.deepEqual(term.joiner!(input), []);
await delay(500);
assert.isTrue(onRefresh.notCalled);
assert.throws(() => term.joiner!(input));
});
it('ensures no empty errors are thrown', async () => {
sinon.stub(fontLigatures, 'loadFile').callsFake(async () => { throw undefined; });
term.setOption('fontFamily', 'Iosevka');
assert.deepEqual(term.joiner!(input), []);
await delay(500);
assert.isTrue(onRefresh.notCalled);
assert.throws(() => term.joiner!(input), 'Failure while loading font');
(fontLigatures.loadFile as sinon.SinonStub).restore();
});
});
class MockTerminal {
private _options: { [name: string]: string | number } = {
fontFamily: 'Fira Code, monospace',
rows: 50
};
public joiner?: (text: string) => [number, number][];
public refresh: (start: number, end: number) => void;
constructor(onRefresh: (start: number, end: number) => void) {
this.refresh = onRefresh;
}
public registerCharacterJoiner(handler: (text: string) => [number, number][]): number {
this.joiner = handler;
return 1;
}
public deregisterCharacterJoiner(id: number): void {
this.joiner = undefined;
}
public setOption(name: string, value: string | number): void {
this._options[name] = value;
}
public getOption(name: string): string | number {
return this._options[name];
}
}
function delay(delayMs: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, delayMs));
}
+85
View File
@@ -0,0 +1,85 @@
/**
* Copyright (c) 2018 The xterm.js authors. All rights reserved.
* @license MIT
*/
import { Terminal } from 'xterm';
import { Font } from 'font-ligatures';
import load from './font';
const enum LoadingState {
UNLOADED,
LOADING,
LOADED,
FAILED
}
// Caches 100K characters worth of ligatures. In practice this works out to
// about 650 KB worth of cache, when a moderate number of ligatures are present.
const CACHE_SIZE = 100000;
/**
* Enable ligature support for the provided Terminal instance. To function
* properly, this must be called after `open()` is called on the therminal. If
* the font currently in use supports ligatures, the terminal will automatically
* start to render them.
* @param term Terminal instance from xterm.js
*/
export function enableLigatures(term: Terminal): void {
let currentFontName: string | undefined = undefined;
let font: Font | undefined = undefined;
let loadingState: LoadingState = LoadingState.UNLOADED;
let loadError: any | undefined = undefined;
term.registerCharacterJoiner((text: string): [number, number][] => {
// If the font hasn't been loaded yet, load it and return an empty result
const termFont = term.getOption('fontFamily');
if (
termFont &&
(loadingState === LoadingState.UNLOADED || currentFontName !== termFont)
) {
font = undefined;
loadingState = LoadingState.LOADING;
currentFontName = termFont;
const currentCallFontName = currentFontName;
load(currentCallFontName, CACHE_SIZE)
.then(f => {
// Another request may have come in while we were waiting, so make
// sure our font is still vaild.
if (currentCallFontName === term.getOption('fontFamily')) {
loadingState = LoadingState.LOADED;
font = f;
// Only refresh things if we actually found a font
if (f) {
term.refresh(0, term.getOption('rows') - 1);
}
}
})
.catch(e => {
// Another request may have come in while we were waiting, so make
// sure our font is still vaild.
if (currentCallFontName === term.getOption('fontFamily')) {
loadingState = LoadingState.FAILED;
font = undefined;
loadError = e;
}
});
}
if (font && loadingState === LoadingState.LOADED) {
// We clone the entries to avoid the internal cache of the ligature finder
// getting messed up.
return font.findLigatureRanges(text).map<[number, number]>(
range => [range[0], range[1]]
);
}
if (loadingState === LoadingState.FAILED) {
throw loadError || new Error('Failure while loading font');
}
return [];
});
}
@@ -0,0 +1,75 @@
/**
* Copyright (c) 2018 The xterm.js authors. All rights reserved.
* @license MIT
*/
import { assert } from 'chai';
import parse from './parse';
// TODO: integrate tests from http://test.csswg.org/suites/css-fonts-4_dev/nightly-unstable/
describe('parse', () => {
it('parses individual families', () => {
assert.deepEqual(parse('monospace'), ['monospace']);
});
it('parses multiple families', () => {
assert.deepEqual(parse('Arial, Verdana, serif'), ['Arial', 'Verdana', 'serif']);
});
it('parses quoted families', () => {
assert.deepEqual(parse('"Times New Roman", serif'), ['Times New Roman', 'serif']);
});
it('parses single quoted families', () => {
assert.deepEqual(parse('\'Times New Roman\', serif'), ['Times New Roman', 'serif']);
});
it('parses families with spaces in their names', () => {
assert.deepEqual(parse('Times New Roman, serif'), ['Times New Roman', 'serif']);
});
it('collapses multiple spaces together in identifiers', () => {
assert.deepEqual(parse('Times New Roman, serif'), ['Times New Roman', 'serif']);
});
it('does not collapse multiple spaces together in quoted strings', () => {
assert.deepEqual(parse('"Times New Roman", serif'), ['Times New Roman', 'serif']);
});
it('handles escaped characters in strings', () => {
assert.deepEqual(parse('"quote \\" slash \\\\ slashquote \\\\\\"", serif'), ['quote " slash \\ slashquote \\"', 'serif']);
});
it('fails if a family has an unterminated string', () => {
assert.throws(() => parse('"Unterminated, serif'));
});
it('handles unicode escape sequences', () => {
assert.deepEqual(parse('"space\\20 between", serif'), ['space between', 'serif']);
});
it('swallows only the first space after a unicode escape', () => {
assert.deepEqual(parse('"two-space\\20 between", serif'), ['two-space between', 'serif']);
});
it('automatically ends the unicode escape after six digits', () => {
assert.deepEqual(parse('space\\000020between, serif'), ['space between', 'serif']);
});
it('handles unicode escapes at the end of the family', () => {
assert.deepEqual(parse('endswithbrace \\7b, serif'), ['endswithbrace {', 'serif']);
});
it('handles unicode escapes at the end of the input', () => {
assert.deepEqual(parse('endswithbrace \\7b'), ['endswithbrace {']);
});
it('handles other escaped characters in identifiers', () => {
assert.deepEqual(parse('has\\,comma'), ['has,comma']);
});
it('swallows escaped newlines in strings', () => {
assert.deepEqual(parse('"multi \\\nline", serif'), ['multi line', 'serif']);
});
});
+180
View File
@@ -0,0 +1,180 @@
/**
* Copyright (c) 2018 The xterm.js authors. All rights reserved.
* @license MIT
*/
interface IParseContext {
input: string;
offset: number;
}
/**
* Parses a CSS font family value, returning the component font families
* contained within.
*
* @param family The CSS font family input string to parse
*/
export default function parse(family: string): string[] {
if (typeof family !== 'string') {
throw new Error('Font family must be a string');
}
const context: IParseContext = {
input: family,
offset: 0
};
const families = [];
let currentFamily = '';
// Work through the input character by character until there are none left.
// This lexing and parsing in one pass.
while (context.offset < context.input.length) {
const char = context.input[context.offset++];
switch (char) {
// String
case '\'':
case '"':
currentFamily += parseString(context, char);
break;
// End of family
case ',':
families.push(currentFamily);
currentFamily = '';
break;
default:
// Identifiers (whitespace between families is swallowed)
if (!/\s/.test(char)) {
context.offset--;
currentFamily += parseIdentifier(context);
families.push(currentFamily);
currentFamily = '';
}
}
}
return families;
}
/**
* Parse a CSS string.
*
* @param context Parsing input and offset
* @param quoteChar The quote character for the string (' or ")
*/
function parseString(context: IParseContext, quoteChar: '\'' | '"'): string {
let str = '';
let escaped = false;
while (context.offset < context.input.length) {
const char = context.input[context.offset++];
if (escaped) {
if (/[0-9a-fA-F]/.test(char)) {
// Unicode escape
context.offset--;
str += parseUnicode(context);
} else if (char !== '\n') {
// Newlines are ignored if escaped. Other characters are used as is.
str += char;
}
escaped = false;
} else {
switch (char) {
// Terminated quote
case quoteChar:
return str;
// Begin escape
case '\\':
escaped = true;
break;
// Add character to string
default:
str += char;
}
}
}
throw new Error('Unterminated string');
}
/**
* Parse a CSS custom identifier.
*
* @param context Parsing input and offset
*/
function parseIdentifier(context: IParseContext): string {
let str = '';
let escaped = false;
while (context.offset < context.input.length) {
const char = context.input[context.offset++];
if (escaped) {
if (/[0-9a-fA-F]/.test(char)) {
// Unicode escape
context.offset--;
str += parseUnicode(context);
} else {
// Everything else is used as is
str += char;
}
escaped = false;
} else {
switch (char) {
// Begin escape
case '\\':
escaped = true;
break;
// Terminate identifier
case ',':
return str;
default:
if (/\s/.test(char)) {
// Whitespace is collapsed into a single space within an identifier
if (!str.endsWith(' ')) {
str += ' ';
}
} else {
// Add other characters directly
str += char;
}
}
}
}
return str;
}
/**
* Parse a CSS unicode escape.
*
* @param context Parsing input and offset
*/
function parseUnicode(context: IParseContext): string {
let str = '';
while (context.offset < context.input.length) {
const char = context.input[context.offset++];
if (/\s/.test(char)) {
// The first whitespace character after a unicode escape indicates the end
// of the escape and is swallowed.
return unicodeToString(str);
}
if (str.length >= 6 || !/[0-9a-fA-F]/.test(char)) {
// If the next character is not a valid hex digit or we have reached the
// maximum of 6 digits in the escape, terminate the escape.
context.offset--;
return unicodeToString(str);
}
// Otherwise, just add it to the escape
str += char;
}
return unicodeToString(str);
}
/**
* Convert a unicode code point from a hex string to a utf8 string.
*
* @param codePoint Unicode code point represented as a hex string
*/
function unicodeToString(codePoint: string): string {
return String.fromCodePoint(parseInt(codePoint, 16));
}
@@ -0,0 +1,16 @@
{
"compilerOptions": {
"target": "es2017",
"module": "commonjs",
"sourceMap": true,
"outDir": "../out",
"rootDir": ".",
"strict": true,
"noUnusedLocals": true,
"preserveWatchOutput": true
},
"include": [
"./**/*",
"../../../typings/xterm.d.ts"
]
}
@@ -0,0 +1,34 @@
/**
* Copyright (c) 2018 The xterm.js authors. All rights reserved.
* @license MIT
*
* This contains the type declarations for the xterm-addon-ligatures library.
* Note that some interfaces may differ between this file and the actual
* implementation in src/, that's because this file declares the *public* API
* which is intended to be stable and consumed by external programs.
*/
import { Terminal, ITerminalAddon } from 'xterm';
declare module 'xterm-addon-ligatures' {
/**
* An xterm.js addon that enables web links.
*/
export class LigaturesAddon implements ITerminalAddon {
/**
* Creates a new ligatures addon.
*/
constructor();
/**
* Activates the addon
* @param terminal The terminal the addon is being loaded in.
*/
public activate(terminal: Terminal): void;
/**
* Disposes the addon.
*/
public dispose(): void;
}
}
@@ -0,0 +1,36 @@
/**
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
* @license MIT
*/
const path = require('path');
const addonName = 'LigaturesAddon';
const mainFile = 'xterm-addon-ligatures.js';
module.exports = {
entry: `./out/${addonName}.js`,
devtool: 'source-map',
target: 'electron-renderer',
module: {
rules: [
{
test: /\.js$/,
use: ["source-map-loader"],
enforce: "pre",
exclude: /node_modules/
}
]
},
output: {
filename: mainFile,
path: path.resolve('./lib'),
library: addonName,
libraryTarget: 'umd'
},
mode: 'production',
externals: {
'font-finder':'font-finder',
'font-ligatures':'font-ligatures'
}
};
+194
View File
@@ -0,0 +1,194 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
"@sinonjs/formatio@^2.0.0":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@sinonjs/formatio/-/formatio-2.0.0.tgz#84db7e9eb5531df18a8c5e0bfb6e449e55e654b2"
dependencies:
samsam "1.3.0"
"@sinonjs/samsam@^2.0.0":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@sinonjs/samsam/-/samsam-2.0.0.tgz#9163742ac35c12d3602dece74317643b35db6a80"
"@types/sinon@^5.0.1":
version "5.0.1"
resolved "https://registry.yarnpkg.com/@types/sinon/-/sinon-5.0.1.tgz#a15b36ec42f1f53166617491feabd1734cb03e21"
axios@^0.18.0:
version "0.18.1"
resolved "https://registry.yarnpkg.com/axios/-/axios-0.18.1.tgz#ff3f0de2e7b5d180e757ad98000f1081b87bcea3"
dependencies:
follow-redirects "1.5.10"
is-buffer "^2.0.2"
buffer-crc32@~0.2.3:
version "0.2.13"
resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242"
debug@=3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
dependencies:
ms "2.0.0"
diff@^3.5.0:
version "3.5.0"
resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12"
fd-slicer@~1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e"
dependencies:
pend "~1.2.0"
follow-redirects@1.5.10:
version "1.5.10"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.5.10.tgz#7b7a9f9aea2fdff36786a94ff643ed07f4ff5e2a"
dependencies:
debug "=3.1.0"
font-finder@^1.0.3, font-finder@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/font-finder/-/font-finder-1.0.4.tgz#2ca944954dd8d0e1b5bdc4c596cc08607761d89b"
dependencies:
get-system-fonts "^2.0.0"
promise-stream-reader "^1.0.1"
font-ligatures@^1.3.2:
version "1.3.2"
resolved "https://registry.yarnpkg.com/font-ligatures/-/font-ligatures-1.3.2.tgz#227eb5fc38fef34b5373aa19b555320b82842a71"
dependencies:
font-finder "^1.0.3"
lru-cache "^4.1.3"
opentype.js "^0.8.0"
get-system-fonts@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/get-system-fonts/-/get-system-fonts-2.0.0.tgz#a43b9a33f05c0715a60176d2aad5ce6e98f0a3c6"
has-flag@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
is-buffer@^2.0.2:
version "2.0.3"
resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.3.tgz#4ecf3fcf749cbd1e472689e109ac66261a25e725"
isarray@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf"
just-extend@^1.1.27:
version "1.1.27"
resolved "https://registry.yarnpkg.com/just-extend/-/just-extend-1.1.27.tgz#ec6e79410ff914e472652abfa0e603c03d60e905"
lodash.get@^4.4.2:
version "4.4.2"
resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99"
lolex@^2.3.2, lolex@^2.4.2:
version "2.7.1"
resolved "https://registry.yarnpkg.com/lolex/-/lolex-2.7.1.tgz#e40a8c4d1f14b536aa03e42a537c7adbaf0c20be"
lru-cache@^4.1.3:
version "4.1.3"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.3.tgz#a1175cf3496dfc8436c156c334b4955992bce69c"
dependencies:
pseudomap "^1.0.2"
yallist "^2.1.2"
minimist@0.0.8:
version "0.0.8"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
mkdirp@^0.5.1:
version "0.5.1"
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
dependencies:
minimist "0.0.8"
ms@2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
nise@^1.3.3:
version "1.4.2"
resolved "https://registry.yarnpkg.com/nise/-/nise-1.4.2.tgz#a9a3800e3994994af9e452333d549d60f72b8e8c"
dependencies:
"@sinonjs/formatio" "^2.0.0"
just-extend "^1.1.27"
lolex "^2.3.2"
path-to-regexp "^1.7.0"
text-encoding "^0.6.4"
opentype.js@^0.8.0:
version "0.8.0"
resolved "https://registry.yarnpkg.com/opentype.js/-/opentype.js-0.8.0.tgz#acabcfa1642fbe894a3e4d759e43ba694e02bd35"
dependencies:
tiny-inflate "^1.0.2"
path-to-regexp@^1.7.0:
version "1.7.0"
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.7.0.tgz#59fde0f435badacba103a84e9d3bc64e96b9937d"
dependencies:
isarray "0.0.1"
pend@~1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50"
promise-stream-reader@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/promise-stream-reader/-/promise-stream-reader-1.0.1.tgz#4e793a79c9d49a73ccd947c6da9c127f12923649"
pseudomap@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3"
samsam@1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/samsam/-/samsam-1.3.0.tgz#8d1d9350e25622da30de3e44ba692b5221ab7c50"
sinon@^6.1.3:
version "6.1.3"
resolved "https://registry.yarnpkg.com/sinon/-/sinon-6.1.3.tgz#98e7d716b7b11f7f1200e9c997e74f50ae094660"
dependencies:
"@sinonjs/formatio" "^2.0.0"
"@sinonjs/samsam" "^2.0.0"
diff "^3.5.0"
lodash.get "^4.4.2"
lolex "^2.4.2"
nise "^1.3.3"
supports-color "^5.4.0"
type-detect "^4.0.8"
supports-color@^5.4.0:
version "5.4.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.4.0.tgz#1c6b337402c2137605efe19f10fec390f6faab54"
dependencies:
has-flag "^3.0.0"
text-encoding@^0.6.4:
version "0.6.4"
resolved "https://registry.yarnpkg.com/text-encoding/-/text-encoding-0.6.4.tgz#e399a982257a276dae428bb92845cb71bdc26d19"
tiny-inflate@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/tiny-inflate/-/tiny-inflate-1.0.2.tgz#93d9decffc8805bd57eae4310f0b745e9b6fb3a7"
type-detect@^4.0.8:
version "4.0.8"
resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c"
yallist@^2.1.2:
version "2.1.2"
resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52"
yauzl@^2.10.0:
version "2.10.0"
resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9"
dependencies:
buffer-crc32 "~0.2.3"
fd-slicer "~1.1.0"
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "xterm-addon-search",
"version": "0.6.0",
"version": "0.7.0",
"author": {
"name": "The xterm.js authors",
"url": "https://xtermjs.org/"
+7 -3
View File
@@ -110,8 +110,12 @@ export class SearchAddon implements ITerminalAddon {
}
}
// If there is only one result, return true.
if (!result && currentSelection) return true;
// If there is only one result, wrap back and return selection if it exists.
if (!result && currentSelection) {
searchPosition.startRow = currentSelection.startRow;
searchPosition.startCol = 0;
result = this._findInLine(term, searchPosition, searchOptions);
}
// Set selection and scroll if a result was found
return this._selectResult(result);
@@ -175,7 +179,7 @@ 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.active.baseY + this._terminal.rows)) {
for (let y = (this._terminal.buffer.active.baseY + this._terminal.rows); y > startRow; y--) {
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) {
@@ -7,11 +7,11 @@ import { perfContext, before, ThroughputRuntimeCase } from 'xterm-benchmark';
import { spawn } from 'node-pty';
import { Utf8ToUtf32, stringFromCodePoint } from 'common/input/TextDecoder';
import { Terminal } from 'public/Terminal';
import { Terminal } from 'browser/public/Terminal';
import { SerializeAddon } from 'SerializeAddon';
class TestTerminal extends Terminal {
writeSync(data: string): void {
public writeSync(data: string): void {
(this as any)._core.writeSync(data);
}
}
@@ -11,8 +11,6 @@
"paths": {
"common/*": ["../../../src/common/*"],
"browser/*": ["../../../src/browser/*"],
"public/*": ["../../../src/public/*"],
"Terminal": ["../../../src/Terminal"],
"SerializeAddon": ["../src/SerializeAddon"]
}
},
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "xterm-addon-serialize",
"version": "0.2.0",
"version": "0.3.0",
"author": {
"name": "The xterm.js authors",
"url": "https://xtermjs.org/"
@@ -15,7 +15,7 @@ function constrain(value: number, low: number, high: number): number {
abstract class BaseSerializeHandler {
constructor(private _buffer: IBuffer) { }
serialize(startRow: number, endRow: number): string {
public serialize(startRow: number, endRow: number): string {
// we need two of them to flip between old and new cell
const cell1 = this._buffer.getNullCell();
const cell2 = this._buffer.getNullCell();
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "xterm-addon-unicode11",
"version": "0.1.1",
"version": "0.2.0",
"author": {
"name": "The xterm.js authors",
"url": "https://xtermjs.org/"
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "xterm-addon-web-links",
"version": "0.3.0",
"version": "0.4.0",
"author": {
"name": "The xterm.js authors",
"url": "https://xtermjs.org/"
@@ -3,7 +3,7 @@
* @license MIT
*/
import { ILinkProvider, IBufferCellPosition, ILink, Terminal, IBuffer } from 'xterm';
import { ILinkProvider, IBufferCellPosition, ILink, Terminal } from 'xterm';
export class WebLinkProvider implements ILinkProvider {
@@ -15,19 +15,20 @@ export class WebLinkProvider implements ILinkProvider {
}
provideLink(position: IBufferCellPosition, callback: (link: ILink | undefined) => void): void {
callback(LinkComputer.computeLink(position, this._regex, this._terminal, this._handler));
public provideLinks(y: number, callback: (links: ILink[] | undefined) => void): void {
callback(LinkComputer.computeLink(y, 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 {
public static computeLink(y: number, regex: RegExp, terminal: Terminal, handler: (event: MouseEvent, uri: string) => void): ILink[] {
const rex = new RegExp(regex.source, (regex.flags || '') + 'g');
const [line, startLineIndex] = LinkComputer._translateBufferLineToStringWithWrap(position.y - 1, false, terminal);
const [line, startLineIndex] = LinkComputer._translateBufferLineToStringWithWrap(y - 1, false, terminal);
let match;
let stringIndex = -1;
const result: ILink[] = [];
while ((match = rex.exec(line)) !== null) {
const text = match[1];
@@ -68,8 +69,10 @@ export class LinkComputer {
}
};
return { range, text, activate: handler };
result.push({ range, text, activate: handler });
}
return result;
}
/**
@@ -3,7 +3,7 @@
* @license MIT
*/
import { Terminal, ILinkMatcherOptions, ITerminalAddon, ILinkProvider, IDisposable } from 'xterm';
import { Terminal, ILinkMatcherOptions, ITerminalAddon, IDisposable } from 'xterm';
import { WebLinkProvider } from './WebLinkProvider';
const protocolClause = '(https?:\\/\\/)';
@@ -15,7 +15,7 @@ const ipClause = '((\\d{1,3}\\.){3}\\d{1,3})';
const localHostClause = '(localhost)';
const portClause = '(:\\d{1,5})';
const hostClause = '((' + domainBodyClause + '\\.' + tldClause + ')|' + ipClause + '|' + localHostClause + ')' + portClause + '?';
const pathCharacterSet = '(\\/[\\/\\w\\.\\-%~:+]*)*([^:"\'\\s])';
const pathCharacterSet = '(\\/[\\/\\w\\.\\-%~:+@]*)*([^:"\'\\s])';
const pathClause = '(' + pathCharacterSet + ')?';
const queryStringHashFragmentCharacterSet = '[0-9\\w\\[\\]\\(\\)\\/\\?\\!#@$%&\'*+,:;~\\=\\.\\-]*';
const queryStringClause = '(\\?' + queryStringHashFragmentCharacterSet + ')?';
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "xterm-addon-webgl",
"version": "0.6.0",
"version": "0.7.0",
"author": {
"name": "The xterm.js authors",
"url": "https://xtermjs.org/"
@@ -213,14 +213,14 @@ export class GlyphRenderer {
// a_cellpos only changes on resize
}
public updateSelection(model: IRenderModel, columnSelectMode: boolean): void {
public updateSelection(model: IRenderModel): void {
const terminal = this._terminal;
this._vertices.selectionAttributes = slice(this._vertices.attributes, 0);
const bg = (this._colors.selectionOpaque.rgba >>> 8) | Attributes.CM_RGB;
if (columnSelectMode) {
if (model.selection.columnSelectMode) {
const startCol = model.selection.startCol;
const width = model.selection.endCol - startCol;
const height = model.selection.viewportCappedEndRow - model.selection.viewportCappedStartRow + 1;
@@ -171,7 +171,7 @@ export class RectangleRenderer {
);
}
public updateSelection(model: ISelectionRenderModel, columnSelectMode: boolean): void {
public updateSelection(model: ISelectionRenderModel): void {
const terminal = this._terminal;
if (!model.hasSelection) {
@@ -179,7 +179,7 @@ export class RectangleRenderer {
return;
}
if (columnSelectMode) {
if (model.columnSelectMode) {
const startCol = model.startCol;
const width = model.endCol - startCol;
const height = model.viewportCappedEndRow - model.viewportCappedStartRow + 1;
@@ -22,6 +22,7 @@ export class RenderModel implements IRenderModel {
this.lineLengths = new Uint32Array(0);
this.selection = {
hasSelection: false,
columnSelectMode: false,
viewportStartRow: 0,
viewportEndRow: 0,
viewportCappedStartRow: 0,
+1
View File
@@ -56,6 +56,7 @@ export interface IRenderModel {
export interface ISelectionRenderModel {
hasSelection: boolean;
columnSelectMode: boolean;
viewportStartRow: number;
viewportEndRow: number;
viewportCappedStartRow: number;
@@ -3,7 +3,7 @@
* @license MIT
*/
import { ITerminalOptions } from '../../../src/Types';
import { ITerminalOptions } from '../../../src/common/Types';
import { ITheme } from 'xterm';
import { assert } from 'chai';
import { openTerminal, pollFor, writeSync, getBrowserType } from '../../../out-test/api/TestUtils';
@@ -341,10 +341,6 @@ describe('WebGL Renderer Integration Tests', async () => {
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];
const r = parseInt(cssColor.substr(1, 2), 16);
const g = parseInt(cssColor.substr(3, 2), 16);
const b = parseInt(cssColor.substr(5, 2), 16);
await pollFor(page, () => getCellColor(x + 1, y + 1), [0, 0, 0, 255]);
}
}
@@ -670,7 +666,6 @@ describe('WebGL Renderer Integration Tests', async () => {
await writeSync(page, data);
for (let y = 0; y < 16; y++) {
for (let x = 0; x < 16; x++) {
const i = y * 16 + x;
await pollFor(page, () => getCellColor(x + 1, y + 1), [0, 0, 0, 255]);
}
}
+22 -19
View File
@@ -3,7 +3,6 @@
* @license MIT
*/
import { ITerminal } from '../../../src/Types';
import { GlyphRenderer } from './GlyphRenderer';
import { LinkRenderLayer } from './renderLayer/LinkRenderLayer';
import { CursorRenderLayer } from './renderLayer/CursorRenderLayer';
@@ -16,8 +15,8 @@ import { Disposable } from 'common/Lifecycle';
import { NULL_CELL_CODE } from 'common/buffer/Constants';
import { Terminal, IEvent } from 'xterm';
import { IRenderLayer } from './renderLayer/Types';
import { IRenderDimensions, IRenderer, IRequestRefreshRowsEvent } from 'browser/renderer/Types';
import { IColorSet } from 'browser/Types';
import { IRenderDimensions, IRenderer, IRequestRedrawEvent } from 'browser/renderer/Types';
import { ITerminal, IColorSet } from 'browser/Types';
import { EventEmitter } from 'common/EventEmitter';
import { CellData } from 'common/buffer/CellData';
@@ -39,8 +38,8 @@ export class WebglRenderer extends Disposable implements IRenderer {
private _core: ITerminal;
private _isAttached: boolean;
private _onRequestRefreshRows = new EventEmitter<IRequestRefreshRowsEvent>();
public get onRequestRefreshRows(): IEvent<IRequestRefreshRowsEvent> { return this._onRequestRefreshRows.event; }
private _onRequestRedraw = new EventEmitter<IRequestRedrawEvent>();
public get onRequestRedraw(): IEvent<IRequestRedrawEvent> { return this._onRequestRedraw.event; }
constructor(
private _terminal: Terminal,
@@ -52,8 +51,8 @@ export class WebglRenderer extends Disposable implements IRenderer {
this._core = (this._terminal as any)._core;
this._renderLayers = [
new LinkRenderLayer(this._core.screenElement, 2, this._colors, this._core),
new CursorRenderLayer(this._core.screenElement, 3, this._colors, this._onRequestRefreshRows)
new LinkRenderLayer(this._core.screenElement!, 2, this._colors, this._core),
new CursorRenderLayer(this._core.screenElement!, 3, this._colors, this._onRequestRedraw)
];
this.dimensions = {
scaledCharWidth: 0,
@@ -83,7 +82,7 @@ export class WebglRenderer extends Disposable implements IRenderer {
if (!this._gl) {
throw new Error('WebGL2 not supported ' + this._gl);
}
this._core.screenElement.appendChild(this._canvas);
this._core.screenElement!.appendChild(this._canvas);
this._rectangleRenderer = new RectangleRenderer(this._terminal, this._colors, this._gl, this.dimensions);
this._glyphRenderer = new GlyphRenderer(this._terminal, this._colors, this._gl, this.dimensions);
@@ -91,12 +90,12 @@ export class WebglRenderer extends Disposable implements IRenderer {
// Update dimensions and acquire char atlas
this.onCharSizeChanged();
this._isAttached = document.body.contains(this._core.screenElement);
this._isAttached = document.body.contains(this._core.screenElement!);
}
public dispose(): void {
this._renderLayers.forEach(l => l.dispose());
this._core.screenElement.removeChild(this._canvas);
this._core.screenElement!.removeChild(this._canvas);
super.dispose();
}
@@ -117,6 +116,9 @@ export class WebglRenderer extends Disposable implements IRenderer {
this._refreshCharAtlas();
this._rectangleRenderer.updateSelection(this._model.selection);
this._glyphRenderer.updateSelection(this._model);
// Force a full refresh
this._model.clear();
}
@@ -147,8 +149,8 @@ export class WebglRenderer extends Disposable implements IRenderer {
this._canvas.style.height = `${this.dimensions.canvasHeight}px`;
// Resize the screen
this._core.screenElement.style.width = `${this.dimensions.canvasWidth}px`;
this._core.screenElement.style.height = `${this.dimensions.canvasHeight}px`;
this._core.screenElement!.style.width = `${this.dimensions.canvasWidth}px`;
this._core.screenElement!.style.height = `${this.dimensions.canvasHeight}px`;
this._glyphRenderer.setDimensions(this.dimensions);
this._glyphRenderer.onResize();
@@ -170,15 +172,15 @@ export class WebglRenderer extends Disposable implements IRenderer {
this._renderLayers.forEach(l => l.onFocus(this._terminal));
}
public onSelectionChanged(start: [number, number], end: [number, number], columnSelectMode: boolean): void {
public onSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void {
this._renderLayers.forEach(l => l.onSelectionChanged(this._terminal, start, end, columnSelectMode));
this._updateSelectionModel(start, end);
this._updateSelectionModel(start, end, columnSelectMode);
this._rectangleRenderer.updateSelection(this._model.selection, columnSelectMode);
this._glyphRenderer.updateSelection(this._model, columnSelectMode);
this._rectangleRenderer.updateSelection(this._model.selection);
this._glyphRenderer.updateSelection(this._model);
this._onRequestRefreshRows.fire({ start: 0, end: this._terminal.rows - 1 });
this._onRequestRedraw.fire({ start: 0, end: this._terminal.rows - 1 });
}
public onCursorMove(): void {
@@ -226,7 +228,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) && (this._core as any)._charSizeService.width && (this._core as any)._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;
@@ -292,7 +294,7 @@ export class WebglRenderer extends Disposable implements IRenderer {
this._rectangleRenderer.updateBackgrounds(this._model);
}
private _updateSelectionModel(start: [number, number], end: [number, number]): void {
private _updateSelectionModel(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void {
const terminal = this._terminal;
// Selection does not exist
@@ -314,6 +316,7 @@ export class WebglRenderer extends Disposable implements IRenderer {
}
this._model.selection.hasSelection = true;
this._model.selection.columnSelectMode = columnSelectMode;
this._model.selection.viewportStartRow = viewportStartRow;
this._model.selection.viewportEndRow = viewportEndRow;
this._model.selection.viewportCappedStartRow = viewportCappedStartRow;
@@ -349,7 +349,7 @@ export class WebglCharAtlas implements IDisposable {
const fontStyle = italic ? 'italic' : '';
this._tmpCtx.font =
`${fontStyle} ${fontWeight} ${this._config.fontSize * this._config.devicePixelRatio}px ${this._config.fontFamily}`;
this._tmpCtx.textBaseline = 'top';
this._tmpCtx.textBaseline = 'middle';
this._tmpCtx.fillStyle = this._getForegroundCss(bg, bgColorMode, bgColor, fg, fgColorMode, fgColor, inverse, bold);
@@ -359,7 +359,7 @@ export class WebglCharAtlas implements IDisposable {
}
// Draw the character
this._tmpCtx.fillText(chars, TMP_CANVAS_GLYPH_PADDING, TMP_CANVAS_GLYPH_PADDING);
this._tmpCtx.fillText(chars, TMP_CANVAS_GLYPH_PADDING, TMP_CANVAS_GLYPH_PADDING + this._config.scaledCharHeight / 2);
this._tmpCtx.restore();
// clear the background from the character to avoid issues with drawing over the previous
@@ -58,7 +58,7 @@ export abstract class BaseRenderLayer implements IRenderLayer {
public onFocus(terminal: Terminal): void {}
public onCursorMove(terminal: Terminal): void {}
public onGridChanged(terminal: Terminal, startRow: number, endRow: number): void {}
public onSelectionChanged(terminal: Terminal, start: [number, number], end: [number, number], columnSelectMode: boolean = false): void {}
public onSelectionChanged(terminal: Terminal, start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean = false): void {}
public setColors(terminal: Terminal, colorSet: IColorSet): void {
this._refreshCharAtlas(terminal, colorSet);
@@ -8,7 +8,7 @@ import { BaseRenderLayer } from './BaseRenderLayer';
import { ICellData } from 'common/Types';
import { CellData } from 'common/buffer/CellData';
import { IColorSet } from 'browser/Types';
import { IRenderDimensions, IRequestRefreshRowsEvent } from 'browser/renderer/Types';
import { IRenderDimensions, IRequestRedrawEvent } from 'browser/renderer/Types';
import { IEventEmitter } from 'common/EventEmitter';
interface ICursorState {
@@ -34,7 +34,7 @@ export class CursorRenderLayer extends BaseRenderLayer {
container: HTMLElement,
zIndex: number,
colors: IColorSet,
private _onRequestRefreshRowsEvent: IEventEmitter<IRequestRefreshRowsEvent>
private _onRequestRefreshRowsEvent: IEventEmitter<IRequestRedrawEvent>
) {
super(container, 'cursor', zIndex, true, colors);
this._state = {
@@ -3,24 +3,23 @@
* @license MIT
*/
import { ILinkifierAccessor } from '../../../../src/Types';
import { Terminal } from 'xterm';
import { BaseRenderLayer } from './BaseRenderLayer';
import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants';
import { is256Color } from '../atlas/CharAtlasUtils';
import { IColorSet, ILinkifierEvent } from 'browser/Types';
import { ITerminal, IColorSet, ILinkifierEvent } from 'browser/Types';
import { IRenderDimensions } from 'browser/renderer/Types';
export class LinkRenderLayer extends BaseRenderLayer {
private _state: ILinkifierEvent | undefined;
constructor(container: HTMLElement, zIndex: number, colors: IColorSet, terminal: ILinkifierAccessor) {
constructor(container: HTMLElement, zIndex: number, colors: IColorSet, terminal: ITerminal) {
super(container, 'link', zIndex, true, colors);
terminal.linkifier.onLinkHover(e => this._onLinkHover(e));
terminal.linkifier.onLinkLeave(e => this._onLinkLeave(e));
terminal.linkifier.onShowLinkUnderline(e => this._onShowLinkUnderline(e));
terminal.linkifier.onHideLinkUnderline(e => this._onHideLinkUnderline(e));
terminal.linkifier2.onLinkHover(e => this._onLinkHover(e));
terminal.linkifier2.onLinkLeave(e => this._onLinkLeave(e));
terminal.linkifier2.onShowLinkUnderline(e => this._onShowLinkUnderline(e));
terminal.linkifier2.onHideLinkUnderline(e => this._onHideLinkUnderline(e));
}
public resize(terminal: Terminal, dim: IRenderDimensions): void {
@@ -45,7 +44,7 @@ export class LinkRenderLayer extends BaseRenderLayer {
}
}
private _onLinkHover(e: ILinkifierEvent): void {
private _onShowLinkUnderline(e: ILinkifierEvent): void {
if (e.fg === INVERTED_DEFAULT_COLOR) {
this._ctx.fillStyle = this._colors.background.css;
} else if (e.fg !== undefined && is256Color(e.fg)) {
@@ -69,7 +68,7 @@ export class LinkRenderLayer extends BaseRenderLayer {
this._state = e;
}
private _onLinkLeave(e: ILinkifierEvent): void {
private _onHideLinkUnderline(e: ILinkifierEvent): void {
this._clearCurrentLink();
}
}
@@ -42,7 +42,7 @@ export interface IRenderLayer extends IDisposable {
/**
* Calls when the selection changes.
*/
onSelectionChanged(terminal: Terminal, start: [number, number], end: [number, number], columnSelectMode: boolean): void;
onSelectionChanged(terminal: Terminal, start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void;
/**
* Registers a handler to join characters to render as a group
+7 -7
View File
@@ -1,7 +1,7 @@
/**
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
* @license MIT
*
*
* Script to extract vt features documented in docstrings.
*/
const fs = require('fs');
@@ -265,7 +265,7 @@ To denote the sequences the tables use the same abbreviations as xterm does:
function hideDetailSections() {
for (let section of document.getElementsByClassName('sequence-details')) section.style.display = 'none';
}
function decorateDetailLinks() {
for (let link of document.getElementsByClassName('link-details')) {
link.addEventListener("click", toggleDetails, false);
@@ -344,11 +344,11 @@ function applyMacros(s) {
return s;
}
function replaceStatus(s) {
if (s === 'supported') return '<span title="supported">✓</span>';
if (s === 'unsupported') return '<span title="unsupported">✗</span>';
return s;
}
// function replaceStatus(s) {
// if (s === 'supported') return '<span title="supported">✓</span>';
// if (s === 'unsupported') return '<span title="unsupported">✗</span>';
// return s;
// }
function createAnchorSlug(s) {
return s.toLowerCase().split(' ').join('-');
+1
View File
@@ -28,6 +28,7 @@ if (changedFiles.some(e => e.search(/^addons\//) === -1)) {
const addonPackageDirs = [
path.resolve(__dirname, '../addons/xterm-addon-attach'),
path.resolve(__dirname, '../addons/xterm-addon-fit'),
path.resolve(__dirname, '../addons/xterm-addon-ligatures'),
path.resolve(__dirname, '../addons/xterm-addon-search'),
path.resolve(__dirname, '../addons/xterm-addon-serialize'),
path.resolve(__dirname, '../addons/xterm-addon-unicode11'),
-1
View File
@@ -13,7 +13,6 @@ const env = { ...process.env };
env.NODE_PATH = path.resolve(__dirname, '../out');
let testFiles = [
'./out/*test.js',
'./out/**/*test.js',
'./addons/**/out/*test.js',
];
+52
View File
@@ -0,0 +1,52 @@
/**
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
* @license MIT
*/
const cp = require('child_process');
const path = require('path');
// Add `out` to the NODE_PATH so absolute paths can be resolved.
const env = { ...process.env };
env.NODE_PATH = path.resolve(__dirname, '../out');
let testFiles = [
'./addons/**/out/*api.js',
'./out-test/**/*api.js',
];
let flagArgs = [];
if (process.argv.length > 2) {
const args = process.argv.slice(2);
flagArgs = args.filter(e => e.startsWith('--')).map(arg => arg.split('=')).reduce((arr, val) => arr.concat([...val], []));
console.info(flagArgs);
// ability to inject particular test files via
// yarn test [testFileA testFileB ...]
files = args.filter(e => !e.startsWith('--'));
if (files.length) {
testFiles = files;
}
}
env.DEBUG = flagArgs.indexOf('--debug') >= 0 ? 'debug' : '';
const run = cp.spawnSync(
npmBinScript('mocha'),
[...testFiles, ...flagArgs],
{
cwd: path.resolve(__dirname, '..'),
env,
stdio: 'inherit'
}
);
function npmBinScript(script) {
return path.resolve(__dirname, `../node_modules/.bin/` + (process.platform === 'win32' ? `${script}.cmd` : script));
}
process.exit(run.status);
+13 -5
View File
@@ -8,7 +8,7 @@
/// <reference path="../typings/xterm.d.ts"/>
// Use tsc version (yarn watch)
import { Terminal } from '../out/public/Terminal';
import { Terminal } from '../out/browser/public/Terminal';
import { AttachAddon } from '../addons/xterm-addon-attach/out/AttachAddon';
import { FitAddon } from '../addons/xterm-addon-fit/out/FitAddon';
import { SearchAddon, ISearchOptions } from '../addons/xterm-addon-search/out/SearchAddon';
@@ -112,9 +112,15 @@ const disposeRecreateButtonHandler = () => {
term = null;
window.term = null;
socket = null;
addons.attach.instance = undefined;
addons.fit.instance = undefined;
addons.search.instance = undefined;
addons.serialize.instance = undefined;
addons.unicode11.instance = undefined;
addons['web-links'].instance = undefined;
addons.webgl.instance = undefined;
document.getElementById('dispose').innerHTML = 'Recreate Terminal';
}
else {
} else {
createTerminal();
document.getElementById('dispose').innerHTML = 'Dispose terminal';
}
@@ -357,7 +363,7 @@ function initAddons(term: TerminalType): void {
if (!addon.canChange) {
checkbox.disabled = true;
}
checkbox.addEventListener('change', () => {
addDomListener(checkbox, 'change', () => {
if (checkbox.checked) {
addon.instance = new addon.ctor();
term.loadAddon(addon.instance);
@@ -386,7 +392,9 @@ function initAddons(term: TerminalType): void {
wrapper.appendChild(label);
fragment.appendChild(wrapper);
});
document.getElementById('addons-container').appendChild(fragment);
const container = document.getElementById('addons-container');
container.innerHTML = '';
container.appendChild(fragment);
}
function addDomListener(element: HTMLElement, type: string, handler: (...args: any[]) => any): void {
+16 -16
View File
@@ -1,7 +1,7 @@
{
"name": "xterm",
"description": "Full xterm terminal, in your browser",
"version": "4.5.0",
"version": "4.6.0",
"main": "lib/xterm.js",
"style": "css/xterm.css",
"types": "typings/xterm.d.ts",
@@ -11,13 +11,13 @@
"prepackage": "npm run build",
"package": "webpack",
"start": "node demo/start",
"lint": "eslint -c .eslintrc.json --ext .ts src/ addons/",
"lint": "eslint -c .eslintrc.json --max-warnings 0 --ext .ts src/ addons/",
"test": "npm run test-unit",
"posttest": "npm run lint",
"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",
"test-api-chromium": "node ./bin/test_api.js --browser=chromium --timeout=20000",
"test-api-firefox": "node ./bin/test_api.js --browser=firefox --timeout=20000",
"test-api-webkit": "node ./bin/test_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",
@@ -40,33 +40,33 @@
"@types/debug": "^4.1.5",
"@types/deep-equal": "^1.0.1",
"@types/glob": "^7.1.1",
"@types/jsdom": "^16.2.0",
"@types/jsdom": "^16.2.1",
"@types/mocha": "^7.0.2",
"@types/node": "^10.17.17",
"@types/utf8": "^2.1.6",
"@types/webpack": "^4.41.10",
"@types/ws": "^7.2.3",
"@typescript-eslint/eslint-plugin": "^2.27.0",
"@typescript-eslint/parser": "^2.27.0",
"@types/webpack": "^4.41.12",
"@types/ws": "^7.2.4",
"@typescript-eslint/eslint-plugin": "^2.33.0",
"@typescript-eslint/parser": "^2.33.0",
"chai": "^4.2.0",
"deep-equal": "^2.0.2",
"deep-equal": "^2.0.3",
"eslint": "^6.8.0",
"express": "^4.17.1",
"express-ws": "^4.0.0",
"glob": "^7.0.5",
"jsdom": "^16.2.2",
"mocha": "^7.1.1",
"mocha": "^7.1.2",
"mustache": "^4.0.1",
"node-pty": "^0.9.0",
"nyc": "^15.0.1",
"playwright-core": "^0.11.1",
"source-map-loader": "^0.2.4",
"ts-loader": "^6.0.4",
"typescript": "3.8",
"ts-loader": "^7.0.4",
"typescript": "3.9",
"utf8": "^3.0.0",
"webpack": "^4.35.3",
"webpack": "^4.43.0",
"webpack-cli": "^3.3.11",
"ws": "^7.0.0",
"ws": "^7.3.0",
"xterm-benchmark": "^0.1.3"
}
}
File diff suppressed because it is too large Load Diff
-360
View File
@@ -1,360 +0,0 @@
/**
* Copyright (c) 2017 The xterm.js authors. All rights reserved.
* @license MIT
*/
import { IRenderer, IRenderDimensions, CharacterJoinerHandler, IRequestRefreshRowsEvent } from 'browser/renderer/Types';
import { IInputHandlingTerminal, ICompositionHelper, ITerminal, IBrowser, ITerminalOptions } from './Types';
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, ILinkProvider } from 'xterm';
import { Terminal } from './Terminal';
import { AttributeData } from 'common/buffer/AttributeData';
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';
import { ISelectionService } from 'browser/services/Services';
export class TestTerminal extends Terminal {
get curAttrData(): IAttributeData { return (this as any)._inputHandler._curAttrData; }
keyDown(ev: any): boolean { return this._keyDown(ev); }
keyPress(ev: any): boolean { return this._keyPress(ev); }
}
export class MockTerminal implements ITerminal {
onBlur: IEvent<void>;
onFocus: IEvent<void>;
onA11yChar: IEvent<string>;
onA11yTab: IEvent<number>;
onCursorMove: IEvent<void>;
onLineFeed: IEvent<void>;
onSelectionChange: IEvent<void>;
onData: IEvent<string>;
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 }>;
markers: IMarker[];
optionsService: IOptionsService;
unicodeService: IUnicodeService;
addMarker(cursorYOffset: number): IMarker {
throw new Error('Method not implemented.');
}
selectLines(start: number, end: number): void {
throw new Error('Method not implemented.');
}
scrollToLine(line: number): void {
throw new Error('Method not implemented.');
}
static string: any;
setOption(key: any, value: any): void {
throw new Error('Method not implemented.');
}
blur(): void {
throw new Error('Method not implemented.');
}
focus(): void {
throw new Error('Method not implemented.');
}
resize(columns: number, rows: number): void {
throw new Error('Method not implemented.');
}
writeln(data: string): void {
throw new Error('Method not implemented.');
}
paste(data: string): void {
throw new Error('Method not implemented.');
}
open(parent: HTMLElement): void {
throw new Error('Method not implemented.');
}
attachCustomKeyEventHandler(customKeyEventHandler: (event: KeyboardEvent) => boolean): void {
throw new Error('Method not implemented.');
}
addCsiHandler(id: IFunctionIdentifier, callback: (params: IParams) => boolean): IDisposable {
throw new Error('Method not implemented.');
}
addDcsHandler(id: IFunctionIdentifier, callback: (data: string, param: IParams) => boolean): IDisposable {
throw new Error('Method not implemented.');
}
addEscHandler(id: IFunctionIdentifier, handler: () => boolean): IDisposable {
throw new Error('Method not implemented.');
}
addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable {
throw new Error('Method not implemented.');
}
registerLinkMatcher(regex: RegExp, handler: (event: MouseEvent, uri: string) => boolean | void, options?: ILinkMatcherOptions): number {
throw new Error('Method not implemented.');
}
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.');
}
getSelection(): string {
throw new Error('Method not implemented.');
}
getSelectionPosition(): ISelectionPosition | undefined {
throw new Error('Method not implemented.');
}
clearSelection(): void {
throw new Error('Method not implemented.');
}
select(column: number, row: number, length: number): void {
throw new Error('Method not implemented.');
}
selectAll(): void {
throw new Error('Method not implemented.');
}
dispose(): void {
throw new Error('Method not implemented.');
}
scrollPages(pageCount: number): void {
throw new Error('Method not implemented.');
}
scrollToTop(): void {
throw new Error('Method not implemented.');
}
scrollToBottom(): void {
throw new Error('Method not implemented.');
}
clear(): void {
throw new Error('Method not implemented.');
}
write(data: string): void {
throw new Error('Method not implemented.');
}
writeUtf8(data: Uint8Array): void {
throw new Error('Method not implemented.');
}
bracketedPasteMode: boolean;
renderer: IRenderer;
linkifier: ILinkifier;
linkifier2: ILinkifier2;
isFocused: boolean;
options: ITerminalOptions = {};
element: HTMLElement;
screenElement: HTMLElement;
rowContainer: HTMLElement;
selectionContainer: HTMLElement;
selectionService: ISelectionService;
textarea: HTMLTextAreaElement;
rows: number;
cols: number;
browser: IBrowser = <any>Browser;
writeBuffer: string[];
children: HTMLElement[];
cursorHidden: boolean;
cursorState: number;
scrollback: number;
buffers: IBufferSet;
buffer: IBuffer;
viewport: IViewport;
applicationCursor: boolean;
handler(data: string): void {
throw new Error('Method not implemented.');
}
on(event: string, callback: (...args: any[]) => void): void {
throw new Error('Method not implemented.');
}
off(type: string, listener: XtermListener): void {
throw new Error('Method not implemented.');
}
addDisposableListener(type: string, handler: XtermListener): IDisposable {
throw new Error('Method not implemented.');
}
scrollLines(disp: number, suppressScrollEvent: boolean): void {
throw new Error('Method not implemented.');
}
scrollToRow(absoluteRow: number): number {
throw new Error('Method not implemented.');
}
cancel(ev: Event, force?: boolean): void {
throw new Error('Method not implemented.');
}
log(text: string): void {
throw new Error('Method not implemented.');
}
emit(event: string, data: any): void {
throw new Error('Method not implemented.');
}
reset(): void {
throw new Error('Method not implemented.');
}
showCursor(): void {
throw new Error('Method not implemented.');
}
refresh(start: number, end: number): void {
throw new Error('Method not implemented.');
}
registerCharacterJoiner(handler: CharacterJoinerHandler): number { return 0; }
deregisterCharacterJoiner(joinerId: number): void { }
}
export class MockInputHandlingTerminal implements IInputHandlingTerminal {
onA11yCharEmitter: EventEmitter<string>;
onA11yTabEmitter: EventEmitter<number>;
insertMode: boolean;
bracketedPasteMode: boolean;
sendFocus: boolean;
buffers: IBufferSet;
buffer: IBuffer = new MockBuffer();
viewport: IViewport;
scroll(eraseAttr: IAttributeData, isWrapped?: boolean): void {
throw new Error('Method not implemented.');
}
is(term: string): boolean {
throw new Error('Method not implemented.');
}
resize(x: number, y: number): void {
throw new Error('Method not implemented.');
}
showCursor(): void {
throw new Error('Method not implemented.');
}
handler(data: string): void {
throw new Error('Method not implemented.');
}
handleTitle(title: string): void {
throw new Error('Method not implemented.');
}
}
export class MockBuffer implements IBuffer {
markers: IMarker[];
addMarker(y: number): IMarker {
throw new Error('Method not implemented.');
}
isCursorInViewport: boolean;
lines: ICircularList<IBufferLine>;
ydisp: number;
ybase: number;
hasScrollback: boolean;
y: number;
x: number;
tabs: any;
scrollBottom: number;
scrollTop: number;
savedY: number;
savedX: number;
savedCharset: ICharset | null;
savedCurAttrData = new AttributeData();
translateBufferLineToString(lineIndex: number, trimRight: boolean, startCol?: number, endCol?: number): string {
return Buffer.prototype.translateBufferLineToString.apply(this, arguments);
}
getWrappedRangeForLine(y: number): { first: number, last: number } {
return Buffer.prototype.getWrappedRangeForLine.apply(this, arguments);
}
nextStop(x?: number): number {
throw new Error('Method not implemented.');
}
prevStop(x?: number): number {
throw new Error('Method not implemented.');
}
setLines(lines: ICircularList<IBufferLine>): void {
this.lines = lines;
}
getBlankLine(attr: IAttributeData, isWrapped?: boolean): IBufferLine {
return Buffer.prototype.getBlankLine.apply(this, arguments);
}
stringIndexToBufferIndex(lineIndex: number, stringIndex: number): number[] {
return Buffer.prototype.stringIndexToBufferIndex.apply(this, arguments);
}
iterator(trimRight: boolean, startIndex?: number, endIndex?: number): IBufferStringIterator {
return Buffer.prototype.iterator.apply(this, arguments);
}
getNullCell(attr?: IAttributeData): ICellData {
throw new Error('Method not implemented.');
}
getWhitespaceCell(attr?: IAttributeData): ICellData {
throw new Error('Method not implemented.');
}
}
export class MockRenderer implements IRenderer {
onRequestRefreshRows: IEvent<IRequestRefreshRowsEvent>;
onCanvasResize: IEvent<{ width: number, height: number }>;
onRender: IEvent<{ start: number, end: number }>;
dispose(): void {
throw new Error('Method not implemented.');
}
colorManager: IColorManager;
on(type: string, listener: XtermListener): void {
throw new Error('Method not implemented.');
}
off(type: string, listener: XtermListener): void {
throw new Error('Method not implemented.');
}
emit(type: string, data?: any): void {
throw new Error('Method not implemented.');
}
addDisposableListener(type: string, handler: XtermListener): IDisposable {
throw new Error('Method not implemented.');
}
dimensions: IRenderDimensions;
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 { }
registerCharacterJoiner(handler: CharacterJoinerHandler): number { return 0; }
deregisterCharacterJoiner(): boolean { return true; }
}
export class MockViewport implements IViewport {
dispose(): void {
throw new Error('Method not implemented.');
}
scrollBarWidth: number = 0;
onThemeChange(colors: IColorSet): void {
throw new Error('Method not implemented.');
}
onWheel(ev: WheelEvent): boolean {
throw new Error('Method not implemented.');
}
onTouchStart(ev: TouchEvent): void {
throw new Error('Method not implemented.');
}
onTouchMove(ev: TouchEvent): boolean {
throw new Error('Method not implemented.');
}
syncScrollArea(): void { }
getLinesScrolled(ev: WheelEvent): number {
throw new Error('Method not implemented.');
}
}
export class MockCompositionHelper implements ICompositionHelper {
compositionstart(): void {
throw new Error('Method not implemented.');
}
compositionupdate(ev: CompositionEvent): void {
throw new Error('Method not implemented.');
}
compositionend(): void {
throw new Error('Method not implemented.');
}
updateCompositionElements(dontRecurse?: boolean): void {
throw new Error('Method not implemented.');
}
keydown(ev: KeyboardEvent): boolean {
return true;
}
}
-236
View File
@@ -1,236 +0,0 @@
/**
* Copyright (c) 2017 The xterm.js authors. All rights reserved.
* @license MIT
*/
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, 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';
export type CustomKeyEventHandler = (event: KeyboardEvent) => boolean;
export type LineData = CharData[];
/**
* This interface encapsulates everything needed from the Terminal by the
* InputHandler. This cleanly separates the large amount of methods needed by
* InputHandler cleanly from the ITerminal interface.
*/
export interface IInputHandlingTerminal {
insertMode: boolean;
bracketedPasteMode: boolean;
sendFocus: boolean;
buffers: IBufferSet;
buffer: IBuffer;
viewport: IViewport;
onA11yCharEmitter: IEventEmitter<string>;
onA11yTabEmitter: IEventEmitter<number>;
scroll(eraseAttr: IAttributeData, isWrapped?: boolean): void;
is(term: string): boolean;
resize(x: number, y: number): void;
showCursor(): void;
handleTitle(title: string): void;
}
export interface ICompositionHelper {
compositionstart(): void;
compositionupdate(ev: CompositionEvent): void;
compositionend(): void;
updateCompositionElements(dontRecurse?: boolean): void;
keydown(ev: KeyboardEvent): boolean;
}
/**
* Calls the parser and handles actions generated by the parser.
*/
export interface IInputHandler {
parse(data: string | Uint8Array): void;
print(data: Uint32Array, start: number, end: number): void;
/** C0 BEL */ bell(): void;
/** C0 LF */ lineFeed(): void;
/** C0 CR */ carriageReturn(): void;
/** C0 BS */ backspace(): void;
/** C0 HT */ tab(): void;
/** C0 SO */ shiftOut(): void;
/** C0 SI */ shiftIn(): void;
/** CSI @ */ insertChars(params: IParams): void;
/** CSI SP @ */ scrollLeft(params: IParams): void;
/** CSI A */ cursorUp(params: IParams): void;
/** CSI SP A */ scrollRight(params: IParams): void;
/** CSI B */ cursorDown(params: IParams): void;
/** CSI C */ cursorForward(params: IParams): void;
/** CSI D */ cursorBackward(params: IParams): void;
/** CSI E */ cursorNextLine(params: IParams): void;
/** CSI F */ cursorPrecedingLine(params: IParams): void;
/** CSI G */ cursorCharAbsolute(params: IParams): void;
/** CSI H */ cursorPosition(params: IParams): void;
/** CSI I */ cursorForwardTab(params: IParams): void;
/** CSI J */ eraseInDisplay(params: IParams): void;
/** CSI K */ eraseInLine(params: IParams): void;
/** CSI L */ insertLines(params: IParams): void;
/** CSI M */ deleteLines(params: IParams): void;
/** CSI P */ deleteChars(params: IParams): void;
/** CSI S */ scrollUp(params: IParams): void;
/** CSI T */ scrollDown(params: IParams, collect?: string): void;
/** CSI X */ eraseChars(params: IParams): void;
/** CSI Z */ cursorBackwardTab(params: IParams): void;
/** CSI ` */ charPosAbsolute(params: IParams): void;
/** CSI a */ hPositionRelative(params: IParams): void;
/** CSI b */ repeatPrecedingCharacter(params: IParams): void;
/** CSI c */ sendDeviceAttributesPrimary(params: IParams): void;
/** CSI > c */ sendDeviceAttributesSecondary(params: IParams): void;
/** CSI d */ linePosAbsolute(params: IParams): void;
/** CSI e */ vPositionRelative(params: IParams): void;
/** CSI f */ hVPosition(params: IParams): void;
/** CSI g */ tabClear(params: IParams): void;
/** CSI h */ setMode(params: IParams, collect?: string): void;
/** CSI l */ resetMode(params: IParams, collect?: string): void;
/** CSI m */ charAttributes(params: IParams): void;
/** CSI n */ deviceStatus(params: IParams, collect?: string): void;
/** CSI p */ softReset(params: IParams, collect?: string): void;
/** CSI q */ setCursorStyle(params: IParams, collect?: string): void;
/** CSI r */ setScrollRegion(params: IParams, collect?: string): void;
/** CSI s */ saveCursor(params: IParams): void;
/** CSI u */ restoreCursor(params: IParams): void;
/** CSI ' } */ insertColumns(params: IParams): void;
/** CSI ' ~ */ deleteColumns(params: IParams): void;
/** OSC 0
OSC 2 */ setTitle(data: string): void;
/** ESC E */ nextLine(): void;
/** ESC = */ keypadApplicationMode(): void;
/** ESC > */ keypadNumericMode(): void;
/** ESC % G
ESC % @ */ selectDefaultCharset(): void;
/** ESC ( C
ESC ) C
ESC * C
ESC + C
ESC - C
ESC . C
ESC / C */ selectCharset(collectAndFlag: string): void;
/** ESC D */ index(): void;
/** ESC H */ tabSet(): void;
/** ESC M */ reverseIndex(): void;
/** ESC c */ fullReset(): void;
/** ESC n
ESC o
ESC |
ESC }
ESC ~ */ setgLevel(level: number): void;
/** ESC # 8 */ screenAlignmentPattern(): void;
}
export interface ITerminal extends IPublicTerminal, IElementAccessor, IBufferAccessor, ILinkifierAccessor {
screenElement: HTMLElement;
browser: IBrowser;
buffer: IBuffer;
buffers: IBufferSet;
viewport: IViewport;
bracketedPasteMode: boolean;
optionsService: IOptionsService;
// TODO: We should remove options once components adopt optionsService
options: ITerminalOptions;
unicodeService: IUnicodeService;
onBlur: IEvent<void>;
onFocus: IEvent<void>;
onA11yChar: IEvent<string>;
onA11yTab: IEvent<number>;
scrollLines(disp: number, suppressScrollEvent?: boolean): void;
cancel(ev: Event, force?: boolean): boolean | void;
showCursor(): void;
}
// Portions of the public API that are required by the internal Terminal
export interface IPublicTerminal extends IDisposable {
textarea: HTMLTextAreaElement | undefined;
rows: number;
cols: number;
buffer: IBuffer;
markers: IMarker[];
onCursorMove: IEvent<void>;
onData: IEvent<string>;
onBinary: IEvent<string>;
onKey: IEvent<{ key: string, domEvent: KeyboardEvent }>;
onLineFeed: IEvent<void>;
onScroll: IEvent<number>;
onSelectionChange: IEvent<void>;
onRender: IEvent<{ start: number, end: number }>;
onResize: IEvent<{ cols: number, rows: number }>;
onTitleChange: IEvent<string>;
blur(): void;
focus(): void;
resize(columns: number, rows: number): void;
open(parent: HTMLElement): void;
attachCustomKeyEventHandler(customKeyEventHandler: (event: KeyboardEvent) => boolean): void;
addCsiHandler(id: IFunctionIdentifier, callback: (params: IParams) => boolean): IDisposable;
addDcsHandler(id: IFunctionIdentifier, callback: (data: string, param: IParams) => boolean): IDisposable;
addEscHandler(id: IFunctionIdentifier, callback: () => boolean): 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;
hasSelection(): boolean;
getSelection(): string;
getSelectionPosition(): ISelectionPosition | undefined;
clearSelection(): void;
select(column: number, row: number, length: number): void;
selectAll(): void;
selectLines(start: number, end: number): void;
dispose(): void;
scrollLines(amount: number): void;
scrollPages(pageCount: number): void;
scrollToTop(): void;
scrollToBottom(): void;
scrollToLine(line: number): void;
clear(): void;
write(data: string | Uint8Array, callback?: () => void): void;
paste(data: string): void;
refresh(start: number, end: number): void;
reset(): void;
}
export interface IBufferAccessor {
buffer: IBuffer;
}
export interface IElementAccessor {
readonly element: HTMLElement | undefined;
}
export interface ILinkifierAccessor {
linkifier: ILinkifier;
linkifier2: ILinkifier2;
}
// TODO: The options that are not in the public API should be reviewed
export interface ITerminalOptions extends IPublicTerminalOptions {
[key: string]: any;
cancelEvents?: boolean;
convertEol?: boolean;
termName?: string;
}
export interface IBrowser {
isNode: boolean;
userAgent: string;
platform: string;
isFirefox: boolean;
isMac: boolean;
isIpad: boolean;
isIphone: boolean;
isWindows: boolean;
}
@@ -3,8 +3,8 @@
* @license MIT
*/
import * as Strings from './browser/LocalizableStrings';
import { ITerminal } from './Types';
import * as Strings from 'browser/LocalizableStrings';
import { ITerminal } from 'browser/Types';
import { IBuffer } from 'common/buffer/Types';
import { isMac } from 'common/Platform';
import { RenderDebouncer } from 'browser/RenderDebouncer';
@@ -79,6 +79,9 @@ export class AccessibilityManager extends Disposable {
this._liveRegion.setAttribute('aria-live', 'assertive');
this._accessibilityTreeRoot.appendChild(this._liveRegion);
if (!this._terminal.element) {
throw new Error('Cannot enable accessibility before Terminal.open');
}
this._terminal.element.insertAdjacentElement('afterbegin', this._accessibilityTreeRoot);
this.register(this._renderRowsDebouncer);
@@ -103,7 +106,7 @@ export class AccessibilityManager extends Disposable {
public dispose(): void {
super.dispose();
this._terminal.element.removeChild(this._accessibilityTreeRoot);
this._terminal.element?.removeChild(this._accessibilityTreeRoot);
this._rowElements.length = 0;
}
+4 -4
View File
@@ -42,17 +42,17 @@ export function copyHandler(ev: ClipboardEvent, selectionService: ISelectionServ
* @param ev The original paste event to be handled
* @param term The terminal on which to apply the handled paste event
*/
export function handlePasteEvent(ev: ClipboardEvent, textarea: HTMLTextAreaElement, bracketedPasteMode: boolean, coreService: ICoreService): void {
export function handlePasteEvent(ev: ClipboardEvent, textarea: HTMLTextAreaElement, coreService: ICoreService): void {
ev.stopPropagation();
if (ev.clipboardData) {
const text = ev.clipboardData.getData('text/plain');
paste(text, textarea, bracketedPasteMode, coreService);
paste(text, textarea, coreService);
}
}
export function paste(text: string, textarea: HTMLTextAreaElement, bracketedPasteMode: boolean, coreService: ICoreService): void {
export function paste(text: string, textarea: HTMLTextAreaElement, coreService: ICoreService): void {
text = prepareTextForTerminal(text);
text = bracketTextForPaste(text, bracketedPasteMode);
text = bracketTextForPaste(text, coreService.decPrivateModes.bracketedPasteMode);
coreService.triggerDataEvent(text, true);
textarea.value = '';
}
+5 -5
View File
@@ -9,13 +9,13 @@ import { IBufferLine } from 'common/Types';
import { Linkifier } from 'browser/Linkifier';
import { BufferLine } from 'common/buffer/BufferLine';
import { CellData } from 'common/buffer/CellData';
import { MockLogService, MockBufferService, MockOptionsService } from 'common/TestUtils.test';
import { MockLogService, MockBufferService } from 'common/TestUtils.test';
import { IBufferService } from 'common/services/Services';
import { UnicodeService } from 'common/services/UnicodeService';
class TestLinkifier extends Linkifier {
constructor(bufferService: IBufferService) {
super(bufferService, new MockLogService(), new MockOptionsService(), new UnicodeService());
super(bufferService, new MockLogService(), new UnicodeService());
Linkifier._timeBeforeLatency = 0;
}
@@ -24,14 +24,14 @@ class TestLinkifier extends Linkifier {
}
class TestMouseZoneManager implements IMouseZoneManager {
dispose(): void {
public dispose(): void {
}
public clears: number = 0;
public zones: IMouseZone[] = [];
add(zone: IMouseZone): void {
public add(zone: IMouseZone): void {
this.zones.push(zone);
}
clearAll(): void {
public clearAll(): void {
this.clears++;
}
}
+9 -10
View File
@@ -35,18 +35,17 @@ export class Linkifier implements ILinkifier {
private _nextLinkMatcherId = 0;
private _rowsToLinkify: { start: number | undefined, end: number | 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; }
private _onShowLinkUnderline = new EventEmitter<ILinkifierEvent>();
public get onShowLinkUnderline(): IEvent<ILinkifierEvent> { return this._onShowLinkUnderline.event; }
private _onHideLinkUnderline = new EventEmitter<ILinkifierEvent>();
public get onHideLinkUnderline(): IEvent<ILinkifierEvent> { return this._onHideLinkUnderline.event; }
private _onLinkTooltip = new EventEmitter<ILinkifierEvent>();
public get onLinkTooltip(): IEvent<ILinkifierEvent> { return this._onLinkTooltip.event; }
constructor(
protected readonly _bufferService: IBufferService,
private readonly _logService: ILogService,
private readonly _optionsService: IOptionsService,
private readonly _unicodeService: IUnicodeService
@IBufferService protected readonly _bufferService: IBufferService,
@ILogService private readonly _logService: ILogService,
@IUnicodeService private readonly _unicodeService: IUnicodeService
) {
this._rowsToLinkify = {
start: undefined,
@@ -307,7 +306,7 @@ export class Linkifier implements ILinkifier {
}
},
() => {
this._onLinkHover.fire(this._createLinkHoverEvent(x1, y1, x2, y2, fg));
this._onShowLinkUnderline.fire(this._createLinkHoverEvent(x1, y1, x2, y2, fg));
this._element!.classList.add('xterm-cursor-pointer');
},
e => {
@@ -319,7 +318,7 @@ export class Linkifier implements ILinkifier {
}
},
() => {
this._onLinkLeave.fire(this._createLinkHoverEvent(x1, y1, x2, y2, fg));
this._onHideLinkUnderline.fire(this._createLinkHoverEvent(x1, y1, x2, y2, fg));
this._element!.classList.remove('xterm-cursor-pointer');
if (matcher.hoverLeaveCallback) {
matcher.hoverLeaveCallback();
+23 -9
View File
@@ -14,6 +14,10 @@ class TestLinkifier2 extends Linkifier2 {
super(bufferService);
}
public set currentLink(link: any) {
this._currentLink = link;
}
public linkHover(element: HTMLElement, link: ILink, event: MouseEvent): void {
this._linkHover(element, link, event);
}
@@ -27,11 +31,6 @@ describe('Linkifier2', () => {
let bufferService: IBufferService;
let linkifier: TestLinkifier2;
beforeEach(() => {
bufferService = new MockBufferService(100, 10);
linkifier = new TestLinkifier2(bufferService);
});
const link: ILink = {
text: 'foo',
range: {
@@ -47,8 +46,23 @@ describe('Linkifier2', () => {
activate: () => { }
};
it('onLinkHover event range is correct', done => {
linkifier.onLinkHover(e => {
beforeEach(() => {
bufferService = new MockBufferService(100, 10);
linkifier = new TestLinkifier2(bufferService);
linkifier.currentLink = {
link,
state: {
decorations: {
underline: true,
pointerCursor: true
},
isHovered: true
}
};
});
it('onShowLinkUnderline event range is correct', done => {
linkifier.onShowLinkUnderline(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);
@@ -60,8 +74,8 @@ describe('Linkifier2', () => {
linkifier.linkHover({ classList: { add: () => { } } } as any, link, {} as any);
});
it('onLinkLeave event range is correct', done => {
linkifier.onLinkLeave(e => {
it('onHideLinkUnderline event range is correct', done => {
linkifier.onHideLinkUnderline(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);
+222 -71
View File
@@ -3,31 +3,47 @@
* @license MIT
*/
import { ILinkifier2, ILinkProvider, IBufferCellPosition, ILink, ILinkifierEvent } from './Types';
import { ILinkifier2, ILinkProvider, IBufferCellPosition, ILink, ILinkifierEvent, ILinkDecorations } from 'browser/Types';
import { IDisposable } from 'common/Types';
import { IMouseService, IRenderService } from './services/Services';
import { IBufferService } from 'common/services/Services';
import { EventEmitter, IEvent } from 'common/EventEmitter';
import { Disposable, getDisposeArrayDisposable, disposeArray } from 'common/Lifecycle';
import { addDisposableDomListener } from 'browser/Lifecycle';
export class Linkifier2 implements ILinkifier2 {
interface ILinkState {
decorations: ILinkDecorations;
isHovered: boolean;
}
interface ILinkWithState {
link: ILink;
state?: ILinkState;
}
export class Linkifier2 extends Disposable implements ILinkifier2 {
private _element: HTMLElement | undefined;
private _mouseService: IMouseService | undefined;
private _renderService: IRenderService | undefined;
private _linkProviders: ILinkProvider[] = [];
private _currentLink: ILink | undefined;
protected _currentLink: ILinkWithState | undefined;
private _lastMouseEvent: MouseEvent | undefined;
private _linkCacheDisposables: IDisposable[] = [];
private _lastBufferCell: IBufferCellPosition | undefined;
private _isMouseOut: boolean = true;
private _activeProviderReplies: Map<Number, ILinkWithState[] | undefined> | undefined;
private _activeLine: number = -1;
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; }
private _onShowLinkUnderline = this.register(new EventEmitter<ILinkifierEvent>());
public get onShowLinkUnderline(): IEvent<ILinkifierEvent> { return this._onShowLinkUnderline.event; }
private _onHideLinkUnderline = this.register(new EventEmitter<ILinkifierEvent>());
public get onHideLinkUnderline(): IEvent<ILinkifierEvent> { return this._onHideLinkUnderline.event; }
constructor(
private readonly _bufferService: IBufferService
@IBufferService private readonly _bufferService: IBufferService
) {
super();
this.register(getDisposeArrayDisposable(this._linkCacheDisposables));
}
public registerLinkProvider(linkProvider: ILinkProvider): IDisposable {
@@ -49,8 +65,12 @@ export class Linkifier2 implements ILinkifier2 {
this._mouseService = mouseService;
this._renderService = renderService;
this._element.addEventListener('mousemove', this._onMouseMove.bind(this));
this._element.addEventListener('click', this._onMouseDown.bind(this));
this.register(addDisposableDomListener(this._element, 'mouseleave', () => {
this._isMouseOut = true;
this._clearCurrentLink();
}));
this.register(addDisposableDomListener(this._element, 'mousemove', this._onMouseMove.bind(this)));
this.register(addDisposableDomListener(this._element, 'click', this._onClick.bind(this)));
}
private _onMouseMove(event: MouseEvent): void {
@@ -61,10 +81,24 @@ export class Linkifier2 implements ILinkifier2 {
}
const position = this._positionFromMouseEvent(event, this._element, this._mouseService);
if (!position) {
return;
}
this._isMouseOut = false;
// Ignore the event if it's an embedder created hover widget
const composedPath = event.composedPath() as HTMLElement[];
for (let i = 0; i < composedPath.length; i++) {
const target = composedPath[i];
// Hit Terminal.element, break and continue
if (target.classList.contains('xterm')) {
break;
}
// It's a hover, don't respect hover event
if (target.classList.contains('xterm-hover')) {
return;
}
}
if (!this._lastBufferCell || (position.x !== this._lastBufferCell.x || position.y !== this._lastBufferCell.y)) {
this._onHover(position);
@@ -73,59 +107,124 @@ export class Linkifier2 implements ILinkifier2 {
}
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);
// TODO: This currently does not cache link provider results across wrapped lines, activeLine should be something like `activeRange: {startY, endY}`
// Check if we need to clear the link
if (this._activeLine !== position.y) {
this._clearCurrentLink();
this._askForLink(position, false);
return;
}
// Check if we need to clear the link
if (!isInPosition) {
this._clearCurrentLink();
this._askForLink(position);
}
} else {
this._askForLink(position);
// Check the if the link is in the mouse position
const isCurrentLinkInPosition = this._currentLink && this._linkAtPosition(this._currentLink.link, position);
if (!isCurrentLinkInPosition) {
this._clearCurrentLink();
this._askForLink(position, true);
}
}
private _askForLink(position: IBufferCellPosition): void {
const providerReplies: Map<Number, ILink | undefined> = new Map();
private _askForLink(position: IBufferCellPosition, useLineCache: boolean): void {
if (!this._activeProviderReplies || !useLineCache) {
this._activeProviderReplies = new Map();
this._activeLine = position.y;
}
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);
if (useLineCache) {
const existingReply = this._activeProviderReplies?.get(i);
// If there isn't a reply, the provider hasn't responded yet.
// 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;
// TODO: If there isn't a reply yet it means that the provider is still resolving. Ensuring
// provideLinks isn't triggered again saves ILink.hover firing twice though. This probably
// needs promises to get fixed
if (existingReply) {
linkProvided = this._checkLinkProviderResult(i, position, linkProvided);
}
} else {
linkProvider.provideLinks(position.y, (links: ILink[] | undefined) => {
if (this._isMouseOut) {
return;
}
}
const linksWithState: ILinkWithState[] | undefined = links?.map(link => ({ link }));
this._activeProviderReplies?.set(i, linksWithState);
linkProvided = this._checkLinkProviderResult(i, position, linkProvided);
// 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;
}
// If all providers have responded, remove lower priority links that intersect ranges of
// higher priority links
if (this._activeProviderReplies?.size === this._linkProviders.length) {
this._removeIntersectingLinks(position.y, this._activeProviderReplies);
}
}
});
});
}
});
}
private _onMouseDown(event: MouseEvent): void {
private _removeIntersectingLinks(y: number, replies: Map<Number, ILinkWithState[] | undefined>): void {
const occupiedCells = new Set<number>();
for (let i = 0; i < replies.size; i++) {
const providerReply = replies.get(i);
if (!providerReply) {
continue;
}
for (let i = 0; i < providerReply.length; i++) {
const linkWithState = providerReply[i];
const startX = linkWithState.link.range.start.y < y ? 0 : linkWithState.link.range.start.x;
const endX = linkWithState.link.range.end.y > y ? this._bufferService.cols : linkWithState.link.range.end.x;
for (let x = startX; x <= endX; x++) {
if (occupiedCells.has(x)) {
providerReply.splice(i--, 1);
break;
}
occupiedCells.add(x);
}
}
}
}
private _checkLinkProviderResult(index: number, position: IBufferCellPosition, linkProvided: boolean): boolean {
if (!this._activeProviderReplies) {
return linkProvided;
}
const links = this._activeProviderReplies.get(index);
// Check if every provider before this one has come back undefined
let hasLinkBefore = false;
for (let j = 0; j < index; j++) {
if (!this._activeProviderReplies.has(j) || this._activeProviderReplies.get(j)) {
hasLinkBefore = true;
}
}
// If all providers with higher priority came back undefined, then this provider's link for
// the position should be used
if (!hasLinkBefore && links) {
const linkAtPosition = links.find(link => this._linkAtPosition(link.link, position));
if (linkAtPosition) {
linkProvided = true;
this._handleNewLink(linkAtPosition);
}
}
// Check if all the providers have responded
if (this._activeProviderReplies.size === this._linkProviders.length && !linkProvided) {
// Respect the order of the link providers
for (let j = 0; j < this._activeProviderReplies.size; j++) {
const currentLink = this._activeProviderReplies.get(j)?.find(link => this._linkAtPosition(link.link, position));
if (currentLink) {
linkProvided = true;
this._handleNewLink(currentLink);
break;
}
}
}
return linkProvided;
}
private _onClick(event: MouseEvent): void {
if (!this._element || !this._mouseService || !this._currentLink) {
return;
}
@@ -136,8 +235,8 @@ export class Linkifier2 implements ILinkifier2 {
return;
}
if (this._linkAtPosition(this._currentLink, position)) {
this._currentLink.activate(event, this._currentLink.text);
if (this._linkAtPosition(this._currentLink.link, position)) {
this._currentLink.link.activate(event, this._currentLink.link.text);
}
}
@@ -147,15 +246,14 @@ export class Linkifier2 implements ILinkifier2 {
}
// 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);
if (!startRow || !endRow || (this._currentLink.link.range.start.y >= startRow && this._currentLink.link.range.end.y <= endRow)) {
this._linkLeave(this._element, this._currentLink.link, this._lastMouseEvent);
this._currentLink = undefined;
this._linkCacheDisposables.forEach(l => l.dispose());
this._linkCacheDisposables = [];
disposeArray(this._linkCacheDisposables);
}
}
private _handleNewLink(link: ILink): void {
private _handleNewLink(linkWithState: ILinkWithState): void {
if (!this._element || !this._lastMouseEvent || !this._mouseService) {
return;
}
@@ -167,37 +265,90 @@ export class Linkifier2 implements ILinkifier2 {
}
// 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);
if (this._linkAtPosition(linkWithState.link, position)) {
this._currentLink = linkWithState;
this._currentLink.state = {
decorations: {
underline: linkWithState.link.decorations === undefined ? true : linkWithState.link.decorations.underline,
pointerCursor: linkWithState.link.decorations === undefined ? true : linkWithState.link.decorations.pointerCursor
},
isHovered: true
};
this._linkHover(this._element, linkWithState.link, this._lastMouseEvent);
// Add listener for tracking decorations changes
linkWithState.link.decorations = {} as ILinkDecorations;
Object.defineProperties(linkWithState.link.decorations, {
pointerCursor: {
get: () => this._currentLink?.state?.decorations.pointerCursor,
set: v => {
if (this._currentLink?.state && this._currentLink.state.decorations.pointerCursor !== v) {
this._currentLink.state.decorations.pointerCursor = v;
if (this._currentLink.state.isHovered) {
this._element?.classList.toggle('xterm-cursor-pointer', v);
}
}
}
},
underline: {
get: () => this._currentLink?.state?.decorations.underline,
set: v => {
if (this._currentLink?.state && this._currentLink?.state?.decorations.underline !== v) {
this._currentLink.state.decorations.underline = v;
if (this._currentLink.state.isHovered) {
this._fireUnderlineEvent(linkWithState.link, v);
}
}
}
}
});
// 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);
this._linkCacheDisposables.push(this._renderService.onRenderedBufferChange(e => {
// When start is 0 a scroll most likely occurred, make sure links above the fold also get
// cleared.
const start = e.start === 0 ? 0 : e.start + 1 + this._bufferService.buffer.ydisp;
this._clearCurrentLink(start, 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 (this._currentLink?.state) {
this._currentLink.state.isHovered = true;
if (this._currentLink.state.decorations.underline) {
this._fireUnderlineEvent(link, true);
}
if (this._currentLink.state.decorations.pointerCursor) {
element.classList.add('xterm-cursor-pointer');
}
}
if (link.hover) {
link.hover(event, link.text);
}
}
protected _linkLeave(element: HTMLElement, link: ILink, event: MouseEvent): void {
private _fireUnderlineEvent(link: ILink, showEvent: boolean): void {
const range = link.range;
const scrollOffset = this._bufferService.buffer.ydisp;
const event = this._createLinkUnderlineEvent(range.start.x - 1, range.start.y - scrollOffset - 1, range.end.x, range.end.y - scrollOffset - 1, undefined);
const emitter = showEvent ? this._onShowLinkUnderline : this._onHideLinkUnderline;
emitter.fire(event);
}
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');
protected _linkLeave(element: HTMLElement, link: ILink, event: MouseEvent): void {
if (this._currentLink?.state) {
this._currentLink.state.isHovered = false;
if (this._currentLink.state.decorations.underline) {
this._fireUnderlineEvent(link, false);
}
if (this._currentLink.state.decorations.pointerCursor) {
element.classList.remove('xterm-cursor-pointer');
}
}
if (link.leave) {
link.leave(event, link.text);
@@ -237,7 +388,7 @@ export class Linkifier2 implements ILinkifier2 {
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 {
private _createLinkUnderlineEvent(x1: number, y1: number, x2: number, y2: number, fg: number | undefined): ILinkifierEvent {
return { x1, y1, x2, y2, cols: this._bufferService.cols, fg };
}
}
+4 -5
View File
@@ -7,9 +7,7 @@ import { Disposable } from 'common/Lifecycle';
import { addDisposableDomListener } from 'browser/Lifecycle';
import { IMouseService, ISelectionService } from 'browser/services/Services';
import { IMouseZoneManager, IMouseZone } from 'browser/Types';
import { IBufferService } from 'common/services/Services';
const HOVER_DURATION = 500;
import { IBufferService, IOptionsService } from 'common/services/Services';
/**
* The MouseZoneManager allows components to register zones within the terminal
@@ -37,7 +35,8 @@ export class MouseZoneManager extends Disposable implements IMouseZoneManager {
private readonly _screenElement: HTMLElement,
@IBufferService private readonly _bufferService: IBufferService,
@IMouseService private readonly _mouseService: IMouseService,
@ISelectionService private readonly _selectionService: ISelectionService
@ISelectionService private readonly _selectionService: ISelectionService,
@IOptionsService private readonly _optionsService: IOptionsService
) {
super();
@@ -151,7 +150,7 @@ export class MouseZoneManager extends Disposable implements IMouseZoneManager {
}
// Restart the tooltip timeout
this._tooltipTimeout = <number><any>setTimeout(() => this._onTooltip(e), HOVER_DURATION);
this._tooltipTimeout = window.setTimeout(() => this._onTooltip(e), this._optionsService.options.linkTooltipHoverDuration);
}
private _onTooltip(e: MouseEvent): void {
+7 -7
View File
@@ -26,7 +26,7 @@ export class RenderDebouncer implements IDisposable {
}
}
public refresh(rowStart: number, rowEnd: number, rowCount: number): void {
public refresh(rowStart: number | undefined, rowEnd: number | undefined, rowCount: number): void {
this._rowCount = rowCount;
// Get the min/max row start/end for the arg values
rowStart = rowStart !== undefined ? rowStart : 0;
@@ -49,15 +49,15 @@ export class RenderDebouncer implements IDisposable {
}
// Clamp values
this._rowStart = Math.max(this._rowStart, 0);
this._rowEnd = Math.min(this._rowEnd, this._rowCount - 1);
const start = Math.max(this._rowStart, 0);
const end = Math.min(this._rowEnd, this._rowCount - 1);
// Run render callback
this._renderCallback(this._rowStart, this._rowEnd);
// Reset debouncer
// Reset debouncer (this happens before render callback as the render could trigger it again)
this._rowStart = undefined;
this._rowEnd = undefined;
this._animationFrame = undefined;
// Run render callback
this._renderCallback(start, end);
}
}
+2 -2
View File
@@ -44,12 +44,12 @@ export class ScreenDprMonitor extends Disposable {
}
private _updateDpr(): void {
if (!this._resolutionMediaMatchList || !this._outerListener) {
if (!this._outerListener) {
return;
}
// Clear listeners for old DPR
this._resolutionMediaMatchList.removeListener(this._outerListener);
this._resolutionMediaMatchList?.removeListener(this._outerListener);
// Add listeners for new DPR
this._currentDevicePixelRatio = window.devicePixelRatio;
@@ -4,12 +4,12 @@
*/
import { assert, expect } from 'chai';
import { MockViewport, MockCompositionHelper, MockRenderer, TestTerminal } from './TestUtils.test';
import { MockViewport, MockCompositionHelper, MockRenderer, TestTerminal } from 'browser/TestUtils.test';
import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine';
import { CellData } from 'common/buffer/CellData';
import { IBufferService, IUnicodeService } from 'common/services/Services';
import { Linkifier } from 'browser/Linkifier';
import { MockLogService, MockOptionsService, MockUnicodeService } from 'common/TestUtils.test';
import { MockLogService, MockUnicodeService } from 'common/TestUtils.test';
import { IRegisteredLinkMatcher, IMouseZoneManager, IMouseZone } from 'browser/Types';
const INIT_COLS = 80;
@@ -121,7 +121,7 @@ describe('Terminal', () => {
assert.equal(e, 'title');
done();
});
term.handleTitle('title');
term.write('\x1b]2;title\x07');
});
});
@@ -140,7 +140,6 @@ describe('Terminal', () => {
};
beforeEach(() => {
term.showCursor = () => { };
term.clearSelection = () => { };
});
@@ -396,62 +395,62 @@ describe('Terminal', () => {
describe('scroll() function', () => {
describe('when scrollback > 0', () => {
it('should create a new line and scroll', () => {
term.buffer.lines.get(0).setCell(0, CellData.fromCharData([0, 'a', 0, 'a'.charCodeAt(0)]));
term.buffer.lines.get(INIT_ROWS - 1).setCell(0, CellData.fromCharData([0, 'b', 0, 'b'.charCodeAt(0)]));
term.buffer.lines.get(0)!.setCell(0, CellData.fromCharData([0, 'a', 0, 'a'.charCodeAt(0)]));
term.buffer.lines.get(INIT_ROWS - 1)!.setCell(0, CellData.fromCharData([0, 'b', 0, 'b'.charCodeAt(0)]));
term.buffer.y = INIT_ROWS - 1; // Move cursor to last line
term.scroll(DEFAULT_ATTR_DATA.clone());
assert.equal(term.buffer.lines.length, INIT_ROWS + 1);
assert.equal(term.buffer.lines.get(0).loadCell(0, new CellData()).getChars(), 'a');
assert.equal(term.buffer.lines.get(INIT_ROWS - 1).loadCell(0, new CellData()).getChars(), 'b');
assert.equal(term.buffer.lines.get(INIT_ROWS).loadCell(0, new CellData()).getChars(), '');
assert.equal(term.buffer.lines.get(0)!.loadCell(0, new CellData()).getChars(), 'a');
assert.equal(term.buffer.lines.get(INIT_ROWS - 1)!.loadCell(0, new CellData()).getChars(), 'b');
assert.equal(term.buffer.lines.get(INIT_ROWS)!.loadCell(0, new CellData()).getChars(), '');
});
it('should properly scroll inside a scroll region (scrollTop set)', () => {
term.buffer.lines.get(0).setCell(0, CellData.fromCharData([0, 'a', 0, 'a'.charCodeAt(0)]));
term.buffer.lines.get(1).setCell(0, CellData.fromCharData([0, 'b', 0, 'b'.charCodeAt(0)]));
term.buffer.lines.get(2).setCell(0, CellData.fromCharData([0, 'c', 0, 'c'.charCodeAt(0)]));
term.buffer.lines.get(0)!.setCell(0, CellData.fromCharData([0, 'a', 0, 'a'.charCodeAt(0)]));
term.buffer.lines.get(1)!.setCell(0, CellData.fromCharData([0, 'b', 0, 'b'.charCodeAt(0)]));
term.buffer.lines.get(2)!.setCell(0, CellData.fromCharData([0, 'c', 0, 'c'.charCodeAt(0)]));
term.buffer.y = INIT_ROWS - 1; // Move cursor to last line
term.buffer.scrollTop = 1;
term.scroll(DEFAULT_ATTR_DATA.clone());
assert.equal(term.buffer.lines.length, INIT_ROWS);
assert.equal(term.buffer.lines.get(0).loadCell(0, new CellData()).getChars(), 'a');
assert.equal(term.buffer.lines.get(1).loadCell(0, new CellData()).getChars(), 'c');
assert.equal(term.buffer.lines.get(0)!.loadCell(0, new CellData()).getChars(), 'a');
assert.equal(term.buffer.lines.get(1)!.loadCell(0, new CellData()).getChars(), 'c');
});
it('should properly scroll inside a scroll region (scrollBottom set)', () => {
term.buffer.lines.get(0).setCell(0, CellData.fromCharData([0, 'a', 0, 'a'.charCodeAt(0)]));
term.buffer.lines.get(1).setCell(0, CellData.fromCharData([0, 'b', 0, 'b'.charCodeAt(0)]));
term.buffer.lines.get(2).setCell(0, CellData.fromCharData([0, 'c', 0, 'c'.charCodeAt(0)]));
term.buffer.lines.get(3).setCell(0, CellData.fromCharData([0, 'd', 0, 'd'.charCodeAt(0)]));
term.buffer.lines.get(4).setCell(0, CellData.fromCharData([0, 'e', 0, 'e'.charCodeAt(0)]));
term.buffer.lines.get(0)!.setCell(0, CellData.fromCharData([0, 'a', 0, 'a'.charCodeAt(0)]));
term.buffer.lines.get(1)!.setCell(0, CellData.fromCharData([0, 'b', 0, 'b'.charCodeAt(0)]));
term.buffer.lines.get(2)!.setCell(0, CellData.fromCharData([0, 'c', 0, 'c'.charCodeAt(0)]));
term.buffer.lines.get(3)!.setCell(0, CellData.fromCharData([0, 'd', 0, 'd'.charCodeAt(0)]));
term.buffer.lines.get(4)!.setCell(0, CellData.fromCharData([0, 'e', 0, 'e'.charCodeAt(0)]));
term.buffer.y = 3;
term.buffer.scrollBottom = 3;
term.scroll(DEFAULT_ATTR_DATA.clone());
assert.equal(term.buffer.lines.length, INIT_ROWS + 1);
assert.equal(term.buffer.lines.get(0).loadCell(0, new CellData()).getChars(), 'a', '\'a\' should be pushed to the scrollback');
assert.equal(term.buffer.lines.get(1).loadCell(0, new CellData()).getChars(), 'b');
assert.equal(term.buffer.lines.get(2).loadCell(0, new CellData()).getChars(), 'c');
assert.equal(term.buffer.lines.get(3).loadCell(0, new CellData()).getChars(), 'd');
assert.equal(term.buffer.lines.get(4).loadCell(0, new CellData()).getChars(), '', 'a blank line should be added at scrollBottom\'s index');
assert.equal(term.buffer.lines.get(5).loadCell(0, new CellData()).getChars(), 'e');
assert.equal(term.buffer.lines.get(0)!.loadCell(0, new CellData()).getChars(), 'a', '\'a\' should be pushed to the scrollback');
assert.equal(term.buffer.lines.get(1)!.loadCell(0, new CellData()).getChars(), 'b');
assert.equal(term.buffer.lines.get(2)!.loadCell(0, new CellData()).getChars(), 'c');
assert.equal(term.buffer.lines.get(3)!.loadCell(0, new CellData()).getChars(), 'd');
assert.equal(term.buffer.lines.get(4)!.loadCell(0, new CellData()).getChars(), '', 'a blank line should be added at scrollBottom\'s index');
assert.equal(term.buffer.lines.get(5)!.loadCell(0, new CellData()).getChars(), 'e');
});
it('should properly scroll inside a scroll region (scrollTop and scrollBottom set)', () => {
term.buffer.lines.get(0).setCell(0, CellData.fromCharData([0, 'a', 0, 'a'.charCodeAt(0)]));
term.buffer.lines.get(1).setCell(0, CellData.fromCharData([0, 'b', 0, 'b'.charCodeAt(0)]));
term.buffer.lines.get(2).setCell(0, CellData.fromCharData([0, 'c', 0, 'c'.charCodeAt(0)]));
term.buffer.lines.get(3).setCell(0, CellData.fromCharData([0, 'd', 0, 'd'.charCodeAt(0)]));
term.buffer.lines.get(4).setCell(0, CellData.fromCharData([0, 'e', 0, 'e'.charCodeAt(0)]));
term.buffer.lines.get(0)!.setCell(0, CellData.fromCharData([0, 'a', 0, 'a'.charCodeAt(0)]));
term.buffer.lines.get(1)!.setCell(0, CellData.fromCharData([0, 'b', 0, 'b'.charCodeAt(0)]));
term.buffer.lines.get(2)!.setCell(0, CellData.fromCharData([0, 'c', 0, 'c'.charCodeAt(0)]));
term.buffer.lines.get(3)!.setCell(0, CellData.fromCharData([0, 'd', 0, 'd'.charCodeAt(0)]));
term.buffer.lines.get(4)!.setCell(0, CellData.fromCharData([0, 'e', 0, 'e'.charCodeAt(0)]));
term.buffer.y = INIT_ROWS - 1; // Move cursor to last line
term.buffer.scrollTop = 1;
term.buffer.scrollBottom = 3;
term.scroll(DEFAULT_ATTR_DATA.clone());
assert.equal(term.buffer.lines.length, INIT_ROWS);
assert.equal(term.buffer.lines.get(0).loadCell(0, new CellData()).getChars(), 'a');
assert.equal(term.buffer.lines.get(1).loadCell(0, new CellData()).getChars(), 'c', '\'b\' should be removed from the buffer');
assert.equal(term.buffer.lines.get(2).loadCell(0, new CellData()).getChars(), 'd');
assert.equal(term.buffer.lines.get(3).loadCell(0, new CellData()).getChars(), '', 'a blank line should be added at scrollBottom\'s index');
assert.equal(term.buffer.lines.get(4).loadCell(0, new CellData()).getChars(), 'e');
assert.equal(term.buffer.lines.get(0)!.loadCell(0, new CellData()).getChars(), 'a');
assert.equal(term.buffer.lines.get(1)!.loadCell(0, new CellData()).getChars(), 'c', '\'b\' should be removed from the buffer');
assert.equal(term.buffer.lines.get(2)!.loadCell(0, new CellData()).getChars(), 'd');
assert.equal(term.buffer.lines.get(3)!.loadCell(0, new CellData()).getChars(), '', 'a blank line should be added at scrollBottom\'s index');
assert.equal(term.buffer.lines.get(4)!.loadCell(0, new CellData()).getChars(), 'e');
});
});
@@ -462,65 +461,65 @@ describe('Terminal', () => {
});
it('should create a new line and shift everything up', () => {
term.buffer.lines.get(0).setCell(0, CellData.fromCharData([0, 'a', 0, 'a'.charCodeAt(0)]));
term.buffer.lines.get(1).setCell(0, CellData.fromCharData([0, 'b', 0, 'b'.charCodeAt(0)]));
term.buffer.lines.get(INIT_ROWS - 1).setCell(0, CellData.fromCharData([0, 'c', 0, 'c'.charCodeAt(0)]));
term.buffer.lines.get(0)!.setCell(0, CellData.fromCharData([0, 'a', 0, 'a'.charCodeAt(0)]));
term.buffer.lines.get(1)!.setCell(0, CellData.fromCharData([0, 'b', 0, 'b'.charCodeAt(0)]));
term.buffer.lines.get(INIT_ROWS - 1)!.setCell(0, CellData.fromCharData([0, 'c', 0, 'c'.charCodeAt(0)]));
term.buffer.y = INIT_ROWS - 1; // Move cursor to last line
assert.equal(term.buffer.lines.length, INIT_ROWS);
term.scroll(DEFAULT_ATTR_DATA.clone());
assert.equal(term.buffer.lines.length, INIT_ROWS);
// 'a' gets pushed out of buffer
assert.equal(term.buffer.lines.get(0).loadCell(0, new CellData()).getChars(), 'b');
assert.equal(term.buffer.lines.get(1).loadCell(0, new CellData()).getChars(), '');
assert.equal(term.buffer.lines.get(INIT_ROWS - 2).loadCell(0, new CellData()).getChars(), 'c');
assert.equal(term.buffer.lines.get(INIT_ROWS - 1).loadCell(0, new CellData()).getChars(), '');
assert.equal(term.buffer.lines.get(0)!.loadCell(0, new CellData()).getChars(), 'b');
assert.equal(term.buffer.lines.get(1)!.loadCell(0, new CellData()).getChars(), '');
assert.equal(term.buffer.lines.get(INIT_ROWS - 2)!.loadCell(0, new CellData()).getChars(), 'c');
assert.equal(term.buffer.lines.get(INIT_ROWS - 1)!.loadCell(0, new CellData()).getChars(), '');
});
it('should properly scroll inside a scroll region (scrollTop set)', () => {
term.buffer.lines.get(0).setCell(0, CellData.fromCharData([0, 'a', 0, 'a'.charCodeAt(0)]));
term.buffer.lines.get(1).setCell(0, CellData.fromCharData([0, 'b', 0, 'b'.charCodeAt(0)]));
term.buffer.lines.get(2).setCell(0, CellData.fromCharData([0, 'c', 0, 'c'.charCodeAt(0)]));
term.buffer.lines.get(0)!.setCell(0, CellData.fromCharData([0, 'a', 0, 'a'.charCodeAt(0)]));
term.buffer.lines.get(1)!.setCell(0, CellData.fromCharData([0, 'b', 0, 'b'.charCodeAt(0)]));
term.buffer.lines.get(2)!.setCell(0, CellData.fromCharData([0, 'c', 0, 'c'.charCodeAt(0)]));
term.buffer.y = INIT_ROWS - 1; // Move cursor to last line
term.buffer.scrollTop = 1;
term.scroll(DEFAULT_ATTR_DATA.clone());
assert.equal(term.buffer.lines.length, INIT_ROWS);
assert.equal(term.buffer.lines.get(0).loadCell(0, new CellData()).getChars(), 'a');
assert.equal(term.buffer.lines.get(1).loadCell(0, new CellData()).getChars(), 'c');
assert.equal(term.buffer.lines.get(0)!.loadCell(0, new CellData()).getChars(), 'a');
assert.equal(term.buffer.lines.get(1)!.loadCell(0, new CellData()).getChars(), 'c');
});
it('should properly scroll inside a scroll region (scrollBottom set)', () => {
term.buffer.lines.get(0).setCell(0, CellData.fromCharData([0, 'a', 0, 'a'.charCodeAt(0)]));
term.buffer.lines.get(1).setCell(0, CellData.fromCharData([0, 'b', 0, 'b'.charCodeAt(0)]));
term.buffer.lines.get(2).setCell(0, CellData.fromCharData([0, 'c', 0, 'c'.charCodeAt(0)]));
term.buffer.lines.get(3).setCell(0, CellData.fromCharData([0, 'd', 0, 'd'.charCodeAt(0)]));
term.buffer.lines.get(4).setCell(0, CellData.fromCharData([0, 'e', 0, 'e'.charCodeAt(0)]));
term.buffer.lines.get(0)!.setCell(0, CellData.fromCharData([0, 'a', 0, 'a'.charCodeAt(0)]));
term.buffer.lines.get(1)!.setCell(0, CellData.fromCharData([0, 'b', 0, 'b'.charCodeAt(0)]));
term.buffer.lines.get(2)!.setCell(0, CellData.fromCharData([0, 'c', 0, 'c'.charCodeAt(0)]));
term.buffer.lines.get(3)!.setCell(0, CellData.fromCharData([0, 'd', 0, 'd'.charCodeAt(0)]));
term.buffer.lines.get(4)!.setCell(0, CellData.fromCharData([0, 'e', 0, 'e'.charCodeAt(0)]));
term.buffer.y = 3;
term.buffer.scrollBottom = 3;
term.scroll(DEFAULT_ATTR_DATA.clone());
assert.equal(term.buffer.lines.length, INIT_ROWS);
assert.equal(term.buffer.lines.get(0).loadCell(0, new CellData()).getChars(), 'b');
assert.equal(term.buffer.lines.get(1).loadCell(0, new CellData()).getChars(), 'c');
assert.equal(term.buffer.lines.get(2).loadCell(0, new CellData()).getChars(), 'd');
assert.equal(term.buffer.lines.get(3).loadCell(0, new CellData()).getChars(), '', 'a blank line should be added at scrollBottom\'s index');
assert.equal(term.buffer.lines.get(4).loadCell(0, new CellData()).getChars(), 'e');
assert.equal(term.buffer.lines.get(0)!.loadCell(0, new CellData()).getChars(), 'b');
assert.equal(term.buffer.lines.get(1)!.loadCell(0, new CellData()).getChars(), 'c');
assert.equal(term.buffer.lines.get(2)!.loadCell(0, new CellData()).getChars(), 'd');
assert.equal(term.buffer.lines.get(3)!.loadCell(0, new CellData()).getChars(), '', 'a blank line should be added at scrollBottom\'s index');
assert.equal(term.buffer.lines.get(4)!.loadCell(0, new CellData()).getChars(), 'e');
});
it('should properly scroll inside a scroll region (scrollTop and scrollBottom set)', () => {
term.buffer.lines.get(0).setCell(0, CellData.fromCharData([0, 'a', 0, 'a'.charCodeAt(0)]));
term.buffer.lines.get(1).setCell(0, CellData.fromCharData([0, 'b', 0, 'b'.charCodeAt(0)]));
term.buffer.lines.get(2).setCell(0, CellData.fromCharData([0, 'c', 0, 'c'.charCodeAt(0)]));
term.buffer.lines.get(3).setCell(0, CellData.fromCharData([0, 'd', 0, 'd'.charCodeAt(0)]));
term.buffer.lines.get(4).setCell(0, CellData.fromCharData([0, 'e', 0, 'e'.charCodeAt(0)]));
term.buffer.lines.get(0)!.setCell(0, CellData.fromCharData([0, 'a', 0, 'a'.charCodeAt(0)]));
term.buffer.lines.get(1)!.setCell(0, CellData.fromCharData([0, 'b', 0, 'b'.charCodeAt(0)]));
term.buffer.lines.get(2)!.setCell(0, CellData.fromCharData([0, 'c', 0, 'c'.charCodeAt(0)]));
term.buffer.lines.get(3)!.setCell(0, CellData.fromCharData([0, 'd', 0, 'd'.charCodeAt(0)]));
term.buffer.lines.get(4)!.setCell(0, CellData.fromCharData([0, 'e', 0, 'e'.charCodeAt(0)]));
term.buffer.y = INIT_ROWS - 1; // Move cursor to last line
term.buffer.scrollTop = 1;
term.buffer.scrollBottom = 3;
term.scroll(DEFAULT_ATTR_DATA.clone());
assert.equal(term.buffer.lines.length, INIT_ROWS);
assert.equal(term.buffer.lines.get(0).loadCell(0, new CellData()).getChars(), 'a');
assert.equal(term.buffer.lines.get(1).loadCell(0, new CellData()).getChars(), 'c', '\'b\' should be removed from the buffer');
assert.equal(term.buffer.lines.get(2).loadCell(0, new CellData()).getChars(), 'd');
assert.equal(term.buffer.lines.get(3).loadCell(0, new CellData()).getChars(), '', 'a blank line should be added at scrollBottom\'s index');
assert.equal(term.buffer.lines.get(4).loadCell(0, new CellData()).getChars(), 'e');
assert.equal(term.buffer.lines.get(0)!.loadCell(0, new CellData()).getChars(), 'a');
assert.equal(term.buffer.lines.get(1)!.loadCell(0, new CellData()).getChars(), 'c', '\'b\' should be removed from the buffer');
assert.equal(term.buffer.lines.get(2)!.loadCell(0, new CellData()).getChars(), 'd');
assert.equal(term.buffer.lines.get(3)!.loadCell(0, new CellData()).getChars(), '', 'a blank line should be added at scrollBottom\'s index');
assert.equal(term.buffer.lines.get(4)!.loadCell(0, new CellData()).getChars(), 'e');
});
});
});
@@ -531,7 +530,6 @@ describe('Terminal', () => {
let evKeyPress: any;
beforeEach(() => {
term.showCursor = () => { };
term.clearSelection = () => { };
// term.compositionHelper = {
// isComposing: false,
@@ -724,11 +722,11 @@ describe('Terminal', () => {
const cell = new CellData();
for (let i = 0xDC00; i <= 0xDCFF; ++i) {
term.writeSync(high + String.fromCharCode(i));
const tchar = term.buffer.lines.get(0).loadCell(0, cell);
const tchar = term.buffer.lines.get(0)!.loadCell(0, cell);
expect(tchar.getChars()).eql(high + String.fromCharCode(i));
expect(tchar.getChars().length).eql(2);
expect(tchar.getWidth()).eql(1);
expect(term.buffer.lines.get(0).loadCell(1, cell).getChars()).eql('');
expect(term.buffer.lines.get(0)!.loadCell(1, cell).getChars()).eql('');
term.reset();
}
});
@@ -738,9 +736,9 @@ describe('Terminal', () => {
for (let i = 0xDC00; i <= 0xDCFF; ++i) {
term.buffer.x = term.cols - 1;
term.writeSync(high + String.fromCharCode(i));
expect(term.buffer.lines.get(0).loadCell(term.buffer.x - 1, cell).getChars()).eql(high + String.fromCharCode(i));
expect(term.buffer.lines.get(0).loadCell(term.buffer.x - 1, cell).getChars().length).eql(2);
expect(term.buffer.lines.get(1).loadCell(0, cell).getChars()).eql('');
expect(term.buffer.lines.get(0)!.loadCell(term.buffer.x - 1, cell).getChars()).eql(high + String.fromCharCode(i));
expect(term.buffer.lines.get(0)!.loadCell(term.buffer.x - 1, cell).getChars().length).eql(2);
expect(term.buffer.lines.get(1)!.loadCell(0, cell).getChars()).eql('');
term.reset();
}
});
@@ -751,10 +749,10 @@ describe('Terminal', () => {
term.buffer.x = term.cols - 1;
term.writeSync('a' + high + String.fromCharCode(i));
expect(term.buffer.lines.get(0).loadCell(term.cols - 1, cell).getChars()).eql('a');
expect(term.buffer.lines.get(1).loadCell(0, cell).getChars()).eql(high + String.fromCharCode(i));
expect(term.buffer.lines.get(1).loadCell(0, cell).getChars().length).eql(2);
expect(term.buffer.lines.get(1).loadCell(1, cell).getChars()).eql('');
expect(term.buffer.lines.get(0)!.loadCell(term.cols - 1, cell).getChars()).eql('a');
expect(term.buffer.lines.get(1)!.loadCell(0, cell).getChars()).eql(high + String.fromCharCode(i));
expect(term.buffer.lines.get(1)!.loadCell(0, cell).getChars().length).eql(2);
expect(term.buffer.lines.get(1)!.loadCell(1, cell).getChars()).eql('');
term.reset();
}
});
@@ -770,9 +768,9 @@ describe('Terminal', () => {
}
term.writeSync('a' + high + String.fromCharCode(i));
// auto wraparound mode should cut off the rest of the line
expect(term.buffer.lines.get(0).loadCell(term.cols - 1, cell).getChars()).eql(high + String.fromCharCode(i));
expect(term.buffer.lines.get(0).loadCell(term.cols - 1, cell).getChars().length).eql(2);
expect(term.buffer.lines.get(1).loadCell(1, cell).getChars()).eql('');
expect(term.buffer.lines.get(0)!.loadCell(term.cols - 1, cell).getChars()).eql(high + String.fromCharCode(i));
expect(term.buffer.lines.get(0)!.loadCell(term.cols - 1, cell).getChars().length).eql(2);
expect(term.buffer.lines.get(1)!.loadCell(1, cell).getChars()).eql('');
term.reset();
}
});
@@ -782,11 +780,11 @@ describe('Terminal', () => {
for (let i = 0xDC00; i <= 0xDCFF; ++i) {
term.writeSync(high);
term.writeSync(String.fromCharCode(i));
const tchar = term.buffer.lines.get(0).loadCell(0, cell);
const tchar = term.buffer.lines.get(0)!.loadCell(0, cell);
expect(tchar.getChars()).eql(high + String.fromCharCode(i));
expect(tchar.getChars().length).eql(2);
expect(tchar.getWidth()).eql(1);
expect(term.buffer.lines.get(0).loadCell(1, cell).getChars()).eql('');
expect(term.buffer.lines.get(0)!.loadCell(1, cell).getChars()).eql('');
term.reset();
}
});
@@ -796,7 +794,7 @@ describe('Terminal', () => {
const cell = new CellData();
it('café', () => {
term.writeSync('cafe\u0301');
term.buffer.lines.get(0).loadCell(3, cell);
term.buffer.lines.get(0)!.loadCell(3, cell);
expect(cell.getChars()).eql('e\u0301');
expect(cell.getChars().length).eql(2);
expect(cell.getWidth()).eql(1);
@@ -804,11 +802,11 @@ describe('Terminal', () => {
it('café - end of line', () => {
term.buffer.x = term.cols - 1 - 3;
term.writeSync('cafe\u0301');
term.buffer.lines.get(0).loadCell(term.cols - 1, cell);
term.buffer.lines.get(0)!.loadCell(term.cols - 1, cell);
expect(cell.getChars()).eql('e\u0301');
expect(cell.getChars().length).eql(2);
expect(cell.getWidth()).eql(1);
term.buffer.lines.get(0).loadCell(1, cell);
term.buffer.lines.get(0)!.loadCell(1, cell);
expect(cell.getChars()).eql('');
expect(cell.getChars().length).eql(0);
expect(cell.getWidth()).eql(1);
@@ -816,12 +814,12 @@ describe('Terminal', () => {
it('multiple combined é', () => {
term.writeSync(Array(100).join('e\u0301'));
for (let i = 0; i < term.cols; ++i) {
term.buffer.lines.get(0).loadCell(i, cell);
term.buffer.lines.get(0)!.loadCell(i, cell);
expect(cell.getChars()).eql('e\u0301');
expect(cell.getChars().length).eql(2);
expect(cell.getWidth()).eql(1);
}
term.buffer.lines.get(1).loadCell(0, cell);
term.buffer.lines.get(1)!.loadCell(0, cell);
expect(cell.getChars()).eql('e\u0301');
expect(cell.getChars().length).eql(2);
expect(cell.getWidth()).eql(1);
@@ -829,12 +827,12 @@ describe('Terminal', () => {
it('multiple surrogate with combined', () => {
term.writeSync(Array(100).join('\uD800\uDC00\u0301'));
for (let i = 0; i < term.cols; ++i) {
term.buffer.lines.get(0).loadCell(i, cell);
term.buffer.lines.get(0)!.loadCell(i, cell);
expect(cell.getChars()).eql('\uD800\uDC00\u0301');
expect(cell.getChars().length).eql(3);
expect(cell.getWidth()).eql(1);
}
term.buffer.lines.get(1).loadCell(0, cell);
term.buffer.lines.get(1)!.loadCell(0, cell);
expect(cell.getChars()).eql('\uD800\uDC00\u0301');
expect(cell.getChars().length).eql(3);
expect(cell.getWidth()).eql(1);
@@ -857,7 +855,7 @@ describe('Terminal', () => {
it('line of ¥ even', () => {
term.writeSync(Array(50).join('¥'));
for (let i = 0; i < term.cols; ++i) {
term.buffer.lines.get(0).loadCell(i, cell);
term.buffer.lines.get(0)!.loadCell(i, cell);
if (i % 2) {
expect(cell.getChars()).eql('');
expect(cell.getChars().length).eql(0);
@@ -868,7 +866,7 @@ describe('Terminal', () => {
expect(cell.getWidth()).eql(2);
}
}
term.buffer.lines.get(1).loadCell(0, cell);
term.buffer.lines.get(1)!.loadCell(0, cell);
expect(cell.getChars()).eql('¥');
expect(cell.getChars().length).eql(1);
expect(cell.getWidth()).eql(2);
@@ -877,7 +875,7 @@ describe('Terminal', () => {
term.buffer.x = 1;
term.writeSync(Array(50).join('¥'));
for (let i = 1; i < term.cols - 1; ++i) {
term.buffer.lines.get(0).loadCell(i, cell);
term.buffer.lines.get(0)!.loadCell(i, cell);
if (!(i % 2)) {
expect(cell.getChars()).eql('');
expect(cell.getChars().length).eql(0);
@@ -888,11 +886,11 @@ describe('Terminal', () => {
expect(cell.getWidth()).eql(2);
}
}
term.buffer.lines.get(0).loadCell(term.cols - 1, cell);
term.buffer.lines.get(0)!.loadCell(term.cols - 1, cell);
expect(cell.getChars()).eql('');
expect(cell.getChars().length).eql(0);
expect(cell.getWidth()).eql(1);
term.buffer.lines.get(1).loadCell(0, cell);
term.buffer.lines.get(1)!.loadCell(0, cell);
expect(cell.getChars()).eql('¥');
expect(cell.getChars().length).eql(1);
expect(cell.getWidth()).eql(2);
@@ -901,7 +899,7 @@ describe('Terminal', () => {
term.buffer.x = 1;
term.writeSync(Array(50).join('¥\u0301'));
for (let i = 1; i < term.cols - 1; ++i) {
term.buffer.lines.get(0).loadCell(i, cell);
term.buffer.lines.get(0)!.loadCell(i, cell);
if (!(i % 2)) {
expect(cell.getChars()).eql('');
expect(cell.getChars().length).eql(0);
@@ -912,11 +910,11 @@ describe('Terminal', () => {
expect(cell.getWidth()).eql(2);
}
}
term.buffer.lines.get(0).loadCell(term.cols - 1, cell);
term.buffer.lines.get(0)!.loadCell(term.cols - 1, cell);
expect(cell.getChars()).eql('');
expect(cell.getChars().length).eql(0);
expect(cell.getWidth()).eql(1);
term.buffer.lines.get(1).loadCell(0, cell);
term.buffer.lines.get(1)!.loadCell(0, cell);
expect(cell.getChars()).eql('¥\u0301');
expect(cell.getChars().length).eql(2);
expect(cell.getWidth()).eql(2);
@@ -924,7 +922,7 @@ describe('Terminal', () => {
it('line of ¥ with combining even', () => {
term.writeSync(Array(50).join('¥\u0301'));
for (let i = 0; i < term.cols; ++i) {
term.buffer.lines.get(0).loadCell(i, cell);
term.buffer.lines.get(0)!.loadCell(i, cell);
if (i % 2) {
expect(cell.getChars()).eql('');
expect(cell.getChars().length).eql(0);
@@ -935,7 +933,7 @@ describe('Terminal', () => {
expect(cell.getWidth()).eql(2);
}
}
term.buffer.lines.get(1).loadCell(0, cell);
term.buffer.lines.get(1)!.loadCell(0, cell);
expect(cell.getChars()).eql('¥\u0301');
expect(cell.getChars().length).eql(2);
expect(cell.getWidth()).eql(2);
@@ -944,7 +942,7 @@ describe('Terminal', () => {
term.buffer.x = 1;
term.writeSync(Array(50).join('\ud843\ude6d\u0301'));
for (let i = 1; i < term.cols - 1; ++i) {
term.buffer.lines.get(0).loadCell(i, cell);
term.buffer.lines.get(0)!.loadCell(i, cell);
if (!(i % 2)) {
expect(cell.getChars()).eql('');
expect(cell.getChars().length).eql(0);
@@ -955,11 +953,11 @@ describe('Terminal', () => {
expect(cell.getWidth()).eql(2);
}
}
term.buffer.lines.get(0).loadCell(term.cols - 1, cell);
term.buffer.lines.get(0)!.loadCell(term.cols - 1, cell);
expect(cell.getChars()).eql('');
expect(cell.getChars().length).eql(0);
expect(cell.getWidth()).eql(1);
term.buffer.lines.get(1).loadCell(0, cell);
term.buffer.lines.get(1)!.loadCell(0, cell);
expect(cell.getChars()).eql('\ud843\ude6d\u0301');
expect(cell.getChars().length).eql(3);
expect(cell.getWidth()).eql(2);
@@ -967,7 +965,7 @@ describe('Terminal', () => {
it('line of surrogate fullwidth with combining even', () => {
term.writeSync(Array(50).join('\ud843\ude6d\u0301'));
for (let i = 0; i < term.cols; ++i) {
term.buffer.lines.get(0).loadCell(i, cell);
term.buffer.lines.get(0)!.loadCell(i, cell);
if (i % 2) {
expect(cell.getChars()).eql('');
expect(cell.getChars().length).eql(0);
@@ -978,7 +976,7 @@ describe('Terminal', () => {
expect(cell.getWidth()).eql(2);
}
}
term.buffer.lines.get(1).loadCell(0, cell);
term.buffer.lines.get(1)!.loadCell(0, cell);
expect(cell.getChars()).eql('\ud843\ude6d\u0301');
expect(cell.getChars().length).eql(3);
expect(cell.getWidth()).eql(2);
@@ -991,42 +989,42 @@ describe('Terminal', () => {
term.writeSync(Array(9).join('0123456789').slice(-80));
term.buffer.x = 10;
term.buffer.y = 0;
term.insertMode = true;
term.write('\x1b[4h');
term.writeSync('abcde');
expect(term.buffer.lines.get(0).length).eql(term.cols);
expect(term.buffer.lines.get(0).loadCell(10, cell).getChars()).eql('a');
expect(term.buffer.lines.get(0).loadCell(14, cell).getChars()).eql('e');
expect(term.buffer.lines.get(0).loadCell(15, cell).getChars()).eql('0');
expect(term.buffer.lines.get(0).loadCell(79, cell).getChars()).eql('4');
expect(term.buffer.lines.get(0)!.length).eql(term.cols);
expect(term.buffer.lines.get(0)!.loadCell(10, cell).getChars()).eql('a');
expect(term.buffer.lines.get(0)!.loadCell(14, cell).getChars()).eql('e');
expect(term.buffer.lines.get(0)!.loadCell(15, cell).getChars()).eql('0');
expect(term.buffer.lines.get(0)!.loadCell(79, cell).getChars()).eql('4');
});
it('fullwidth - insert', () => {
term.writeSync(Array(9).join('0123456789').slice(-80));
term.buffer.x = 10;
term.buffer.y = 0;
term.insertMode = true;
term.write('\x1b[4h');
term.writeSync('¥¥¥');
expect(term.buffer.lines.get(0).length).eql(term.cols);
expect(term.buffer.lines.get(0).loadCell(10, cell).getChars()).eql('¥');
expect(term.buffer.lines.get(0).loadCell(11, cell).getChars()).eql('');
expect(term.buffer.lines.get(0).loadCell(14, cell).getChars()).eql('¥');
expect(term.buffer.lines.get(0).loadCell(15, cell).getChars()).eql('');
expect(term.buffer.lines.get(0).loadCell(79, cell).getChars()).eql('3');
expect(term.buffer.lines.get(0)!.length).eql(term.cols);
expect(term.buffer.lines.get(0)!.loadCell(10, cell).getChars()).eql('¥');
expect(term.buffer.lines.get(0)!.loadCell(11, cell).getChars()).eql('');
expect(term.buffer.lines.get(0)!.loadCell(14, cell).getChars()).eql('¥');
expect(term.buffer.lines.get(0)!.loadCell(15, cell).getChars()).eql('');
expect(term.buffer.lines.get(0)!.loadCell(79, cell).getChars()).eql('3');
});
it('fullwidth - right border', () => {
term.writeSync(Array(41).join('¥'));
term.buffer.x = 10;
term.buffer.y = 0;
term.insertMode = true;
term.write('\x1b[4h');
term.writeSync('a');
expect(term.buffer.lines.get(0).length).eql(term.cols);
expect(term.buffer.lines.get(0).loadCell(10, cell).getChars()).eql('a');
expect(term.buffer.lines.get(0).loadCell(11, cell).getChars()).eql('¥');
expect(term.buffer.lines.get(0).loadCell(79, cell).getChars()).eql(''); // fullwidth char got replaced
expect(term.buffer.lines.get(0)!.length).eql(term.cols);
expect(term.buffer.lines.get(0)!.loadCell(10, cell).getChars()).eql('a');
expect(term.buffer.lines.get(0)!.loadCell(11, cell).getChars()).eql('¥');
expect(term.buffer.lines.get(0)!.loadCell(79, cell).getChars()).eql(''); // fullwidth char got replaced
term.writeSync('b');
expect(term.buffer.lines.get(0).length).eql(term.cols);
expect(term.buffer.lines.get(0).loadCell(11, cell).getChars()).eql('b');
expect(term.buffer.lines.get(0).loadCell(12, cell).getChars()).eql('¥');
expect(term.buffer.lines.get(0).loadCell(79, cell).getChars()).eql(''); // empty cell after fullwidth
expect(term.buffer.lines.get(0)!.length).eql(term.cols);
expect(term.buffer.lines.get(0)!.loadCell(11, cell).getChars()).eql('b');
expect(term.buffer.lines.get(0)!.loadCell(12, cell).getChars()).eql('¥');
expect(term.buffer.lines.get(0)!.loadCell(79, cell).getChars()).eql(''); // empty cell after fullwidth
});
});
@@ -1271,9 +1269,9 @@ describe('Terminal', () => {
terminal.writeSync(input);
const s = terminal.buffer.iterator(true).next().content;
assert.equal(input, s);
const stringIndex = s.match(/😃/).index;
const stringIndex = s.match(/😃/)!.index!;
const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, stringIndex);
assert(terminal.buffer.lines.get(bufferIndex[0]).loadCell(bufferIndex[1], new CellData()).getChars(), '😃');
assert(terminal.buffer.lines.get(bufferIndex[0])!.loadCell(bufferIndex[1], new CellData()).getChars(), '😃');
});
it('multiline fullwidth chars with offset 1 (currently tests for broken behavior)', () => {
@@ -1300,7 +1298,7 @@ describe('Terminal', () => {
assert.equal(input, s);
for (let i = 0; i < input.length; ++i) {
const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i, true);
assert.equal(input[i], terminal.buffer.lines.get(bufferIndex[0]).loadCell(bufferIndex[1], new CellData()).getChars());
assert.equal(input[i], terminal.buffer.lines.get(bufferIndex[0])!.loadCell(bufferIndex[1], new CellData()).getChars());
}
});
@@ -1318,7 +1316,7 @@ describe('Terminal', () => {
: (i % 3 === 1)
? input.substr(i, 2)
: input.substr(i - 1, 2),
terminal.buffer.lines.get(bufferIndex[0]).loadCell(bufferIndex[1], new CellData()).getChars());
terminal.buffer.lines.get(bufferIndex[0])!.loadCell(bufferIndex[1], new CellData()).getChars());
}
});
@@ -1372,15 +1370,15 @@ describe('Terminal', () => {
const normalTerminal = new TestTerminal({rows: 5, cols: 10, windowsMode: false});
normalTerminal.writeSync(data.join(''));
assert.equal(normalTerminal.buffer.lines.get(0).isWrapped, false);
assert.equal(normalTerminal.buffer.lines.get(1).isWrapped, false);
assert.equal(normalTerminal.buffer.lines.get(2).isWrapped, false);
assert.equal(normalTerminal.buffer.lines.get(0)!.isWrapped, false);
assert.equal(normalTerminal.buffer.lines.get(1)!.isWrapped, false);
assert.equal(normalTerminal.buffer.lines.get(2)!.isWrapped, false);
const windowsModeTerminal = new TestTerminal({rows: 5, cols: 10, windowsMode: true});
windowsModeTerminal.writeSync(data.join(''));
assert.equal(windowsModeTerminal.buffer.lines.get(0).isWrapped, false);
assert.equal(windowsModeTerminal.buffer.lines.get(1).isWrapped, true, 'This line should wrap in Windows mode as the previous line ends in a non-null character');
assert.equal(windowsModeTerminal.buffer.lines.get(2).isWrapped, false);
assert.equal(windowsModeTerminal.buffer.lines.get(0)!.isWrapped, false);
assert.equal(windowsModeTerminal.buffer.lines.get(1)!.isWrapped, true, 'This line should wrap in Windows mode as the previous line ends in a non-null character');
assert.equal(windowsModeTerminal.buffer.lines.get(2)!.isWrapped, false);
});
it('should mark lines as wrapped when the line ends in a non-null character after a CUP', () => {
@@ -1392,22 +1390,154 @@ describe('Terminal', () => {
const normalTerminal = new TestTerminal({rows: 5, cols: 10, windowsMode: false});
normalTerminal.writeSync(data.join(''));
assert.equal(normalTerminal.buffer.lines.get(0).isWrapped, false);
assert.equal(normalTerminal.buffer.lines.get(1).isWrapped, false);
assert.equal(normalTerminal.buffer.lines.get(2).isWrapped, false);
assert.equal(normalTerminal.buffer.lines.get(0)!.isWrapped, false);
assert.equal(normalTerminal.buffer.lines.get(1)!.isWrapped, false);
assert.equal(normalTerminal.buffer.lines.get(2)!.isWrapped, false);
const windowsModeTerminal = new TestTerminal({rows: 5, cols: 10, windowsMode: true});
windowsModeTerminal.writeSync(data.join(''));
assert.equal(windowsModeTerminal.buffer.lines.get(0).isWrapped, false);
assert.equal(windowsModeTerminal.buffer.lines.get(1).isWrapped, true, 'This line should wrap in Windows mode as the previous line ends in a non-null character');
assert.equal(windowsModeTerminal.buffer.lines.get(2).isWrapped, false);
assert.equal(windowsModeTerminal.buffer.lines.get(0)!.isWrapped, false);
assert.equal(windowsModeTerminal.buffer.lines.get(1)!.isWrapped, true, 'This line should wrap in Windows mode as the previous line ends in a non-null character');
assert.equal(windowsModeTerminal.buffer.lines.get(2)!.isWrapped, false);
});
});
it('convertEol setting', function(): void {
// not converting
const termNotConverting = new TestTerminal({cols: 15, rows: 10});
termNotConverting.writeSync('Hello\nWorld');
expect(termNotConverting.buffer.lines.get(0)!.translateToString(false)).equals('Hello ');
expect(termNotConverting.buffer.lines.get(1)!.translateToString(false)).equals(' World ');
expect(termNotConverting.buffer.lines.get(0)!.translateToString(true)).equals('Hello');
expect(termNotConverting.buffer.lines.get(1)!.translateToString(true)).equals(' World');
// converting
const termConverting = new TestTerminal({cols: 15, rows: 10, convertEol: true});
termConverting.writeSync('Hello\nWorld');
expect(termConverting.buffer.lines.get(0)!.translateToString(false)).equals('Hello ');
expect(termConverting.buffer.lines.get(1)!.translateToString(false)).equals('World ');
expect(termConverting.buffer.lines.get(0)!.translateToString(true)).equals('Hello');
expect(termConverting.buffer.lines.get(1)!.translateToString(true)).equals('World');
});
describe('Terminal InputHandler integration', () => {
function getLines(term: TestTerminal, limit: number = term.rows): string[] {
const res: string[] = [];
for (let i = 0; i < limit; ++i) {
res.push(term.buffer.lines.get(i)!.translateToString(true));
}
return res;
}
// This suite cannot live in InputHandler unless Terminal.scroll moved into IBufferService
describe('SL/SR/DECIC/DECDC', () => {
let term: TestTerminal;
beforeEach(() => {
term = new TestTerminal({cols: 5, rows: 5, scrollback: 1});
});
it('SL (scrollLeft)', () => {
term.writeSync('12345'.repeat(6));
term.writeSync('\x1b[ @');
assert.deepEqual(getLines(term, term.rows + 1), ['12345', '2345', '2345', '2345', '2345', '2345']);
term.writeSync('\x1b[0 @');
assert.deepEqual(getLines(term, term.rows + 1), ['12345', '345', '345', '345', '345', '345']);
term.writeSync('\x1b[2 @');
assert.deepEqual(getLines(term, term.rows + 1), ['12345', '5', '5', '5', '5', '5']);
});
it('SR (scrollRight)', () => {
term.writeSync('12345'.repeat(6));
term.writeSync('\x1b[ A');
assert.deepEqual(getLines(term, term.rows + 1), ['12345', ' 1234', ' 1234', ' 1234', ' 1234', ' 1234']);
term.writeSync('\x1b[0 A');
assert.deepEqual(getLines(term, term.rows + 1), ['12345', ' 123', ' 123', ' 123', ' 123', ' 123']);
term.writeSync('\x1b[2 A');
assert.deepEqual(getLines(term, term.rows + 1), ['12345', ' 1', ' 1', ' 1', ' 1', ' 1']);
});
it('insertColumns (DECIC)', () => {
term.writeSync('12345'.repeat(6));
term.writeSync('\x1b[3;3H');
term.writeSync('\x1b[\'}');
assert.deepEqual(getLines(term, term.rows + 1), ['12345', '12 34', '12 34', '12 34', '12 34', '12 34']);
term.reset();
term.writeSync('12345'.repeat(6));
term.writeSync('\x1b[3;3H');
term.writeSync('\x1b[1\'}');
assert.deepEqual(getLines(term, term.rows + 1), ['12345', '12 34', '12 34', '12 34', '12 34', '12 34']);
term.reset();
term.writeSync('12345'.repeat(6));
term.writeSync('\x1b[3;3H');
term.writeSync('\x1b[2\'}');
assert.deepEqual(getLines(term, term.rows + 1), ['12345', '12 3', '12 3', '12 3', '12 3', '12 3']);
});
it('deleteColumns (DECDC)', () => {
term.writeSync('12345'.repeat(6));
term.writeSync('\x1b[3;3H');
term.writeSync('\x1b[\'~');
assert.deepEqual(getLines(term, term.rows + 1), ['12345', '1245', '1245', '1245', '1245', '1245']);
term.reset();
term.writeSync('12345'.repeat(6));
term.writeSync('\x1b[3;3H');
term.writeSync('\x1b[1\'~');
assert.deepEqual(getLines(term, term.rows + 1), ['12345', '1245', '1245', '1245', '1245', '1245']);
term.reset();
term.writeSync('12345'.repeat(6));
term.writeSync('\x1b[3;3H');
term.writeSync('\x1b[2\'~');
assert.deepEqual(getLines(term, term.rows + 1), ['12345', '125', '125', '125', '125', '125']);
});
});
describe('BS with reverseWraparound set/unset', () => {
const ttyBS = '\x08 \x08'; // tty ICANON sends <BS SP BS> on pressing BS
beforeEach(() => {
term = new TestTerminal({cols: 5, rows: 5, scrollback: 1});
});
describe('reverseWraparound set', () => {
it('should not reverse outside of scroll margins', () => {
// prepare buffer content
term.writeSync('#####abcdefghijklmnopqrstuvwxy');
assert.deepEqual(getLines(term, 6), ['#####', 'abcde', 'fghij', 'klmno', 'pqrst', 'uvwxy']);
assert.equal(term.buffer.ydisp, 1);
assert.equal(term.buffer.x, 5);
assert.equal(term.buffer.y, 4);
term.writeSync(ttyBS.repeat(100));
assert.deepEqual(getLines(term, 6), ['#####', 'abcde', 'fghij', 'klmno', 'pqrst', ' y']);
term.writeSync('\x1b[?45h');
term.writeSync('uvwxy');
// set top/bottom to 1/3 (0-based)
term.writeSync('\x1b[2;4r');
// place cursor below scroll bottom
term.buffer.x = 5;
term.buffer.y = 4;
term.writeSync(ttyBS.repeat(100));
assert.deepEqual(getLines(term, 6), ['#####', 'abcde', 'fghij', 'klmno', 'pqrst', ' ']);
term.writeSync('uvwxy');
// place cursor within scroll margins
term.buffer.x = 5;
term.buffer.y = 3;
term.writeSync(ttyBS.repeat(100));
assert.deepEqual(getLines(term, 6), ['#####', 'abcde', ' ', ' ', ' ', 'uvwxy']);
assert.equal(term.buffer.x, 0);
assert.equal(term.buffer.y, term.buffer.scrollTop); // stops at 0, scrollTop
term.writeSync('fghijklmnopqrst');
// place cursor above scroll top
term.buffer.x = 5;
term.buffer.y = 0;
term.writeSync(ttyBS.repeat(100));
assert.deepEqual(getLines(term, 6), ['#####', ' ', 'fghij', 'klmno', 'pqrst', 'uvwxy']);
});
});
});
});
});
class TestLinkifier extends Linkifier {
constructor(bufferService: IBufferService, unicodeService: IUnicodeService) {
super(bufferService, new MockLogService(), new MockOptionsService(), unicodeService);
super(bufferService, new MockLogService(), unicodeService);
Linkifier._timeBeforeLatency = 0;
}
@@ -1416,14 +1546,14 @@ class TestLinkifier extends Linkifier {
}
class TestMouseZoneManager implements IMouseZoneManager {
dispose(): void {
public dispose(): void {
}
public clears: number = 0;
public zones: IMouseZone[] = [];
add(zone: IMouseZone): void {
public add(zone: IMouseZone): void {
this.zones.push(zone);
}
clearAll(): void {
public clearAll(): void {
this.clears++;
}
}
File diff suppressed because it is too large Load Diff
@@ -8,18 +8,18 @@ import * as path from 'path';
import * as os from 'os';
import * as fs from 'fs';
import * as pty from 'node-pty';
import { Terminal } from './Terminal';
import { Terminal } from 'browser/Terminal';
import { IDisposable } from 'xterm';
// all test files expect terminal in 80x25
const COLS = 80;
const ROWS = 25;
const TESTFILES = glob.sync('**/escape_sequence_files/*.in', { cwd: path.join(__dirname, '..')});
const TESTFILES = glob.sync('**/escape_sequence_files/*.in', { cwd: path.join(__dirname, '../..')});
const SKIP_FILES = [
't0084-CBT.in',
't0101-NLM.in',
't0103-reverse_wrap.in',
't0103-reverse_wrap.in', // not comparable, we deviate from xterm reverse wrap on purpose
't0504-vim.in'
];
if (os.platform() === 'darwin') {
@@ -124,7 +124,7 @@ function terminalToString(term: Terminal): string {
let result = '';
let lineText = '';
for (let line = term.buffer.ybase; line < term.buffer.ybase + term.rows; line++) {
lineText = term.buffer.lines.get(line).translateToString(true);
lineText = term.buffer.lines.get(line)!.translateToString(true);
// rtrim empty cells as xterm does
lineText = lineText.replace(/\s+$/, '');
result += lineText;
+395 -6
View File
@@ -3,19 +3,339 @@
* @license MIT
*/
import { IDisposable, IMarker, ISelectionPosition, ILinkProvider } from 'xterm';
import { IEvent, EventEmitter } from 'common/EventEmitter';
import { ICharSizeService, IMouseService } from 'browser/services/Services';
import { ICharSizeService, IMouseService, IRenderService, ISelectionService } from 'browser/services/Services';
import { IRenderDimensions, IRenderer, CharacterJoinerHandler, IRequestRedrawEvent } from 'browser/renderer/Types';
import { IColorSet, ILinkMatcherOptions, ITerminal, ILinkifier, ILinkifier2, IBrowser, IViewport, IColorManager, ICompositionHelper } from 'browser/Types';
import { IBuffer, IBufferStringIterator, IBufferSet } from 'common/buffer/Types';
import { IBufferLine, ICellData, IAttributeData, ICircularList, XtermListener, ICharset, ITerminalOptions } from 'common/Types';
import { Buffer } from 'common/buffer/Buffer';
import * as Browser from 'common/Platform';
import { Terminal } from 'browser/Terminal';
import { IUnicodeService, IOptionsService } from 'common/services/Services';
import { IFunctionIdentifier, IParams } from 'common/parser/Types';
import { AttributeData } from 'common/buffer/AttributeData';
export class TestTerminal extends Terminal {
public get curAttrData(): IAttributeData { return (this as any)._inputHandler._curAttrData; }
public keyDown(ev: any): boolean | undefined { return this._keyDown(ev); }
public keyPress(ev: any): boolean { return this._keyPress(ev); }
}
export class MockTerminal implements ITerminal {
public onBlur!: IEvent<void>;
public onFocus!: IEvent<void>;
public onA11yChar!: IEvent<string>;
public onA11yTab!: IEvent<number>;
public onCursorMove!: IEvent<void>;
public onLineFeed!: IEvent<void>;
public onSelectionChange!: IEvent<void>;
public onData!: IEvent<string>;
public onBinary!: IEvent<string>;
public onTitleChange!: IEvent<string>;
public onScroll!: IEvent<number>;
public onKey!: IEvent<{ key: string, domEvent: KeyboardEvent }>;
public onRender!: IEvent<{ start: number, end: number }>;
public onResize!: IEvent<{ cols: number, rows: number }>;
public markers!: IMarker[];
public optionsService!: IOptionsService;
public unicodeService!: IUnicodeService;
public addMarker(cursorYOffset: number): IMarker {
throw new Error('Method not implemented.');
}
public selectLines(start: number, end: number): void {
throw new Error('Method not implemented.');
}
public scrollToLine(line: number): void {
throw new Error('Method not implemented.');
}
public static string: any;
public setOption(key: any, value: any): void {
throw new Error('Method not implemented.');
}
public blur(): void {
throw new Error('Method not implemented.');
}
public focus(): void {
throw new Error('Method not implemented.');
}
public resize(columns: number, rows: number): void {
throw new Error('Method not implemented.');
}
public writeln(data: string): void {
throw new Error('Method not implemented.');
}
public paste(data: string): void {
throw new Error('Method not implemented.');
}
public open(parent: HTMLElement): void {
throw new Error('Method not implemented.');
}
public attachCustomKeyEventHandler(customKeyEventHandler: (event: KeyboardEvent) => boolean): void {
throw new Error('Method not implemented.');
}
public addCsiHandler(id: IFunctionIdentifier, callback: (params: IParams) => boolean): IDisposable {
throw new Error('Method not implemented.');
}
public addDcsHandler(id: IFunctionIdentifier, callback: (data: string, param: IParams) => boolean): IDisposable {
throw new Error('Method not implemented.');
}
public addEscHandler(id: IFunctionIdentifier, handler: () => boolean): IDisposable {
throw new Error('Method not implemented.');
}
public addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable {
throw new Error('Method not implemented.');
}
public registerLinkMatcher(regex: RegExp, handler: (event: MouseEvent, uri: string) => boolean | void, options?: ILinkMatcherOptions): number {
throw new Error('Method not implemented.');
}
public deregisterLinkMatcher(matcherId: number): void {
throw new Error('Method not implemented.');
}
public registerLinkProvider(linkProvider: ILinkProvider): IDisposable {
throw new Error('Method not implemented.');
}
public hasSelection(): boolean {
throw new Error('Method not implemented.');
}
public getSelection(): string {
throw new Error('Method not implemented.');
}
public getSelectionPosition(): ISelectionPosition | undefined {
throw new Error('Method not implemented.');
}
public clearSelection(): void {
throw new Error('Method not implemented.');
}
public select(column: number, row: number, length: number): void {
throw new Error('Method not implemented.');
}
public selectAll(): void {
throw new Error('Method not implemented.');
}
public dispose(): void {
throw new Error('Method not implemented.');
}
public scrollPages(pageCount: number): void {
throw new Error('Method not implemented.');
}
public scrollToTop(): void {
throw new Error('Method not implemented.');
}
public scrollToBottom(): void {
throw new Error('Method not implemented.');
}
public clear(): void {
throw new Error('Method not implemented.');
}
public write(data: string): void {
throw new Error('Method not implemented.');
}
public writeUtf8(data: Uint8Array): void {
throw new Error('Method not implemented.');
}
public bracketedPasteMode!: boolean;
public renderer!: IRenderer;
public linkifier!: ILinkifier;
public linkifier2!: ILinkifier2;
public isFocused!: boolean;
public options: ITerminalOptions = {};
public element!: HTMLElement;
public screenElement!: HTMLElement;
public rowContainer!: HTMLElement;
public selectionContainer!: HTMLElement;
public selectionService!: ISelectionService;
public textarea!: HTMLTextAreaElement;
public rows!: number;
public cols!: number;
public browser: IBrowser = <any>Browser;
public writeBuffer!: string[];
public children!: HTMLElement[];
public cursorHidden!: boolean;
public cursorState!: number;
public scrollback!: number;
public buffers!: IBufferSet;
public buffer!: IBuffer;
public viewport!: IViewport;
public applicationCursor!: boolean;
public handler(data: string): void {
throw new Error('Method not implemented.');
}
public on(event: string, callback: (...args: any[]) => void): void {
throw new Error('Method not implemented.');
}
public off(type: string, listener: XtermListener): void {
throw new Error('Method not implemented.');
}
public addDisposableListener(type: string, handler: XtermListener): IDisposable {
throw new Error('Method not implemented.');
}
public scrollLines(disp: number): void {
throw new Error('Method not implemented.');
}
public scrollToRow(absoluteRow: number): number {
throw new Error('Method not implemented.');
}
public cancel(ev: Event, force?: boolean): void {
throw new Error('Method not implemented.');
}
public log(text: string): void {
throw new Error('Method not implemented.');
}
public emit(event: string, data: any): void {
throw new Error('Method not implemented.');
}
public reset(): void {
throw new Error('Method not implemented.');
}
public refresh(start: number, end: number): void {
throw new Error('Method not implemented.');
}
public registerCharacterJoiner(handler: CharacterJoinerHandler): number { return 0; }
public deregisterCharacterJoiner(joinerId: number): void { }
}
export class MockBuffer implements IBuffer {
public markers!: IMarker[];
public addMarker(y: number): IMarker {
throw new Error('Method not implemented.');
}
public isCursorInViewport!: boolean;
public lines!: ICircularList<IBufferLine>;
public ydisp!: number;
public ybase!: number;
public hasScrollback!: boolean;
public y!: number;
public x!: number;
public tabs: any;
public scrollBottom!: number;
public scrollTop!: number;
public savedY!: number;
public savedX!: number;
public savedCharset: ICharset | undefined;
public savedCurAttrData = new AttributeData();
public translateBufferLineToString(lineIndex: number, trimRight: boolean, startCol?: number, endCol?: number): string {
return Buffer.prototype.translateBufferLineToString.apply(this, arguments as any);
}
public getWrappedRangeForLine(y: number): { first: number, last: number } {
return Buffer.prototype.getWrappedRangeForLine.apply(this, arguments as any);
}
public nextStop(x?: number): number {
throw new Error('Method not implemented.');
}
public prevStop(x?: number): number {
throw new Error('Method not implemented.');
}
public setLines(lines: ICircularList<IBufferLine>): void {
this.lines = lines;
}
public getBlankLine(attr: IAttributeData, isWrapped?: boolean): IBufferLine {
return Buffer.prototype.getBlankLine.apply(this, arguments as any);
}
public stringIndexToBufferIndex(lineIndex: number, stringIndex: number): number[] {
return Buffer.prototype.stringIndexToBufferIndex.apply(this, arguments as any);
}
public iterator(trimRight: boolean, startIndex?: number, endIndex?: number): IBufferStringIterator {
return Buffer.prototype.iterator.apply(this, arguments as any);
}
public getNullCell(attr?: IAttributeData): ICellData {
throw new Error('Method not implemented.');
}
public getWhitespaceCell(attr?: IAttributeData): ICellData {
throw new Error('Method not implemented.');
}
}
export class MockRenderer implements IRenderer {
public onRequestRedraw!: IEvent<IRequestRedrawEvent>;
public onCanvasResize!: IEvent<{ width: number, height: number }>;
public onRender!: IEvent<{ start: number, end: number }>;
public dispose(): void {
throw new Error('Method not implemented.');
}
public colorManager!: IColorManager;
public on(type: string, listener: XtermListener): void {
throw new Error('Method not implemented.');
}
public off(type: string, listener: XtermListener): void {
throw new Error('Method not implemented.');
}
public emit(type: string, data?: any): void {
throw new Error('Method not implemented.');
}
public addDisposableListener(type: string, handler: XtermListener): IDisposable {
throw new Error('Method not implemented.');
}
public dimensions!: IRenderDimensions;
public setColors(colors: IColorSet): void {
throw new Error('Method not implemented.');
}
public onResize(cols: number, rows: number): void { }
public onCharSizeChanged(): void { }
public onBlur(): void { }
public onFocus(): void { }
public onSelectionChanged(start: [number, number], end: [number, number]): void { }
public onCursorMove(): void { }
public onOptionsChanged(): void { }
public onDevicePixelRatioChange(): void { }
public clear(): void { }
public renderRows(start: number, end: number): void { }
public registerCharacterJoiner(handler: CharacterJoinerHandler): number { return 0; }
public deregisterCharacterJoiner(): boolean { return true; }
}
export class MockViewport implements IViewport {
public dispose(): void {
throw new Error('Method not implemented.');
}
public scrollBarWidth: number = 0;
public onThemeChange(colors: IColorSet): void {
throw new Error('Method not implemented.');
}
public onWheel(ev: WheelEvent): boolean {
throw new Error('Method not implemented.');
}
public onTouchStart(ev: TouchEvent): void {
throw new Error('Method not implemented.');
}
public onTouchMove(ev: TouchEvent): boolean {
throw new Error('Method not implemented.');
}
public syncScrollArea(): void { }
public getLinesScrolled(ev: WheelEvent): number {
throw new Error('Method not implemented.');
}
}
export class MockCompositionHelper implements ICompositionHelper {
public compositionstart(): void {
throw new Error('Method not implemented.');
}
public compositionupdate(ev: CompositionEvent): void {
throw new Error('Method not implemented.');
}
public compositionend(): void {
throw new Error('Method not implemented.');
}
public updateCompositionElements(dontRecurse?: boolean): void {
throw new Error('Method not implemented.');
}
public keydown(ev: KeyboardEvent): boolean {
return true;
}
}
export class MockCharSizeService implements ICharSizeService {
serviceBrand: any;
get hasValidSize(): boolean { return this.width > 0 && this.height > 0; }
onCharSizeChange: IEvent<void> = new EventEmitter<void>().event;
public serviceBrand: undefined;
public get hasValidSize(): boolean { return this.width > 0 && this.height > 0; }
public onCharSizeChange: IEvent<void> = new EventEmitter<void>().event;
constructor(public width: number, public height: number) {}
measure(): void {}
public measure(): void {}
}
export class MockMouseService implements IMouseService {
serviceBrand: any;
public serviceBrand: undefined;
public getCoords(event: {clientX: number, clientY: number}, element: HTMLElement, colCount: number, rowCount: number, isSelection?: boolean): [number, number] | undefined {
throw new Error('Not implemented');
}
@@ -24,3 +344,72 @@ export class MockMouseService implements IMouseService {
throw new Error('Not implemented');
}
}
export class MockRenderService implements IRenderService {
public serviceBrand: undefined;
public onDimensionsChange: IEvent<IRenderDimensions> = new EventEmitter<IRenderDimensions>().event;
public onRenderedBufferChange: IEvent<{ start: number, end: number }, void> = new EventEmitter<{ start: number, end: number }>().event;
public onRefreshRequest: IEvent<{ start: number, end: number}, void> = new EventEmitter<{ start: number, end: number }>().event;
public dimensions: IRenderDimensions = {
scaledCharWidth: 0,
scaledCharHeight: 0,
scaledCellWidth: 0,
scaledCellHeight: 0,
scaledCharLeft: 0,
scaledCharTop: 0,
scaledCanvasWidth: 0,
scaledCanvasHeight: 0,
canvasWidth: 0,
canvasHeight: 0,
actualCellWidth: 0,
actualCellHeight: 0
};
public refreshRows(start: number, end: number): void {
throw new Error('Method not implemented.');
}
public resize(cols: number, rows: number): void {
throw new Error('Method not implemented.');
}
public changeOptions(): void {
throw new Error('Method not implemented.');
}
public setRenderer(renderer: IRenderer): void {
throw new Error('Method not implemented.');
}
public setColors(colors: IColorSet): void {
throw new Error('Method not implemented.');
}
public onDevicePixelRatioChange(): void {
throw new Error('Method not implemented.');
}
public onResize(cols: number, rows: number): void {
throw new Error('Method not implemented.');
}
public onCharSizeChanged(): void {
throw new Error('Method not implemented.');
}
public onBlur(): void {
throw new Error('Method not implemented.');
}
public onFocus(): void {
throw new Error('Method not implemented.');
}
public onSelectionChanged(start: [number, number], end: [number, number], columnSelectMode: boolean): void {
throw new Error('Method not implemented.');
}
public onCursorMove(): void {
throw new Error('Method not implemented.');
}
public clear(): void {
throw new Error('Method not implemented.');
}
public registerCharacterJoiner(handler: CharacterJoinerHandler): number {
throw new Error('Method not implemented.');
}
public deregisterCharacterJoiner(joinerId: number): boolean {
throw new Error('Method not implemented.');
}
public dispose(): void {
throw new Error('Method not implemented.');
}
}
+110 -6
View File
@@ -3,9 +3,107 @@
* @license MIT
*/
import { IDisposable, IMarker, ISelectionPosition } from 'xterm';
import { IEvent } from 'common/EventEmitter';
import { IDisposable } from 'common/Types';
import { ICoreTerminal, CharData, ITerminalOptions } from 'common/Types';
import { IMouseService, IRenderService } from './services/Services';
import { IBuffer, IBufferSet } from 'common/buffer/Types';
import { IFunctionIdentifier, IParams } from 'common/parser/Types';
export interface ITerminal extends IPublicTerminal, ICoreTerminal {
element: HTMLElement | undefined;
screenElement: HTMLElement | undefined;
browser: IBrowser;
buffer: IBuffer;
buffers: IBufferSet;
viewport: IViewport | undefined;
// TODO: We should remove options once components adopt optionsService
options: ITerminalOptions;
linkifier: ILinkifier;
linkifier2: ILinkifier2;
onBlur: IEvent<void>;
onFocus: IEvent<void>;
onA11yChar: IEvent<string>;
onA11yTab: IEvent<number>;
cancel(ev: Event, force?: boolean): boolean | void;
}
// Portions of the public API that are required by the internal Terminal
export interface IPublicTerminal extends IDisposable {
textarea: HTMLTextAreaElement | undefined;
rows: number;
cols: number;
buffer: IBuffer;
markers: IMarker[];
onCursorMove: IEvent<void>;
onData: IEvent<string>;
onBinary: IEvent<string>;
onKey: IEvent<{ key: string, domEvent: KeyboardEvent }>;
onLineFeed: IEvent<void>;
onScroll: IEvent<number>;
onSelectionChange: IEvent<void>;
onRender: IEvent<{ start: number, end: number }>;
onResize: IEvent<{ cols: number, rows: number }>;
onTitleChange: IEvent<string>;
blur(): void;
focus(): void;
resize(columns: number, rows: number): void;
open(parent: HTMLElement): void;
attachCustomKeyEventHandler(customKeyEventHandler: (event: KeyboardEvent) => boolean): void;
addCsiHandler(id: IFunctionIdentifier, callback: (params: IParams) => boolean): IDisposable;
addDcsHandler(id: IFunctionIdentifier, callback: (data: string, param: IParams) => boolean): IDisposable;
addEscHandler(id: IFunctionIdentifier, callback: () => boolean): 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 | undefined;
hasSelection(): boolean;
getSelection(): string;
getSelectionPosition(): ISelectionPosition | undefined;
clearSelection(): void;
select(column: number, row: number, length: number): void;
selectAll(): void;
selectLines(start: number, end: number): void;
dispose(): void;
scrollLines(amount: number): void;
scrollPages(pageCount: number): void;
scrollToTop(): void;
scrollToBottom(): void;
scrollToLine(line: number): void;
clear(): void;
write(data: string | Uint8Array, callback?: () => void): void;
paste(data: string): void;
refresh(start: number, end: number): void;
reset(): void;
}
export type CustomKeyEventHandler = (event: KeyboardEvent) => boolean;
export type LineData = CharData[];
export interface ICompositionHelper {
compositionstart(): void;
compositionupdate(ev: CompositionEvent): void;
compositionend(): void;
updateCompositionElements(dontRecurse?: boolean): void;
keydown(ev: KeyboardEvent): boolean;
}
export interface IBrowser {
isNode: boolean;
userAgent: string;
platform: string;
isFirefox: boolean;
isMac: boolean;
isIpad: boolean;
isIphone: boolean;
isWindows: boolean;
}
export interface IColorManager {
colors: IColorSet;
@@ -96,8 +194,8 @@ export interface ILinkifierEvent {
}
export interface ILinkifier {
onLinkHover: IEvent<ILinkifierEvent>;
onLinkLeave: IEvent<ILinkifierEvent>;
onShowLinkUnderline: IEvent<ILinkifierEvent>;
onHideLinkUnderline: IEvent<ILinkifierEvent>;
onLinkTooltip: IEvent<ILinkifierEvent>;
attachToDom(element: HTMLElement, mouseZoneManager: IMouseZoneManager): void;
@@ -107,8 +205,8 @@ export interface ILinkifier {
}
export interface ILinkifier2 {
onLinkHover: IEvent<ILinkifierEvent>;
onLinkLeave: IEvent<ILinkifierEvent>;
onShowLinkUnderline: IEvent<ILinkifierEvent>;
onHideLinkUnderline: IEvent<ILinkifierEvent>;
attachToDom(element: HTMLElement, mouseService: IMouseService, renderService: IRenderService): void;
registerLinkProvider(linkProvider: ILinkProvider): IDisposable;
@@ -166,17 +264,23 @@ export interface IMouseZone {
}
interface ILinkProvider {
provideLink(position: IBufferCellPosition, callback: (link: ILink | undefined) => void): void;
provideLinks(y: number, callback: (links: ILink[] | undefined) => void): void;
}
interface ILink {
range: IBufferRange;
text: string;
decorations?: ILinkDecorations;
activate(event: MouseEvent, text: string): void;
hover?(event: MouseEvent, text: string): void;
leave?(event: MouseEvent, text: string): void;
}
interface ILinkDecorations {
pointerCursor: boolean;
underline: boolean;
}
interface IBufferRange {
start: IBufferCellPosition;
end: IBufferCellPosition;
-5
View File
@@ -105,11 +105,6 @@ function moveToRequestedCol(startX: number, startY: number, targetX: number, tar
).length, sequence(direction, applicationCursor));
}
function moveHorizontallyOnly(startX: number, startY: number, targetX: number, targetY: number, bufferService: IBufferService, applicationCursor: boolean): string {
const direction = horizontalDirection(startX, startY, targetX, targetY, bufferService, applicationCursor);
return repeat(Math.abs(startX - targetX), sequence(direction, applicationCursor));
}
/**
* Utility functions
*/
@@ -24,11 +24,11 @@ describe('AddonManager', () => {
it('should call addon constructor', () => {
let called = false;
class Addon implements ITerminalAddon {
activate(terminal: any): void {
public activate(terminal: any): void {
assert.equal(terminal, 'foo', 'The first constructor arg should be Terminal');
called = true;
}
dispose(): void { }
public dispose(): void { }
}
manager.loadAddon('foo' as any, new Addon());
assert.equal(called, true);
@@ -39,12 +39,12 @@ describe('AddonManager', () => {
it('should dispose all loaded addons', () => {
let called = 0;
class Addon implements ITerminalAddon {
activate(): void {}
dispose(): void { called++; }
public activate(): void {}
public dispose(): void { called++; }
}
manager.loadAddon(null, new Addon());
manager.loadAddon(null, new Addon());
manager.loadAddon(null, new Addon());
manager.loadAddon(null!, new Addon());
manager.loadAddon(null!, new Addon());
manager.loadAddon(null!, new Addon());
assert.equal(manager.addons.length, 3);
manager.dispose();
assert.equal(called, 3);
@@ -4,12 +4,12 @@
*/
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 { ITerminal } from 'browser/Types';
import { IBufferLine, ICellData } from 'common/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 * as Strings from '../LocalizableStrings';
import { IEvent, EventEmitter } from 'common/EventEmitter';
import { AddonManager } from './AddonManager';
import { IParams } from 'common/parser/Types';
@@ -17,13 +17,19 @@ import { IParams } from 'common/parser/Types';
export class Terminal implements ITerminalApi {
private _core: ITerminal;
private _addonManager: AddonManager;
private _parser: IParser;
private _parser: IParser | undefined;
constructor(options?: ITerminalOptions) {
this._core = new TerminalCore(options);
this._addonManager = new AddonManager();
}
private _checkProposedApi(): void {
if (!this._core.optionsService.options.allowProposedApi) {
throw new Error('You must set the allowProposedApi option to true to use proposed API');
}
}
public get onCursorMove(): IEvent<void> { return this._core.onCursorMove; }
public get onLineFeed(): IEvent<void> { return this._core.onLineFeed; }
public get onSelectionChange(): IEvent<void> { return this._core.onSelectionChange; }
@@ -37,19 +43,27 @@ export class Terminal implements ITerminalApi {
public get element(): HTMLElement | undefined { return this._core.element; }
public get parser(): IParser {
this._checkProposedApi();
if (!this._parser) {
this._parser = new ParserApi(this._core);
}
return this._parser;
}
public get unicode(): IUnicodeHandling {
this._checkProposedApi();
return new UnicodeApi(this._core);
}
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(): IBufferNamespaceApi { return new BufferNamespaceApi(this._core.buffers); }
public get markers(): ReadonlyArray<IMarker> { return this._core.markers; }
public get buffer(): IBufferNamespaceApi {
this._checkProposedApi();
return new BufferNamespaceApi(this._core.buffers);
}
public get markers(): ReadonlyArray<IMarker> {
this._checkProposedApi();
return this._core.markers;
}
public blur(): void {
this._core.blur();
}
@@ -67,25 +81,31 @@ export class Terminal implements ITerminalApi {
this._core.attachCustomKeyEventHandler(customKeyEventHandler);
}
public registerLinkMatcher(regex: RegExp, handler: (event: MouseEvent, uri: string) => void, options?: ILinkMatcherOptions): number {
this._checkProposedApi();
return this._core.registerLinkMatcher(regex, handler, options);
}
public deregisterLinkMatcher(matcherId: number): void {
this._checkProposedApi();
this._core.deregisterLinkMatcher(matcherId);
}
public registerLinkProvider(linkProvider: ILinkProvider): IDisposable {
this._checkProposedApi();
return this._core.registerLinkProvider(linkProvider);
}
public registerCharacterJoiner(handler: (text: string) => [number, number][]): number {
this._checkProposedApi();
return this._core.registerCharacterJoiner(handler);
}
public deregisterCharacterJoiner(joinerId: number): void {
this._checkProposedApi();
this._core.deregisterCharacterJoiner(joinerId);
}
public registerMarker(cursorYOffset: number): IMarker {
public registerMarker(cursorYOffset: number): IMarker | undefined {
this._checkProposedApi();
this._verifyIntegers(cursorYOffset);
return this._core.addMarker(cursorYOffset);
}
public addMarker(cursorYOffset: number): IMarker {
public addMarker(cursorYOffset: number): IMarker | undefined {
return this.registerMarker(cursorYOffset);
}
public hasSelection(): boolean {
+2 -2
View File
@@ -60,7 +60,7 @@ export abstract class BaseRenderLayer implements IRenderLayer {
}
public dispose(): void {
this._container.removeChild(this._canvas);
this._canvas.parentElement?.removeChild(this._canvas);
this._charAtlas?.dispose();
}
@@ -77,7 +77,7 @@ export abstract class BaseRenderLayer implements IRenderLayer {
public onFocus(): void {}
public onCursorMove(): void {}
public onGridChanged(startRow: number, endRow: number): void {}
public onSelectionChanged(start: [number, number], end: [number, number], columnSelectMode: boolean = false): void {}
public onSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean = false): void {}
public setColors(colorSet: IColorSet): void {
this._refreshCharAtlas(colorSet);
+7 -7
View File
@@ -3,7 +3,7 @@
* @license MIT
*/
import { IRenderDimensions, IRequestRefreshRowsEvent } from 'browser/renderer/Types';
import { IRenderDimensions, IRequestRedrawEvent } from 'browser/renderer/Types';
import { BaseRenderLayer } from 'browser/renderer/BaseRenderLayer';
import { ICellData } from 'common/Types';
import { CellData } from 'common/buffer/CellData';
@@ -36,9 +36,9 @@ export class CursorRenderLayer extends BaseRenderLayer {
zIndex: number,
colors: IColorSet,
rendererId: number,
private _onRequestRefreshRowsEvent: IEventEmitter<IRequestRefreshRowsEvent>,
readonly bufferService: IBufferService,
readonly optionsService: IOptionsService,
private _onRequestRedraw: IEventEmitter<IRequestRedrawEvent>,
bufferService: IBufferService,
optionsService: IOptionsService,
private readonly _coreService: ICoreService,
private readonly _coreBrowserService: ICoreBrowserService
) {
@@ -83,14 +83,14 @@ export class CursorRenderLayer extends BaseRenderLayer {
if (this._cursorBlinkStateManager) {
this._cursorBlinkStateManager.pause();
}
this._onRequestRefreshRowsEvent.fire({ start: this._bufferService.buffer.y, end: this._bufferService.buffer.y });
this._onRequestRedraw.fire({ start: this._bufferService.buffer.y, end: this._bufferService.buffer.y });
}
public onFocus(): void {
if (this._cursorBlinkStateManager) {
this._cursorBlinkStateManager.resume();
} else {
this._onRequestRefreshRowsEvent.fire({ start: this._bufferService.buffer.y, end: this._bufferService.buffer.y });
this._onRequestRedraw.fire({ start: this._bufferService.buffer.y, end: this._bufferService.buffer.y });
}
}
@@ -107,7 +107,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: this._bufferService.buffer.y, end: this._bufferService.buffer.y });
this._onRequestRedraw.fire({ start: this._bufferService.buffer.y, end: this._bufferService.buffer.y });
}
public onCursorMove(): void {
+8 -8
View File
@@ -20,15 +20,15 @@ export class LinkRenderLayer extends BaseRenderLayer {
rendererId: number,
linkifier: ILinkifier,
linkifier2: ILinkifier2,
readonly bufferService: IBufferService,
readonly optionsService: IOptionsService
bufferService: IBufferService,
optionsService: IOptionsService
) {
super(container, 'link', zIndex, true, colors, rendererId, bufferService, optionsService);
linkifier.onLinkHover(e => this._onLinkHover(e));
linkifier.onLinkLeave(e => this._onLinkLeave(e));
linkifier.onShowLinkUnderline(e => this._onShowLinkUnderline(e));
linkifier.onHideLinkUnderline(e => this._onHideLinkUnderline(e));
linkifier2.onLinkHover(e => this._onLinkHover(e));
linkifier2.onLinkLeave(e => this._onLinkLeave(e));
linkifier2.onShowLinkUnderline(e => this._onShowLinkUnderline(e));
linkifier2.onHideLinkUnderline(e => this._onHideLinkUnderline(e));
}
public resize(dim: IRenderDimensions): void {
@@ -53,7 +53,7 @@ export class LinkRenderLayer extends BaseRenderLayer {
}
}
private _onLinkHover(e: ILinkifierEvent): void {
private _onShowLinkUnderline(e: ILinkifierEvent): void {
if (e.fg === INVERTED_DEFAULT_COLOR) {
this._ctx.fillStyle = this._colors.background.css;
} else if (e.fg && is256Color(e.fg)) {
@@ -77,7 +77,7 @@ export class LinkRenderLayer extends BaseRenderLayer {
this._state = e;
}
private _onLinkLeave(e: ILinkifierEvent): void {
private _onHideLinkUnderline(e: ILinkifierEvent): void {
this._clearCurrentLink();
}
}
+10 -10
View File
@@ -6,7 +6,7 @@
import { TextRenderLayer } from 'browser/renderer/TextRenderLayer';
import { SelectionRenderLayer } from 'browser/renderer/SelectionRenderLayer';
import { CursorRenderLayer } from 'browser/renderer/CursorRenderLayer';
import { IRenderLayer, IRenderer, IRenderDimensions, CharacterJoinerHandler, ICharacterJoinerRegistry, IRequestRefreshRowsEvent } from 'browser/renderer/Types';
import { IRenderLayer, IRenderer, IRenderDimensions, CharacterJoinerHandler, ICharacterJoinerRegistry, IRequestRedrawEvent } from 'browser/renderer/Types';
import { LinkRenderLayer } from 'browser/renderer/LinkRenderLayer';
import { CharacterJoinerRegistry } from 'browser/renderer/CharacterJoinerRegistry';
import { Disposable } from 'common/Lifecycle';
@@ -27,19 +27,19 @@ export class Renderer extends Disposable implements IRenderer {
public dimensions: IRenderDimensions;
private _onRequestRefreshRows = new EventEmitter<IRequestRefreshRowsEvent>();
public get onRequestRefreshRows(): IEvent<IRequestRefreshRowsEvent> { return this._onRequestRefreshRows.event; }
private _onRequestRedraw = new EventEmitter<IRequestRedrawEvent>();
public get onRequestRedraw(): IEvent<IRequestRedrawEvent> { return this._onRequestRedraw.event; }
constructor(
private _colors: IColorSet,
private readonly _screenElement: HTMLElement,
readonly linkifier: ILinkifier,
readonly linkifier2: ILinkifier2,
linkifier: ILinkifier,
linkifier2: ILinkifier2,
@IBufferService private readonly _bufferService: IBufferService,
@ICharSizeService private readonly _charSizeService: ICharSizeService,
@IOptionsService private readonly _optionsService: IOptionsService,
@ICoreService readonly coreService: ICoreService,
@ICoreBrowserService readonly coreBrowserService: ICoreBrowserService
@ICoreService coreService: ICoreService,
@ICoreBrowserService coreBrowserService: ICoreBrowserService
) {
super();
const allowTransparency = this._optionsService.options.allowTransparency;
@@ -49,7 +49,7 @@ export class Renderer extends Disposable implements IRenderer {
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, linkifier, linkifier2, this._bufferService, _optionsService),
new CursorRenderLayer(this._screenElement, 3, this._colors, this._id, this._onRequestRefreshRows, this._bufferService, _optionsService, coreService, coreBrowserService)
new CursorRenderLayer(this._screenElement, 3, this._colors, this._id, this._onRequestRedraw, this._bufferService, _optionsService, coreService, coreBrowserService)
];
this.dimensions = {
scaledCharWidth: 0,
@@ -71,8 +71,8 @@ export class Renderer extends Disposable implements IRenderer {
}
public dispose(): void {
super.dispose();
this._renderLayers.forEach(l => l.dispose());
super.dispose();
removeTerminalFromCache(this._id);
}
@@ -119,7 +119,7 @@ export class Renderer extends Disposable implements IRenderer {
this._runOperation(l => l.onFocus());
}
public onSelectionChanged(start: [number, number], end: [number, number], columnSelectMode: boolean = false): void {
public onSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean = false): void {
this._runOperation(l => l.onSelectionChanged(start, end, columnSelectMode));
}
+4 -4
View File
@@ -23,8 +23,8 @@ export class SelectionRenderLayer extends BaseRenderLayer {
zIndex: number,
colors: IColorSet,
rendererId: number,
readonly bufferService: IBufferService,
readonly optionsService: IOptionsService
bufferService: IBufferService,
optionsService: IOptionsService
) {
super(container, 'selection', zIndex, true, colors, rendererId, bufferService, optionsService);
this._clearState();
@@ -52,7 +52,7 @@ export class SelectionRenderLayer extends BaseRenderLayer {
}
}
public onSelectionChanged(start: [number, number], end: [number, number], columnSelectMode: boolean): void {
public onSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void {
// Selection has not changed
if (!this._didStateChange(start, end, columnSelectMode, this._bufferService.buffer.ydisp)) {
return;
@@ -110,7 +110,7 @@ export class SelectionRenderLayer extends BaseRenderLayer {
this._state.ydisp = this._bufferService.buffer.ydisp;
}
private _didStateChange(start: [number, number], end: [number, number], columnSelectMode: boolean, ydisp: number): boolean {
private _didStateChange(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean, ydisp: number): boolean {
return !this._areCoordinatesEqual(start, this._state.start) ||
!this._areCoordinatesEqual(end, this._state.end) ||
columnSelectMode !== this._state.columnSelectMode ||
+2 -2
View File
@@ -36,8 +36,8 @@ export class TextRenderLayer extends BaseRenderLayer {
characterJoinerRegistry: ICharacterJoinerRegistry,
alpha: boolean,
rendererId: number,
readonly bufferService: IBufferService,
readonly optionsService: IOptionsService
bufferService: IBufferService,
optionsService: IOptionsService
) {
super(container, 'text', zIndex, alpha, colors, rendererId, bufferService, optionsService);
this._state = new GridCache<CharData>();
+8 -4
View File
@@ -24,7 +24,7 @@ export interface IRenderDimensions {
actualCellHeight: number;
}
export interface IRequestRefreshRowsEvent {
export interface IRequestRedrawEvent {
start: number;
end: number;
}
@@ -36,7 +36,11 @@ export interface IRequestRefreshRowsEvent {
export interface IRenderer extends IDisposable {
readonly dimensions: IRenderDimensions;
readonly onRequestRefreshRows: IEvent<IRequestRefreshRowsEvent>;
/**
* Fires when the renderer is requesting to be redrawn on the next animation
* frame but is _not_ a result of content changing (eg. selection changes).
*/
readonly onRequestRedraw: IEvent<IRequestRedrawEvent>;
dispose(): void;
setColors(colors: IColorSet): void;
@@ -45,7 +49,7 @@ export interface IRenderer extends IDisposable {
onCharSizeChanged(): void;
onBlur(): void;
onFocus(): void;
onSelectionChanged(start: [number, number], end: [number, number], columnSelectMode: boolean): void;
onSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void;
onCursorMove(): void;
onOptionsChanged(): void;
clear(): void;
@@ -100,7 +104,7 @@ export interface IRenderLayer extends IDisposable {
/**
* Calls when the selection changes.
*/
onSelectionChanged(start: [number, number], end: [number, number], columnSelectMode: boolean): void;
onSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void;
/**
* Registers a handler to join characters to render as a group
+7 -8
View File
@@ -3,7 +3,7 @@
* @license MIT
*/
import { IRenderer, IRenderDimensions, CharacterJoinerHandler, IRequestRefreshRowsEvent } from 'browser/renderer/Types';
import { IRenderer, IRenderDimensions, CharacterJoinerHandler, IRequestRedrawEvent } from 'browser/renderer/Types';
import { BOLD_CLASS, ITALIC_CLASS, CURSOR_CLASS, CURSOR_STYLE_BLOCK_CLASS, CURSOR_BLINK_CLASS, CURSOR_STYLE_BAR_CLASS, CURSOR_STYLE_UNDERLINE_CLASS, DomRendererRowFactory } from 'browser/renderer/dom/DomRendererRowFactory';
import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants';
import { Disposable } from 'common/Lifecycle';
@@ -39,8 +39,7 @@ export class DomRenderer extends Disposable implements IRenderer {
public dimensions: IRenderDimensions;
private _onRequestRefreshRows = new EventEmitter<IRequestRefreshRowsEvent>();
public get onRequestRefreshRows(): IEvent<IRequestRefreshRowsEvent> { return this._onRequestRefreshRows.event; }
public get onRequestRedraw(): IEvent<IRequestRedrawEvent> { return new EventEmitter<IRequestRedrawEvent>().event; }
constructor(
private _colors: IColorSet,
@@ -87,11 +86,11 @@ export class DomRenderer extends Disposable implements IRenderer {
this._screenElement.appendChild(this._rowContainer);
this._screenElement.appendChild(this._selectionContainer);
this._linkifier.onLinkHover(e => this._onLinkHover(e));
this._linkifier.onLinkLeave(e => this._onLinkLeave(e));
this._linkifier.onShowLinkUnderline(e => this._onLinkHover(e));
this._linkifier.onHideLinkUnderline(e => this._onLinkLeave(e));
this._linkifier2.onLinkHover(e => this._onLinkHover(e));
this._linkifier2.onLinkLeave(e => this._onLinkLeave(e));
this._linkifier2.onShowLinkUnderline(e => this._onLinkHover(e));
this._linkifier2.onHideLinkUnderline(e => this._onLinkLeave(e));
}
public dispose(): void {
@@ -274,7 +273,7 @@ export class DomRenderer extends Disposable implements IRenderer {
this._rowContainer.classList.add(FOCUS_CLASS);
}
public onSelectionChanged(start: [number, number], end: [number, number], columnSelectMode: boolean): void {
public onSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void {
// Remove all selections
while (this._selectionContainer.children.length) {
this._selectionContainer.removeChild(this._selectionContainer.children[0]);
+5
View File
@@ -8,3 +8,8 @@ export interface ISelectionRedrawRequestEvent {
end: [number, number] | undefined;
columnSelectMode: boolean;
}
export interface ISelectionRequestScrollLinesEvent {
amount: number;
suppressScrollEvent: boolean;
}
+3 -3
View File
@@ -8,7 +8,7 @@ import { IEvent, EventEmitter } from 'common/EventEmitter';
import { ICharSizeService } from 'browser/services/Services';
export class CharSizeService implements ICharSizeService {
serviceBrand: any;
public serviceBrand: undefined;
public width: number = 0;
public height: number = 0;
@@ -20,8 +20,8 @@ export class CharSizeService implements ICharSizeService {
public get onCharSizeChange(): IEvent<void> { return this._onCharSizeChange.event; }
constructor(
readonly document: Document,
readonly parentElement: HTMLElement,
document: Document,
parentElement: HTMLElement,
@IOptionsService private readonly _optionsService: IOptionsService
) {
this._measureStrategy = new DomMeasureStrategy(document, parentElement, this._optionsService);
+1 -1
View File
@@ -6,7 +6,7 @@
import { ICoreBrowserService } from './Services';
export class CoreBrowserService implements ICoreBrowserService {
serviceBrand: any;
public serviceBrand: undefined;
constructor(
private _textarea: HTMLTextAreaElement
+1 -1
View File
@@ -7,7 +7,7 @@ import { ICharSizeService, IRenderService, IMouseService } from './Services';
import { getCoords, getRawByteCoords } from 'browser/input/Mouse';
export class MouseService implements IMouseService {
serviceBrand: any;
public serviceBrand: undefined;
constructor(
@IRenderService private readonly _renderService: IRenderService,
+47 -12
View File
@@ -10,24 +10,37 @@ import { Disposable } from 'common/Lifecycle';
import { ScreenDprMonitor } from 'browser/ScreenDprMonitor';
import { addDisposableDomListener } from 'browser/Lifecycle';
import { IColorSet } from 'browser/Types';
import { IOptionsService } from 'common/services/Services';
import { IOptionsService, IBufferService } from 'common/services/Services';
import { ICharSizeService, IRenderService } from 'browser/services/Services';
interface ISelectionState {
start: [number, number] | undefined;
end: [number, number] | undefined;
columnSelectMode: boolean;
}
export class RenderService extends Disposable implements IRenderService {
serviceBrand: any;
public serviceBrand: undefined;
private _renderDebouncer: RenderDebouncer;
private _screenDprMonitor: ScreenDprMonitor;
private _isPaused: boolean = false;
private _needsFullRefresh: boolean = false;
private _isNextRenderRedrawOnly: boolean = true;
private _needsSelectionRefresh: boolean = false;
private _canvasWidth: number = 0;
private _canvasHeight: number = 0;
private _selectionState: ISelectionState = {
start: undefined,
end: undefined,
columnSelectMode: false
};
private _onDimensionsChange = new EventEmitter<IRenderDimensions>();
public get onDimensionsChange(): IEvent<IRenderDimensions> { return this._onDimensionsChange.event; }
private _onRender = new EventEmitter<{ start: number, end: number }>();
public get onRender(): IEvent<{ start: number, end: number }> { return this._onRender.event; }
public get onRenderedBufferChange(): IEvent<{ start: number, end: number }> { return this._onRender.event; }
private _onRefreshRequest = new EventEmitter<{ start: number, end: number }>();
public get onRefreshRequest(): IEvent<{ start: number, end: number }> { return this._onRefreshRequest.event; }
@@ -36,9 +49,10 @@ export class RenderService extends Disposable implements IRenderService {
constructor(
private _renderer: IRenderer,
private _rowCount: number,
readonly screenElement: HTMLElement,
@IOptionsService readonly optionsService: IOptionsService,
@ICharSizeService readonly charSizeService: ICharSizeService
screenElement: HTMLElement,
@IOptionsService optionsService: IOptionsService,
@ICharSizeService charSizeService: ICharSizeService,
@IBufferService bufferService: IBufferService
) {
super();
this._renderDebouncer = new RenderDebouncer((start, end) => this._renderRows(start, end));
@@ -48,11 +62,12 @@ export class RenderService extends Disposable implements IRenderService {
this._screenDprMonitor.setListener(() => this.onDevicePixelRatioChange());
this.register(this._screenDprMonitor);
this.register(bufferService.onResize(e => this._fullRefresh()));
this.register(optionsService.onOptionChange(() => this._renderer.onOptionsChanged()));
this.register(charSizeService.onCharSizeChange(() => this.onCharSizeChanged()));
// No need to register this as renderer is explicitly disposed in RenderService.dispose
this._renderer.onRequestRefreshRows(e => this.refreshRows(e.start, e.end));
this._renderer.onRequestRedraw(e => this.refreshRows(e.start, e.end, true));
// dprchange should handle this case, we need this as well for browsers that don't support the
// matchMedia query.
@@ -75,17 +90,31 @@ export class RenderService extends Disposable implements IRenderService {
}
}
public refreshRows(start: number, end: number): void {
public refreshRows(start: number, end: number, isRedrawOnly: boolean = false): void {
if (this._isPaused) {
this._needsFullRefresh = true;
return;
}
if (!isRedrawOnly) {
this._isNextRenderRedrawOnly = false;
}
this._renderDebouncer.refresh(start, end, this._rowCount);
}
private _renderRows(start: number, end: number): void {
this._renderer.renderRows(start, end);
this._onRender.fire({ start, end });
// Update selection if needed
if (this._needsSelectionRefresh) {
this._renderer.onSelectionChanged(this._selectionState.start, this._selectionState.end, this._selectionState.columnSelectMode);
this._needsSelectionRefresh = false;
}
// Fire render event only if it was not a redraw
if (!this._isNextRenderRedrawOnly) {
this._onRender.fire({ start, end });
}
this._isNextRenderRedrawOnly = true;
}
public resize(cols: number, rows: number): void {
@@ -116,8 +145,11 @@ export class RenderService extends Disposable implements IRenderService {
// TODO: RenderService should be the only one to dispose the renderer
this._renderer.dispose();
this._renderer = renderer;
this._renderer.onRequestRefreshRows(e => this.refreshRows(e.start, e.end));
this.refreshRows(0, this._rowCount - 1);
this._renderer.onRequestRedraw(e => this.refreshRows(e.start, e.end, true));
// Force a refresh
this._needsSelectionRefresh = true;
this._fullRefresh();
}
private _fullRefresh(): void {
@@ -156,7 +188,10 @@ export class RenderService extends Disposable implements IRenderService {
this._renderer.onFocus();
}
public onSelectionChanged(start: [number, number], end: [number, number], columnSelectMode: boolean): void {
public onSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void {
this._selectionState.start = start;
this._selectionState.end = end;
this._selectionState.columnSelectMode = columnSelectMode;
this._renderer.onSelectionChanged(start, end, columnSelectMode);
}
@@ -10,16 +10,18 @@ import { IBufferLine } from 'common/Types';
import { MockBufferService, MockOptionsService, MockCoreService } from 'common/TestUtils.test';
import { BufferLine } from 'common/buffer/BufferLine';
import { IBufferService, IOptionsService } from 'common/services/Services';
import { MockCharSizeService, MockMouseService } from 'browser/TestUtils.test';
import { MockMouseService, MockRenderService } from 'browser/TestUtils.test';
import { CellData } from 'common/buffer/CellData';
import { IBuffer } from 'common/buffer/Types';
import { IRenderService } from 'browser/services/Services';
class TestSelectionService extends SelectionService {
constructor(
bufferService: IBufferService,
optionsService: IOptionsService
optionsService: IOptionsService,
renderService: IRenderService
) {
super(() => {}, null!, null!, new MockCharSizeService(10, 10), bufferService, new MockCoreService(), new MockMouseService(), optionsService);
super(null!, null!, bufferService, new MockCoreService(), new MockMouseService(), optionsService, renderService);
}
public get model(): SelectionModel { return this._model; }
@@ -46,7 +48,10 @@ describe('SelectionService', () => {
optionsService = new MockOptionsService();
bufferService = new MockBufferService(20, 20, optionsService);
buffer = bufferService.buffer;
selectionService = new TestSelectionService(bufferService, optionsService);
const renderService = new MockRenderService();
renderService.dimensions.canvasHeight = 10 * 20;
renderService.dimensions.canvasWidth = 10 * 20;
selectionService = new TestSelectionService(bufferService, optionsService, renderService);
});
function stringToRow(text: string): IBufferLine {
+18 -14
View File
@@ -3,17 +3,18 @@
* @license MIT
*/
import { ISelectionRedrawRequestEvent } from 'browser/selection/Types';
import { ISelectionRedrawRequestEvent, ISelectionRequestScrollLinesEvent } from 'browser/selection/Types';
import { IBuffer } from 'common/buffer/Types';
import { IBufferLine, IDisposable } from 'common/Types';
import * as Browser from 'common/Platform';
import { SelectionModel } from 'browser/selection/SelectionModel';
import { CellData } from 'common/buffer/CellData';
import { EventEmitter, IEvent } from 'common/EventEmitter';
import { ICharSizeService, IMouseService, ISelectionService } from 'browser/services/Services';
import { ICharSizeService, IMouseService, ISelectionService, IRenderService } from 'browser/services/Services';
import { IBufferService, IOptionsService, ICoreService } from 'common/services/Services';
import { getCoordsRelativeToElement } from 'browser/input/Mouse';
import { moveToCellSequence } from 'browser/input/MoveToCell';
import { Disposable } from 'common/Lifecycle';
/**
* The number of pixels the mouse needs to be above or below the viewport in
@@ -66,8 +67,8 @@ export const enum SelectionMode {
* not handled by the SelectionService but the onRedrawRequest event is fired
* when the selection is ready to be redrawn (on an animation frame).
*/
export class SelectionService implements ISelectionService {
serviceBrand: any;
export class SelectionService extends Disposable implements ISelectionService {
public serviceBrand: undefined;
protected _model: SelectionModel;
@@ -105,23 +106,26 @@ export class SelectionService implements ISelectionService {
private _mouseDownTimeStamp: number = 0;
private _onLinuxMouseSelection = new EventEmitter<string>();
private _onLinuxMouseSelection = this.register(new EventEmitter<string>());
public get onLinuxMouseSelection(): IEvent<string> { return this._onLinuxMouseSelection.event; }
private _onRedrawRequest = new EventEmitter<ISelectionRedrawRequestEvent>();
public get onRedrawRequest(): IEvent<ISelectionRedrawRequestEvent> { return this._onRedrawRequest.event; }
private _onSelectionChange = new EventEmitter<void>();
private _onRedrawRequest = this.register(new EventEmitter<ISelectionRedrawRequestEvent>());
public get onRequestRedraw(): IEvent<ISelectionRedrawRequestEvent> { return this._onRedrawRequest.event; }
private _onSelectionChange = this.register(new EventEmitter<void>());
public get onSelectionChange(): IEvent<void> { return this._onSelectionChange.event; }
private _onRequestScrollLines = this.register(new EventEmitter<ISelectionRequestScrollLinesEvent>());
public get onRequestScrollLines(): IEvent<ISelectionRequestScrollLinesEvent> { return this._onRequestScrollLines.event; }
constructor(
private readonly _scrollLines: (amount: number, suppressEvent: boolean) => void,
private readonly _element: HTMLElement,
private readonly _screenElement: HTMLElement,
@ICharSizeService private readonly _charSizeService: ICharSizeService,
@IBufferService private readonly _bufferService: IBufferService,
@ICoreService private readonly _coreService: ICoreService,
@IMouseService private readonly _mouseService: IMouseService,
@IOptionsService private readonly _optionsService: IOptionsService
@IOptionsService private readonly _optionsService: IOptionsService,
@IRenderService private readonly _renderService: IRenderService
) {
super();
// Init listeners
this._mouseMoveListener = event => this._onMouseMove(<MouseEvent>event);
this._mouseUpListener = event => this._onMouseUp(<MouseEvent>event);
@@ -131,7 +135,7 @@ export class SelectionService implements ISelectionService {
}
});
this._trimListener = this._bufferService.buffer.lines.onTrim(amount => this._onTrim(amount));
this._bufferService.buffers.onBufferActivate(e => this._onBufferActivate(e));
this.register(this._bufferService.buffers.onBufferActivate(e => this._onBufferActivate(e)));
this.enable();
@@ -374,7 +378,7 @@ export class SelectionService implements ISelectionService {
*/
private _getMouseEventScrollAmount(event: MouseEvent): number {
let offset = getCoordsRelativeToElement(event, this._screenElement)[1];
const terminalHeight = this._bufferService.rows * Math.ceil(this._charSizeService.height * this._optionsService.options.lineHeight);
const terminalHeight = this._renderService.dimensions.canvasHeight;
if (offset >= 0 && offset <= terminalHeight) {
return 0;
}
@@ -633,7 +637,7 @@ export class SelectionService implements ISelectionService {
return;
}
if (this._dragScrollAmount) {
this._scrollLines(this._dragScrollAmount, false);
this._onRequestScrollLines.fire({ amount: this._dragScrollAmount, suppressScrollEvent: false });
// Re-evaluate selection
// If the cursor was above or below the viewport, make sure it's at the
// start or end of the viewport respectively. This should only happen when
+15 -10
View File
@@ -6,13 +6,13 @@
import { IEvent } from 'common/EventEmitter';
import { IRenderDimensions, IRenderer, CharacterJoinerHandler } from 'browser/renderer/Types';
import { IColorSet } from 'browser/Types';
import { ISelectionRedrawRequestEvent } from 'browser/selection/Types';
import { ISelectionRedrawRequestEvent as ISelectionRequestRedrawEvent, ISelectionRequestScrollLinesEvent } from 'browser/selection/Types';
import { createDecorator } from 'common/services/ServiceRegistry';
import { IDisposable } from 'common/Types';
export const ICharSizeService = createDecorator<ICharSizeService>('CharSizeService');
export interface ICharSizeService {
serviceBrand: any;
serviceBrand: undefined;
readonly width: number;
readonly height: number;
@@ -25,14 +25,14 @@ export interface ICharSizeService {
export const ICoreBrowserService = createDecorator<ICoreBrowserService>('CoreBrowserService');
export interface ICoreBrowserService {
serviceBrand: any;
serviceBrand: undefined;
readonly isFocused: boolean;
}
export const IMouseService = createDecorator<IMouseService>('MouseService');
export interface IMouseService {
serviceBrand: any;
serviceBrand: undefined;
getCoords(event: {clientX: number, clientY: number}, element: HTMLElement, colCount: number, rowCount: number, isSelection?: boolean): [number, number] | undefined;
getRawByteCoords(event: MouseEvent, element: HTMLElement, colCount: number, rowCount: number): { x: number, y: number } | undefined;
@@ -40,10 +40,14 @@ export interface IMouseService {
export const IRenderService = createDecorator<IRenderService>('RenderService');
export interface IRenderService extends IDisposable {
serviceBrand: any;
serviceBrand: undefined;
onDimensionsChange: IEvent<IRenderDimensions>;
onRender: IEvent<{ start: number, end: number }>;
/**
* Fires when buffer changes are rendered. This does not fire when only cursor
* or selections are rendered.
*/
onRenderedBufferChange: IEvent<{ start: number, end: number }>;
onRefreshRequest: IEvent<{ start: number, end: number }>;
dimensions: IRenderDimensions;
@@ -59,7 +63,7 @@ export interface IRenderService extends IDisposable {
onCharSizeChanged(): void;
onBlur(): void;
onFocus(): void;
onSelectionChanged(start: [number, number], end: [number, number], columnSelectMode: boolean): void;
onSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void;
onCursorMove(): void;
clear(): void;
registerCharacterJoiner(handler: CharacterJoinerHandler): number;
@@ -68,7 +72,7 @@ export interface IRenderService extends IDisposable {
export const ISelectionService = createDecorator<ISelectionService>('SelectionService');
export interface ISelectionService {
serviceBrand: any;
serviceBrand: undefined;
readonly selectionText: string;
readonly hasSelection: boolean;
@@ -76,7 +80,8 @@ export interface ISelectionService {
readonly selectionEnd: [number, number] | undefined;
readonly onLinuxMouseSelection: IEvent<string>;
readonly onRedrawRequest: IEvent<ISelectionRedrawRequestEvent>;
readonly onRequestRedraw: IEvent<ISelectionRequestRedrawEvent>;
readonly onRequestScrollLines: IEvent<ISelectionRequestScrollLinesEvent>;
readonly onSelectionChange: IEvent<void>;
disable(): void;
@@ -96,7 +101,7 @@ export interface ISelectionService {
export const ISoundService = createDecorator<ISoundService>('SoundService');
export interface ISoundService {
serviceBrand: any;
serviceBrand: undefined;
playBellSound(): void;
}
+2 -2
View File
@@ -7,11 +7,11 @@ import { IOptionsService } from 'common/services/Services';
import { ISoundService } from 'browser/services/Services';
export class SoundService implements ISoundService {
serviceBrand: any;
public serviceBrand: undefined;
private static _audioContext: AudioContext;
static get audioContext(): AudioContext | null {
public static get audioContext(): AudioContext | null {
if (!SoundService._audioContext) {
const audioContextCtor: typeof AudioContext = (<any>window).AudioContext || (<any>window).webkitAudioContext;
if (!audioContextCtor) {
+4 -1
View File
@@ -14,7 +14,10 @@
"common/*": [ "./common/*" ]
}
},
"include": [ "./**/*" ],
"include": [
"./**/*",
"../../typings/xterm.d.ts"
],
"references": [
{ "path": "../common" }
]
+336
View File
@@ -0,0 +1,336 @@
/**
* Copyright (c) 2014-2020 The xterm.js authors. All rights reserved.
* Copyright (c) 2012-2013, Christopher Jeffrey (MIT License)
* @license MIT
*
* Originally forked from (with the author's permission):
* Fabrice Bellard's javascript vt100 for jslinux:
* http://bellard.org/jslinux/
* Copyright (c) 2011 Fabrice Bellard
* The original design remains. The terminal itself
* has been extended to include xterm CSI codes, among
* other features.
*
* Terminal Emulation References:
* http://vt100.net/
* http://invisible-island.net/xterm/ctlseqs/ctlseqs.txt
* http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
* http://invisible-island.net/vttest/
* http://www.inwap.com/pdp10/ansicode.txt
* http://linux.die.net/man/4/console_codes
* http://linux.die.net/man/7/urxvt
*/
import { Disposable } from 'common/Lifecycle';
import { IInstantiationService, IOptionsService, IBufferService, ILogService, ICharsetService, ICoreService, ICoreMouseService, IUnicodeService, IDirtyRowService } from 'common/services/Services';
import { InstantiationService } from 'common/services/InstantiationService';
import { LogService } from 'common/services/LogService';
import { BufferService, MINIMUM_COLS, MINIMUM_ROWS } from 'common/services/BufferService';
import { OptionsService } from 'common/services/OptionsService';
import { ITerminalOptions, IDisposable, IBufferLine, IAttributeData, ICoreTerminal } from 'common/Types';
import { CoreService } from 'common/services/CoreService';
import { EventEmitter, IEvent, forwardEvent } from 'common/EventEmitter';
import { CoreMouseService } from 'common/services/CoreMouseService';
import { DirtyRowService } from 'common/services/DirtyRowService';
import { UnicodeService } from 'common/services/UnicodeService';
import { CharsetService } from 'common/services/CharsetService';
import { updateWindowsModeWrappedState } from 'common/WindowsMode';
import { IFunctionIdentifier, IParams } from 'common/parser/Types';
import { IBufferSet } from 'common/buffer/Types';
import { InputHandler } from 'common/InputHandler';
import { WriteBuffer } from 'common/input/WriteBuffer';
export abstract class CoreTerminal extends Disposable implements ICoreTerminal {
protected readonly _instantiationService: IInstantiationService;
protected readonly _bufferService: IBufferService;
protected readonly _logService: ILogService;
protected readonly _coreService: ICoreService;
protected readonly _charsetService: ICharsetService;
protected readonly _coreMouseService: ICoreMouseService;
protected readonly _dirtyRowService: IDirtyRowService;
public readonly unicodeService: IUnicodeService;
public readonly optionsService: IOptionsService;
protected _inputHandler: InputHandler;
private _writeBuffer: WriteBuffer;
private _windowsMode: IDisposable | undefined;
/** An IBufferline to clone/copy from for new blank lines */
private _cachedBlankLine: IBufferLine | undefined;
private _onBinary = new EventEmitter<string>();
public get onBinary(): IEvent<string> { return this._onBinary.event; }
private _onData = new EventEmitter<string>();
public get onData(): IEvent<string> { return this._onData.event; }
protected _onLineFeed = new EventEmitter<void>();
public get onLineFeed(): IEvent<void> { return this._onLineFeed.event; }
private _onResize = new EventEmitter<{ cols: number, rows: number }>();
public get onResize(): IEvent<{ cols: number, rows: number }> { return this._onResize.event; }
protected _onScroll = new EventEmitter<number>();
public get onScroll(): IEvent<number> { return this._onScroll.event; }
public get cols(): number { return this._bufferService.cols; }
public get rows(): number { return this._bufferService.rows; }
public get buffers(): IBufferSet { return this._bufferService.buffers; }
constructor(
options: ITerminalOptions
) {
super();
// Setup and initialize services
this._instantiationService = new InstantiationService();
this.optionsService = new OptionsService(options);
this._instantiationService.setService(IOptionsService, this.optionsService);
this._bufferService = this.register(this._instantiationService.createInstance(BufferService));
this._instantiationService.setService(IBufferService, this._bufferService);
this._logService = this._instantiationService.createInstance(LogService);
this._instantiationService.setService(ILogService, this._logService);
this._coreService = this.register(this._instantiationService.createInstance(CoreService, () => this.scrollToBottom()));
this._instantiationService.setService(ICoreService, this._coreService);
this._coreMouseService = this._instantiationService.createInstance(CoreMouseService);
this._instantiationService.setService(ICoreMouseService, this._coreMouseService);
this._dirtyRowService = this._instantiationService.createInstance(DirtyRowService);
this._instantiationService.setService(IDirtyRowService, this._dirtyRowService);
this.unicodeService = this._instantiationService.createInstance(UnicodeService);
this._instantiationService.setService(IUnicodeService, this.unicodeService);
this._charsetService = this._instantiationService.createInstance(CharsetService);
this._instantiationService.setService(ICharsetService, this._charsetService);
// Register input handler and handle/forward events
this._inputHandler = new InputHandler(this._bufferService, this._charsetService, this._coreService, this._dirtyRowService, this._logService, this.optionsService, this._coreMouseService, this.unicodeService);
this.register(forwardEvent(this._inputHandler.onLineFeed, this._onLineFeed));
this.register(this._inputHandler);
// Setup listeners
this.register(forwardEvent(this._bufferService.onResize, this._onResize));
this.register(forwardEvent(this._coreService.onData, this._onData));
this.register(forwardEvent(this._coreService.onBinary, this._onBinary));
this.register(this.optionsService.onOptionChange(key => this._updateOptions(key)));
// Setup WriteBuffer
this._writeBuffer = new WriteBuffer(data => this._inputHandler.parse(data));
}
public dispose(): void {
if (this._isDisposed) {
return;
}
super.dispose();
this._windowsMode?.dispose();
this._windowsMode = undefined;
}
public write(data: string | Uint8Array, callback?: () => void): void {
this._writeBuffer.write(data, callback);
}
public writeSync(data: string | Uint8Array): void {
this._writeBuffer.writeSync(data);
}
public resize(x: number, y: number): void {
if (isNaN(x) || isNaN(y)) {
return;
}
x = Math.max(x, MINIMUM_COLS);
y = Math.max(y, MINIMUM_ROWS);
this._bufferService.resize(x, y);
}
/**
* Scroll the terminal down 1 row, creating a blank line.
* @param isWrapped Whether the new line is wrapped from the previous line.
*/
public scroll(eraseAttr: IAttributeData, isWrapped: boolean = false): void {
const buffer = this._bufferService.buffer;
let newLine: IBufferLine | undefined;
newLine = this._cachedBlankLine;
if (!newLine || newLine.length !== this.cols || newLine.getFg(0) !== eraseAttr.fg || newLine.getBg(0) !== eraseAttr.bg) {
newLine = buffer.getBlankLine(eraseAttr, isWrapped);
this._cachedBlankLine = newLine;
}
newLine.isWrapped = isWrapped;
const topRow = buffer.ybase + buffer.scrollTop;
const bottomRow = buffer.ybase + buffer.scrollBottom;
if (buffer.scrollTop === 0) {
// Determine whether the buffer is going to be trimmed after insertion.
const willBufferBeTrimmed = buffer.lines.isFull;
// Insert the line using the fastest method
if (bottomRow === buffer.lines.length - 1) {
if (willBufferBeTrimmed) {
buffer.lines.recycle().copyFrom(newLine);
} else {
buffer.lines.push(newLine.clone());
}
} else {
buffer.lines.splice(bottomRow + 1, 0, newLine.clone());
}
// Only adjust ybase and ydisp when the buffer is not trimmed
if (!willBufferBeTrimmed) {
buffer.ybase++;
// Only scroll the ydisp with ybase if the user has not scrolled up
if (!this._bufferService.isUserScrolling) {
buffer.ydisp++;
}
} else {
// When the buffer is full and the user has scrolled up, keep the text
// stable unless ydisp is right at the top
if (this._bufferService.isUserScrolling) {
buffer.ydisp = Math.max(buffer.ydisp - 1, 0);
}
}
} 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 */;
buffer.lines.shiftElements(topRow + 1, scrollRegionHeight - 1, -1);
buffer.lines.set(bottomRow, newLine.clone());
}
// Move the viewport to the bottom of the buffer unless the user is
// scrolling.
if (!this._bufferService.isUserScrolling) {
buffer.ydisp = buffer.ybase;
}
// Flag rows that need updating
this._dirtyRowService.markRangeDirty(buffer.scrollTop, buffer.scrollBottom);
this._onScroll.fire(buffer.ydisp);
}
/**
* Scroll the display of the terminal
* @param disp The number of lines to scroll down (negative scroll up).
* @param suppressScrollEvent Don't emit the scroll event as scrollLines. This is used
* to avoid unwanted events being handled by the viewport when the event was triggered from the
* viewport originally.
*/
public scrollLines(disp: number, suppressScrollEvent?: boolean): void {
const buffer = this._bufferService.buffer;
if (disp < 0) {
if (buffer.ydisp === 0) {
return;
}
this._bufferService.isUserScrolling = true;
} else if (disp + buffer.ydisp >= buffer.ybase) {
this._bufferService.isUserScrolling = false;
}
const oldYdisp = buffer.ydisp;
buffer.ydisp = Math.max(Math.min(buffer.ydisp + disp, buffer.ybase), 0);
// No change occurred, don't trigger scroll/refresh
if (oldYdisp === buffer.ydisp) {
return;
}
if (!suppressScrollEvent) {
this._onScroll.fire(buffer.ydisp);
}
}
/**
* Scroll the display of the terminal by a number of pages.
* @param pageCount The number of pages to scroll (negative scrolls up).
*/
public scrollPages(pageCount: number): void {
this.scrollLines(pageCount * (this.rows - 1));
}
/**
* Scrolls the display of the terminal to the top.
*/
public scrollToTop(): void {
this.scrollLines(-this._bufferService.buffer.ydisp);
}
/**
* Scrolls the display of the terminal to the bottom.
*/
public scrollToBottom(): void {
this.scrollLines(this._bufferService.buffer.ybase - this._bufferService.buffer.ydisp);
}
public scrollToLine(line: number): void {
const scrollAmount = line - this._bufferService.buffer.ydisp;
if (scrollAmount !== 0) {
this.scrollLines(scrollAmount);
}
}
/** Add handler for ESC escape sequence. See xterm.d.ts for details. */
public addEscHandler(id: IFunctionIdentifier, callback: () => boolean): IDisposable {
return this._inputHandler.addEscHandler(id, callback);
}
/** Add handler for DCS escape sequence. See xterm.d.ts for details. */
public addDcsHandler(id: IFunctionIdentifier, callback: (data: string, param: IParams) => boolean): IDisposable {
return this._inputHandler.addDcsHandler(id, callback);
}
/** Add handler for CSI escape sequence. See xterm.d.ts for details. */
public addCsiHandler(id: IFunctionIdentifier, callback: (params: IParams) => boolean): IDisposable {
return this._inputHandler.addCsiHandler(id, callback);
}
/** Add handler for OSC escape sequence. See xterm.d.ts for details. */
public addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable {
return this._inputHandler.addOscHandler(ident, callback);
}
protected _setup(): void {
if (this.optionsService.options.windowsMode) {
this._enableWindowsMode();
}
}
public reset(): void {
this._inputHandler.reset();
this._bufferService.reset();
this._charsetService.reset();
this._coreService.reset();
this._coreMouseService.reset();
}
protected _updateOptions(key: string): void {
// TODO: These listeners should be owned by individual components
switch (key) {
case 'scrollback':
this.buffers.resize(this.cols, this.rows);
break;
case 'windowsMode':
if (this.optionsService.options.windowsMode) {
this._enableWindowsMode();
} else {
this._windowsMode?.dispose();
this._windowsMode = undefined;
}
break;
}
}
protected _enableWindowsMode(): void {
if (!this._windowsMode) {
const disposables: IDisposable[] = [];
disposables.push(this.onLineFeed(updateWindowsModeWrappedState.bind(null, this._bufferService)));
disposables.push(this.addCsiHandler({ final: 'H' }, () => {
updateWindowsModeWrappedState(this._bufferService);
return false;
}));
this._windowsMode = {
dispose: () => {
disposables.forEach(d => d.dispose());
}
};
}
}
}
+4
View File
@@ -63,3 +63,7 @@ export class EventEmitter<T, U = void> implements IEventEmitter<T, U> {
this._disposed = true;
}
}
export function forwardEvent<T>(from: IEvent<T>, to: IEventEmitter<T>): IDisposable {
return from(e => to.fire(e));
}
File diff suppressed because it is too large Load Diff
+247 -117
View File
@@ -4,7 +4,7 @@
* @license MIT
*/
import { IInputHandler, IInputHandlingTerminal } from './Types';
import { IInputHandler, IAttributeData, IDisposable, IWindowOptions } from 'common/Types';
import { C0, C1 } from 'common/data/EscapeSequences';
import { CHARSETS, DEFAULT_CHARSET } from 'common/data/Charsets';
import { EscapeSequenceParser } from 'common/parser/EscapeSequenceParser';
@@ -14,14 +14,12 @@ import { StringToUtf32, stringFromCodePoint, utf32ToString, Utf8ToUtf32 } from '
import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine';
import { EventEmitter, IEvent } from 'common/EventEmitter';
import { IParsingState, IDcsHandler, IEscapeSequenceParser, IParams, IFunctionIdentifier } from 'common/parser/Types';
import { NULL_CELL_CODE, NULL_CELL_WIDTH, Attributes, FgFlags, BgFlags, Content } from 'common/buffer/Constants';
import { NULL_CELL_CODE, NULL_CELL_WIDTH, Attributes, FgFlags, BgFlags, Content, UnderlineStyle } from 'common/buffer/Constants';
import { CellData } from 'common/buffer/CellData';
import { AttributeData } from 'common/buffer/AttributeData';
import { IAttributeData, IDisposable, IWindowOptions } from 'common/Types';
import { ICoreService, IBufferService, IOptionsService, ILogService, IDirtyRowService, ICoreMouseService, ICharsetService, IUnicodeService, IInstantiationService } from 'common/services/Services';
import { ICoreService, IBufferService, IOptionsService, ILogService, IDirtyRowService, ICoreMouseService, ICharsetService, IUnicodeService } from 'common/services/Services';
import { OscHandler } from 'common/parser/OscParser';
import { DcsHandler } from 'common/parser/DcsParser';
import { IRenderService } from 'browser/services/Services';
/**
* Map collect to glevel. Used in `selectCharset`.
@@ -94,7 +92,10 @@ function paramToWindowOption(n: number, opts: IWindowOptions): boolean {
return false;
}
export enum WindowsOptionsReportType {
GET_WIN_SIZE_PIXELS = 0,
GET_CELL_SIZE_PIXELS = 1
}
/**
* DCS subparser implementations
@@ -137,15 +138,15 @@ class DECRQSS implements IDcsHandler {
private _optionsService: IOptionsService
) { }
hook(params: IParams): void {
public hook(params: IParams): void {
this._data = new Uint32Array(0);
}
put(data: Uint32Array, start: number, end: number): void {
public put(data: Uint32Array, start: number, end: number): void {
this._data = concat(this._data, data.subarray(start, end));
}
unhook(success: boolean): void {
public unhook(success: boolean): void {
if (!success) {
this._data = new Uint32Array(0);
return;
@@ -218,27 +219,39 @@ export class InputHandler extends Disposable implements IInputHandler {
private _workCell: CellData = new CellData();
private _windowTitle = '';
private _iconName = '';
private _windowTitleStack: string[] = [];
private _iconNameStack: string[] = [];
protected _windowTitleStack: string[] = [];
protected _iconNameStack: string[] = [];
private _curAttrData: IAttributeData = DEFAULT_ATTR_DATA.clone();
private _eraseAttrDataInternal: IAttributeData = DEFAULT_ATTR_DATA.clone();
private _onRequestBell = new EventEmitter<void>();
public get onRequestBell(): IEvent<void> { return this._onRequestBell.event; }
private _onRequestRefreshRows = new EventEmitter<number, number>();
public get onRequestRefreshRows(): IEvent<number, number> { return this._onRequestRefreshRows.event; }
private _onRequestReset = new EventEmitter<void>();
public get onRequestReset(): IEvent<void> { return this._onRequestReset.event; }
private _onRequestBell = new EventEmitter<void>();
public get onRequestBell(): IEvent<void> { return this._onRequestBell.event; }
private _onRequestScroll = new EventEmitter<IAttributeData, boolean | void>();
public get onRequestScroll(): IEvent<IAttributeData, boolean | void> { return this._onRequestScroll.event; }
private _onRequestSyncScrollBar = new EventEmitter<void>();
public get onRequestSyncScrollBar(): IEvent<void> { return this._onRequestSyncScrollBar.event; }
private _onRequestWindowsOptionsReport = new EventEmitter<WindowsOptionsReportType>();
public get onRequestWindowsOptionsReport(): IEvent<WindowsOptionsReportType> { return this._onRequestWindowsOptionsReport.event; }
private _onA11yChar = new EventEmitter<string>();
public get onA11yChar(): IEvent<string> { return this._onA11yChar.event; }
private _onA11yTab = new EventEmitter<number>();
public get onA11yTab(): IEvent<number> { return this._onA11yTab.event; }
private _onCursorMove = new EventEmitter<void>();
public get onCursorMove(): IEvent<void> { return this._onCursorMove.event; }
private _onLineFeed = new EventEmitter<void>();
public get onLineFeed(): IEvent<void> { return this._onLineFeed.event; }
private _onScroll = new EventEmitter<number>();
public get onScroll(): IEvent<number> { return this._onScroll.event; }
private _onTitleChange = new EventEmitter<string>();
public get onTitleChange(): IEvent<string> { return this._onTitleChange.event; }
constructor(
private _terminal: IInputHandlingTerminal,
private readonly _bufferService: IBufferService,
private readonly _charsetService: ICharsetService,
private readonly _coreService: ICoreService,
@@ -247,9 +260,8 @@ export class InputHandler extends Disposable implements IInputHandler {
private readonly _optionsService: IOptionsService,
private readonly _coreMouseService: ICoreMouseService,
private readonly _unicodeService: IUnicodeService,
private readonly _instantiationService: IInstantiationService,
private readonly _parser: IEscapeSequenceParser = new EscapeSequenceParser())
{
private readonly _parser: IEscapeSequenceParser = new EscapeSequenceParser()
) {
super();
this.register(this._parser);
@@ -489,15 +501,15 @@ export class InputHandler extends Disposable implements IInputHandler {
const screenReaderMode = this._optionsService.options.screenReaderMode;
const cols = this._bufferService.cols;
const wraparoundMode = this._coreService.decPrivateModes.wraparound;
const insertMode = this._terminal.insertMode;
const insertMode = this._coreService.modes.insertMode;
const curAttr = this._curAttrData;
let bufferRow = buffer.lines.get(buffer.y + buffer.ybase);
let bufferRow = buffer.lines.get(buffer.ybase + buffer.y)!;
this._dirtyRowService.markDirty(buffer.y);
// handle wide chars: reset start_cell-1 if we would overwrite the second cell of a wide char
if (buffer.x && end - start > 0 && bufferRow.getWidth(buffer.x - 1) === 2) {
bufferRow.setCellFromCodePoint(buffer.x - 1, 0, 1, curAttr.fg, curAttr.bg);
bufferRow.setCellFromCodePoint(buffer.x - 1, 0, 1, curAttr.fg, curAttr.bg, curAttr.extended);
}
for (let pos = start; pos < end; ++pos) {
@@ -518,7 +530,7 @@ export class InputHandler extends Disposable implements IInputHandler {
}
if (screenReaderMode) {
this._terminal.onA11yCharEmitter.fire(stringFromCodePoint(code));
this._onA11yChar.fire(stringFromCodePoint(code));
}
// insert combining char at last cursor position
@@ -544,21 +556,25 @@ export class InputHandler extends Disposable implements IInputHandler {
// autowrap - DECAWM
// automatically wraps to the beginning of the next line
if (wraparoundMode) {
// clear left over cells to the right
while (buffer.x < cols) {
bufferRow.setCellFromCodePoint(buffer.x++, 0, 1, curAttr.fg, curAttr.bg, curAttr.extended);
}
buffer.x = 0;
buffer.y++;
if (buffer.y === buffer.scrollBottom + 1) {
buffer.y--;
this._terminal.scroll(this._eraseAttrData(), true);
this._onRequestScroll.fire(this._eraseAttrData(), true);
} else {
if (buffer.y >= this._bufferService.rows) {
buffer.y = this._bufferService.rows - 1;
}
// The line already exists (eg. the initial viewport), mark it as a
// wrapped line
buffer.lines.get(buffer.y).isWrapped = true;
buffer.lines.get(buffer.ybase + buffer.y)!.isWrapped = true;
}
// row changed, get it again
bufferRow = buffer.lines.get(buffer.y + buffer.ybase);
bufferRow = buffer.lines.get(buffer.ybase + buffer.y)!;
} else {
buffer.x = cols - 1;
if (chWidth === 2) {
@@ -577,12 +593,12 @@ export class InputHandler extends Disposable implements IInputHandler {
// a halfwidth char any fullwidth shifted there is lost
// and will be set to empty cell
if (bufferRow.getWidth(cols - 1) === 2) {
bufferRow.setCellFromCodePoint(cols - 1, NULL_CELL_CODE, NULL_CELL_WIDTH, curAttr.fg, curAttr.bg);
bufferRow.setCellFromCodePoint(cols - 1, NULL_CELL_CODE, NULL_CELL_WIDTH, curAttr.fg, curAttr.bg, curAttr.extended);
}
}
// write current char to buffer and advance cursor
bufferRow.setCellFromCodePoint(buffer.x++, code, chWidth, curAttr.fg, curAttr.bg);
bufferRow.setCellFromCodePoint(buffer.x++, code, chWidth, curAttr.fg, curAttr.bg, curAttr.extended);
// fullwidth char - also set next cell to placeholder stub and advance cursor
// for graphemes bigger than fullwidth we can simply loop to zero
@@ -590,7 +606,7 @@ export class InputHandler extends Disposable implements IInputHandler {
if (chWidth > 0) {
while (--chWidth) {
// other than a regular empty cell a cell following a wide char has no width
bufferRow.setCellFromCodePoint(buffer.x++, 0, 0, curAttr.fg, curAttr.bg);
bufferRow.setCellFromCodePoint(buffer.x++, 0, 0, curAttr.fg, curAttr.bg, curAttr.extended);
}
}
}
@@ -611,7 +627,7 @@ export class InputHandler extends Disposable implements IInputHandler {
// handle wide chars: reset cell to the right if it is second cell of a wide char
if (buffer.x < cols && end - start > 0 && bufferRow.getWidth(buffer.x) === 0 && !bufferRow.hasContent(buffer.x)) {
bufferRow.setCellFromCodePoint(buffer.x, 0, 1, curAttr.fg, curAttr.bg);
bufferRow.setCellFromCodePoint(buffer.x, 0, 1, curAttr.fg, curAttr.bg, curAttr.extended);
}
this._dirtyRowService.markDirty(buffer.y);
@@ -687,7 +703,7 @@ export class InputHandler extends Disposable implements IInputHandler {
buffer.y++;
if (buffer.y === buffer.scrollBottom + 1) {
buffer.y--;
this._terminal.scroll(this._eraseAttrData());
this._onRequestScroll.fire(this._eraseAttrData());
} else if (buffer.y >= this._bufferService.rows) {
buffer.y = this._bufferService.rows - 1;
}
@@ -715,12 +731,62 @@ export class InputHandler extends Disposable implements IInputHandler {
* Backspace (Ctrl-H).
*
* @vt: #Y C0 BS "Backspace" "\b, \x08" "Move the cursor one position to the left."
* By default it is not possible to move the cursor past the leftmost position.
* If `reverse wrap-around` (`CSI ? 45 h`) is set, a previous soft line wrap (DECAWM)
* can be undone with BS within the scroll margins. In that case the cursor will wrap back
* to the end of the previous row. Note that it is not possible to peek back into the scrollbuffer
* with the cursor, thus at the home position (top-leftmost cell) this has no effect.
*/
public backspace(): void {
this._restrictCursor();
if (this._bufferService.buffer.x > 0) {
this._bufferService.buffer.x--;
const buffer = this._bufferService.buffer;
// reverse wrap-around is disabled
if (!this._coreService.decPrivateModes.reverseWraparound) {
this._restrictCursor();
if (buffer.x > 0) {
buffer.x--;
}
return;
}
// reverse wrap-around is enabled
// other than for normal operation mode, reverse wrap-around allows the cursor
// to be at x=cols to be able to address the last cell of a row by BS
this._restrictCursor(this._bufferService.cols);
if (buffer.x > 0) {
buffer.x--;
} else {
/**
* reverse wrap-around handling:
* Our implementation deviates from xterm on purpose. Details:
* - only previous soft NLs can be reversed (isWrapped=true)
* - only works within scrollborders (top/bottom, left/right not yet supported)
* - cannot peek into scrollbuffer
* - any cursor movement sequence keeps working as expected
*/
if (buffer.x === 0
&& buffer.y > buffer.scrollTop
&& buffer.y <= buffer.scrollBottom
&& buffer.lines.get(buffer.ybase + buffer.y)?.isWrapped)
{
buffer.lines.get(buffer.ybase + buffer.y)!.isWrapped = false;
buffer.y--;
buffer.x = this._bufferService.cols - 1;
// find last taken cell - last cell can have 3 different states:
// - hasContent(true) + hasWidth(1): narrow char - we are done
// - hasWidth(0): second part of wide char - we are done
// - hasContent(false) + hasWidth(1): empty cell due to early wrapping wide char, go one cell further back
const line = buffer.lines.get(buffer.ybase + buffer.y)!;
if (line.hasWidth(buffer.x) && !line.hasContent(buffer.x)) {
buffer.x--;
// We do this only once, since width=1 + hasContent=false currently happens only once before
// early wrapping of a wide char.
// This needs to be fixed once we support graphemes taking more than 2 cells.
}
}
}
this._restrictCursor();
}
/**
@@ -736,7 +802,7 @@ export class InputHandler extends Disposable implements IInputHandler {
const originalX = this._bufferService.buffer.x;
this._bufferService.buffer.x = this._bufferService.buffer.nextStop();
if (this._optionsService.options.screenReaderMode) {
this._terminal.onA11yTabEmitter.fire(this._bufferService.buffer.x - originalX);
this._onA11yTab.fire(this._bufferService.buffer.x - originalX);
}
}
@@ -765,8 +831,8 @@ export class InputHandler extends Disposable implements IInputHandler {
/**
* Restrict cursor to viewport size / scroll margin (origin mode).
*/
private _restrictCursor(): void {
this._bufferService.buffer.x = Math.min(this._bufferService.cols - 1, Math.max(0, this._bufferService.buffer.x));
private _restrictCursor(maxCol: number = this._bufferService.cols - 1): void {
this._bufferService.buffer.x = Math.min(maxCol, Math.max(0, this._bufferService.buffer.x));
this._bufferService.buffer.y = this._coreService.decPrivateModes.origin
? Math.min(this._bufferService.buffer.scrollBottom, Math.max(this._bufferService.buffer.scrollTop, this._bufferService.buffer.y))
: Math.min(this._bufferService.rows - 1, Math.max(0, this._bufferService.buffer.y));
@@ -1024,7 +1090,7 @@ export class InputHandler extends Disposable implements IInputHandler {
* @param end end - 1 is last erased cell
*/
private _eraseInBufferLine(y: number, start: number, end: number, clearWrap: boolean = false): void {
const line = this._bufferService.buffer.lines.get(this._bufferService.buffer.ybase + y);
const line = this._bufferService.buffer.lines.get(this._bufferService.buffer.ybase + y)!;
line.replaceCells(
start,
end,
@@ -1042,7 +1108,7 @@ export class InputHandler extends Disposable implements IInputHandler {
* @param y row index
*/
private _resetBufferLine(y: number): void {
const line = this._bufferService.buffer.lines.get(this._bufferService.buffer.ybase + y);
const line = this._bufferService.buffer.lines.get(this._bufferService.buffer.ybase + y)!;
line.fill(this._bufferService.buffer.getNullCell(this._eraseAttrData()));
line.isWrapped = false;
}
@@ -1091,7 +1157,7 @@ export class InputHandler extends Disposable implements IInputHandler {
this._eraseInBufferLine(j, 0, this._bufferService.buffer.x + 1, true);
if (this._bufferService.buffer.x + 1 >= this._bufferService.cols) {
// Deleted entire previous line. This next line can no longer be wrapped.
this._bufferService.buffer.lines.get(j + 1).isWrapped = false;
this._bufferService.buffer.lines.get(j + 1)!.isWrapped = false;
}
while (j--) {
this._resetBufferLine(j);
@@ -1178,7 +1244,7 @@ export class InputHandler extends Disposable implements IInputHandler {
return;
}
const row: number = buffer.y + buffer.ybase;
const row: number = buffer.ybase + buffer.y;
const scrollBottomRowsOffset = this._bufferService.rows - 1 - buffer.scrollBottom;
const scrollBottomAbsolute = this._bufferService.rows - 1 + buffer.ybase - scrollBottomRowsOffset + 1;
@@ -1213,7 +1279,7 @@ export class InputHandler extends Disposable implements IInputHandler {
return;
}
const row: number = buffer.y + buffer.ybase;
const row: number = buffer.ybase + buffer.y;
let j: number;
j = this._bufferService.rows - 1 - buffer.scrollBottom;
@@ -1242,7 +1308,7 @@ export class InputHandler extends Disposable implements IInputHandler {
*/
public insertChars(params: IParams): void {
this._restrictCursor();
const line = this._bufferService.buffer.lines.get(this._bufferService.buffer.y + this._bufferService.buffer.ybase);
const line = this._bufferService.buffer.lines.get(this._bufferService.buffer.ybase + this._bufferService.buffer.y);
if (line) {
line.insertCells(
this._bufferService.buffer.x,
@@ -1267,7 +1333,7 @@ export class InputHandler extends Disposable implements IInputHandler {
*/
public deleteChars(params: IParams): void {
this._restrictCursor();
const line = this._bufferService.buffer.lines.get(this._bufferService.buffer.y + this._bufferService.buffer.ybase);
const line = this._bufferService.buffer.lines.get(this._bufferService.buffer.ybase + this._bufferService.buffer.y);
if (line) {
line.deleteCells(
this._bufferService.buffer.x,
@@ -1343,7 +1409,7 @@ export class InputHandler extends Disposable implements IInputHandler {
}
const param = params.params[0] || 1;
for (let y = buffer.scrollTop; y <= buffer.scrollBottom; ++y) {
const line = buffer.lines.get(buffer.ybase + y);
const line = buffer.lines.get(buffer.ybase + y)!;
line.deleteCells(0, param, buffer.getNullCell(this._eraseAttrData()), this._eraseAttrData());
line.isWrapped = false;
}
@@ -1376,7 +1442,7 @@ export class InputHandler extends Disposable implements IInputHandler {
}
const param = params.params[0] || 1;
for (let y = buffer.scrollTop; y <= buffer.scrollBottom; ++y) {
const line = buffer.lines.get(buffer.ybase + y);
const line = buffer.lines.get(buffer.ybase + y)!;
line.insertCells(0, param, buffer.getNullCell(this._eraseAttrData()), this._eraseAttrData());
line.isWrapped = false;
}
@@ -1399,7 +1465,7 @@ export class InputHandler extends Disposable implements IInputHandler {
}
const param = params.params[0] || 1;
for (let y = buffer.scrollTop; y <= buffer.scrollBottom; ++y) {
const line = this._bufferService.buffer.lines.get(buffer.ybase + y);
const line = this._bufferService.buffer.lines.get(buffer.ybase + y)!;
line.insertCells(buffer.x, param, buffer.getNullCell(this._eraseAttrData()), this._eraseAttrData());
line.isWrapped = false;
}
@@ -1422,7 +1488,7 @@ export class InputHandler extends Disposable implements IInputHandler {
}
const param = params.params[0] || 1;
for (let y = buffer.scrollTop; y <= buffer.scrollBottom; ++y) {
const line = buffer.lines.get(buffer.ybase + y);
const line = buffer.lines.get(buffer.ybase + y)!;
line.deleteCells(buffer.x, param, buffer.getNullCell(this._eraseAttrData()), this._eraseAttrData());
line.isWrapped = false;
}
@@ -1439,7 +1505,7 @@ export class InputHandler extends Disposable implements IInputHandler {
*/
public eraseChars(params: IParams): void {
this._restrictCursor();
const line = this._bufferService.buffer.lines.get(this._bufferService.buffer.y + this._bufferService.buffer.ybase);
const line = this._bufferService.buffer.lines.get(this._bufferService.buffer.ybase + this._bufferService.buffer.y);
if (line) {
line.replaceCells(
this._bufferService.buffer.x,
@@ -1520,9 +1586,9 @@ export class InputHandler extends Disposable implements IInputHandler {
if (params.params[0] > 0) {
return;
}
if (this._terminal.is('xterm') || this._terminal.is('rxvt-unicode') || this._terminal.is('screen')) {
if (this._is('xterm') || this._is('rxvt-unicode') || this._is('screen')) {
this._coreService.triggerDataEvent(C0.ESC + '[?1;2c');
} else if (this._terminal.is('linux')) {
} else if (this._is('linux')) {
this._coreService.triggerDataEvent(C0.ESC + '[?6c');
}
}
@@ -1558,19 +1624,27 @@ export class InputHandler extends Disposable implements IInputHandler {
// xterm and urxvt
// seem to spit this
// out around ~370 times (?).
if (this._terminal.is('xterm')) {
if (this._is('xterm')) {
this._coreService.triggerDataEvent(C0.ESC + '[>0;276;0c');
} else if (this._terminal.is('rxvt-unicode')) {
} else if (this._is('rxvt-unicode')) {
this._coreService.triggerDataEvent(C0.ESC + '[>85;95;0c');
} else if (this._terminal.is('linux')) {
} else if (this._is('linux')) {
// not supported by linux console.
// linux console echoes parameters.
this._coreService.triggerDataEvent(params.params[0] + 'c');
} else if (this._terminal.is('screen')) {
} else if (this._is('screen')) {
this._coreService.triggerDataEvent(C0.ESC + '[>83;40003;0c');
}
}
/**
* Evaluate if the current terminal is the given argument.
* @param term The terminal name to evaluate
*/
private _is(term: string): boolean {
return (this._optionsService.options.termName + '').indexOf(term) === 0;
}
/**
* CSI Pm h Set Mode (SM).
* Ps = 2 -> Keyboard Action Mode (AM).
@@ -1587,15 +1661,12 @@ export class InputHandler extends Disposable implements IInputHandler {
* | 4 | Insert Mode (IRM). | #Y |
* | 12 | Send/receive (SRM). Always off. | #N |
* | 20 | Automatic Newline (LNM). Always off. | #N |
*
*
* FIXME: why is LNM commented out?
*/
public setMode(params: IParams): void {
for (let i = 0; i < params.length; i++) {
switch (params.params[i]) {
case 4:
this._terminal.insertMode = true;
this._coreService.modes.insertMode = true;
break;
case 20:
// this._t.convertEol = true;
@@ -1699,6 +1770,7 @@ export class InputHandler extends Disposable implements IInputHandler {
* | 9 | X10 xterm mouse protocol. | #Y |
* | 12 | Start Blinking Cursor. | #Y |
* | 25 | Show Cursor (DECTCEM). | #Y |
* | 45 | Reverse wrap-around. | #Y |
* | 47 | Use Alternate Screen Buffer. | #Y |
* | 66 | Application keypad (DECNKM). | #Y |
* | 1000 | X11 xterm mouse protocol. | #Y |
@@ -1736,7 +1808,7 @@ export class InputHandler extends Disposable implements IInputHandler {
* through `options.windowsOptions`.
*/
if (this._optionsService.options.windowOptions.setWinLines) {
this._terminal.resize(132, this._bufferService.rows);
this._bufferService.resize(132, this._bufferService.rows);
this._onRequestReset.fire();
}
break;
@@ -1750,10 +1822,13 @@ export class InputHandler extends Disposable implements IInputHandler {
case 12:
// this.cursorBlink = true;
break;
case 45:
this._coreService.decPrivateModes.reverseWraparound = true;
break;
case 66:
this._logService.debug('Serial port requested application keypad.');
this._coreService.decPrivateModes.applicationKeypad = true;
this._terminal.viewport?.syncScrollArea();
this._onRequestSyncScrollBar.fire();
break;
case 9: // X10 Mouse
// no release, no motion, no wheel, no modifiers.
@@ -1774,7 +1849,7 @@ export class InputHandler extends Disposable implements IInputHandler {
case 1004: // send focusin/focusout events
// focusin: ^[[I
// focusout: ^[[O
this._terminal.sendFocus = true;
this._coreService.decPrivateModes.sendFocus = true;
break;
case 1005: // utf8 ext mode mouse - removed in #2507
this._logService.debug('DECSET 1005 not supported (see #2507)');
@@ -1797,12 +1872,12 @@ export class InputHandler extends Disposable implements IInputHandler {
case 47: // alt screen buffer
case 1047: // alt screen buffer
this._bufferService.buffers.activateAltBuffer(this._eraseAttrData());
this._coreService.isCursorInitialized = true;
this._onRequestRefreshRows.fire(0, this._bufferService.rows - 1);
this._terminal.viewport?.syncScrollArea();
this._terminal.showCursor();
this._onRequestSyncScrollBar.fire();
break;
case 2004: // bracketed paste mode (https://cirw.in/blog/bracketed-paste)
this._terminal.bracketedPasteMode = true;
this._coreService.decPrivateModes.bracketedPasteMode = true;
break;
}
}
@@ -1833,7 +1908,7 @@ export class InputHandler extends Disposable implements IInputHandler {
for (let i = 0; i < params.length; i++) {
switch (params.params[i]) {
case 4:
this._terminal.insertMode = false;
this._coreService.modes.insertMode = false;
break;
case 20:
// this._t.convertEol = false;
@@ -1933,6 +2008,7 @@ export class InputHandler extends Disposable implements IInputHandler {
* | 9 | Don't send Mouse X & Y on button press. | #Y |
* | 12 | Stop Blinking Cursor. | #Y |
* | 25 | Hide Cursor (DECTCEM). | #Y |
* | 45 | No reverse wrap-around. | #Y |
* | 47 | Use Normal Screen Buffer. | #Y |
* | 66 | Numeric keypad (DECNKM). | #Y |
* | 1000 | Don't send Mouse reports. | #Y |
@@ -1963,7 +2039,7 @@ export class InputHandler extends Disposable implements IInputHandler {
* through `options.windowsOptions`.
*/
if (this._optionsService.options.windowOptions.setWinLines) {
this._terminal.resize(80, this._bufferService.rows);
this._bufferService.resize(80, this._bufferService.rows);
this._onRequestReset.fire();
}
break;
@@ -1977,10 +2053,13 @@ export class InputHandler extends Disposable implements IInputHandler {
case 12:
// this.cursorBlink = false;
break;
case 45:
this._coreService.decPrivateModes.reverseWraparound = false;
break;
case 66:
this._logService.debug('Switching back to normal keypad.');
this._coreService.decPrivateModes.applicationKeypad = false;
this._terminal.viewport?.syncScrollArea();
this._onRequestSyncScrollBar.fire();
break;
case 9: // X10 Mouse
case 1000: // vt200 mouse
@@ -1989,7 +2068,7 @@ export class InputHandler extends Disposable implements IInputHandler {
this._coreMouseService.activeProtocol = 'NONE';
break;
case 1004: // send focusin/focusout events
this._terminal.sendFocus = false;
this._coreService.decPrivateModes.sendFocus = false;
break;
case 1005: // utf8 ext mode mouse - removed in #2507
this._logService.debug('DECRST 1005 not supported (see #2507)');
@@ -2015,17 +2094,32 @@ export class InputHandler extends Disposable implements IInputHandler {
if (params.params[i] === 1049) {
this.restoreCursor();
}
this._coreService.isCursorInitialized = true;
this._onRequestRefreshRows.fire(0, this._bufferService.rows - 1);
this._terminal.viewport?.syncScrollArea();
this._terminal.showCursor();
this._onRequestSyncScrollBar.fire();
break;
case 2004: // bracketed paste mode (https://cirw.in/blog/bracketed-paste)
this._terminal.bracketedPasteMode = false;
this._coreService.decPrivateModes.bracketedPasteMode = false;
break;
}
}
}
/**
* Helper to write color information packed with color mode.
*/
private _updateAttrColor(color: number, mode: number, c1: number, c2: number, c3: number): number {
if (mode === 2) {
color |= Attributes.CM_RGB;
color &= ~Attributes.RGB_MASK;
color |= AttributeData.fromColorRGB([c1, c2, c3]);
} else if (mode === 5) {
color &= ~(Attributes.CM_MASK | Attributes.PCOLOR_MASK);
color |= Attributes.CM_P256 | (c1 & 0xff);
}
return color;
}
/**
* Helper to extract and apply color params/subparams.
* Returns advance for params index.
@@ -2046,7 +2140,7 @@ export class InputHandler extends Disposable implements IInputHandler {
do {
accu[advance + cSpace] = params.params[pos + advance];
if (params.hasSubParams(pos + advance)) {
const subparams = params.getSubParams(pos + advance);
const subparams = params.getSubParams(pos + advance)!;
let i = 0;
do {
if (accu[1] === 5) {
@@ -2075,35 +2169,57 @@ export class InputHandler extends Disposable implements IInputHandler {
}
// apply colors
if (accu[0] === 38) {
if (accu[1] === 2) {
attr.fg |= Attributes.CM_RGB;
attr.fg &= ~Attributes.RGB_MASK;
attr.fg |= AttributeData.fromColorRGB([accu[3], accu[4], accu[5]]);
} else if (accu[1] === 5) {
attr.fg &= ~(Attributes.CM_MASK | Attributes.PCOLOR_MASK);
attr.fg |= Attributes.CM_P256 | (accu[3] & 0xff);
}
} else if (accu[0] === 48) {
if (accu[1] === 2) {
attr.bg |= Attributes.CM_RGB;
attr.bg &= ~Attributes.RGB_MASK;
attr.bg |= AttributeData.fromColorRGB([accu[3], accu[4], accu[5]]);
} else if (accu[1] === 5) {
attr.bg &= ~(Attributes.CM_MASK | Attributes.PCOLOR_MASK);
attr.bg |= Attributes.CM_P256 | (accu[3] & 0xff);
}
switch (accu[0]) {
case 38:
attr.fg = this._updateAttrColor(attr.fg, accu[1], accu[3], accu[4], accu[5]);
break;
case 48:
attr.bg = this._updateAttrColor(attr.bg, accu[1], accu[3], accu[4], accu[5]);
break;
case 58:
attr.extended = attr.extended.clone();
attr.extended.underlineColor = this._updateAttrColor(attr.extended.underlineColor, accu[1], accu[3], accu[4], accu[5]);
}
return advance;
}
/**
* SGR 4 subparams:
* 4:0 - equal to SGR 24 (turn off all underline)
* 4:1 - equal to SGR 4 (single underline)
* 4:2 - equal to SGR 21 (double underline)
* 4:3 - curly underline
* 4:4 - dotted underline
* 4:5 - dashed underline
*/
private _processUnderline(style: number, attr: IAttributeData): void {
// treat extended attrs as immutable, thus always clone from old one
// this is needed since the buffer only holds references to it
attr.extended = attr.extended.clone();
// default to 1 == single underline
if (!~style || style > 5) {
style = 1;
}
attr.extended.underlineStyle = style;
attr.fg |= FgFlags.UNDERLINE;
// 0 deactivates underline
if (style === 0) {
attr.fg &= ~FgFlags.UNDERLINE;
}
// update HAS_EXTENDED in BG
attr.updateExtended();
}
/**
* CSI Pm m Character Attributes (SGR).
*
* @vt: #P[See below for supported attributes.] CSI SGR "Select Graphic Rendition" "CSI Pm m" "Set/Reset various text attributes."
* SGR selects one or more character attributes at the same time. Multiple params (up to 32)
* are applied from in order from left to right. The changed attributes are applied to all new
* are applied in order from left to right. The changed attributes are applied to all new
* characters received. If you move characters in the viewport by scrolling or any other means,
* then the attributes move with the characters.
*
@@ -2115,13 +2231,13 @@ export class InputHandler extends Disposable implements IInputHandler {
* | 1 | Bold. (also see `options.drawBoldTextInBrightColors`) | #Y |
* | 2 | Faint, decreased intensity. | #Y |
* | 3 | Italic. | #Y |
* | 4 | Underlined. (no support for newer underline styles) | #Y |
* | 4 | Underlined (see below for style support). | #Y |
* | 5 | Slowly blinking. | #N |
* | 6 | Rapidly blinking. | #N |
* | 7 | Inverse. Flips foreground and background color. | #Y |
* | 8 | Invisible (hidden). | #Y |
* | 9 | Crossed-out characters. | #N |
* | 21 | Doubly underlined. | #N |
* | 21 | Doubly underlined. | #P[Currently outputs a single underline.] |
* | 22 | Normal (neither bold nor faint). | #Y |
* | 23 | No italic. | #Y |
* | 24 | Not underlined. | #Y |
@@ -2152,6 +2268,18 @@ export class InputHandler extends Disposable implements IInputHandler {
* | 90 - 97 | Bright foreground color (analogous to 30 - 37). | #Y |
* | 100 - 107 | Bright background color (analogous to 40 - 47). | #Y |
*
* Underline supports subparams to denote the style in the form `4 : x`:
*
* | x | Meaning | Support |
* | ------ | ------------------------------------------------------------- | ------- |
* | 0 | No underline. Same as `SGR 24 m`. | #Y |
* | 1 | Single underline. Same as `SGR 4 m`. | #Y |
* | 2 | Double underline. | #P[Currently outputs a single underline.] |
* | 3 | Curly underline. | #P[Currently outputs a single underline.] |
* | 4 | Dotted underline. | #P[Currently outputs a single underline.] |
* | 5 | Dashed underline. | #P[Currently outputs a single underline.] |
* | other | Single underline. Same as `SGR 4 m`. | #Y |
*
* Extended colors are supported for foreground (Ps=38) and background (Ps=48) as follows:
*
* | Ps + 1 | Meaning | Support |
@@ -2210,6 +2338,7 @@ export class InputHandler extends Disposable implements IInputHandler {
} else if (p === 4) {
// underlined text
attr.fg |= FgFlags.UNDERLINE;
this._processUnderline(params.hasSubParams(i) ? params.getSubParams(i)![0] : UnderlineStyle.SINGLE, attr);
} else if (p === 5) {
// blink
attr.fg |= FgFlags.BLINK;
@@ -2223,6 +2352,9 @@ export class InputHandler extends Disposable implements IInputHandler {
} else if (p === 2) {
// dimmed text
attr.bg |= BgFlags.DIM;
} else if (p === 21) {
// double underline
this._processUnderline(UnderlineStyle.DOUBLE, attr);
} else if (p === 22) {
// not bold nor faint
attr.fg &= ~FgFlags.BOLD;
@@ -2250,9 +2382,13 @@ export class InputHandler extends Disposable implements IInputHandler {
// reset bg
attr.bg &= ~(Attributes.CM_MASK | Attributes.RGB_MASK);
attr.bg |= DEFAULT_ATTR_DATA.bg & (Attributes.PCOLOR_MASK | Attributes.RGB_MASK);
} else if (p === 38 || p === 48) {
} else if (p === 38 || p === 48 || p === 58) {
// fg color 256 and RGB
i += this._extractColor(params, i, attr);
} else if (p === 59) {
attr.extended = attr.extended.clone();
attr.extended.underlineColor = -1;
attr.updateExtended();
} else if (p === 100) { // FIXME: dead branch, p=100 already handled above!
// reset fg/bg
attr.fg &= ~(Attributes.CM_MASK | Attributes.RGB_MASK);
@@ -2356,8 +2492,7 @@ export class InputHandler extends Disposable implements IInputHandler {
*/
public softReset(params: IParams): void {
this._coreService.isCursorHidden = false;
this._terminal.insertMode = false;
this._terminal.viewport?.syncScrollArea();
this._onRequestSyncScrollBar.fire();
this._bufferService.buffer.scrollTop = 0;
this._bufferService.buffer.scrollBottom = this._bufferService.rows - 1;
this._curAttrData = DEFAULT_ATTR_DATA.clone();
@@ -2471,22 +2606,14 @@ export class InputHandler extends Disposable implements IInputHandler {
return;
}
const second = (params.length > 1) ? params.params[1] : 0;
const rs = this._instantiationService.getService(IRenderService);
switch (params.params[0]) {
case 14: // GetWinSizePixels, returns CSI 4 ; height ; width t
if (rs && second !== 2) {
console.log(rs.dimensions);
const w = rs.dimensions.scaledCanvasWidth.toFixed(0);
const h = rs.dimensions.scaledCanvasHeight.toFixed(0);
this._coreService.triggerDataEvent(`${C0.ESC}[4;${h};${w}t`);
if (second !== 2) {
this._onRequestWindowsOptionsReport.fire(WindowsOptionsReportType.GET_WIN_SIZE_PIXELS);
}
break;
case 16: // GetCellSizePixels, returns CSI 6 ; height ; width t
if (rs) {
const w = rs.dimensions.scaledCellWidth.toFixed(0);
const h = rs.dimensions.scaledCellHeight.toFixed(0);
this._coreService.triggerDataEvent(`${C0.ESC}[6;${h};${w}t`);
}
this._onRequestWindowsOptionsReport.fire(WindowsOptionsReportType.GET_CELL_SIZE_PIXELS);
break;
case 18: // GetWinSizeChars, returns CSI 8 ; height ; width t
if (this._bufferService) {
@@ -2510,12 +2637,12 @@ export class InputHandler extends Disposable implements IInputHandler {
case 23: // PopTitle
if (second === 0 || second === 2) {
if (this._windowTitleStack.length) {
this.setTitle(this._windowTitleStack.pop());
this.setTitle(this._windowTitleStack.pop()!);
}
}
if (second === 0 || second === 1) {
if (this._iconNameStack.length) {
this.setIconName(this._iconNameStack.pop());
this.setIconName(this._iconNameStack.pop()!);
}
}
break;
@@ -2573,7 +2700,7 @@ export class InputHandler extends Disposable implements IInputHandler {
*/
public setTitle(data: string): void {
this._windowTitle = data;
this._terminal.handleTitle(data);
this._onTitleChange.fire(data);
}
/**
@@ -2606,7 +2733,7 @@ export class InputHandler extends Disposable implements IInputHandler {
public keypadApplicationMode(): void {
this._logService.debug('Serial port requested application keypad.');
this._coreService.decPrivateModes.applicationKeypad = true;
this._terminal.viewport?.syncScrollArea();
this._onRequestSyncScrollBar.fire();
}
/**
@@ -2617,7 +2744,7 @@ export class InputHandler extends Disposable implements IInputHandler {
public keypadNumericMode(): void {
this._logService.debug('Switching back to normal keypad.');
this._coreService.decPrivateModes.applicationKeypad = false;
this._terminal.viewport?.syncScrollArea();
this._onRequestSyncScrollBar.fire();
}
/**
@@ -2674,7 +2801,7 @@ export class InputHandler extends Disposable implements IInputHandler {
this._bufferService.buffer.y++;
if (buffer.y === buffer.scrollBottom + 1) {
buffer.y--;
this._terminal.scroll(this._eraseAttrData());
this._onRequestScroll.fire(this._eraseAttrData());
} else if (buffer.y >= this._bufferService.rows) {
buffer.y = this._bufferService.rows - 1;
}
@@ -2712,8 +2839,8 @@ export class InputHandler extends Disposable implements IInputHandler {
// test: echo -ne '\e[1;1H\e[44m\eM\e[0m'
// blankLine(true) is xterm/linux behavior
const scrollRegionHeight = buffer.scrollBottom - buffer.scrollTop;
buffer.lines.shiftElements(buffer.y + buffer.ybase, scrollRegionHeight, 1);
buffer.lines.set(buffer.y + buffer.ybase, buffer.getBlankLine(this._eraseAttrData()));
buffer.lines.shiftElements(buffer.ybase + buffer.y, scrollRegionHeight, 1);
buffer.lines.set(buffer.ybase + buffer.y, buffer.getBlankLine(this._eraseAttrData()));
this._dirtyRowService.markRangeDirty(buffer.scrollTop, buffer.scrollBottom);
} else {
buffer.y--;
@@ -2778,9 +2905,12 @@ export class InputHandler extends Disposable implements IInputHandler {
this._setCursor(0, 0);
for (let yOffset = 0; yOffset < this._bufferService.rows; ++yOffset) {
const row = buffer.y + buffer.ybase + yOffset;
buffer.lines.get(row).fill(cell);
buffer.lines.get(row).isWrapped = false;
const row = buffer.ybase + buffer.y + yOffset;
const line = buffer.lines.get(row);
if (line) {
line.fill(cell);
line.isWrapped = false;
}
}
this._dirtyRowService.markAllDirty();
this._setCursor(0, 0);
+18 -1
View File
@@ -28,9 +28,11 @@ export abstract class Disposable implements IDisposable {
/**
* Registers a disposable object.
* @param d The disposable to register.
* @returns The disposable.
*/
public register<T extends IDisposable>(d: T): void {
public register<T extends IDisposable>(d: T): T {
this._disposables.push(d);
return d;
}
/**
@@ -45,3 +47,18 @@ export abstract class Disposable implements IDisposable {
}
}
}
/**
* Dispose of all disposables in an array and set its length to 0.
*/
export function disposeArray(disposables: IDisposable[]): void {
disposables.forEach(d => d.dispose());
disposables.length = 0;
}
/**
* Creates a disposable that will dispose of an array of disposables when disposed.
*/
export function getDisposeArrayDisposable(array: IDisposable[]): IDisposable {
return { dispose: () => disposeArray(array) };
}
+62 -53
View File
@@ -9,13 +9,15 @@ import { clone } from 'common/Clone';
import { DEFAULT_OPTIONS } from 'common/services/OptionsService';
import { IBufferSet, IBuffer } from 'common/buffer/Types';
import { BufferSet } from 'common/buffer/BufferSet';
import { IDecPrivateModes, ICoreMouseEvent, CoreMouseEventType, ICharset } from 'common/Types';
import { IDecPrivateModes, ICoreMouseEvent, CoreMouseEventType, ICharset, IModes } from 'common/Types';
import { UnicodeV6 } from 'common/input/UnicodeV6';
export class MockBufferService implements IBufferService {
serviceBrand: any;
public serviceBrand: any;
public get buffer(): IBuffer { return this.buffers.active; }
public buffers: IBufferSet = {} as any;
public onResize: IEvent<{ cols: number, rows: number }> = new EventEmitter<{ cols: number, rows: number }>().event;
public isUserScrolling: boolean = false;
constructor(
public cols: number,
public rows: number,
@@ -23,101 +25,108 @@ export class MockBufferService implements IBufferService {
) {
this.buffers = new BufferSet(optionsService, this);
}
resize(cols: number, rows: number): void {
public resize(cols: number, rows: number): void {
this.cols = cols;
this.rows = rows;
}
reset(): void {}
public reset(): void {}
}
export class MockCoreMouseService implements ICoreMouseService {
activeEncoding: string = '';
activeProtocol: string = '';
addEncoding(name: string): void {}
addProtocol(name: string): void {}
reset(): void {}
triggerMouseEvent(event: ICoreMouseEvent): boolean { return false; }
onProtocolChange: IEvent<CoreMouseEventType> = new EventEmitter<CoreMouseEventType>().event;
explainEvents(events: CoreMouseEventType): {[event: string]: boolean} {
public areMouseEventsActive: boolean = false;
public activeEncoding: string = '';
public activeProtocol: string = '';
public addEncoding(name: string): void {}
public addProtocol(name: string): void {}
public reset(): void {}
public triggerMouseEvent(event: ICoreMouseEvent): boolean { return false; }
public onProtocolChange: IEvent<CoreMouseEventType> = new EventEmitter<CoreMouseEventType>().event;
public explainEvents(events: CoreMouseEventType): {[event: string]: boolean} {
throw new Error('Method not implemented.');
}
}
export class MockCharsetService implements ICharsetService {
serviceBrand: any;
charset: ICharset | undefined;
glevel: number = 0;
charsets: ReadonlyArray<ICharset> = [];
reset(): void {}
setgLevel(g: number): void {}
setgCharset(g: number, charset: ICharset): void {}
public serviceBrand: any;
public charset: ICharset | undefined;
public glevel: number = 0;
public reset(): void {}
public setgLevel(g: number): void {}
public setgCharset(g: number, charset: ICharset): void {}
}
export class MockCoreService implements ICoreService {
serviceBrand: any;
isCursorInitialized: boolean = false;
isCursorHidden: boolean = false;
isFocused: boolean = false;
decPrivateModes: IDecPrivateModes = {
public serviceBrand: any;
public isCursorInitialized: boolean = false;
public isCursorHidden: boolean = false;
public isFocused: boolean = false;
public modes: IModes = {
insertMode: false
};
public decPrivateModes: IDecPrivateModes = {
applicationCursorKeys: false,
applicationKeypad: false,
bracketedPasteMode: false,
origin: false,
reverseWraparound: false,
sendFocus: false,
wraparound: true
};
onData: IEvent<string> = new EventEmitter<string>().event;
onUserInput: IEvent<void> = new EventEmitter<void>().event;
onBinary: IEvent<string> = new EventEmitter<string>().event;
reset(): void {}
triggerDataEvent(data: string, wasUserInput?: boolean): void {}
triggerBinaryEvent(data: string): void {}
public onData: IEvent<string> = new EventEmitter<string>().event;
public onUserInput: IEvent<void> = new EventEmitter<void>().event;
public onBinary: IEvent<string> = new EventEmitter<string>().event;
public reset(): void {}
public triggerDataEvent(data: string, wasUserInput?: boolean): void {}
public triggerBinaryEvent(data: string): void {}
}
export class MockDirtyRowService implements IDirtyRowService {
serviceBrand: any;
start: number = 0;
end: number = 0;
clearRange(): void {}
markDirty(y: number): void {}
markRangeDirty(y1: number, y2: number): void {}
markAllDirty(): void {}
public serviceBrand: any;
public start: number = 0;
public end: number = 0;
public clearRange(): void {}
public markDirty(y: number): void {}
public markRangeDirty(y1: number, y2: number): void {}
public markAllDirty(): void {}
}
export class MockLogService implements ILogService {
serviceBrand: any;
debug(message: any, ...optionalParams: any[]): void {}
info(message: any, ...optionalParams: any[]): void {}
warn(message: any, ...optionalParams: any[]): void {}
error(message: any, ...optionalParams: any[]): void {}
public serviceBrand: any;
public debug(message: any, ...optionalParams: any[]): void {}
public info(message: any, ...optionalParams: any[]): void {}
public warn(message: any, ...optionalParams: any[]): void {}
public error(message: any, ...optionalParams: any[]): void {}
}
export class MockOptionsService implements IOptionsService {
serviceBrand: any;
options: ITerminalOptions = clone(DEFAULT_OPTIONS);
onOptionChange: IEvent<string> = new EventEmitter<string>().event;
public serviceBrand: any;
public options: ITerminalOptions = clone(DEFAULT_OPTIONS);
public onOptionChange: IEvent<string> = new EventEmitter<string>().event;
constructor(testOptions?: IPartialTerminalOptions) {
if (testOptions) {
Object.keys(testOptions).forEach(key => this.options[key] = (<any>testOptions)[key]);
}
}
setOption<T>(key: string, value: T): void {
public setOption<T>(key: string, value: T): void {
throw new Error('Method not implemented.');
}
getOption<T>(key: string): T {
public getOption<T>(key: string): T {
throw new Error('Method not implemented.');
}
}
// defaults to V6 always to keep tests passing
export class MockUnicodeService implements IUnicodeService {
public serviceBrand: any;
private _provider = new UnicodeV6();
register(provider: IUnicodeVersionProvider): void {
public register(provider: IUnicodeVersionProvider): void {
throw new Error('Method not implemented.');
}
versions: string[] = [];
activeVersion: string = '';
onChange: IEvent<string> = new EventEmitter<string>().event;
wcwidth = (codepoint: number): number => this._provider.wcwidth(codepoint);
getStringCellWidth(s: string): number {
public versions: string[] = [];
public activeVersion: string = '';
public onChange: IEvent<string> = new EventEmitter<string>().event;
public wcwidth = (codepoint: number): number => this._provider.wcwidth(codepoint);
public getStringCellWidth(s: string): number {
throw new Error('Method not implemented.');
}
}
+127 -2
View File
@@ -3,13 +3,29 @@
* @license MIT
*/
import { ITerminalOptions as IPublicTerminalOptions } from 'xterm';
import { IEvent, IEventEmitter } from 'common/EventEmitter';
import { IDeleteEvent, IInsertEvent } from 'common/CircularList';
import { IParams } from 'common/parser/Types';
import { IOptionsService, IUnicodeService } from 'common/services/Services';
export interface ICoreTerminal {
optionsService: IOptionsService;
unicodeService: IUnicodeService;
}
export interface IDisposable {
dispose(): void;
}
// TODO: The options that are not in the public API should be reviewed
export interface ITerminalOptions extends IPublicTerminalOptions {
[key: string]: any;
cancelEvents?: boolean;
convertEol?: boolean;
termName?: string;
}
export type XtermListener = (...args: any[]) => void;
/**
@@ -41,7 +57,7 @@ export interface ICircularList<T> {
get(index: number): T | undefined;
set(index: number, value: T): void;
push(value: T): void;
recycle(): T | undefined;
recycle(): T;
pop(): T | undefined;
splice(start: number, deleteCount: number, ...items: T[]): void;
trimStart(count: number): void;
@@ -68,10 +84,18 @@ export interface ICharset {
export type CharData = [number, string, number, number];
export type IColorRGB = [number, number, number];
export interface IExtendedAttrs {
underlineStyle: number;
underlineColor: number;
clone(): IExtendedAttrs;
isEmpty(): boolean;
}
/** Attribute data */
export interface IAttributeData {
fg: number;
bg: number;
extended: IExtendedAttrs;
clone(): IAttributeData;
@@ -98,6 +122,16 @@ export interface IAttributeData {
// colors
getFgColor(): number;
getBgColor(): number;
// extended attrs
hasExtendedAttrs(): number;
updateExtended(): void;
getUnderlineColor(): number;
getUnderlineColorMode(): number;
isUnderlineColorRGB(): boolean;
isUnderlineColorPalette(): boolean;
isUnderlineColorDefault(): boolean;
getUnderlineStyle(): number;
}
/** Cell data */
@@ -122,7 +156,7 @@ export interface IBufferLine {
set(index: number, value: CharData): void;
loadCell(index: number, cell: ICellData): ICellData;
setCell(index: number, cell: ICellData): void;
setCellFromCodePoint(index: number, codePoint: number, width: number, fg: number, bg: number): void;
setCellFromCodePoint(index: number, codePoint: number, width: number, fg: number, bg: number, eAttrs: IExtendedAttrs): void;
addCodepointToCell(index: number, codePoint: number): void;
insertCells(pos: number, n: number, ch: ICellData, eraseAttr?: IAttributeData): void;
deleteCells(pos: number, n: number, fill: ICellData, eraseAttr?: IAttributeData): void;
@@ -150,11 +184,17 @@ export interface IMarker extends IDisposable {
readonly isDisposed: boolean;
readonly line: number;
}
export interface IModes {
insertMode: boolean;
}
export interface IDecPrivateModes {
applicationCursorKeys: boolean;
applicationKeypad: boolean;
bracketedPasteMode: boolean;
origin: boolean;
reverseWraparound: boolean;
sendFocus: boolean;
wraparound: boolean; // defaults: xterm - true, vt100 - false
}
@@ -286,3 +326,88 @@ export interface IWindowOptions {
popTitle?: boolean;
setWinLines?: boolean;
}
/**
* Calls the parser and handles actions generated by the parser.
*/
export interface IInputHandler {
onTitleChange: IEvent<string>;
onRequestScroll: IEvent<IAttributeData, boolean | void>;
parse(data: string | Uint8Array): void;
print(data: Uint32Array, start: number, end: number): void;
/** C0 BEL */ bell(): void;
/** C0 LF */ lineFeed(): void;
/** C0 CR */ carriageReturn(): void;
/** C0 BS */ backspace(): void;
/** C0 HT */ tab(): void;
/** C0 SO */ shiftOut(): void;
/** C0 SI */ shiftIn(): void;
/** CSI @ */ insertChars(params: IParams): void;
/** CSI SP @ */ scrollLeft(params: IParams): void;
/** CSI A */ cursorUp(params: IParams): void;
/** CSI SP A */ scrollRight(params: IParams): void;
/** CSI B */ cursorDown(params: IParams): void;
/** CSI C */ cursorForward(params: IParams): void;
/** CSI D */ cursorBackward(params: IParams): void;
/** CSI E */ cursorNextLine(params: IParams): void;
/** CSI F */ cursorPrecedingLine(params: IParams): void;
/** CSI G */ cursorCharAbsolute(params: IParams): void;
/** CSI H */ cursorPosition(params: IParams): void;
/** CSI I */ cursorForwardTab(params: IParams): void;
/** CSI J */ eraseInDisplay(params: IParams): void;
/** CSI K */ eraseInLine(params: IParams): void;
/** CSI L */ insertLines(params: IParams): void;
/** CSI M */ deleteLines(params: IParams): void;
/** CSI P */ deleteChars(params: IParams): void;
/** CSI S */ scrollUp(params: IParams): void;
/** CSI T */ scrollDown(params: IParams, collect?: string): void;
/** CSI X */ eraseChars(params: IParams): void;
/** CSI Z */ cursorBackwardTab(params: IParams): void;
/** CSI ` */ charPosAbsolute(params: IParams): void;
/** CSI a */ hPositionRelative(params: IParams): void;
/** CSI b */ repeatPrecedingCharacter(params: IParams): void;
/** CSI c */ sendDeviceAttributesPrimary(params: IParams): void;
/** CSI > c */ sendDeviceAttributesSecondary(params: IParams): void;
/** CSI d */ linePosAbsolute(params: IParams): void;
/** CSI e */ vPositionRelative(params: IParams): void;
/** CSI f */ hVPosition(params: IParams): void;
/** CSI g */ tabClear(params: IParams): void;
/** CSI h */ setMode(params: IParams, collect?: string): void;
/** CSI l */ resetMode(params: IParams, collect?: string): void;
/** CSI m */ charAttributes(params: IParams): void;
/** CSI n */ deviceStatus(params: IParams, collect?: string): void;
/** CSI p */ softReset(params: IParams, collect?: string): void;
/** CSI q */ setCursorStyle(params: IParams, collect?: string): void;
/** CSI r */ setScrollRegion(params: IParams, collect?: string): void;
/** CSI s */ saveCursor(params: IParams): void;
/** CSI u */ restoreCursor(params: IParams): void;
/** CSI ' } */ insertColumns(params: IParams): void;
/** CSI ' ~ */ deleteColumns(params: IParams): void;
/** OSC 0
OSC 2 */ setTitle(data: string): void;
/** ESC E */ nextLine(): void;
/** ESC = */ keypadApplicationMode(): void;
/** ESC > */ keypadNumericMode(): void;
/** ESC % G
ESC % @ */ selectDefaultCharset(): void;
/** ESC ( C
ESC ) C
ESC * C
ESC + C
ESC - C
ESC . C
ESC / C */ selectCharset(collectAndFlag: string): void;
/** ESC D */ index(): void;
/** ESC H */ tabSet(): void;
/** ESC M */ reverseIndex(): void;
/** ESC c */ fullReset(): void;
/** ESC n
ESC o
ESC |
ESC }
ESC ~ */ setgLevel(level: number): void;
/** ESC # 8 */ screenAlignmentPattern(): void;
}
+84 -6
View File
@@ -3,18 +3,19 @@
* @license MIT
*/
import { IAttributeData, IColorRGB } from 'common/Types';
import { Attributes, FgFlags, BgFlags } from 'common/buffer/Constants';
import { IAttributeData, IColorRGB, IExtendedAttrs } from 'common/Types';
import { Attributes, FgFlags, BgFlags, UnderlineStyle } from 'common/buffer/Constants';
export class AttributeData implements IAttributeData {
static toColorRGB(value: number): IColorRGB {
public static toColorRGB(value: number): IColorRGB {
return [
value >>> Attributes.RED_SHIFT & 255,
value >>> Attributes.GREEN_SHIFT & 255,
value & 255
];
}
static fromColorRGB(value: IColorRGB): number {
public static fromColorRGB(value: IColorRGB): number {
return (value[0] & 255) << Attributes.RED_SHIFT | (value[1] & 255) << Attributes.GREEN_SHIFT | value[2] & 255;
}
@@ -22,12 +23,14 @@ export class AttributeData implements IAttributeData {
const newObj = new AttributeData();
newObj.fg = this.fg;
newObj.bg = this.bg;
newObj.extended = this.extended.clone();
return newObj;
}
// data
public fg: number = 0;
public bg: number = 0;
public fg = 0;
public bg = 0;
public extended = new ExtendedAttrs();
// flags
public isInverse(): number { return this.fg & FgFlags.INVERSE; }
@@ -66,4 +69,79 @@ export class AttributeData implements IAttributeData {
default: return -1; // CM_DEFAULT defaults to -1
}
}
// extended attrs
public hasExtendedAttrs(): number {
return this.bg & BgFlags.HAS_EXTENDED;
}
public updateExtended(): void {
if (this.extended.isEmpty()) {
this.bg &= ~BgFlags.HAS_EXTENDED;
} else {
this.bg |= BgFlags.HAS_EXTENDED;
}
}
public getUnderlineColor(): number {
if ((this.bg & BgFlags.HAS_EXTENDED) && ~this.extended.underlineColor) {
switch (this.extended.underlineColor & Attributes.CM_MASK) {
case Attributes.CM_P16:
case Attributes.CM_P256: return this.extended.underlineColor & Attributes.PCOLOR_MASK;
case Attributes.CM_RGB: return this.extended.underlineColor & Attributes.RGB_MASK;
default: return this.getFgColor();
}
}
return this.getFgColor();
}
public getUnderlineColorMode(): number {
return (this.bg & BgFlags.HAS_EXTENDED) && ~this.extended.underlineColor
? this.extended.underlineColor & Attributes.CM_MASK
: this.getFgColorMode();
}
public isUnderlineColorRGB(): boolean {
return (this.bg & BgFlags.HAS_EXTENDED) && ~this.extended.underlineColor
? (this.extended.underlineColor & Attributes.CM_MASK) === Attributes.CM_RGB
: this.isFgRGB();
}
public isUnderlineColorPalette(): boolean {
return (this.bg & BgFlags.HAS_EXTENDED) && ~this.extended.underlineColor
? (this.extended.underlineColor & Attributes.CM_MASK) === Attributes.CM_P16
|| (this.extended.underlineColor & Attributes.CM_MASK) === Attributes.CM_P256
: this.isFgPalette();
}
public isUnderlineColorDefault(): boolean {
return (this.bg & BgFlags.HAS_EXTENDED) && ~this.extended.underlineColor
? (this.extended.underlineColor & Attributes.CM_MASK) === 0
: this.isFgDefault();
}
public getUnderlineStyle(): UnderlineStyle {
return this.fg & FgFlags.UNDERLINE
? (this.bg & BgFlags.HAS_EXTENDED ? this.extended.underlineStyle : UnderlineStyle.SINGLE)
: UnderlineStyle.NONE;
}
}
/**
* Extended attributes for a cell.
* Holds information about different underline styles and color.
*/
export class ExtendedAttrs implements IExtendedAttrs {
constructor(
// underline style, NONE is empty
public underlineStyle: UnderlineStyle = UnderlineStyle.NONE,
// underline color, -1 is empty (same as FG)
public underlineColor: number = -1
) {}
public clone(): IExtendedAttrs {
return new ExtendedAttrs(this.underlineStyle, this.underlineColor);
}
/**
* Convenient method to indicate whether the object holds no additional information,
* that needs to be persistant in the buffer.
*/
public isEmpty(): boolean {
return this.underlineStyle === UnderlineStyle.NONE;
}
}
+6 -1
View File
@@ -13,6 +13,7 @@ import { reflowLargerApplyNewLayout, reflowLargerCreateNewLayout, reflowLargerGe
import { Marker } from 'common/buffer/Marker';
import { IOptionsService, IBufferService } from 'common/services/Services';
import { DEFAULT_CHARSET } from 'common/data/Charsets';
import { ExtendedAttrs } from 'common/buffer/AttributeData';
export const MAX_BUFFER_SIZE = 4294967295; // 2^32 - 1
@@ -36,7 +37,7 @@ export class Buffer implements IBuffer {
public savedY: number = 0;
public savedX: number = 0;
public savedCurAttrData = DEFAULT_ATTR_DATA.clone();
public savedCharset: ICharset | null = DEFAULT_CHARSET;
public savedCharset: ICharset | undefined = DEFAULT_CHARSET;
public markers: Marker[] = [];
private _nullCell: ICellData = CellData.fromCharData([0, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]);
private _whitespaceCell: ICellData = CellData.fromCharData([0, WHITESPACE_CELL_CHAR, WHITESPACE_CELL_WIDTH, WHITESPACE_CELL_CODE]);
@@ -60,9 +61,11 @@ export class Buffer implements IBuffer {
if (attr) {
this._nullCell.fg = attr.fg;
this._nullCell.bg = attr.bg;
this._nullCell.extended = attr.extended;
} else {
this._nullCell.fg = 0;
this._nullCell.bg = 0;
this._nullCell.extended = new ExtendedAttrs();
}
return this._nullCell;
}
@@ -71,9 +74,11 @@ export class Buffer implements IBuffer {
if (attr) {
this._whitespaceCell.fg = attr.fg;
this._whitespaceCell.bg = attr.bg;
this._whitespaceCell.extended = attr.extended;
} else {
this._whitespaceCell.fg = 0;
this._whitespaceCell.bg = 0;
this._whitespaceCell.extended = new ExtendedAttrs();
}
return this._whitespaceCell;
}
+430 -122
View File
@@ -2,11 +2,13 @@
* Copyright (c) 2018 The xterm.js authors. All rights reserved.
* @license MIT
*/
import * as chai from 'chai';
import { NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE, DEFAULT_ATTR, Content } from 'common/buffer/Constants';
import { NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE, DEFAULT_ATTR, Content, UnderlineStyle, BgFlags, Attributes, FgFlags } from 'common/buffer/Constants';
import { BufferLine } from 'common/buffer//BufferLine';
import { CellData } from 'common/buffer/CellData';
import { CharData, IBufferLine } from '../Types';
import { assert, expect } from 'chai';
import { AttributeData } from 'common/buffer/AttributeData';
class TestBufferLine extends BufferLine {
public get combined(): {[index: number]: string} {
@@ -22,49 +24,147 @@ class TestBufferLine extends BufferLine {
}
}
describe('AttributeData', () => {
describe('extended attributes', () => {
it('hasExtendedAttrs', () => {
const attrs = new AttributeData();
assert.equal(!!attrs.hasExtendedAttrs(), false);
attrs.bg |= BgFlags.HAS_EXTENDED;
assert.equal(!!attrs.hasExtendedAttrs(), true);
});
it('getUnderlineColor - P256', () => {
const attrs = new AttributeData();
// set a P256 color
attrs.extended.underlineColor = Attributes.CM_P256 | 45;
// should use FG color if BgFlags.HAS_EXTENDED is not set
assert.equal(attrs.getUnderlineColor(), -1);
// should use underlineColor if BgFlags.HAS_EXTENDED is set and underlineColor holds a value
attrs.bg |= BgFlags.HAS_EXTENDED;
assert.equal(attrs.getUnderlineColor(), 45);
// should use FG color if underlineColor holds no value
attrs.extended.underlineColor = -1;
attrs.fg |= Attributes.CM_P256 | 123;
assert.equal(attrs.getUnderlineColor(), 123);
});
it('getUnderlineColor - RGB', () => {
const attrs = new AttributeData();
// set a P256 color
attrs.extended.underlineColor = Attributes.CM_RGB | (1 << 16) | (2 << 8) | 3;
// should use FG color if BgFlags.HAS_EXTENDED is not set
assert.equal(attrs.getUnderlineColor(), -1);
// should use underlineColor if BgFlags.HAS_EXTENDED is set and underlineColor holds a value
attrs.bg |= BgFlags.HAS_EXTENDED;
assert.equal(attrs.getUnderlineColor(), (1 << 16) | (2 << 8) | 3);
// should use FG color if underlineColor holds no value
attrs.extended.underlineColor = -1;
attrs.fg |= Attributes.CM_P256 | 123;
assert.equal(attrs.getUnderlineColor(), 123);
});
it('getUnderlineColorMode / isUnderlineColorRGB / isUnderlineColorPalette / isUnderlineColorDefault', () => {
const attrs = new AttributeData();
// should always return color mode of fg
for (const mode of [Attributes.CM_DEFAULT, Attributes.CM_P16, Attributes.CM_P256, Attributes.CM_RGB]) {
attrs.extended.underlineColor = mode;
assert.equal(attrs.getUnderlineColorMode(), attrs.getFgColorMode());
assert.equal(attrs.isUnderlineColorDefault(), true);
}
attrs.fg = Attributes.CM_RGB;
for (const mode of [Attributes.CM_DEFAULT, Attributes.CM_P16, Attributes.CM_P256, Attributes.CM_RGB]) {
attrs.extended.underlineColor = mode;
assert.equal(attrs.getUnderlineColorMode(), attrs.getFgColorMode());
assert.equal(attrs.isUnderlineColorDefault(), false);
assert.equal(attrs.isUnderlineColorRGB(), true);
}
// should return own mode
attrs.bg |= BgFlags.HAS_EXTENDED;
attrs.extended.underlineColor = Attributes.CM_DEFAULT;
assert.equal(attrs.getUnderlineColorMode(), Attributes.CM_DEFAULT);
attrs.extended.underlineColor = Attributes.CM_P16;
assert.equal(attrs.getUnderlineColorMode(), Attributes.CM_P16);
assert.equal(attrs.isUnderlineColorPalette(), true);
attrs.extended.underlineColor = Attributes.CM_P256;
assert.equal(attrs.getUnderlineColorMode(), Attributes.CM_P256);
assert.equal(attrs.isUnderlineColorPalette(), true);
attrs.extended.underlineColor = Attributes.CM_RGB;
assert.equal(attrs.getUnderlineColorMode(), Attributes.CM_RGB);
assert.equal(attrs.isUnderlineColorRGB(), true);
});
it('getUnderlineStyle', () => {
const attrs = new AttributeData();
// defaults to no underline style
assert.equal(attrs.getUnderlineStyle(), UnderlineStyle.NONE);
// should return NONE if UNDERLINE is not set
attrs.extended.underlineStyle = UnderlineStyle.CURLY;
assert.equal(attrs.getUnderlineStyle(), UnderlineStyle.NONE);
// should return SINGLE style if UNDERLINE is set and HAS_EXTENDED is false
attrs.fg |= FgFlags.UNDERLINE;
assert.equal(attrs.getUnderlineStyle(), UnderlineStyle.SINGLE);
// should return correct style if both is set
attrs.bg |= BgFlags.HAS_EXTENDED;
assert.equal(attrs.getUnderlineStyle(), UnderlineStyle.CURLY);
// should return NONE if UNDERLINE is not set, but HAS_EXTENDED is true
attrs.fg &= ~FgFlags.UNDERLINE;
assert.equal(attrs.getUnderlineStyle(), UnderlineStyle.NONE);
});
});
});
describe('CellData', () => {
it('CharData <--> CellData equality', () => {
const cell = new CellData();
// ASCII
cell.setFromCharData([123, 'a', 1, 'a'.charCodeAt(0)]);
chai.assert.deepEqual(cell.getAsCharData(), [123, 'a', 1, 'a'.charCodeAt(0)]);
chai.assert.equal(cell.isCombined(), 0);
assert.deepEqual(cell.getAsCharData(), [123, 'a', 1, 'a'.charCodeAt(0)]);
assert.equal(cell.isCombined(), 0);
// combining
cell.setFromCharData([123, 'e\u0301', 1, '\u0301'.charCodeAt(0)]);
chai.assert.deepEqual(cell.getAsCharData(), [123, 'e\u0301', 1, '\u0301'.charCodeAt(0)]);
chai.assert.equal(cell.isCombined(), Content.IS_COMBINED_MASK);
assert.deepEqual(cell.getAsCharData(), [123, 'e\u0301', 1, '\u0301'.charCodeAt(0)]);
assert.equal(cell.isCombined(), Content.IS_COMBINED_MASK);
// surrogate
cell.setFromCharData([123, '𝄞', 1, 0x1D11E]);
chai.assert.deepEqual(cell.getAsCharData(), [123, '𝄞', 1, 0x1D11E]);
chai.assert.equal(cell.isCombined(), 0);
assert.deepEqual(cell.getAsCharData(), [123, '𝄞', 1, 0x1D11E]);
assert.equal(cell.isCombined(), 0);
// surrogate + combining
cell.setFromCharData([123, '𓂀\u0301', 1, '𓂀\u0301'.charCodeAt(2)]);
chai.assert.deepEqual(cell.getAsCharData(), [123, '𓂀\u0301', 1, '𓂀\u0301'.charCodeAt(2)]);
chai.assert.equal(cell.isCombined(), Content.IS_COMBINED_MASK);
assert.deepEqual(cell.getAsCharData(), [123, '𓂀\u0301', 1, '𓂀\u0301'.charCodeAt(2)]);
assert.equal(cell.isCombined(), Content.IS_COMBINED_MASK);
// wide char
cell.setFromCharData([123, '1', 2, '1'.charCodeAt(0)]);
chai.assert.deepEqual(cell.getAsCharData(), [123, '1', 2, '1'.charCodeAt(0)]);
chai.assert.equal(cell.isCombined(), 0);
assert.deepEqual(cell.getAsCharData(), [123, '1', 2, '1'.charCodeAt(0)]);
assert.equal(cell.isCombined(), 0);
});
});
describe('BufferLine', function(): void {
it('ctor', function(): void {
let line: IBufferLine = new TestBufferLine(0);
chai.expect(line.length).equals(0);
chai.expect(line.isWrapped).equals(false);
expect(line.length).equals(0);
expect(line.isWrapped).equals(false);
line = new TestBufferLine(10);
chai.expect(line.length).equals(10);
chai.expect(line.loadCell(0, new CellData()).getAsCharData()).eql([0, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]);
chai.expect(line.isWrapped).equals(false);
expect(line.length).equals(10);
expect(line.loadCell(0, new CellData()).getAsCharData()).eql([0, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]);
expect(line.isWrapped).equals(false);
line = new TestBufferLine(10, undefined, true);
chai.expect(line.length).equals(10);
chai.expect(line.loadCell(0, new CellData()).getAsCharData()).eql([0, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]);
chai.expect(line.isWrapped).equals(true);
expect(line.length).equals(10);
expect(line.loadCell(0, new CellData()).getAsCharData()).eql([0, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]);
expect(line.isWrapped).equals(true);
line = new TestBufferLine(10, CellData.fromCharData([123, 'a', 456, 'a'.charCodeAt(0)]), true);
chai.expect(line.length).equals(10);
chai.expect(line.loadCell(0, new CellData()).getAsCharData()).eql([123, 'a', 456, 'a'.charCodeAt(0)]);
chai.expect(line.isWrapped).equals(true);
expect(line.length).equals(10);
expect(line.loadCell(0, new CellData()).getAsCharData()).eql([123, 'a', 456, 'a'.charCodeAt(0)]);
expect(line.isWrapped).equals(true);
});
it('insertCells', function(): void {
const line = new TestBufferLine(3);
@@ -72,7 +172,7 @@ describe('BufferLine', function(): void {
line.setCell(1, CellData.fromCharData([2, 'b', 0, 'b'.charCodeAt(0)]));
line.setCell(2, CellData.fromCharData([3, 'c', 0, 'c'.charCodeAt(0)]));
line.insertCells(1, 3, CellData.fromCharData([4, 'd', 0, 'd'.charCodeAt(0)]));
chai.expect(line.toArray()).eql([
expect(line.toArray()).eql([
[1, 'a', 0, 'a'.charCodeAt(0)],
[4, 'd', 0, 'd'.charCodeAt(0)],
[4, 'd', 0, 'd'.charCodeAt(0)]
@@ -86,7 +186,7 @@ describe('BufferLine', function(): void {
line.setCell(3, CellData.fromCharData([4, 'd', 0, 'd'.charCodeAt(0)]));
line.setCell(4, CellData.fromCharData([5, 'e', 0, 'e'.charCodeAt(0)]));
line.deleteCells(1, 2, CellData.fromCharData([6, 'f', 0, 'f'.charCodeAt(0)]));
chai.expect(line.toArray()).eql([
expect(line.toArray()).eql([
[1, 'a', 0, 'a'.charCodeAt(0)],
[4, 'd', 0, 'd'.charCodeAt(0)],
[5, 'e', 0, 'e'.charCodeAt(0)],
@@ -102,7 +202,7 @@ describe('BufferLine', function(): void {
line.setCell(3, CellData.fromCharData([4, 'd', 0, 'd'.charCodeAt(0)]));
line.setCell(4, CellData.fromCharData([5, 'e', 0, 'e'.charCodeAt(0)]));
line.replaceCells(2, 4, CellData.fromCharData([6, 'f', 0, 'f'.charCodeAt(0)]));
chai.expect(line.toArray()).eql([
expect(line.toArray()).eql([
[1, 'a', 0, 'a'.charCodeAt(0)],
[2, 'b', 0, 'b'.charCodeAt(0)],
[6, 'f', 0, 'f'.charCodeAt(0)],
@@ -118,7 +218,7 @@ describe('BufferLine', function(): void {
line.setCell(3, CellData.fromCharData([4, 'd', 0, 'd'.charCodeAt(0)]));
line.setCell(4, CellData.fromCharData([5, 'e', 0, 'e'.charCodeAt(0)]));
line.fill(CellData.fromCharData([123, 'z', 0, 'z'.charCodeAt(0)]));
chai.expect(line.toArray()).eql([
expect(line.toArray()).eql([
[123, 'z', 0, 'z'.charCodeAt(0)],
[123, 'z', 0, 'z'.charCodeAt(0)],
[123, 'z', 0, 'z'.charCodeAt(0)],
@@ -134,9 +234,9 @@ describe('BufferLine', function(): void {
line.setCell(3, CellData.fromCharData([4, 'd', 0, 'd'.charCodeAt(0)]));
line.setCell(4, CellData.fromCharData([5, 'e', 0, 'e'.charCodeAt(0)]));
const line2 = line.clone();
chai.expect(TestBufferLine.prototype.toArray.apply(line2)).eql(line.toArray());
chai.expect(line2.length).equals(line.length);
chai.expect(line2.isWrapped).equals(line.isWrapped);
expect(TestBufferLine.prototype.toArray.apply(line2)).eql(line.toArray());
expect(line2.length).equals(line.length);
expect(line2.isWrapped).equals(line.isWrapped);
});
it('copyFrom', function(): void {
const line = new TestBufferLine(5);
@@ -147,92 +247,92 @@ describe('BufferLine', function(): void {
line.setCell(4, CellData.fromCharData([5, 'e', 0, 'e'.charCodeAt(0)]));
const line2 = new TestBufferLine(5, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]), true);
line2.copyFrom(line);
chai.expect(line2.toArray()).eql(line.toArray());
chai.expect(line2.length).equals(line.length);
chai.expect(line2.isWrapped).equals(line.isWrapped);
expect(line2.toArray()).eql(line.toArray());
expect(line2.length).equals(line.length);
expect(line2.isWrapped).equals(line.isWrapped);
});
it('should support combining chars', function(): void {
// CHAR_DATA_CODE_INDEX resembles current behavior in InputHandler.print
// --> set code to the last charCodeAt value of the string
// Note: needs to be fixed once the string pointer is in place
const line = new TestBufferLine(2, CellData.fromCharData([1, 'e\u0301', 0, '\u0301'.charCodeAt(0)]));
chai.expect(line.toArray()).eql([[1, 'e\u0301', 0, '\u0301'.charCodeAt(0)], [1, 'e\u0301', 0, '\u0301'.charCodeAt(0)]]);
expect(line.toArray()).eql([[1, 'e\u0301', 0, '\u0301'.charCodeAt(0)], [1, 'e\u0301', 0, '\u0301'.charCodeAt(0)]]);
const line2 = new TestBufferLine(5, CellData.fromCharData([1, 'a', 0, '\u0301'.charCodeAt(0)]), true);
line2.copyFrom(line);
chai.expect(line2.toArray()).eql(line.toArray());
expect(line2.toArray()).eql(line.toArray());
const line3 = line.clone();
chai.expect(TestBufferLine.prototype.toArray.apply(line3)).eql(line.toArray());
expect(TestBufferLine.prototype.toArray.apply(line3)).eql(line.toArray());
});
describe('resize', function(): void {
it('enlarge(false)', function(): void {
const line = new TestBufferLine(5, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]), false);
line.resize(10, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]));
chai.expect(line.toArray()).eql((Array(10) as any).fill([1, 'a', 0, 'a'.charCodeAt(0)]));
expect(line.toArray()).eql((Array(10) as any).fill([1, 'a', 0, 'a'.charCodeAt(0)]));
});
it('enlarge(true)', function(): void {
const line = new TestBufferLine(5, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]), false);
line.resize(10, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]));
chai.expect(line.toArray()).eql((Array(10) as any).fill([1, 'a', 0, 'a'.charCodeAt(0)]));
expect(line.toArray()).eql((Array(10) as any).fill([1, 'a', 0, 'a'.charCodeAt(0)]));
});
it('shrink(true) - should apply new size', function(): void {
const line = new TestBufferLine(10, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]), false);
line.resize(5, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]));
chai.expect(line.toArray()).eql((Array(5) as any).fill([1, 'a', 0, 'a'.charCodeAt(0)]));
expect(line.toArray()).eql((Array(5) as any).fill([1, 'a', 0, 'a'.charCodeAt(0)]));
});
it('shrink to 0 length', function(): void {
const line = new TestBufferLine(10, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]), false);
line.resize(0, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]));
chai.expect(line.toArray()).eql((Array(0) as any).fill([1, 'a', 0, 'a'.charCodeAt(0)]));
expect(line.toArray()).eql((Array(0) as any).fill([1, 'a', 0, 'a'.charCodeAt(0)]));
});
it('should remove combining data on replaced cells after shrinking then enlarging', () => {
const line = new TestBufferLine(10, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]), false);
line.set(2, [ 0, '😁', 1, '😁'.charCodeAt(0) ]);
line.set(9, [ 0, '😁', 1, '😁'.charCodeAt(0) ]);
chai.expect(line.translateToString()).eql('aa😁aaaaaa😁');
chai.expect(Object.keys(line.combined).length).eql(2);
expect(line.translateToString()).eql('aa😁aaaaaa😁');
expect(Object.keys(line.combined).length).eql(2);
line.resize(5, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]));
chai.expect(line.translateToString()).eql('aa😁aa');
expect(line.translateToString()).eql('aa😁aa');
line.resize(10, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]));
chai.expect(line.translateToString()).eql('aa😁aaaaaaa');
chai.expect(Object.keys(line.combined).length).eql(1);
expect(line.translateToString()).eql('aa😁aaaaaaa');
expect(Object.keys(line.combined).length).eql(1);
});
});
describe('getTrimLength', function(): void {
it('empty line', function(): void {
const line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]), false);
chai.expect(line.getTrimmedLength()).equal(0);
expect(line.getTrimmedLength()).equal(0);
});
it('ASCII', function(): void {
const line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]), false);
line.setCell(0, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)]));
line.setCell(2, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)]));
chai.expect(line.getTrimmedLength()).equal(3);
expect(line.getTrimmedLength()).equal(3);
});
it('surrogate', function(): void {
const line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]), false);
line.setCell(0, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)]));
line.setCell(2, CellData.fromCharData([1, '𝄞', 1, '𝄞'.charCodeAt(0)]));
chai.expect(line.getTrimmedLength()).equal(3);
expect(line.getTrimmedLength()).equal(3);
});
it('combining', function(): void {
const line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]), false);
line.setCell(0, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)]));
line.setCell(2, CellData.fromCharData([1, 'e\u0301', 1, '\u0301'.charCodeAt(0)]));
chai.expect(line.getTrimmedLength()).equal(3);
expect(line.getTrimmedLength()).equal(3);
});
it('fullwidth', function(): void {
const line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]), false);
line.setCell(0, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)]));
line.setCell(2, CellData.fromCharData([1, '1', 2, '1'.charCodeAt(0)]));
line.setCell(3, CellData.fromCharData([0, '', 0, 0]));
chai.expect(line.getTrimmedLength()).equal(4); // also counts null cell after fullwidth
expect(line.getTrimmedLength()).equal(4); // also counts null cell after fullwidth
});
});
describe('translateToString with and w\'o trimming', function(): void {
it('empty line', function(): void {
const line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]), false);
chai.expect(line.translateToString(false)).equal(' ');
chai.expect(line.translateToString(true)).equal('');
expect(line.translateToString(false)).equal(' ');
expect(line.translateToString(true)).equal('');
});
it('ASCII', function(): void {
const line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]), false);
@@ -240,14 +340,14 @@ describe('BufferLine', function(): void {
line.setCell(2, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)]));
line.setCell(4, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)]));
line.setCell(5, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)]));
chai.expect(line.translateToString(false)).equal('a a aa ');
chai.expect(line.translateToString(true)).equal('a a aa');
chai.expect(line.translateToString(false, 0, 5)).equal('a a a');
chai.expect(line.translateToString(false, 0, 4)).equal('a a ');
chai.expect(line.translateToString(false, 0, 3)).equal('a a');
chai.expect(line.translateToString(true, 0, 5)).equal('a a a');
chai.expect(line.translateToString(true, 0, 4)).equal('a a ');
chai.expect(line.translateToString(true, 0, 3)).equal('a a');
expect(line.translateToString(false)).equal('a a aa ');
expect(line.translateToString(true)).equal('a a aa');
expect(line.translateToString(false, 0, 5)).equal('a a a');
expect(line.translateToString(false, 0, 4)).equal('a a ');
expect(line.translateToString(false, 0, 3)).equal('a a');
expect(line.translateToString(true, 0, 5)).equal('a a a');
expect(line.translateToString(true, 0, 4)).equal('a a ');
expect(line.translateToString(true, 0, 3)).equal('a a');
});
it('surrogate', function(): void {
@@ -256,14 +356,14 @@ describe('BufferLine', function(): void {
line.setCell(2, CellData.fromCharData([1, '𝄞', 1, '𝄞'.charCodeAt(0)]));
line.setCell(4, CellData.fromCharData([1, '𝄞', 1, '𝄞'.charCodeAt(0)]));
line.setCell(5, CellData.fromCharData([1, '𝄞', 1, '𝄞'.charCodeAt(0)]));
chai.expect(line.translateToString(false)).equal('a 𝄞 𝄞𝄞 ');
chai.expect(line.translateToString(true)).equal('a 𝄞 𝄞𝄞');
chai.expect(line.translateToString(false, 0, 5)).equal('a 𝄞 𝄞');
chai.expect(line.translateToString(false, 0, 4)).equal('a 𝄞 ');
chai.expect(line.translateToString(false, 0, 3)).equal('a 𝄞');
chai.expect(line.translateToString(true, 0, 5)).equal('a 𝄞 𝄞');
chai.expect(line.translateToString(true, 0, 4)).equal('a 𝄞 ');
chai.expect(line.translateToString(true, 0, 3)).equal('a 𝄞');
expect(line.translateToString(false)).equal('a 𝄞 𝄞𝄞 ');
expect(line.translateToString(true)).equal('a 𝄞 𝄞𝄞');
expect(line.translateToString(false, 0, 5)).equal('a 𝄞 𝄞');
expect(line.translateToString(false, 0, 4)).equal('a 𝄞 ');
expect(line.translateToString(false, 0, 3)).equal('a 𝄞');
expect(line.translateToString(true, 0, 5)).equal('a 𝄞 𝄞');
expect(line.translateToString(true, 0, 4)).equal('a 𝄞 ');
expect(line.translateToString(true, 0, 3)).equal('a 𝄞');
});
it('combining', function(): void {
const line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]), false);
@@ -271,14 +371,14 @@ describe('BufferLine', function(): void {
line.setCell(2, CellData.fromCharData([1, 'e\u0301', 1, '\u0301'.charCodeAt(0)]));
line.setCell(4, CellData.fromCharData([1, 'e\u0301', 1, '\u0301'.charCodeAt(0)]));
line.setCell(5, CellData.fromCharData([1, 'e\u0301', 1, '\u0301'.charCodeAt(0)]));
chai.expect(line.translateToString(false)).equal('a e\u0301 e\u0301e\u0301 ');
chai.expect(line.translateToString(true)).equal('a e\u0301 e\u0301e\u0301');
chai.expect(line.translateToString(false, 0, 5)).equal('a e\u0301 e\u0301');
chai.expect(line.translateToString(false, 0, 4)).equal('a e\u0301 ');
chai.expect(line.translateToString(false, 0, 3)).equal('a e\u0301');
chai.expect(line.translateToString(true, 0, 5)).equal('a e\u0301 e\u0301');
chai.expect(line.translateToString(true, 0, 4)).equal('a e\u0301 ');
chai.expect(line.translateToString(true, 0, 3)).equal('a e\u0301');
expect(line.translateToString(false)).equal('a e\u0301 e\u0301e\u0301 ');
expect(line.translateToString(true)).equal('a e\u0301 e\u0301e\u0301');
expect(line.translateToString(false, 0, 5)).equal('a e\u0301 e\u0301');
expect(line.translateToString(false, 0, 4)).equal('a e\u0301 ');
expect(line.translateToString(false, 0, 3)).equal('a e\u0301');
expect(line.translateToString(true, 0, 5)).equal('a e\u0301 e\u0301');
expect(line.translateToString(true, 0, 4)).equal('a e\u0301 ');
expect(line.translateToString(true, 0, 3)).equal('a e\u0301');
});
it('fullwidth', function(): void {
const line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]), false);
@@ -289,20 +389,20 @@ describe('BufferLine', function(): void {
line.setCell(6, CellData.fromCharData([0, '', 0, 0]));
line.setCell(7, CellData.fromCharData([1, '1', 2, '1'.charCodeAt(0)]));
line.setCell(8, CellData.fromCharData([0, '', 0, 0]));
chai.expect(line.translateToString(false)).equal('a 1 11 ');
chai.expect(line.translateToString(true)).equal('a 1 11');
chai.expect(line.translateToString(false, 0, 7)).equal('a 1 1');
chai.expect(line.translateToString(false, 0, 6)).equal('a 1 1');
chai.expect(line.translateToString(false, 0, 5)).equal('a 1 ');
chai.expect(line.translateToString(false, 0, 4)).equal('a 1');
chai.expect(line.translateToString(false, 0, 3)).equal('a 1');
chai.expect(line.translateToString(false, 0, 2)).equal('a ');
chai.expect(line.translateToString(true, 0, 7)).equal('a 1 1');
chai.expect(line.translateToString(true, 0, 6)).equal('a 1 1');
chai.expect(line.translateToString(true, 0, 5)).equal('a 1 ');
chai.expect(line.translateToString(true, 0, 4)).equal('a 1');
chai.expect(line.translateToString(true, 0, 3)).equal('a 1');
chai.expect(line.translateToString(true, 0, 2)).equal('a ');
expect(line.translateToString(false)).equal('a 1 11 ');
expect(line.translateToString(true)).equal('a 1 11');
expect(line.translateToString(false, 0, 7)).equal('a 1 1');
expect(line.translateToString(false, 0, 6)).equal('a 1 1');
expect(line.translateToString(false, 0, 5)).equal('a 1 ');
expect(line.translateToString(false, 0, 4)).equal('a 1');
expect(line.translateToString(false, 0, 3)).equal('a 1');
expect(line.translateToString(false, 0, 2)).equal('a ');
expect(line.translateToString(true, 0, 7)).equal('a 1 1');
expect(line.translateToString(true, 0, 6)).equal('a 1 1');
expect(line.translateToString(true, 0, 5)).equal('a 1 ');
expect(line.translateToString(true, 0, 4)).equal('a 1');
expect(line.translateToString(true, 0, 3)).equal('a 1');
expect(line.translateToString(true, 0, 2)).equal('a ');
});
it('space at end', function(): void {
const line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]), false);
@@ -311,21 +411,21 @@ describe('BufferLine', function(): void {
line.setCell(4, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)]));
line.setCell(5, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)]));
line.setCell(6, CellData.fromCharData([1, ' ', 1, ' '.charCodeAt(0)]));
chai.expect(line.translateToString(false)).equal('a a aa ');
chai.expect(line.translateToString(true)).equal('a a aa ');
expect(line.translateToString(false)).equal('a a aa ');
expect(line.translateToString(true)).equal('a a aa ');
});
it('should always return some sane value', function(): void {
// sanity check - broken line with invalid out of bound null width cells
// this can atm happen with deleting/inserting chars in inputhandler by "breaking"
// fullwidth pairs --> needs to be fixed after settling BufferLine impl
const line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, 0, NULL_CELL_CODE]), false);
chai.expect(line.translateToString(false)).equal(' ');
chai.expect(line.translateToString(true)).equal('');
expect(line.translateToString(false)).equal(' ');
expect(line.translateToString(true)).equal('');
});
it('should work with endCol=0', () => {
const line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, 0, NULL_CELL_CODE]), false);
line.setCell(0, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)]));
chai.expect(line.translateToString(true, 0, 0)).equal('');
expect(line.translateToString(true, 0, 0)).equal('');
});
});
describe('addCharToCell', () => {
@@ -335,9 +435,9 @@ describe('BufferLine', function(): void {
const cell = line.loadCell(0, new CellData());
// chars contains single combining char
// width is set to 1
chai.assert.deepEqual(cell.getAsCharData(), [DEFAULT_ATTR, '\u0301', 1, 0x0301]);
assert.deepEqual(cell.getAsCharData(), [DEFAULT_ATTR, '\u0301', 1, 0x0301]);
// do not account a single combining char as combined
chai.assert.equal(cell.isCombined(), 0);
assert.equal(cell.isCombined(), 0);
});
it('should add char to combining string in cell', () => {
const line = new TestBufferLine(3, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]), false);
@@ -348,9 +448,9 @@ describe('BufferLine', function(): void {
line.loadCell(0, cell);
// chars contains 3 chars
// width is set to 1
chai.assert.deepEqual(cell.getAsCharData(), [123, 'e\u0301\u0301', 1, 0x0301]);
assert.deepEqual(cell.getAsCharData(), [123, 'e\u0301\u0301', 1, 0x0301]);
// do not account a single combining char as combined
chai.assert.equal(cell.isCombined(), Content.IS_COMBINED_MASK);
assert.equal(cell.isCombined(), Content.IS_COMBINED_MASK);
});
it('should create combining string on taken cell', () => {
const line = new TestBufferLine(3, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]), false);
@@ -361,9 +461,9 @@ describe('BufferLine', function(): void {
line.loadCell(0, cell);
// chars contains 2 chars
// width is set to 1
chai.assert.deepEqual(cell.getAsCharData(), [123, 'e\u0301', 1, 0x0301]);
assert.deepEqual(cell.getAsCharData(), [123, 'e\u0301', 1, 0x0301]);
// do not account a single combining char as combined
chai.assert.equal(cell.isCombined(), Content.IS_COMBINED_MASK);
assert.equal(cell.isCombined(), Content.IS_COMBINED_MASK);
});
});
describe('correct fullwidth handling', () => {
@@ -377,83 +477,291 @@ describe('BufferLine', function(): void {
const line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, 0, NULL_CELL_CODE]), false);
populate(line);
line.insertCells(9, 1, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)]));
chai.assert.equal(line.translateToString(), '¥¥¥¥ a');
assert.equal(line.translateToString(), '¥¥¥¥ a');
line.insertCells(8, 1, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)]));
chai.assert.equal(line.translateToString(), '¥¥¥¥a ');
assert.equal(line.translateToString(), '¥¥¥¥a ');
line.insertCells(1, 1, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)]));
chai.assert.equal(line.translateToString(), ' a ¥¥¥a');
assert.equal(line.translateToString(), ' a ¥¥¥a');
});
it('insert - wide char at end', () => {
const line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, 0, NULL_CELL_CODE]), false);
populate(line);
line.insertCells(0, 3, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)]));
chai.assert.equal(line.translateToString(), 'aaa¥¥¥ ');
assert.equal(line.translateToString(), 'aaa¥¥¥ ');
line.insertCells(4, 1, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)]));
chai.assert.equal(line.translateToString(), 'aaa a ¥¥');
assert.equal(line.translateToString(), 'aaa a ¥¥');
line.insertCells(4, 1, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)]));
chai.assert.equal(line.translateToString(), 'aaa aa ¥ ');
assert.equal(line.translateToString(), 'aaa aa ¥ ');
});
it('delete', () => {
const line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, 0, NULL_CELL_CODE]), false);
populate(line);
line.deleteCells(0, 1, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)]));
chai.assert.equal(line.translateToString(), ' ¥¥¥¥a');
assert.equal(line.translateToString(), ' ¥¥¥¥a');
line.deleteCells(5, 2, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)]));
chai.assert.equal(line.translateToString(), ' ¥¥¥aaa');
assert.equal(line.translateToString(), ' ¥¥¥aaa');
line.deleteCells(0, 2, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)]));
chai.assert.equal(line.translateToString(), ' ¥¥aaaaa');
assert.equal(line.translateToString(), ' ¥¥aaaaa');
});
it('replace - start at 0', () => {
let line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, 0, NULL_CELL_CODE]), false);
populate(line);
line.replaceCells(0, 1, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)]));
chai.assert.equal(line.translateToString(), 'a ¥¥¥¥');
assert.equal(line.translateToString(), 'a ¥¥¥¥');
line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, 0, NULL_CELL_CODE]), false);
populate(line);
line.replaceCells(0, 2, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)]));
chai.assert.equal(line.translateToString(), 'aa¥¥¥¥');
assert.equal(line.translateToString(), 'aa¥¥¥¥');
line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, 0, NULL_CELL_CODE]), false);
populate(line);
line.replaceCells(0, 3, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)]));
chai.assert.equal(line.translateToString(), 'aaa ¥¥¥');
assert.equal(line.translateToString(), 'aaa ¥¥¥');
line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, 0, NULL_CELL_CODE]), false);
populate(line);
line.replaceCells(0, 8, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)]));
chai.assert.equal(line.translateToString(), 'aaaaaaaa¥');
assert.equal(line.translateToString(), 'aaaaaaaa¥');
line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, 0, NULL_CELL_CODE]), false);
populate(line);
line.replaceCells(0, 9, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)]));
chai.assert.equal(line.translateToString(), 'aaaaaaaaa ');
assert.equal(line.translateToString(), 'aaaaaaaaa ');
line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, 0, NULL_CELL_CODE]), false);
populate(line);
line.replaceCells(0, 10, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)]));
chai.assert.equal(line.translateToString(), 'aaaaaaaaaa');
assert.equal(line.translateToString(), 'aaaaaaaaaa');
});
it('replace - start at 1', () => {
let line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, 0, NULL_CELL_CODE]), false);
populate(line);
line.replaceCells(1, 2, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)]));
chai.assert.equal(line.translateToString(), ' a¥¥¥¥');
assert.equal(line.translateToString(), ' a¥¥¥¥');
line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, 0, NULL_CELL_CODE]), false);
populate(line);
line.replaceCells(1, 3, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)]));
chai.assert.equal(line.translateToString(), ' aa ¥¥¥');
assert.equal(line.translateToString(), ' aa ¥¥¥');
line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, 0, NULL_CELL_CODE]), false);
populate(line);
line.replaceCells(1, 4, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)]));
chai.assert.equal(line.translateToString(), ' aaa¥¥¥');
assert.equal(line.translateToString(), ' aaa¥¥¥');
line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, 0, NULL_CELL_CODE]), false);
populate(line);
line.replaceCells(1, 8, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)]));
chai.assert.equal(line.translateToString(), ' aaaaaaa¥');
assert.equal(line.translateToString(), ' aaaaaaa¥');
line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, 0, NULL_CELL_CODE]), false);
populate(line);
line.replaceCells(1, 9, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)]));
chai.assert.equal(line.translateToString(), ' aaaaaaaa ');
assert.equal(line.translateToString(), ' aaaaaaaa ');
line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, 0, NULL_CELL_CODE]), false);
populate(line);
line.replaceCells(1, 10, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)]));
chai.assert.equal(line.translateToString(), ' aaaaaaaaa');
assert.equal(line.translateToString(), ' aaaaaaaaa');
});
});
describe('extended attributes', () => {
it('setCells', function(): void {
const line = new TestBufferLine(5);
const cell = CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]);
// no eAttrs
line.setCell(0, cell);
// some underline style
cell.extended.underlineStyle = UnderlineStyle.CURLY;
cell.bg |= BgFlags.HAS_EXTENDED;
line.setCell(1, cell);
// same eAttr, different codepoint
cell.content = 65; // 'A'
line.setCell(2, cell);
// different eAttr
cell.extended = cell.extended.clone();
cell.extended.underlineStyle = UnderlineStyle.DOTTED;
line.setCell(3, cell);
// no eAttrs again
cell.bg &= ~BgFlags.HAS_EXTENDED;
line.setCell(4, cell);
assert.deepEqual(line.toArray(), [
[1, 'a', 0, 'a'.charCodeAt(0)],
[1, 'a', 0, 'a'.charCodeAt(0)],
[1, 'A', 0, 'A'.charCodeAt(0)],
[1, 'A', 0, 'A'.charCodeAt(0)],
[1, 'A', 0, 'A'.charCodeAt(0)]
]);
assert.equal((line as any)._extendedAttrs[0], undefined);
assert.equal((line as any)._extendedAttrs[1].underlineStyle, UnderlineStyle.CURLY);
assert.equal((line as any)._extendedAttrs[2].underlineStyle, UnderlineStyle.CURLY);
assert.equal((line as any)._extendedAttrs[3].underlineStyle, UnderlineStyle.DOTTED);
assert.equal((line as any)._extendedAttrs[4], undefined);
// should be ref to the same object
assert.equal((line as any)._extendedAttrs[1], (line as any)._extendedAttrs[2]);
// should be a different obj
assert.notEqual((line as any)._extendedAttrs[1], (line as any)._extendedAttrs[3]);
});
it('loadCell', () => {
const line = new TestBufferLine(5);
const cell = CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]);
// no eAttrs
line.setCell(0, cell);
// some underline style
cell.extended.underlineStyle = UnderlineStyle.CURLY;
cell.bg |= BgFlags.HAS_EXTENDED;
line.setCell(1, cell);
// same eAttr, different codepoint
cell.content = 65; // 'A'
line.setCell(2, cell);
// different eAttr
cell.extended = cell.extended.clone();
cell.extended.underlineStyle = UnderlineStyle.DOTTED;
line.setCell(3, cell);
// no eAttrs again
cell.bg &= ~BgFlags.HAS_EXTENDED;
line.setCell(4, cell);
const cell0 = new CellData();
line.loadCell(0, cell0);
const cell1 = new CellData();
line.loadCell(1, cell1);
const cell2 = new CellData();
line.loadCell(2, cell2);
const cell3 = new CellData();
line.loadCell(3, cell3);
const cell4 = new CellData();
line.loadCell(4, cell4);
assert.equal(cell0.extended.underlineStyle, UnderlineStyle.NONE);
assert.equal(cell1.extended.underlineStyle, UnderlineStyle.CURLY);
assert.equal(cell2.extended.underlineStyle, UnderlineStyle.CURLY);
assert.equal(cell3.extended.underlineStyle, UnderlineStyle.DOTTED);
assert.equal(cell4.extended.underlineStyle, UnderlineStyle.NONE);
assert.equal(cell1.extended, cell2.extended);
assert.notEqual(cell2.extended, cell3.extended);
});
it('fill', () => {
const line = new TestBufferLine(3);
const cell = CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]);
cell.extended.underlineStyle = UnderlineStyle.CURLY;
cell.bg |= BgFlags.HAS_EXTENDED;
line.fill(cell);
assert.equal((line as any)._extendedAttrs[0].underlineStyle, UnderlineStyle.CURLY);
assert.equal((line as any)._extendedAttrs[1].underlineStyle, UnderlineStyle.CURLY);
assert.equal((line as any)._extendedAttrs[2].underlineStyle, UnderlineStyle.CURLY);
});
it('insertCells', () => {
const line = new TestBufferLine(5);
const cell = CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]);
cell.extended.underlineStyle = UnderlineStyle.CURLY;
cell.bg |= BgFlags.HAS_EXTENDED;
line.insertCells(1, 3, cell);
assert.equal((line as any)._extendedAttrs[1].underlineStyle, UnderlineStyle.CURLY);
assert.equal((line as any)._extendedAttrs[2].underlineStyle, UnderlineStyle.CURLY);
assert.equal((line as any)._extendedAttrs[3].underlineStyle, UnderlineStyle.CURLY);
assert.equal((line as any)._extendedAttrs[4], undefined);
cell.extended = cell.extended.clone();
cell.extended.underlineStyle = UnderlineStyle.DOTTED;
line.insertCells(2, 2, cell);
assert.equal((line as any)._extendedAttrs[1].underlineStyle, UnderlineStyle.CURLY);
assert.equal((line as any)._extendedAttrs[2].underlineStyle, UnderlineStyle.DOTTED);
assert.equal((line as any)._extendedAttrs[3].underlineStyle, UnderlineStyle.DOTTED);
assert.equal((line as any)._extendedAttrs[4].underlineStyle, UnderlineStyle.CURLY);
});
it('deleteCells', () => {
const line = new TestBufferLine(5);
const fillCell = CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]);
fillCell.extended.underlineStyle = UnderlineStyle.CURLY;
fillCell.bg |= BgFlags.HAS_EXTENDED;
line.fill(fillCell);
fillCell.extended = fillCell.extended.clone();
fillCell.extended.underlineStyle = UnderlineStyle.DOUBLE;
line.deleteCells(1, 3, fillCell);
assert.equal((line as any)._extendedAttrs[0].underlineStyle, UnderlineStyle.CURLY);
assert.equal((line as any)._extendedAttrs[1].underlineStyle, UnderlineStyle.CURLY);
assert.equal((line as any)._extendedAttrs[2].underlineStyle, UnderlineStyle.DOUBLE);
assert.equal((line as any)._extendedAttrs[3].underlineStyle, UnderlineStyle.DOUBLE);
assert.equal((line as any)._extendedAttrs[4].underlineStyle, UnderlineStyle.DOUBLE);
});
it('replaceCells', () => {
const line = new TestBufferLine(5);
const fillCell = CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]);
fillCell.extended.underlineStyle = UnderlineStyle.CURLY;
fillCell.bg |= BgFlags.HAS_EXTENDED;
line.fill(fillCell);
fillCell.extended = fillCell.extended.clone();
fillCell.extended.underlineStyle = UnderlineStyle.DOUBLE;
line.replaceCells(1, 3, fillCell);
assert.equal((line as any)._extendedAttrs[0].underlineStyle, UnderlineStyle.CURLY);
assert.equal((line as any)._extendedAttrs[1].underlineStyle, UnderlineStyle.DOUBLE);
assert.equal((line as any)._extendedAttrs[2].underlineStyle, UnderlineStyle.DOUBLE);
assert.equal((line as any)._extendedAttrs[3].underlineStyle, UnderlineStyle.CURLY);
assert.equal((line as any)._extendedAttrs[4].underlineStyle, UnderlineStyle.CURLY);
});
it('clone', () => {
const line = new TestBufferLine(5);
const cell = CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]);
// no eAttrs
line.setCell(0, cell);
// some underline style
cell.extended.underlineStyle = UnderlineStyle.CURLY;
cell.bg |= BgFlags.HAS_EXTENDED;
line.setCell(1, cell);
// same eAttr, different codepoint
cell.content = 65; // 'A'
line.setCell(2, cell);
// different eAttr
cell.extended = cell.extended.clone();
cell.extended.underlineStyle = UnderlineStyle.DOTTED;
line.setCell(3, cell);
// no eAttrs again
cell.bg &= ~BgFlags.HAS_EXTENDED;
line.setCell(4, cell);
const nLine = line.clone();
assert.equal((nLine as any)._extendedAttrs[0], (line as any)._extendedAttrs[0]);
assert.equal((nLine as any)._extendedAttrs[1], (line as any)._extendedAttrs[1]);
assert.equal((nLine as any)._extendedAttrs[2], (line as any)._extendedAttrs[2]);
assert.equal((nLine as any)._extendedAttrs[3], (line as any)._extendedAttrs[3]);
assert.equal((nLine as any)._extendedAttrs[4], (line as any)._extendedAttrs[4]);
});
it('copyFrom', () => {
const initial = new TestBufferLine(5);
const cell = CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]);
// no eAttrs
initial.setCell(0, cell);
// some underline style
cell.extended.underlineStyle = UnderlineStyle.CURLY;
cell.bg |= BgFlags.HAS_EXTENDED;
initial.setCell(1, cell);
// same eAttr, different codepoint
cell.content = 65; // 'A'
initial.setCell(2, cell);
// different eAttr
cell.extended = cell.extended.clone();
cell.extended.underlineStyle = UnderlineStyle.DOTTED;
initial.setCell(3, cell);
// no eAttrs again
cell.bg &= ~BgFlags.HAS_EXTENDED;
initial.setCell(4, cell);
const line = new TestBufferLine(5);
line.fill(CellData.fromCharData([1, 'b', 0, 'b'.charCodeAt(0)]));
line.copyFrom(initial);
assert.equal((line as any)._extendedAttrs[0], (initial as any)._extendedAttrs[0]);
assert.equal((line as any)._extendedAttrs[1], (initial as any)._extendedAttrs[1]);
assert.equal((line as any)._extendedAttrs[2], (initial as any)._extendedAttrs[2]);
assert.equal((line as any)._extendedAttrs[3], (initial as any)._extendedAttrs[3]);
assert.equal((line as any)._extendedAttrs[4], (initial as any)._extendedAttrs[4]);
});
});
});
+30 -12
View File
@@ -3,11 +3,11 @@
* @license MIT
*/
import { CharData, IBufferLine, ICellData, IAttributeData } from 'common/Types';
import { CharData, IBufferLine, ICellData, IAttributeData, IExtendedAttrs } from 'common/Types';
import { stringFromCodePoint } from 'common/input/TextDecoder';
import { CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX, CHAR_DATA_ATTR_INDEX, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE, WHITESPACE_CELL_CHAR, Content } from 'common/buffer/Constants';
import { CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX, CHAR_DATA_ATTR_INDEX, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE, WHITESPACE_CELL_CHAR, Content, BgFlags } from 'common/buffer/Constants';
import { CellData } from 'common/buffer/CellData';
import { AttributeData } from 'common/buffer/AttributeData';
import { AttributeData, ExtendedAttrs } from 'common/buffer/AttributeData';
/**
* buffer memory layout:
@@ -55,6 +55,7 @@ export const DEFAULT_ATTR_DATA = Object.freeze(new AttributeData());
export class BufferLine implements IBufferLine {
protected _data: Uint32Array;
protected _combined: {[index: number]: string} = {};
protected _extendedAttrs: {[index: number]: ExtendedAttrs} = {};
public length: number;
constructor(cols: number, fillCellData?: ICellData, public isWrapped: boolean = false) {
@@ -174,6 +175,9 @@ export class BufferLine implements IBufferLine {
if (cell.content & Content.IS_COMBINED_MASK) {
cell.combinedData = this._combined[index];
}
if (cell.bg & BgFlags.HAS_EXTENDED) {
cell.extended = this._extendedAttrs[index];
}
return cell;
}
@@ -184,6 +188,9 @@ export class BufferLine implements IBufferLine {
if (cell.content & Content.IS_COMBINED_MASK) {
this._combined[index] = cell.combinedData;
}
if (cell.bg & BgFlags.HAS_EXTENDED) {
this._extendedAttrs[index] = cell.extended;
}
this._data[index * CELL_SIZE + Cell.CONTENT] = cell.content;
this._data[index * CELL_SIZE + Cell.FG] = cell.fg;
this._data[index * CELL_SIZE + Cell.BG] = cell.bg;
@@ -194,7 +201,10 @@ export class BufferLine implements IBufferLine {
* Since the input handler see the incoming chars as UTF32 codepoints,
* it gets an optimized access method.
*/
public setCellFromCodePoint(index: number, codePoint: number, width: number, fg: number, bg: number): void {
public setCellFromCodePoint(index: number, codePoint: number, width: number, fg: number, bg: number, eAttrs: IExtendedAttrs): void {
if (bg & BgFlags.HAS_EXTENDED) {
this._extendedAttrs[index] = eAttrs;
}
this._data[index * CELL_SIZE + Cell.CONTENT] = codePoint | (width << Content.WIDTH_SHIFT);
this._data[index * CELL_SIZE + Cell.FG] = fg;
this._data[index * CELL_SIZE + Cell.BG] = bg;
@@ -233,7 +243,7 @@ export class BufferLine implements IBufferLine {
// handle fullwidth at pos: reset cell one to the left if pos is second cell of a wide char
if (pos && this.getWidth(pos - 1) === 2) {
this.setCellFromCodePoint(pos - 1, 0, 1, eraseAttr?.fg || 0, eraseAttr?.bg || 0);
this.setCellFromCodePoint(pos - 1, 0, 1, eraseAttr?.fg || 0, eraseAttr?.bg || 0, eraseAttr?.extended || new ExtendedAttrs());
}
if (n < this.length - pos) {
@@ -252,7 +262,7 @@ export class BufferLine implements IBufferLine {
// handle fullwidth at line end: reset last cell if it is first cell of a wide char
if (this.getWidth(this.length - 1) === 2) {
this.setCellFromCodePoint(this.length - 1, 0, 1, eraseAttr?.fg || 0, eraseAttr?.bg || 0);
this.setCellFromCodePoint(this.length - 1, 0, 1, eraseAttr?.fg || 0, eraseAttr?.bg || 0, eraseAttr?.extended || new ExtendedAttrs());
}
}
@@ -276,21 +286,21 @@ export class BufferLine implements IBufferLine {
// - reset pos-1 if wide char
// - reset pos if width==0 (previous second cell of a wide char)
if (pos && this.getWidth(pos - 1) === 2) {
this.setCellFromCodePoint(pos - 1, 0, 1, eraseAttr?.fg || 0, eraseAttr?.bg || 0);
this.setCellFromCodePoint(pos - 1, 0, 1, eraseAttr?.fg || 0, eraseAttr?.bg || 0, eraseAttr?.extended || new ExtendedAttrs());
}
if (this.getWidth(pos) === 0 && !this.hasContent(pos)) {
this.setCellFromCodePoint(pos, 0, 1, eraseAttr?.fg || 0, eraseAttr?.bg || 0);
this.setCellFromCodePoint(pos, 0, 1, eraseAttr?.fg || 0, eraseAttr?.bg || 0, eraseAttr?.extended || new ExtendedAttrs());
}
}
public replaceCells(start: number, end: number, fillCellData: ICellData, eraseAttr?: IAttributeData): void {
// handle fullwidth at start: reset cell one to the left if start is second cell of a wide char
if (start && this.getWidth(start - 1) === 2) {
this.setCellFromCodePoint(start - 1, 0, 1, eraseAttr?.fg || 0, eraseAttr?.bg || 0);
this.setCellFromCodePoint(start - 1, 0, 1, eraseAttr?.fg || 0, eraseAttr?.bg || 0, eraseAttr?.extended || new ExtendedAttrs());
}
// handle fullwidth at last cell + 1: reset to empty cell if it is second part of a wide char
if (end < this.length && this.getWidth(end - 1) === 2) {
this.setCellFromCodePoint(end, 0, 1, eraseAttr?.fg || 0, eraseAttr?.bg || 0);
this.setCellFromCodePoint(end, 0, 1, eraseAttr?.fg || 0, eraseAttr?.bg || 0, eraseAttr?.extended || new ExtendedAttrs());
}
while (start < end && start < this.length) {
@@ -320,7 +330,7 @@ export class BufferLine implements IBufferLine {
const data = new Uint32Array(cols * CELL_SIZE);
data.set(this._data.subarray(0, cols * CELL_SIZE));
this._data = data;
// Remove any cut off combined data
// Remove any cut off combined data, FIXME: repeat this for extended attrs
const keys = Object.keys(this._combined);
for (let i = 0; i < keys.length; i++) {
const key = parseInt(keys[i], 10);
@@ -339,6 +349,7 @@ export class BufferLine implements IBufferLine {
/** fill a line with fillCharData */
public fill(fillCellData: ICellData): void {
this._combined = {};
this._extendedAttrs = {};
for (let i = 0; i < this.length; ++i) {
this.setCell(i, fillCellData);
}
@@ -357,6 +368,10 @@ export class BufferLine implements IBufferLine {
for (const el in line._combined) {
this._combined[el] = line._combined[el];
}
this._extendedAttrs = {};
for (const el in line._extendedAttrs) {
this._extendedAttrs[el] = line._extendedAttrs[el];
}
this.isWrapped = line.isWrapped;
}
@@ -368,6 +383,9 @@ export class BufferLine implements IBufferLine {
for (const el in this._combined) {
newLine._combined[el] = this._combined[el];
}
for (const el in this._extendedAttrs) {
newLine._extendedAttrs[el] = this._extendedAttrs[el];
}
newLine.isWrapped = this.isWrapped;
return newLine;
}
@@ -397,7 +415,7 @@ export class BufferLine implements IBufferLine {
}
}
// Move any combined data over as needed
// Move any combined data over as needed, FIXME: repeat for extended attrs
const srcCombinedKeys = Object.keys(src._combined);
for (let i = 0; i < srcCombinedKeys.length; i++) {
const key = parseInt(srcCombinedKeys[i], 10);
+7 -4
View File
@@ -8,18 +8,19 @@ import { IAttributeData } from 'common/Types';
import { Buffer } from 'common/buffer/Buffer';
import { EventEmitter, IEvent } from 'common/EventEmitter';
import { IOptionsService, IBufferService } from 'common/services/Services';
import { Disposable } from 'common/Lifecycle';
/**
* The BufferSet represents the set of two buffers used by xterm terminals (normal and alt) and
* provides also utilities for working with them.
*/
export class BufferSet implements IBufferSet {
export class BufferSet extends Disposable implements IBufferSet {
private _normal: Buffer;
private _alt: Buffer;
private _activeBuffer: Buffer;
private _onBufferActivate = new EventEmitter<{activeBuffer: IBuffer, inactiveBuffer: IBuffer}>();
private _onBufferActivate = this.register(new EventEmitter<{activeBuffer: IBuffer, inactiveBuffer: IBuffer}>());
public get onBufferActivate(): IEvent<{activeBuffer: IBuffer, inactiveBuffer: IBuffer}> { return this._onBufferActivate.event; }
/**
@@ -27,9 +28,11 @@ export class BufferSet implements IBufferSet {
* @param _terminal - The terminal the BufferSet will belong to
*/
constructor(
readonly optionsService: IOptionsService,
readonly bufferService: IBufferService
optionsService: IOptionsService,
bufferService: IBufferService
) {
super();
this._normal = new Buffer(true, optionsService, bufferService);
this._normal.fillViewportRows();
+7 -6
View File
@@ -3,10 +3,10 @@
* @license MIT
*/
import { CharData, ICellData } from 'common/Types';
import { CharData, ICellData, IExtendedAttrs } from 'common/Types';
import { stringFromCodePoint } from 'common/input/TextDecoder';
import { CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX, CHAR_DATA_ATTR_INDEX, Content } from 'common/buffer/Constants';
import { AttributeData } from 'common/buffer/AttributeData';
import { AttributeData, ExtendedAttrs } from 'common/buffer/AttributeData';
/**
* CellData - represents a single Cell in the terminal buffer.
@@ -19,10 +19,11 @@ export class CellData extends AttributeData implements ICellData {
return obj;
}
/** Primitives from terminal buffer. */
public content: number = 0;
public fg: number = 0;
public bg: number = 0;
public combinedData: string = '';
public content = 0;
public fg = 0;
public bg = 0;
public extended: IExtendedAttrs = new ExtendedAttrs();
public combinedData = '';
/** Whether cell contains a combined string. */
public isCombined(): number {
return this.content & Content.IS_COMBINED_MASK;

Some files were not shown because too many files have changed in this diff Show More