Compare commits

...
522 Commits
Author SHA1 Message Date
Daniel Imms e40cf8043a Merge pull request #3544 from xtermjs/tyriar/length_warning
Add warning to IBufferLine.length
2021-11-05 09:09:37 -07:00
Daniel Imms 32cb95c7fb Add warning to IBufferLine.length 2021-11-05 08:53:48 -07:00
jerch b2068531dd Merge pull request #3537 from jerch/update_deps
upgrade dev deps
2021-11-01 18:55:50 +01:00
Jörg Breitbart b29d7d2c8d update to windows-2019 on azure 2021-11-01 18:39:48 +01:00
Jörg Breitbart b684929d1e upgrade dev deps 2021-11-01 18:20:17 +01:00
Daniel Imms 07ba3dc197 Merge pull request #3532 from jeffg2k/patch-1
Check to make sure cols & rows are INT
2021-10-26 08:49:07 -07:00
ElJeffe 5c9adeef93 Check to make sure cols & rows are INT
I've noticed that if the terminal is hidden and the screen experiences a resize trigger from a blur / focus, it can cause the dimensions to be undefined.  This creates an exception in Xterm.js that the values are not integers.  Figured it would be ok to just check before doing a resize.
2021-10-26 11:44:52 -04:00
Daniel Imms 394d7a2ee6 Merge pull request #3370 from dstein64/master
Set text baseline conditionally by browser.
2021-10-25 03:15:38 -07:00
Daniel Imms 987bb8dd35 Merge pull request #3476 from silamon/cleanbuild
Update npmignore files
2021-10-25 02:50:23 -07:00
Daniel Imms ce314844d2 Merge branch 'master' into cleanbuild 2021-10-25 02:47:52 -07:00
Simon Lamon 645263b0ac update npmignore, don't include test files 2021-10-23 08:22:00 +00:00
Daniel Imms 6978fafd28 Merge pull request #3448 from silamon/options1
Expose typed options from OptionsService through terminal.options
2021-10-22 14:15:39 -07:00
Simon Lamon c7ee362ae7 revert changes to interface ITerminalOptions 2021-10-22 20:54:42 +00:00
Simon Lamon 57e6cfd500 setOption and getOption should look at publicOptions 2021-10-22 20:53:06 +00:00
Simon Lamon 33bbc4f4b8 cache options and type _options 2021-10-22 20:50:00 +00:00
Daniel Imms 8549915afa Merge pull request #3525 from metonym/fix-viewport-scrollbar
Include xterm padding when computing viewport width
2021-10-22 13:09:57 -07:00
Daniel Steinberg 59f940c19c Set text baseline conditionally by browser. 2021-10-22 19:20:21 +00:00
metonym ed1aecb251 chore(viewport): run yarn lint --fix 2021-10-22 11:28:30 -07:00
metonym 7deb01267b Use computed padding of terminal element to compute viewport width 2021-10-22 11:02:59 -07:00
Daniel Imms 4b9ab81848 Add deprecated to getOption/setOption 2021-10-22 10:34:25 -07:00
Daniel Imms 6c8d801894 Adopt typed options in demo client 2021-10-22 10:14:03 -07:00
Daniel Imms 1a9de5f227 Merge branch 'master' into options1 2021-10-22 10:07:36 -07:00
metonym 9b1eec8ade fix(viewport): account for fallback scrollbar width 2021-10-22 09:49:01 -07:00
Daniel Imms 1a697bd844 Merge pull request #3479 from silamon/devcontainermocha
Add Mocha Test Explorer to devcontainer
2021-10-22 07:41:01 -07:00
jerch b23e100266 Merge pull request #3523 from jerch/fix_3347
properly await all parseP in inputhandler tests
2021-10-22 13:43:20 +02:00
Jörg Breitbart c9e8db38b6 properly await all parseP calls 2021-10-22 13:32:31 +02:00
Daniel Imms 0a7ac8b978 Merge pull request #3484 from JohanKnutzen/master
Add FleetDeck to real-world uses
2021-10-21 12:54:18 -07:00
Daniel Imms 780b48db53 Move newest to bottom of list 2021-10-21 12:54:01 -07:00
Daniel Imms 82d09ec909 Merge pull request #3522 from Tyriar/bump_release
Use Ubuntu 18.04 in release job
2021-10-21 11:44:13 -07:00
Daniel Imms b876639429 Use Ubuntu 18.04 in release job
Fixes #3521
2021-10-21 11:43:20 -07:00
jerch afe19e96fe simplify wheel handlers
- merge wheel handlers
- remove passive handler
2021-10-21 20:25:26 +02:00
jerch c599fbb7a1 Merge branch 'master' into fix_3518 2021-10-21 20:06:40 +02:00
Daniel Imms c63e9ef876 Merge pull request #3520 from meganrogge/cursor-blink
webgl - initialize CursorBlinkStateManager when CursorRenderLayer is created
2021-10-21 10:55:19 -07:00
Megan Rogge d6e828a2f3 fix merge conflict 2021-10-21 10:44:21 -07:00
Megan Rogge 2445f35a32 Merge branch 'master' into cursor-blink 2021-10-21 12:37:38 -05:00
Megan RoggeandDaniel Imms d2bcbc73d3 fixes #3517
Co-authored-by: Daniel Imms <Tyriar@users.noreply.github.com>
2021-10-21 10:33:22 -07:00
Jörg Breitbart f46ed82d7e simplify wheel handlers 2021-10-21 12:35:03 +02:00
Daniel Imms 4222dfb69d Merge pull request #3511 from Tyriar/windows_opener
Swallow error when opener can't be sent
2021-10-19 10:50:20 -07:00
Daniel Imms 1b3eb412b8 Merge pull request #3512 from Tyriar/serialize_doc
Call out deserialize into same size terminal
2021-10-19 10:50:10 -07:00
Daniel Imms e9f6636df1 Merge branch 'master' into serialize_doc 2021-10-19 10:49:47 -07:00
Daniel ImmsandMegan Rogge 472e410205 Call out deserialize into same size terminal
Fixes #3093

Co-authored-by: Megan Rogge <merogge@microsoft.com>
2021-10-19 10:49:01 -07:00
Daniel ImmsandMegan Rogge 581272ee51 Swallow error when opener can't be sent
Fixes #2943

Co-authored-by: Megan Rogge <merogge@microsoft.com>
2021-10-19 10:28:09 -07:00
Johan Knutzen e8e21e9d0b Merge branch 'master' into master 2021-10-18 11:02:13 -07:00
Daniel Imms 80d4742ded Merge pull request #3506 from Tyriar/2333_sendfocus
Report focus state on DECSET 1004
2021-10-12 11:58:43 -07:00
Daniel Imms 50939e0b1d Merge branch 'master' into 2333_sendfocus 2021-10-12 09:38:45 -07:00
Daniel Imms 5d5f34f65f Report focus state on DECSET 1004
Fixes #2333
2021-10-12 09:05:44 -07:00
jerch d3b762eb97 Merge pull request #3503 from jerch/fix_cursor_dom
fix DECTCEM in DOM renderer
2021-10-11 15:07:23 +02:00
Jörg Breitbart 0c60478953 fix DECTCEM in DOM renderer 2021-10-10 19:36:20 +02:00
Daniel Imms 3b23921a41 Merge pull request #3500 from meganrogge/merogge/safari-webgl
throw if activate webgl is called on safari
2021-10-08 08:00:53 -07:00
Megan Rogge e3422eda58 Merge branch 'master' into merogge/safari-webgl 2021-10-08 09:46:03 -05:00
Megan Rogge 37a4100220 throw if activate webgl called on safari 2021-10-08 07:45:48 -07:00
Daniel Imms 8592c5cca3 Merge pull request #3495 from MyunghoBae/master
Added goormIDE on Real-world uses
2021-10-07 06:50:35 -07:00
mac.bae 014530f582 Added goormIDE on Real-world uses 2021-10-07 07:44:30 +00:00
Johan Knutzen a994fbc865 Merge branch 'master' into master 2021-09-22 17:43:52 -07:00
Johan Knutzen 0d14336ec5 Add FleetDeck to real-world uses 2021-09-22 17:39:21 -07:00
Daniel Imms c20c07c6d8 Merge pull request #3483 from Tyriar/document_role
Add document role to accessibility tree root
2021-09-22 14:31:42 -07:00
Daniel Imms 3a71b3f11e Add document role to accessibility tree root
See microsoft/vscode#98918
2021-09-22 14:21:50 -07:00
Daniel Imms f2384694a1 Merge pull request #3481 from Tyriar/clear_texture_atlas
Add API to clear canvas renderer texture atlas
2021-09-22 06:51:56 -07:00
Daniel Imms ca46b8c805 Merge branch 'master' into clear_texture_atlas 2021-09-22 06:39:53 -07:00
Daniel Imms 189ff56224 Add API to clear canvas renderer texture atlas
Fixes #3455
2021-09-22 06:38:47 -07:00
Daniel Imms ef6516e561 Merge pull request #3480 from silamon/strikethrough-serialize
Support strikethrough in serialize addon
2021-09-22 05:22:40 -07:00
Simon Lamon 1d1fffdabc Include src files in distribution 2021-09-22 10:08:48 +00:00
Simon Lamon 3fe32c0724 Adjust test to include strikethrough test 2021-09-22 09:38:37 +00:00
Simon Lamon 2e4e29ad73 Support strikethrough in serialize addon 2021-09-22 08:47:36 +00:00
Simon Lamon 9c4a08a55a devcontainer mocha 2021-09-22 08:41:57 +00:00
Simon Lamon 5188633458 Update npmignore files 2021-09-15 17:39:39 +00:00
Daniel Imms a8a24eba51 Merge pull request #3473 from Tyriar/serialize_granularity
Add granularity to what the serialize addon serializes
2021-09-15 06:03:47 -07:00
Daniel Imms 6493edcddf Add new serialize option tests 2021-09-15 05:40:46 -07:00
Daniel Imms c186fbeafc Add exclude mode/alt buffer options to serialize addon
Fixes #3472
2021-09-15 05:04:11 -07:00
Daniel Imms d32c6db967 Merge pull request #3470 from xtermjs/r4_14_1
v4.14.1
2021-09-10 09:50:06 -07:00
Daniel Imms db6b3c4bcc v4.14.1 2021-09-10 09:44:29 -07:00
Daniel Imms 50c5579c83 Merge pull request #3469 from Tyriar/3467
Disable emoji ime when screenReaderMode is on
2021-09-10 09:43:44 -07:00
Daniel Imms 5a65fd9c6a Disable emoji ime when screenReaderMode is on
Fixes #3467
2021-09-10 09:42:19 -07:00
Daniel Imms be7fcbe485 Merge pull request #3466 from xtermjs/dependabot/npm_and_yarn/addons/xterm-addon-ligatures/axios-0.21.2
Bump axios from 0.18.1 to 0.21.2 in /addons/xterm-addon-ligatures
2021-09-09 12:51:21 -07:00
Simon Lamon 99239e9160 Allow modifying constructor only options 2021-09-09 18:02:40 +00:00
dependabot[bot] 662154123d Bump axios from 0.18.1 to 0.21.2 in /addons/xterm-addon-ligatures
Bumps [axios](https://github.com/axios/axios) from 0.18.1 to 0.21.2.
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/master/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v0.18.1...v0.21.2)

---
updated-dependencies:
- dependency-name: axios
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-09-09 13:24:07 +00:00
Daniel Imms cdaea7b0cd Merge pull request #3465 from xtermjs/release_4_14_0
v4.14.0
2021-09-09 06:19:11 -07:00
Daniel Imms 3cb374076a v4.14.0 2021-09-09 05:49:04 -07:00
Daniel Imms 8d48616e0d Merge pull request #3462 from anirudh1713/master
fix: switch active unicode version in demo
2021-09-07 05:09:01 -07:00
anirudh1713 3eeec14462 switch active unicode version in demo 2021-09-05 20:35:07 +05:30
Daniel Imms b5df612b4e Merge pull request #3453 from silamon/undefinedrowscols
Passing cols or rows into ctor as undefined will break terminal
2021-09-03 07:53:55 -07:00
Simon Lamon fa778257b9 Formatting 2021-09-03 04:40:29 +00:00
Megan Rogge 011b58dc74 Merge branch 'master' into undefinedrowscols 2021-09-02 16:02:42 -07:00
Daniel Imms b9b42efade Merge pull request #3452 from Tyriar/perf_fixes
Throughput performance improvements
2021-09-02 07:44:46 -07:00
Daniel Imms 20460a2be1 Fire buffer activate event on buffer service reset 2021-09-02 07:34:04 -07:00
Daniel Imms a649192c7f Merge branch 'master' into perf_fixes 2021-09-02 07:03:32 -07:00
Daniel Imms 77461a3340 Merge pull request #3458 from Tyriar/split_tests
Split up unicode surrogates tests to avoid timeout
2021-09-02 06:56:49 -07:00
Daniel Imms 453688a555 Split up unicode surrogates tests to avoid timeout
Fixes #3441
2021-09-02 06:34:00 -07:00
Daniel Imms 504a8eb472 Merge pull request #3454 from meganrogge/master
fix #3348
2021-09-01 14:00:51 -07:00
meganrogge 199e477349 3 -> 1 line 2021-09-01 13:45:49 -07:00
meganrogge 3c8f600c57 fix #3348 2021-09-01 11:27:14 -07:00
Daniel Imms 263c6d75bf Avoid scrollTop call in hot code
This seems to have been added in f6d5abf but it's not clear why, scroll APIs
seem to work fine without it and using a DOM API here is causing slowness
2021-09-01 06:44:34 -07:00
Daniel Imms ffef3dba00 Avoid property use and float->number conversion 2021-09-01 06:41:10 -07:00
Simon Lamon aa046b73fb Handle undefined rows or cols better 2021-09-01 13:34:27 +00:00
Daniel Imms cece3db0cb Cache a copy of the active buffer as a private prop
This reduces GC from the const buffer workaround that avoids excessive getter access
with far less getter access

Part of #3450
2021-09-01 06:15:59 -07:00
Daniel Imms 554f7dab72 Merge pull request #3451 from Tyriar/demo_loadtest
Add loadtest button to demo
2021-09-01 05:45:44 -07:00
Daniel Imms b3c0c2bd3c Add loadtest button to demo 2021-09-01 05:45:00 -07:00
Simon Lamon 72a557cc1a Make options available in terminal 2021-09-01 07:59:45 +00:00
Daniel Imms 52d00a434e Merge pull request #3445 from daiyam/refactor-better-mocha
refactor(test): enhance mocha usability
2021-08-31 12:10:45 -07:00
Daniel Imms 37c4cae5ed Merge branch 'master' into refactor-better-mocha 2021-08-31 08:32:20 -07:00
Baptiste Augrain f41eea025e refactor: rename script 2021-08-31 16:58:20 +02:00
Baptiste Augrain ac75059684 refactor: rename file to a better name 2021-08-31 16:56:40 +02:00
Daniel Imms c71f947d06 Merge pull request #3447 from xtermjs/Tyriar-patch-1
Make addons tab title consistent
2021-08-31 07:33:07 -07:00
Daniel Imms f12b917930 Make addons tab title consistent 2021-08-31 07:32:54 -07:00
Daniel Imms d260a725bc Merge pull request #3439 from simran916/ui-reformed
Issue #3425 (Terminal controls shifted to the right of the demo)
2021-08-31 07:29:55 -07:00
Daniel Imms 8edf0d5e1d Improve checkbox and italics styling 2021-08-31 07:29:31 -07:00
Daniel Imms 5f2b298b81 Merge branch 'master' into ui-reformed 2021-08-31 07:25:28 -07:00
Baptiste Augrain 7cda0017aa fix: make Mocha Explorer working on Windows 2021-08-31 15:53:43 +02:00
Baptiste Augrain ab900017db Merge branch 'refactor-better-mocha' of github.com:daiyam/xterm.js into refactor-better-mocha 2021-08-31 15:40:39 +02:00
Baptiste Augrain 8801926b10 fix: test:dev is running on Windows 2021-08-31 15:40:35 +02:00
Daniel Imms ac71a8aea8 Merge branch 'master' into refactor-better-mocha 2021-08-31 05:41:17 -07:00
Daniel Imms 813e97df3b Merge pull request #3444 from daiyam/refactor-test-browser
refactor(test): use common function to launch the browser
2021-08-31 05:40:38 -07:00
Baptiste Augrain 50e8727111 refactor(test): add command to run an unique test and support Mocha Explorer extension 2021-08-31 13:04:40 +02:00
Baptiste Augrain 9e40614b5c feat(test): use common function to launch the browser 2021-08-31 12:50:54 +02:00
Simran Narang b19b3f0fb6 Implemented all the changes requested!
All Suggested changes committed successfully!
2021-08-31 11:41:00 +05:30
Daniel Imms 7068954334 Merge pull request #3398 from Puneethnaik/issue_3074_fix
A way to turn off the scrollbar #3074
2021-08-30 10:48:26 -07:00
Daniel Imms 640ea60945 Action feedback 2021-08-30 10:48:12 -07:00
Daniel Imms 52518bdc47 Merge branch 'master' into issue_3074_fix 2021-08-30 10:37:44 -07:00
Daniel Imms fb43ff76af Merge pull request #3429 from Eugeny/emoji-input
input: handle input from macOS and Windows emoji panels
2021-08-30 10:30:24 -07:00
Daniel Imms 9c49b31609 Merge branch 'master' into emoji-input 2021-08-30 10:16:17 -07:00
Daniel Imms d9b8c62cd4 Merge pull request #3437 from Eugeny/alt-group-is-dead
input: treat AltGraph as a dead key
2021-08-30 07:37:43 -07:00
Simran Narang 2be3fc82ba Terminal controls shifted to the right of the demo
The terminals have been shifted using a fixed tab bar on the right side of the demo.
2021-08-30 15:14:15 +05:30
Eugeny 862ca53d07 Merge branch 'master' into alt-group-is-dead 2021-08-28 14:18:06 +02:00
Eugene Pankov f2ef2b0b04 input: treat AltGraph as a dead key too 2021-08-28 14:14:16 +02:00
Daniel Imms fec3a7f71a Merge pull request #3432 from Eugeny/alt-graph-dead-key
input: support AltGraph as a third level shift modifier
2021-08-25 13:02:09 -07:00
Eugene Pankov fa9126c4e6 input: support AltGraph as a third level shift modifier 2021-08-25 21:48:25 +02:00
Eugeny a7b7674bb6 Merge branch 'master' into emoji-input 2021-08-23 16:39:22 +02:00
Daniel Imms a706e010f4 Merge pull request #3430 from Eugeny/dead-key-support
input: handle dead keys
2021-08-23 07:33:29 -07:00
Daniel Imms 441ec1bfb6 Merge pull request #3431 from Tyriar/serialize_repo
Add repository key to serialize package.json
2021-08-23 05:25:02 -07:00
Daniel Imms c4262e7df5 Merge branch 'master' into serialize_repo 2021-08-23 05:23:39 -07:00
Daniel Imms 44be1f5d2a Add repository key to serialize package.json
Not having this breaks some of vscode's tooling
2021-08-23 05:23:17 -07:00
Eugeny f2d0a5020a Merge branch 'master' into dead-key-support 2021-08-22 20:29:26 +02:00
Eugene Pankov b3cd4948ad input: handle input from macOS and Windows emoji panels 2021-08-22 17:35:22 +02:00
Eugene Pankov f0636ed083 input: handle dead keys 2021-08-22 17:15:00 +02:00
Daniel Imms 7bd7967ab7 Merge pull request #3428 from meganrogge/merogge/webgl-line-height
adjust for lineHeight and letterSpacing in webgl custom glyph rendering
2021-08-20 12:53:09 -07:00
meganrogge c1413ce58d fix #3427 2021-08-19 18:24:49 -07:00
Daniel Imms ac116b958e Merge pull request #3426 from Tyriar/underscore_fix
Ensure underscore is within cell bounds
2021-08-19 07:14:13 -07:00
Daniel Imms 53dbcbafb2 Ensure underscore is within cell bounds
Fixes #3423
2021-08-19 07:05:42 -07:00
Daniel Imms 569708677f Merge pull request #3416 from meganrogge/merogge/pixelPerfect
pixel perfect canvas rendering of box and block characters
2021-08-19 06:00:58 -07:00
Daniel Imms 1a75416dbf Add missing block elements (include all 0x2580-0x259F) 2021-08-19 06:00:36 -07:00
Daniel Imms ca4c9c3dab Merge branch 'master' into merogge/pixelPerfect 2021-08-19 05:43:58 -07:00
Daniel Imms 338d94daf9 Fix webgl alignment issues
No idea why this worked but it did
2021-08-19 05:41:49 -07:00
Daniel Imms 24cbedb715 Fix custom glyphs when using transparency 2021-08-19 05:32:46 -07:00
Daniel Imms 3be97841a3 Rename setting customGlyphs 2021-08-19 05:20:40 -07:00
Daniel Imms 158dcb06bd Remove custom glyph tests 2021-08-19 05:16:01 -07:00
Daniel Imms 54ce0172b4 Add test custom glyph button to demo 2021-08-18 09:48:27 -07:00
Daniel Imms 011026a8d1 Get webgl glyphs scaling to cell size, invalidate on option change 2021-08-18 08:29:07 -07:00
Daniel Imms da0928102b Support shade char with patterns 2021-08-18 08:19:23 -07:00
Daniel Imms fe7f28a7a0 Fix block elements on webgl, scale block elements to cell 2021-08-18 07:38:02 -07:00
Daniel Imms 0aaa20f873 Start of webgl integration 2021-08-18 07:31:35 -07:00
Daniel Imms e5d66d63f5 Fix offset of block elements 2021-08-18 07:00:10 -07:00
Daniel Imms 86a30dc689 Clean up usage of instruction/definition 2021-08-18 06:45:40 -07:00
Daniel Imms 9fb1ce11fb Add setting, clean up drawing, fix true color 2021-08-18 06:44:13 -07:00
Daniel Imms 9490c5ce1b Merge pull request #3399 from pattch/master
Add a Time-Based Debouncer to update Screen Readers at most once per second
2021-08-18 06:09:34 -07:00
Samuel Sampson 42e0fcda29 Fix lint error due to private variable 2021-08-17 22:26:14 +00:00
Samuel Sampson e778df672a Don't clear timeouts in TimeBasedDebouncer if they've already been executed. 2021-08-17 22:20:04 +00:00
Samuel Sampson ae58302dfa Allow render debounce interval to be configured at construction time in TimeBasedDebouncer 2021-08-17 22:09:51 +00:00
Samuel Sampson 9f24cf7e72 Cancel queued refreshes in TimeBasedDebouncer on dispose 2021-08-17 21:28:58 +00:00
Samuel Sampson f1849cafaf Introduce IRenderDebouncer interface, unifying RenderDebouncer and TimeBasedDebouncer types 2021-08-17 20:50:47 +00:00
Samuel f1eed44e14 Merge branch 'master' into master 2021-08-17 13:18:13 -07:00
Daniel Imms ca88476934 Improve spacing of dash characters 2021-08-17 08:48:23 -07:00
Daniel Imms bcfbbb481c Correct horizontal dash and a mixed weight char 2021-08-17 08:14:46 -07:00
Daniel Imms 9269e91c49 Make bold triple normal weight so 1 dpr = 3px bold 2021-08-17 08:04:17 -07:00
Daniel Imms fd25ee0127 Add some doc comments 2021-08-17 08:02:19 -07:00
Daniel Imms f43ae16a47 Implement curved lines 2021-08-17 07:57:32 -07:00
Daniel Imms 2b3c763982 Clean up mixed weight characters 2021-08-17 07:42:47 -07:00
Daniel Imms 3aace14492 Merge branch 'master' into merogge/pixelPerfect 2021-08-17 07:15:03 -07:00
Daniel Imms 9cdb8067f7 Merge pull request #3421 from Tyriar/node_addons
Make serialize and unicode11 compatible with node
2021-08-17 05:57:06 -07:00
Daniel Imms cee8f6727f Merge branch 'master' into node_addons 2021-08-17 05:49:25 -07:00
Daniel Imms 7a2621f6ab Make serialize and unicode11 compatible with node
Fixes #3410
Fixes #3411
2021-08-17 05:48:52 -07:00
Daniel Imms 03c18368cc Merge pull request #3420 from Tyriar/readme
Call out xterm-headless in main readme
2021-08-17 04:55:27 -07:00
Daniel Imms 5ea91c4d69 Merge pull request #3419 from Tyriar/vscode98918
Remove role=document from terminal element
2021-08-17 04:55:20 -07:00
Daniel Imms 7c0a28eb59 Call out xterm-headless in main readme
Fixes #3412
2021-08-17 04:54:44 -07:00
Daniel Imms 936e0f80fa Merge branch 'master' into vscode98918 2021-08-17 04:50:51 -07:00
Daniel Imms 280ce85b74 Remove role=document from terminal element
Part of microsoft/vscode#98918
2021-08-17 04:50:29 -07:00
Daniel Imms cbe65f22ca Merge pull request #3418 from Tyriar/serialize_cleanup
Expose modes API and serialize modes in serialize addon
2021-08-17 04:49:07 -07:00
Daniel Imms c1b4f4edb1 Fix webgl access of core service 2021-08-17 04:41:44 -07:00
Daniel Imms 6c834aecb4 Serialize modes
Fixes #3417
2021-08-16 15:31:34 -07:00
Daniel Imms bef865e196 Add xterm api tests 2021-08-16 15:06:29 -07:00
Daniel Imms 8c25d57c26 xterm-headless modes api, tests for headless 2021-08-16 15:01:30 -07:00
Daniel Imms 6affe68ca3 Expose modes api
Part of #3417
2021-08-16 14:44:39 -07:00
Daniel Imms 012468785b Clean up serialize addon 2021-08-16 12:27:12 -07:00
Samuel 9a48975ba9 Merge branch 'master' into master 2021-08-16 11:58:44 -07:00
meganrogge 63dcd6ef7a Merge branch 'merogge/pixelPerfect' of https://github.com/meganrogge/xterm.js into merogge/pixelPerfect 2021-08-16 09:51:50 -07:00
meganrogge 62aa91a34d clean up 2021-08-16 09:51:20 -07:00
Megan Rogge 323317b49e Merge branch 'master' into merogge/pixelPerfect 2021-08-16 12:44:50 -04:00
meganrogge 7344d62e81 clean up 2021-08-16 09:39:44 -07:00
Daniel Imms 0286f0cb93 Merge pull request #3403 from Eugeny/master
Properly dispose of CursorBlinkStateManager
2021-08-16 05:00:18 -07:00
Eugeny b2121fde11 Merge branch 'master' into master 2021-08-15 22:54:13 +02:00
Daniel Imms 7549f0e924 Clean up 2021-08-13 15:09:04 -07:00
Daniel ImmsandMegan Rogge f83ebdbe67 Refactors and implementing new shapes
Co-authored-by: Megan Rogge <merogge@microsoft.com>
2021-08-13 14:53:45 -07:00
Daniel Imms 263e5b95cf Improve testing characters and use fake terminal for demo 2021-08-13 12:52:50 -07:00
meganrogge 6b7b279742 refactor 2021-08-13 10:47:30 -07:00
Daniel Imms cf411ad798 Merge pull request #3415 from Tyriar/benchmark_lint
Fix eslint errors in test/benchmark
2021-08-12 13:21:38 -07:00
Daniel Imms d8899b0b5d Fix eslint errors in test/benchmark 2021-08-12 12:59:22 -07:00
meganrogge 07513c927f more 2021-08-12 11:46:52 -07:00
meganrogge 9104385883 convert a bunch of them 2021-08-12 11:38:45 -07:00
meganrogge d13afa1b37 get things to work
Co-authored-by: Daniel Imms <daimms@microsoft.com>
2021-08-12 10:12:43 -07:00
Daniel Imms 0cbec2d0ec Merge pull request #3414 from Tyriar/fix_package_headless
Run yarn package before publishing
2021-08-12 07:21:13 -07:00
Daniel Imms 0a287e01ab Run yarn package before publishing
Part of #2749
2021-08-12 07:20:44 -07:00
Daniel Imms 51175437e9 Merge pull request #3413 from Tyriar/fix_npmignore
Include lib-headless in xterm-headless
2021-08-12 06:54:45 -07:00
Daniel Imms 7f5af09f52 Include lib-headless in xterm-headless
Part of #3212
2021-08-12 06:54:04 -07:00
Daniel Imms 930b1468a5 Merge pull request #3409 from cs01/cs01/add-termpair-gdbgui
add TermPair and gdbgui to real world uses list
2021-08-12 06:40:27 -07:00
Daniel Imms 46aa8de4f6 Merge pull request #3212 from joyceerhl/nodetarget
Create a node.js target for xterm.js
2021-08-12 06:19:37 -07:00
Daniel Imms d694febdbf Polish readme 2021-08-12 06:19:15 -07:00
Daniel Imms f45f5b4b9f Remove unneeded manual headless test 2021-08-12 06:11:30 -07:00
Daniel Imms f8066d6dee Full tests for xterm-headless 2021-08-12 06:00:55 -07:00
Daniel Imms df48e9f6d5 Headless event tests 2021-08-12 05:52:40 -07:00
Daniel Imms e96b472ede Start on headless unit tests 2021-08-12 05:35:20 -07:00
Daniel Imms ec80fc9421 Remove alpha from package.json version 2021-08-12 04:51:49 -07:00
Daniel Imms 7e7dcd9917 Move node-test into headless folder 2021-08-12 04:51:29 -07:00
Daniel Imms 6ac7e181c5 Merge branch 'master' into nodetarget 2021-08-12 04:48:06 -07:00
Chad Smith 34d6449d07 add TermPair and gdbgui to real world uses list 2021-08-11 23:40:25 -07:00
meganrogge 46cb37876d more working 2021-08-11 15:46:50 -07:00
meganrogge 927a142480 add more 2021-08-11 15:36:29 -07:00
meganrogge 57c7e6af47 add more characters 2021-08-11 15:19:12 -07:00
meganrogge db845a6f24 make a bunch of progress 2021-08-11 14:14:17 -07:00
meganrogge 12980fbe0c start from scratch, some progress 2021-08-11 11:18:47 -07:00
Daniel Imms dda4904d9d Fix xterm-headless lib file 2021-08-11 06:42:00 -07:00
Daniel Imms 454c2726a5 Add npmignore 2021-08-11 06:30:15 -07:00
Daniel Imms be16429317 Remove unused compile script 2021-08-11 06:23:42 -07:00
Daniel Imms 370d0c562a Copy logo-full.png 2021-08-11 06:19:56 -07:00
Daniel Imms 3e9b1bafbe Remove force publish changes 2021-08-11 06:10:12 -07:00
Daniel Imms 5affa70097 Undo headless force publish changes 2021-08-11 06:06:06 -07:00
Daniel Imms b6e5430702 Revert "Force publish"
This reverts commit f685d8eb21.
2021-08-11 06:04:48 -07:00
Daniel Imms 97fa4a59b7 Package headless in release step 2021-08-11 05:54:37 -07:00
Daniel Imms f685d8eb21 Force publish 2021-08-11 04:52:37 -07:00
Daniel Imms 83ddf0fc4b Force publish of headless even in PR 2021-08-11 04:46:27 -07:00
Daniel Imms a54a4321ea Merge pull request #3408 from xtermjs/dependabot/npm_and_yarn/path-parse-1.0.7
Bump path-parse from 1.0.6 to 1.0.7
2021-08-11 04:01:41 -07:00
dependabot[bot] 69ce01c794 Bump path-parse from 1.0.6 to 1.0.7
Bumps [path-parse](https://github.com/jbgutierrez/path-parse) from 1.0.6 to 1.0.7.
- [Release notes](https://github.com/jbgutierrez/path-parse/releases)
- [Commits](https://github.com/jbgutierrez/path-parse/commits/v1.0.7)

---
updated-dependencies:
- dependency-name: path-parse
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-08-10 22:58:26 +00:00
meganrogge ee759e245d line up vertically 2021-08-10 13:41:24 -07:00
Daniel Imms 6aa0782ce3 Publish dry run in package_headless 2021-08-10 13:14:05 -07:00
Daniel Imms 15bd827c2e Improve headless packaging 2021-08-10 12:57:45 -07:00
Daniel Imms 4a200e59ac Create headless package script 2021-08-10 12:50:22 -07:00
Daniel Imms f769d03e0c xterm-core -> xterm-headless 2021-08-10 12:27:43 -07:00
Daniel Imms 468f419a58 Merge branch 'master' into nodetarget 2021-08-10 12:21:59 -07:00
meganrogge 3b8218e36e work with Daniel 2021-08-10 10:03:38 -07:00
Daniel Imms 6dbdd03815 Merge pull request #3405 from alur/master
Add missing escape
2021-08-10 05:36:00 -07:00
Daniel Imms f43fcafc99 Move addon manager to common and use in xterm-core 2021-08-10 05:08:03 -07:00
Daniel Imms b15fbec69c Merge pull request #3407 from DingoEatingFuzz/patch-1
Add HashiCorp Nomad to the real world uses list
2021-08-10 04:51:53 -07:00
Michael Lange a48cffd4ab Add HashiCorp Nomad to the real world uses list
The equivalent of https://github.com/xtermjs/xtermjs.org/pull/149 but in the right place this time 🙂
2021-08-09 18:53:04 -07:00
meganrogge 3b3ef09d87 keep working 2021-08-09 14:52:47 -07:00
meganrogge e01e9c9584 start on #130406 2021-08-09 14:23:09 -07:00
Erik Welander 4c4160ae03 Add missing escape
Previously the \ was unnecessarily escaping the ';'.
2021-08-04 11:31:32 -07:00
Eugene Pankov 8c94980315 Properly dispose of CursorBlinkStateManager 2021-07-31 18:53:23 +02:00
Samuel Sampson 732927b0e2 Add back IDisposable implementation in TimeBasedDebouncer for the ease of interoperability with RenderDebouncer 2021-07-29 20:20:02 +00:00
Samuel Sampson 4a9ecb33bd Remove unneeded dispose method from TimeBasedDebouncer 2021-07-29 19:58:59 +00:00
Samuel Sampson e82f475c9b Separate the regular render debouncer from a Time-Based debouncer to be used to update Screen Readers 2021-07-29 19:57:51 +00:00
Samuel Sampson 12e1e422e3 Update RenderDebouncer to update screen readers once per second instead of once per animation frame. 2021-07-29 19:35:14 +00:00
Puneethnaik dab73aa263 update the viewportElement style width upon refreshing of the viewport to accomodate changes in scrollBarWidth 2021-07-28 15:56:26 +00:00
Daniel Imms a6e6c07a3c Add first headless test 2021-07-22 16:23:04 -07:00
Daniel Imms 40992a7f76 Update node test 2021-07-22 16:18:07 -07:00
Daniel Imms 01babd1491 Do a pass of headless/Terminal members 2021-07-22 16:11:56 -07:00
Daniel Imms 2191d1a16a Output commonjs for headless 2021-07-22 16:01:20 -07:00
Daniel Imms 9b2e511bcb Fix webpack headless 2021-07-22 15:57:21 -07:00
Daniel Imms 7117951233 Output headless to out/headless 2021-07-22 15:52:27 -07:00
Daniel Imms 61ede1cc0d Update instructions to run 2021-07-22 15:41:24 -07:00
Daniel Imms df17288c1e Create headless project, fix compile 2021-07-22 15:40:13 -07:00
Daniel Imms 625e17ee1c Fix some errors, move TerminalCore to common/ 2021-07-22 15:30:55 -07:00
Daniel Imms 91c7032aac Use core terminal in common, remove duplicate classes 2021-07-22 15:17:28 -07:00
Daniel Imms 238f6ab011 Merge branch 'master' into nodetarget 2021-07-22 14:54:53 -07:00
Daniel Imms 522c8bf3af Merge pull request #3214 from joyceerhl/api-view-classes
Move API view classes under common/public
2021-07-22 14:53:27 -07:00
Daniel Imms a14bb3ead3 Add buffer back to ITerminal 2021-07-22 14:45:39 -07:00
Daniel Imms cb284447c8 Clean up imports 2021-07-22 14:44:49 -07:00
Daniel Imms 41e4de35e9 common/public use ICoreTerminal instead of concrete 2021-07-22 14:42:11 -07:00
Daniel Imms a5c6252ff5 Remove browser references from common/public 2021-07-22 14:36:45 -07:00
Daniel Imms d3e42e342d Merge remote-tracking branch 'ups/master' into pr/joyceerhl/3214 2021-07-22 14:33:17 -07:00
Daniel Imms 5a4b305a68 Merge pull request #3393 from Tyriar/tyriar/less_than
Use less than instead of non-integer check
2021-07-16 13:26:26 -07:00
Daniel Imms 0ceeb087e9 Use less than instead of non-integer check
Part of #3391
2021-07-16 13:25:56 -07:00
Daniel Imms 6f838091c3 Merge pull request #3392 from Tyriar/tyriar/3391
Fix cursor ghosting in canvas/webgl renderers
2021-07-16 10:18:42 -07:00
Daniel Imms cec71efc3e Merge branch 'master' into tyriar/3391 2021-07-16 10:10:20 -07:00
Daniel Imms 361998bd34 Fix cursor ghosting in canvas/webgl renderers
Fixes #3391
2021-07-16 10:08:36 -07:00
Daniel Imms 6ae71adcac Merge pull request #3387 from Tyriar/space_char_fix
Fix underline/strikethrough on space chars
2021-07-09 13:53:35 -07:00
Daniel Imms 8ec3368636 Fix underline/strikethrough on space chars 2021-07-09 08:17:52 -07:00
Daniel Imms e746cd55dd Merge pull request #3386 from Tyriar/webgl_line
Support underline and strikethrough in WebGL renderer
2021-07-09 07:55:57 -07:00
Daniel Imms 1f019093fc Update SGR support table 2021-07-09 07:48:26 -07:00
Daniel Imms aad6f8147e Increase underline/strikethrough width with font size 2021-07-09 07:44:10 -07:00
Daniel Imms bc702d28ff Support webgl strikethrough, fix underline position
Fixes #580
2021-07-09 07:39:19 -07:00
Daniel Imms 96dd021ca4 Support underline in webgl renderer
Fixes #2251
2021-07-09 07:32:25 -07:00
Daniel Imms c8e7edf11d Merge pull request #3384 from Tyriar/underline
Set underlined style support to partial
2021-07-09 07:09:14 -07:00
Daniel Imms d8c790cb7a Set underlined style support to partial
Feature tracked in #2251
2021-07-09 07:05:46 -07:00
Daniel Imms 29ddee4869 Merge pull request #3343 from silamon/strikethrough
Support strikethrough text style
2021-07-09 07:03:02 -07:00
Daniel Imms a2352f345b Merge branch 'master' into strikethrough 2021-07-09 06:53:05 -07:00
Daniel Imms fa8a4853f7 Clarify crossed out = strikethrough 2021-07-09 06:52:54 -07:00
Daniel Imms a1f65a2f77 Update crossed out characters support to partial 2021-07-09 06:52:28 -07:00
Daniel Imms a554d3291f Merge pull request #3372 from Puneethnaik/issue_3319_fix
Scrollbar still visible after clearing screen and scrollback buffer with escape sequence #3319
2021-07-09 06:46:48 -07:00
Puneethnaik 6f159aa2fc Merge branch 'master' of https://github.com/xtermjs/xterm.js into issue_3319_fix 2021-07-09 07:56:13 +05:30
Daniel Imms a78e07eda9 Merge pull request #3371 from LabhanshAgrawal/fonts
Fix navigator types issue
2021-07-08 06:01:26 -07:00
Puneethnaik b7e96f9e54 attach a listener to inputHandler._onScroll in CoreTerminal 2021-06-20 03:31:12 +00:00
Labhansh Agrawal c7ced80092 Fix navigator types issue 2021-06-18 08:27:43 +05:30
Daniel Imms 094bcbd81d v0.5.1 2021-06-15 04:02:35 -07:00
Megan Rogge 19bd040453 Update xterm version to 4.13.0 2021-06-11 14:18:04 -07:00
jerch a11ba0a89c Merge pull request #3365 from jerch/fix_3362
allow cursor to stick at cols in ED/EL
2021-06-11 16:07:10 +02:00
Daniel Imms 2134e050cb Merge branch 'master' into fix_3362 2021-06-11 04:48:33 -07:00
Daniel Imms e2ed603ab8 Merge pull request #3366 from angerson/patch-1
Remove Shellvault
2021-06-10 12:11:44 -07:00
Austin Anderson ff3781a431 Remove Shellvault
Shellvault was shut down a while ago and the link is dead.
2021-06-10 12:01:03 -07:00
Jörg Breitbart 55cd97ec68 remove left over only test clause 2021-06-10 10:42:27 +02:00
Jörg Breitbart 2f0a4fc5c0 allow cursor to stick at cols in ED/EL 2021-06-10 10:35:58 +02:00
Daniel Imms 6dd61f96c0 Merge pull request #3363 from meganrogge/bufferRange
+1 to rangeLength when on a wrapped line to match standard behavior
2021-06-09 06:17:59 -07:00
Megan Rogge fdae61380c Merge branch 'master' into bufferRange 2021-06-08 16:53:28 -05:00
meganrogge 09acb2b5f9 +1 to rangeLength when on a wrapped line to match standard behavior 2021-06-08 14:52:05 -07:00
Daniel Imms 19c9fef8e2 Merge pull request #3361 from xtermjs/dependabot/npm_and_yarn/glob-parent-5.1.2
[Security] Bump glob-parent from 5.1.1 to 5.1.2
2021-06-08 03:26:53 -07:00
dependabot-preview[bot] c335682559 [Security] Bump glob-parent from 5.1.1 to 5.1.2
Bumps [glob-parent](https://github.com/gulpjs/glob-parent) from 5.1.1 to 5.1.2. **This update includes a security fix.**
- [Release notes](https://github.com/gulpjs/glob-parent/releases)
- [Changelog](https://github.com/gulpjs/glob-parent/blob/main/CHANGELOG.md)
- [Commits](https://github.com/gulpjs/glob-parent/compare/v5.1.1...v5.1.2)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2021-06-08 07:00:20 +00:00
Daniel Imms 96806ee384 Merge pull request #3341 from coderaiser/feature/lint-with-putout
chore: lint using putout
2021-06-04 13:03:30 -07:00
Daniel Imms 096655a3eb Merge branch 'master' into feature/lint-with-putout 2021-06-04 12:39:25 -07:00
Daniel Imms a9788c364d Merge pull request #3360 from Tyriar/eslint_as
Add eslint type assertions rule
2021-06-04 12:29:12 -07:00
Daniel Imms f593d14823 Fix tests, add explanatory comment 2021-06-04 12:21:15 -07:00
coderaiser 6801793547 chore: inc -> prefix -> postfix 2021-06-04 19:37:25 +03:00
Daniel Imms 8a9d843ad7 Merge branch 'master' into strikethrough 2021-06-04 06:44:32 -07:00
Daniel Imms f52d631fa4 Add eslint type assertions rule
Fixes #3359
2021-06-04 06:23:12 -07:00
Daniel Imms ca70d7b6b4 Merge branch 'master' into feature/lint-with-putout 2021-06-04 06:11:52 -07:00
jerch fe1d2f6af1 Merge pull request #3354 from xtermjs/dependabot/npm_and_yarn/ws-7.4.6
Bump ws from 7.4.5 to 7.4.6
2021-05-29 10:41:04 +02:00
dependabot[bot] 64b37ef518 Bump ws from 7.4.5 to 7.4.6
Bumps [ws](https://github.com/websockets/ws) from 7.4.5 to 7.4.6.
- [Release notes](https://github.com/websockets/ws/releases)
- [Commits](https://github.com/websockets/ws/compare/7.4.5...7.4.6)

Signed-off-by: dependabot[bot] <support@github.com>
2021-05-29 03:56:37 +00:00
Daniel Imms 654a0a671d Merge pull request #3352 from SquitchYT/master
Add Tess to Readme
2021-05-24 11:56:20 -07:00
Daniel Imms dc1b1b4537 Update README.md 2021-05-24 11:55:53 -07:00
Squitch 579e43c4cc Update README.md 2021-05-24 20:30:33 +02:00
Squitch f8d8288e0e Update README.md 2021-05-24 20:27:07 +02:00
coderaiser ea7f89842a chore: browser: Linkifier: increment 2021-05-17 17:45:35 +03:00
coderaiser 91a79e9dee Merge branch 'master' into feature/lint-with-putout 2021-05-17 17:30:45 +03:00
Daniel Imms a385ab0802 Merge pull request #3344 from silamon/devcontainerupdate
devcontainer: upgrade to node v14.x
2021-05-17 06:32:33 -07:00
Daniel Imms 9175a220be Merge pull request #3345 from tsadarsh/master
Including my project in "Real-World uses" section of README.md
2021-05-17 03:58:40 -07:00
Adarsh TS 0131fc9ebf Update README.md 2021-05-16 09:39:18 +05:30
Simon Lamon d19a740825 Support strikethrough text style 2021-05-15 17:35:23 +02:00
Simon Lamon c9eab73bd4 devcontainer: upgrade to node v14.x 2021-05-15 16:06:30 +02:00
coderaiser e42e20f776 Merge branch 'master' into feature/lint-with-putout 2021-05-15 01:24:29 +03:00
coderaiser d046e2a77f chore: lint using putout 2021-05-15 01:17:17 +03:00
Daniel Imms 873dd76167 Merge pull request #3337 from LabhanshAgrawal/fonts
Update local fonts query
2021-05-13 08:48:23 -07:00
Labhansh Agrawal 6824d6f4df Merge branch 'master' into fonts 2021-05-13 18:15:46 +05:30
Labhansh Agrawal 74edc6fdd4 Add navigator typing for fonts access 2021-05-13 18:12:07 +05:30
Daniel Imms 99926e67cd Merge pull request #3338 from xtermjs/Tyriar-patch-1
v0.11.1 webgl - types fix
2021-05-13 05:09:59 -07:00
Daniel Imms de712d9b43 v0.11.1 webgl - types fix 2021-05-13 05:09:39 -07:00
Labhansh Agrawal 01f68c50ed Update local fonts query 2021-05-13 12:55:46 +05:30
jerch 840774c034 Merge pull request #3334 from UziTech/patch-2
chore(types): use IEvent from xterm
2021-05-12 11:31:14 +02:00
jerch da898b7efa Merge branch 'master' into patch-2 2021-05-12 11:22:40 +02:00
jerch d4f4e19056 Merge pull request #3330 from jerch/update_dependencies
update package dependencies, switch to node v14
2021-05-12 11:19:43 +02:00
Jörg Breitbart 94ba682b57 Merge branch 'master' into update_dependencies 2021-05-12 11:06:44 +02:00
Jörg Breitbart 8cc6df1f70 upgrade to node v14.x 2021-05-12 10:59:33 +02:00
Tony Brix 7a0b462e7e chore(types): use IEvent from xterm 2021-05-11 20:33:58 -05:00
Daniel Imms 24f431b290 Merge pull request #3333 from Tyriar/v4_12
v4.12.0
2021-05-11 07:11:42 -07:00
Daniel Imms 0c47cd4d4d v4.12.0 2021-05-11 07:09:41 -07:00
Daniel Imms 33f0105d29 Merge pull request #3332 from Tyriar/scroll_refresh
Make sure all rows are refreshed on input
2021-05-11 07:02:41 -07:00
Daniel Imms bcbcc71bfc Trigger scroll when ignoring event
This is a less aggressive fix that doesn't regress tests
2021-05-11 06:52:04 -07:00
Daniel Imms da39129bd2 Revert "Make sure all rows are refreshed on input"
This reverts commit 973eb7b350.
2021-05-11 06:48:17 -07:00
Daniel Imms 973eb7b350 Make sure all rows are refreshed on input
Fixes #3323
2021-05-11 05:10:04 -07:00
Jörg Breitbart d229b775ab Merge branch 'master' into update_dependencies 2021-05-10 23:12:29 +02:00
Jörg Breitbart e91d90dbbc switch to 12.x for CI 2021-05-10 23:04:06 +02:00
Jörg Breitbart 8c65fcacb3 flip eslint rule to reflect code base 2021-05-10 22:56:30 +02:00
Jörg Breitbart e55b9fe922 update package dependencies 2021-05-10 22:35:05 +02:00
Daniel Imms 8f9f2550ef Merge pull request #3327 from Tyriar/tasks
Don't start npm start until watch is done
2021-05-10 05:36:54 -07:00
Daniel Imms 673695068e Don't start npm start until watch is done 2021-05-10 05:35:06 -07:00
Daniel Imms 07c123309f Merge pull request #3322 from vladdoster/patch-1
(docs) update README.md
2021-05-07 04:59:19 -07:00
vlad doster 6a63be9107 (docs) update README.md
- correct spelling
- correct grammar
- reduce verbiage
- correct punctuation
2021-05-07 04:38:12 -05:00
Daniel Imms 978b2a9886 Merge pull request #3321 from xtermjs/dependabot/npm_and_yarn/addons/xterm-addon-ligatures/lodash-4.17.21
Bump lodash from 4.17.19 to 4.17.21 in /addons/xterm-addon-ligatures
2021-05-06 14:18:43 -07:00
dependabot[bot] d1ce8a0586 Bump lodash from 4.17.19 to 4.17.21 in /addons/xterm-addon-ligatures
Bumps [lodash](https://github.com/lodash/lodash) from 4.17.19 to 4.17.21.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.19...4.17.21)

Signed-off-by: dependabot[bot] <support@github.com>
2021-05-06 21:08:45 +00:00
Daniel Imms dc61950311 Merge pull request #3320 from xtermjs/dependabot/npm_and_yarn/lodash-4.17.21
[Security] Bump lodash from 4.17.20 to 4.17.21
2021-05-06 14:07:58 -07:00
dependabot-preview[bot] 0ba0845563 [Security] Bump lodash from 4.17.20 to 4.17.21
Bumps [lodash](https://github.com/lodash/lodash) from 4.17.20 to 4.17.21. **This update includes a security fix.**
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.20...4.17.21)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2021-05-06 20:39:34 +00:00
Daniel Imms d70ba1dc23 Merge pull request #3318 from meganrogge/bufferRange
Fix selection range
2021-05-06 05:02:25 -07:00
meganrogge 9403478049 tweak test 2021-05-05 19:49:33 -07:00
meganrogge 20f320327c Tweak range 2021-05-05 19:45:36 -07:00
meganrogge 9a5f85cc24 remove .only 2021-05-05 12:29:55 -07:00
meganrogge be32590ef0 fix #https://github.com/microsoft/vscode/issues/123000 2021-05-05 08:21:26 -07:00
Daniel Imms e5c9b0e27f Merge pull request #3314 from jeanp413/fix-undefined-soundService
Fixes undefined soundService
2021-05-04 10:39:25 -07:00
jeanp413 58527bb2ff Fixes undefined soundService 2021-05-04 11:23:31 -05:00
Daniel Imms 4a7bbe6738 Merge pull request #3313 from Tyriar/scroll_suppress
Ensure all viewport scroll events are handled by buffer
2021-04-28 13:00:38 -07:00
Daniel Imms d616aaa4f8 Ensure all viewport scroll events are handled by buffer
Some scroll events were never making it to the buffer service when using
a trackpad.

See microsoft/vscode#121017
Fixes #3311
2021-04-28 12:51:40 -07:00
Daniel Imms 376b29673b Merge pull request #3296 from Tyriar/asserttimeout
Don't assert on leave events
2021-04-06 14:05:39 -07:00
Daniel Imms 636e59b949 Merge branch 'master' into asserttimeout 2021-04-06 13:15:01 -07:00
Daniel Imms e3615f63e0 Merge pull request #3299 from bmf-ribeiro/feat/enable-windows-integration-tests
Feat/enable windows integration tests
2021-04-06 13:07:01 -07:00
Daniel Imms 0b02a3c774 Merge branch 'master' into asserttimeout 2021-04-06 13:00:42 -07:00
Daniel Imms 87ef131945 Merge branch 'master' into feat/enable-windows-integration-tests 2021-04-06 12:59:16 -07:00
Daniel Imms 6370a936b8 Merge pull request #3298 from bmf-ribeiro/feat/2714-start-server-on-integration-tests
Fix #2714 - Start demo server before running integration tests
2021-04-06 12:58:31 -07:00
Bruno Ribeiro e686527f87 Try to enable windows integration tests 2021-04-06 17:47:12 +01:00
Bruno Ribeiro c312317af0 Merge branch 'master' into feat/2714-start-server-on-integration-tests 2021-04-06 17:34:01 +01:00
Bruno Ribeiro b8af0ecf05 Await for server to be fully started 2021-04-06 17:21:39 +01:00
Bruno Ribeiro ab659fda5f Remove Start Test Server from pipelines as it now starts in the tests 2021-04-06 16:51:17 +01:00
Bruno Ribeiro 27c87319c1 Fix #2714 - Start demo server before running integration tests
- Use port 3001 instead of  300 to not conflict with the demo
2021-04-06 15:48:48 +01:00
Daniel Imms 5d4cea2119 Merge branch 'master' into asserttimeout 2021-04-06 07:07:58 -07:00
Daniel Imms 26cda84eab Merge pull request #3295 from xtermjs/Tyriar-patch-1
Fix whitespace
2021-04-06 06:50:22 -07:00
Daniel Imms c4fc90e3f8 Don't assert on leave events 2021-04-06 06:50:07 -07:00
Daniel Imms 985040e1cc Merge branch 'master' into Tyriar-patch-1 2021-04-06 06:34:52 -07:00
Daniel Imms 95fcf2f354 Merge pull request #3294 from Tyriar/pollfor
Give actionable error when pollFor times out
2021-04-06 06:34:43 -07:00
Daniel Imms 1422d935d0 Fix whitespace 2021-04-06 06:34:20 -07:00
Daniel Imms 5973e8f0dd Remove unused function 2021-04-06 06:28:49 -07:00
Daniel Imms 934467d484 Give actionable error when pollFor times out 2021-04-06 06:27:39 -07:00
Daniel Imms eedc314ca2 Merge pull request #3290 from nikonso/feat/3014-add-onBell-event-listener
Fix #3014  - Add onBell event listener to allow embeders to hook into it
2021-04-06 05:56:43 -07:00
Daniel Imms 2556137b51 Remove only 2021-04-06 05:44:36 -07:00
Daniel Imms 826493161a Api polish 2021-04-06 05:40:56 -07:00
Daniel Imms 19716957e4 Fix api test 2021-04-06 05:40:29 -07:00
Daniel Imms 3d29357d1d Fix test/lint 2021-04-06 05:34:20 -07:00
Daniel Imms 9143cf4904 Merge branch 'master' into feat/3014-add-onBell-event-listener 2021-04-06 05:28:29 -07:00
Daniel Imms c7978f78a2 Merge pull request #3291 from whattheserver/patch-1
Update README.md
2021-04-06 05:25:57 -07:00
WhatTheServer af26e27e23 Update README.md 2021-04-04 00:09:10 -04:00
Bruno Ribeiro 7b7b7a36db Fix #3014
- Add onBell event listener to allow embeders to hook into it
2021-04-03 16:34:00 +01:00
Daniel Imms 8a1d2cf0f4 Whitespace change to force ligatures publish 2021-04-02 14:22:49 -07:00
Daniel Imms 98f2576497 Merge pull request #3286 from Tyriar/webgl_liga
Webgl ligature support
2021-04-02 11:39:45 -07:00
Daniel Imms 00fa7bec42 Null out secondary ligature chars in cache 2021-04-02 11:32:02 -07:00
Daniel Imms b03a25f4c7 Webgl ligature support
Fixes #3094
2021-04-02 11:15:57 -07:00
Daniel Imms cf8a3514bc Merge pull request #3285 from Tyriar/dom_ligatures
Support for ligatures in DOM renderer
2021-04-02 10:45:41 -07:00
Daniel Imms b7320794bb Ligature support for dom renderer
Fixes #3283
2021-04-02 10:34:14 -07:00
Daniel Imms d8113aa533 Default to Fira Code in demo 2021-04-02 10:21:20 -07:00
Daniel Imms 16a70bdeb7 Merge pull request #3264 from LabhanshAgrawal/local_fonts
Enable using ligatures addon outside electron
2021-04-02 09:43:34 -07:00
Daniel Imms 0564ad4653 Add comments to webpack configs 2021-04-02 09:42:10 -07:00
Daniel Imms a57910e2fa Check navigator safely in node 2021-04-02 08:33:00 -07:00
Daniel Imms 4fa58c978b Fix lint 2021-04-02 07:58:59 -07:00
Daniel Imms 54cdbb3035 Merge branch 'master' into local_fonts 2021-04-02 07:57:51 -07:00
Daniel Imms 367eb45586 Merge pull request #3284 from Tyriar/tyriar/charJoinerService
Convert CharacterJoinerRegistry to a service
2021-04-02 07:57:38 -07:00
Daniel Imms 1124f8b513 Improve error when adding char joiner before open 2021-04-02 07:57:24 -07:00
Daniel Imms 233b78d258 Merge pull request #3282 from Tyriar/spacing_eslint
Require spacing of objects outside of test files
2021-04-02 07:52:37 -07:00
Daniel Imms 5f96f33cf0 Fix dangling comma 2021-04-02 07:50:22 -07:00
Daniel Imms b511f1b52a Fix other case of no spacing 2021-04-02 07:46:30 -07:00
Daniel Imms 6b3fd2f31a Merge branch 'master' into spacing_eslint 2021-04-02 07:45:31 -07:00
Daniel Imms 6501ab044c Merge branch 'master' into tyriar/charJoinerService 2021-04-02 07:44:28 -07:00
Daniel Imms 999116c536 Convert CharacterJoinerRegistry to a service
Part of #3094, #3283
2021-04-02 07:43:39 -07:00
Daniel Imms f19d7f0b70 Require spacing of objects outside of test files 2021-04-02 07:08:06 -07:00
Daniel Imms 1e499c7867 Disable ligatures addon by default
Since it needs a user action to trigger the permissions prompt
2021-04-02 06:57:11 -07:00
Daniel Imms b9d3deae56 Merge branch 'master' into local_fonts 2021-04-02 06:42:48 -07:00
Daniel Imms 0421f6ebcf Merge pull request #3279 from Tyriar/tyriar/powerline
Don't pad powerline glyphs and use ideographic text baseline
2021-04-01 17:45:44 -07:00
Daniel Imms a510ffb3c0 Use ideographic baseline for canvas 2021-04-01 17:10:14 -07:00
Daniel Imms 62f5b8291d Use ideographic over middle
This seems to correctly center the glyphs within the cell, making powerline
fonts appear perfectly aligned.

Fixes #3281
2021-04-01 17:05:29 -07:00
Megan Rogge f9945b3439 Merge pull request #3276 from meganrogge/webgl
add onContextLoss to webgl addon
2021-04-01 17:02:57 -07:00
Daniel Imms 62ca4b7e13 Correct restricted glyph padding 2021-04-01 17:00:02 -07:00
Megan Rogge 6f59b56958 Merge branch 'master' into webgl 2021-04-01 16:57:21 -07:00
meganrogge 113086f97a tweak readme, expose API 2021-04-01 16:57:19 -07:00
Daniel Imms b6d3120086 Restrict all sides of powerline glyphs 2021-04-01 16:55:27 -07:00
Daniel Imms 117f990055 Don't pad powerline glyphs
Fixes #3278
2021-04-01 16:39:59 -07:00
Daniel Imms 337c11d580 Merge pull request #3277 from meganrogge/bufferService
Move scroll to BufferService
2021-04-01 16:01:30 -07:00
Daniel Imms ecb485d2f5 Remove _cachedBlankLine 2021-04-01 15:54:47 -07:00
Daniel Imms c5339116b5 Add note to readme on handling context loss 2021-04-01 15:45:10 -07:00
meganrogge b1877b4dc7 onRecoverContext -> onContextLoss 2021-04-01 13:32:35 -07:00
meganrogge 1c087f6bcd cherry picked commits and scrollSource 2021-04-01 13:24:02 -07:00
meganrogge e40f340036 use bufferService methods 2021-04-01 13:18:20 -07:00
meganrogge c096633f91 get tests to pass
Co-authored-by: Daniel Imms <daimms@microsoft.com>
2021-04-01 13:17:53 -07:00
meganrogge b074c2552a use bufferService in terminal to reduce code a bunch 2021-04-01 13:17:48 -07:00
meganrogge e2885f3a38 move stuff to inputHandler and move scroll to bufferService 2021-04-01 13:16:39 -07:00
Megan Rogge 47ac4dd9fe Merge branch 'master' into webgl 2021-04-01 12:18:21 -07:00
meganrogge fa7889cdd0 add onRecovercontext event 2021-04-01 12:17:34 -07:00
Daniel Imms 759379dfb1 Merge pull request #3205 from schrej/fix/scroll-events
emit onScroll events when user is scrolling
2021-04-01 12:11:47 -07:00
Daniel Imms 87ed4f07cd Tidy up syncTextArea 2021-04-01 12:05:24 -07:00
Daniel Imms 07499380d1 Remove RenderService check, it must be passed to ctor 2021-04-01 12:05:21 -07:00
Daniel Imms 3ae61fc1dd Use buffer y for relative cursor pos 2021-04-01 12:05:17 -07:00
Daniel Imms 085a2b545f Remove now unneeded CharSizeService 2021-04-01 12:05:12 -07:00
kena0ki 49cb6f74bc Remove non-null assertion 2021-04-01 12:05:05 -07:00
kena0ki d4a93fe7e4 Use RenderService.dimensions instead of CharSizeService for textarea position 2021-04-01 12:05:00 -07:00
Daniel Imms f4861aa74a Move onScroll next to _onScroll 2021-04-01 08:54:29 -07:00
Daniel Imms 1925f1f442 Improve whitespace 2021-04-01 08:41:29 -07:00
Daniel Imms 6355852d09 Merge remote-tracking branch 'ups/master' into pr/schrej/3205 2021-04-01 08:36:14 -07:00
Daniel Imms e1641c57fc Merge pull request #3169 from kena0ki/issue#3024
Use RenderService.dimensions instead of CharSizeService for textarea position
2021-04-01 08:34:39 -07:00
Daniel Imms 7b920adbe9 Tidy up syncTextArea 2021-04-01 08:26:35 -07:00
Daniel Imms 05516750b5 Remove RenderService check, it must be passed to ctor 2021-04-01 08:23:35 -07:00
Daniel Imms b62a64519f Use buffer y for relative cursor pos 2021-04-01 08:23:18 -07:00
Daniel Imms e9e716964e Remove now unneeded CharSizeService 2021-04-01 08:19:12 -07:00
kena0ki a15f61bb85 Remove non-null assertion 2021-04-01 00:11:30 +09:00
kena0ki de334ea116 Use RenderService.dimensions instead of CharSizeService for textarea position 2021-03-31 23:32:53 +09:00
Daniel Imms dd952ac166 Merge pull request #3230 from marvinthepa/682_url_selector
Use linkifier2 to double click select links
2021-03-30 06:25:26 -07:00
Daniel Imms d2471b4301 Update src/browser/Types.d.ts 2021-03-30 06:19:39 -07:00
Daniel Imms 04092c80e8 Update src/browser/services/SelectionService.ts 2021-03-30 06:19:34 -07:00
Daniel Imms b7e0489a53 Remove offset from link
The link range is absolute, not relative to viewport
2021-03-30 06:18:17 -07:00
Daniel Imms daf1128df7 Merge branch 'master' into 682_url_selector 2021-03-30 05:34:38 -07:00
Daniel Imms 36c436d4e0 Merge pull request #3273 from Tyriar/webgl_stable
Remove experimental note from webgl renderer
2021-03-30 05:33:59 -07:00
Daniel Imms edf783f155 Merge pull request #3270 from xtermjs/dependabot/npm_and_yarn/y18n-4.0.1
[Security] Bump y18n from 4.0.0 to 4.0.1
2021-03-30 05:16:20 -07:00
Daniel Imms dafe7ea3da Remove experimental note from webgl renderer
Fixes #2033
2021-03-30 04:45:41 -07:00
dependabot-preview[bot] cbe056c27e [Security] Bump y18n from 4.0.0 to 4.0.1
Bumps [y18n](https://github.com/yargs/y18n) from 4.0.0 to 4.0.1. **This update includes a security fix.**
- [Release notes](https://github.com/yargs/y18n/releases)
- [Changelog](https://github.com/yargs/y18n/blob/master/CHANGELOG.md)
- [Commits](https://github.com/yargs/y18n/commits)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2021-03-29 18:46:53 +00:00
Daniel Imms e78561b7b6 Merge pull request #3269 from coderaiser/lint-using-putout
Lint using putout part 3
2021-03-29 02:07:47 -07:00
coderaiser de791cfb35 chore: lint using putout 2021-03-28 15:27:03 +03:00
Daniel Imms 6d1a2f3b57 Merge pull request #3267 from jerch/writeSync_fix
writeSync callstack fix
2021-03-26 05:10:35 -07:00
Jörg Breitbart e106cd02cf fix callstack overflow of writeSync 2021-03-26 12:46:39 +01:00
Thomas Zilz 787ef30279 Revert "Noch mehr änderungen"
This reverts commit b666e6a924.
2021-03-25 14:09:47 +01:00
Thomas Zilz 61519cc10c Revert "Test test"
This reverts commit 61213159ee.
2021-03-25 14:09:44 +01:00
Thomas Zilz b666e6a924 Noch mehr änderungen 2021-03-25 13:59:20 +01:00
Thomas Zilz 61213159ee Test test 2021-03-25 13:55:29 +01:00
Labhansh Agrawal 67934ff35f Enable using ligatures addon outside electron 2021-03-24 20:44:14 +05:30
Daniel Imms 8c1833f117 Merge pull request #3258 from dstein64/master
Add gifcast to "Real-world uses" in README.md
2021-03-19 18:10:37 -07:00
Daniel Steinberg c2f4c4dd32 Add gifcast to "Real-world uses" in README.md 2021-03-19 18:40:36 -04:00
Daniel Imms c4ed61e831 Merge pull request #3257 from xtermjs/Tyriar-patch-1
Change warning to one per session not terminal
2021-03-19 06:32:45 -07:00
Daniel Imms 5c5ca4e213 Change warning to one per session not terminal 2021-03-19 06:24:42 -07:00
Daniel Imms 6edfa513f3 Merge pull request #3256 from jerch/silence_writeSync_warning
silence writeSync warning
2021-03-19 06:02:40 -07:00
Jörg Breitbart 5170a12cb1 silence writeSync warning 2021-03-19 01:07:43 +01:00
Daniel Imms 9a90f1f4ab Merge pull request #3251 from Python-37/Fix-bug-of-some-IMEs-cannot-input-in-terminal
Fix bug of some IMEs cannot input in terminal
2021-03-18 08:49:56 -07:00
Daniel Imms 886f285ab7 Remove old setting dimensions 2021-03-18 08:31:13 -07:00
Daniel Imms 00bdd2805a Ensure in js that the textarea is always > 1x1px 2021-03-18 08:14:27 -07:00
Daniel Imms d392bdb5ea Merge branch 'master' into Fix-bug-of-some-IMEs-cannot-input-in-terminal 2021-03-18 07:00:43 -07:00
jerch 145b4874cb Merge pull request #3222 from jerch/async_handlers
async parser handler support
2021-03-17 22:50:14 +01:00
Jörg Breitbart 869c11f1d8 interface for subparser stack saves 2021-03-17 22:39:09 +01:00
Jörg Breitbart 8e7cdd2587 interface for inputhandler parse stack, proper typing of LogService.logLevel 2021-03-17 22:29:01 +01:00
Jörg Breitbart 76fdf9d378 Merge branch 'async_handlers' of github.com:jerch/xterm.js into async_handlers 2021-03-17 22:08:36 +01:00
Jörg Breitbart a7488456d8 Merge branch 'master' into async_handlers 2021-03-17 22:08:19 +01:00
Martin Sander 5185e5f894 Use linkifier2 to double click select links
Also works with right click select.
Fixes #682.
2021-03-16 16:42:38 +01:00
jerch c19cc9b2d6 Merge branch 'master' into async_handlers 2021-03-15 21:53:59 +01:00
Python-37 7968b7ce7e Fix bug of some IMEs cannot input in terminal
Fix bug of QQ pinyin and Rime IME cannot input characters in terminal.
2021-03-10 17:27:58 +08:00
Jörg Breitbart cdd238d75e do not timeout on faulty async handlers, continue with false to give default handlers chance to run 2021-02-28 13:53:06 +01:00
Jörg Breitbart 48a93c379f log a warning if an async handler takes too long 2021-02-27 22:24:55 +01:00
Jörg Breitbart cc005a274d multiple changes:
- reorder exception throwing to be sync to band position
- document WriteBuffer._innerWrite
- remove any declarations
- remove conditional assigments
- use faster return value branching in all parsers
- remove dead code in benchmark
2021-02-27 20:18:41 +01:00
Jörg Breitbart 26bf1ab840 Merge branch 'master' into async_handlers 2021-02-25 19:07:28 +01:00
Jörg Breitbart b4635eddf3 make reset async aware 2021-02-25 19:04:37 +01:00
Jörg Breitbart 6b57921567 async handler API tests 2021-02-25 17:41:16 +01:00
Jörg Breitbart d500b0f204 inputhandler tests 2021-02-25 17:06:32 +01:00
Jörg Breitbart 9985ae2c38 fix linter warning 2021-02-25 15:44:39 +01:00
Jörg Breitbart bce3269e44 async OSC/DCS registering/dispose tests on parser instance 2021-02-25 15:41:10 +01:00
Jörg Breitbart 26502114d4 minor tweaks, API docs update 2021-02-15 16:24:29 +01:00
Jörg Breitbart 5db479c25d update public API 2021-02-08 03:35:42 +01:00
Jörg Breitbart d0b555687f update parser hooks api and tests 2021-02-08 03:28:49 +01:00
Jörg Breitbart 041e259c11 OSC impl, OscParser tests 2021-02-08 03:03:28 +01:00
Jörg Breitbart d59f4d2f6f async impl for DCS, DcsParser tests 2021-02-08 01:53:59 +01:00
Jörg Breitbart 898a18db21 Merge branch 'master' into async_handlers 2021-02-07 23:14:30 +01:00
Jörg Breitbart 68b7f88523 async parser tests for CSI and ESC 2021-01-24 17:09:16 +01:00
Jörg Breitbart a9eb1de4b4 Merge branch 'master' into async_handlers 2021-01-24 09:11:51 +01:00
Jörg Breitbart 4ba7aef441 Merge branch 'master' into async_handlers 2021-01-23 13:06:50 +01:00
Jörg Breitbart 2f07ed9976 simplify stack save in parser 2021-01-23 13:05:57 +01:00
Jörg Breitbart b07ca8bb24 fix timeout issue on macos 2021-01-22 21:34:12 +01:00
Jörg Breitbart 22d777c9f9 make linter happy 2021-01-22 21:24:54 +01:00
Jörg Breitbart adfeffec76 deprecate writeSync 2021-01-22 21:17:34 +01:00
Jörg Breitbart 4a1bdad759 fix tests for async handlers in inputhandler 2021-01-22 19:48:50 +01:00
Jörg Breitbart 0ad9f70645 Merge branch 'master' into async_handlers 2021-01-22 17:49:31 +01:00
Jörg Breitbart bc1048062d early version of async support for CSI and ESC handlers 2021-01-20 20:05:14 +01:00
Jakob b0605e4cd1 Merge branch 'master' into fix/scroll-events 2021-01-13 11:12:14 +01:00
Jakob Schrettenbrunner 82a9ee62d7 emit onScroll event when user is scrolling 2021-01-12 22:35:47 +00:00
Jakob Schrettenbrunner 8b2b0f6abc add richer ScrollEvent that includes the source 2021-01-12 22:32:20 +00:00
joyceerhl 0f9240207d Move API view classes under common/public 2021-01-11 20:29:15 -08:00
Daniel Imms c015379c51 Merge branch 'master' into nodetarget 2021-01-11 06:46:32 -08:00
joyceerhl 2f88498ad5 Add index.js used to test xterm-core 2021-01-10 22:32:36 -08:00
joyceerhl 5e6e65c8da Get dupe of src/browser/public/Terminal compiling 2021-01-10 22:18:41 -08:00
joyceerhl 157055c03b Original files 2021-01-10 22:17:31 -08:00
161 changed files with 11271 additions and 5247 deletions
+1 -1
View File
@@ -1,4 +1,4 @@
FROM node:10
FROM node:14
# Configure apt
ENV DEBIAN_FRONTEND=noninteractive
+2 -1
View File
@@ -4,6 +4,7 @@
"appPort": 3000,
"extensions": [
"dbaeumer.vscode-eslint",
"editorconfig.editorconfig"
"editorconfig.editorconfig",
"hbenl.vscode-mocha-test-adapter"
]
}
+16 -2
View File
@@ -9,6 +9,7 @@
"project": [
"src/browser/tsconfig.json",
"src/common/tsconfig.json",
"src/headless/tsconfig.json",
"test/api/tsconfig.json",
"test/benchmark/tsconfig.json",
"addons/xterm-addon-attach/src/tsconfig.json",
@@ -43,10 +44,11 @@
"@typescript-eslint/array-type": [
"warn",
{
"default": "array-simple",
"default": "array",
"readonly": "generic"
}
],
"@typescript-eslint/consistent-type-assertions": "warn",
"@typescript-eslint/consistent-type-definitions": "warn",
"@typescript-eslint/explicit-function-return-type": [
"warn",
@@ -151,6 +153,10 @@
"warn",
"never"
],
"object-curly-spacing": [
"warn",
"always"
],
"prefer-const": "warn",
"spaced-comment": [
"warn",
@@ -160,5 +166,13 @@
"exceptions": ["-"]
}
]
}
},
"overrides": [
{
"files": ["**/*.test.ts"],
"rules": {
"object-curly-spacing": "off"
}
}
]
}
+1
View File
@@ -0,0 +1 @@
NODE_PATH=./out
+11
View File
@@ -0,0 +1,11 @@
require:
- source-map-support/register
spec:
- out/**/*.test.js
- addons/**/out/*.test.js
watch-files:
- out/**/*.js
- addons/**/out/*.js
reporter: spec
color: true
check-leaks: true
+8 -22
View File
@@ -2,10 +2,6 @@
*
!*/
# Whitelist - entries to be included must be negated with "!"
!*.js
!*.json
# Whitelist - css/
!css/**/*.css
@@ -32,25 +28,15 @@
!src/**/*.css
# Blacklist - src/ test files
src/**/*.test.ts
src/**/*.test.d.ts
src/**/*.test.js
src/**/*.test.js.map
# Whitelist - typings/
!typings/*.d.ts
# Blacklist - (normal behavior) these will override any whitelist
*.d.ts.map
*.test.ts
*.test.d.ts
*.test.js
*.test.js.map
lib/test/
webpack.config.js
docs/
/.idea/
.vscode/
bin/
build/
fixtures/
demo/
.devcontainer/
out/
addons/
headless/
typings/xterm-headless.d.ts
+2 -1
View File
@@ -1,4 +1,5 @@
{
"typescript.preferences.importModuleSpecifier": "non-relative",
"typescript.preferences.quoteStyle": "single"
"typescript.preferences.quoteStyle": "single",
"mochaExplorer.envPath": ".mocha.env"
}
+68 -49
View File
@@ -1,51 +1,70 @@
{
"version": "2.0.0",
"presentation": {
"echo": false,
"reveal": "always",
"focus": false,
"panel": "dedicated",
"showReuseMessage": true
},
"tasks": [
{
"type": "npm",
"script": "test",
"group": "test",
"problemMatcher": []
},
{
"type": "npm",
"script": "watch",
"group": "build",
"isBackground": true,
"problemMatcher": [],
"presentation": {
"group": "vscode"
}
},
{
"type": "npm",
"script": "start",
"group": "build",
"isBackground": true,
"problemMatcher": [],
"presentation": {
"group": "vscode"
}
},
{
"label": "Start demo",
"dependsOn": ["npm: watch", "npm: start"],
"group": {
"kind": "build",
"isDefault": true
},
"isBackground": true,
"problemMatcher": [],
"presentation": {
"group": "vscode"
}
}
]
"version": "2.0.0",
"presentation": {
"echo": false,
"reveal": "always",
"focus": false,
"panel": "dedicated",
"showReuseMessage": true
},
"tasks": [
{
"type": "npm",
"script": "test",
"group": "test",
"problemMatcher": []
},
{
"type": "npm",
"script": "watch",
"group": "build",
"isBackground": true,
"problemMatcher": "$tsc-watch",
"presentation": {
"group": "vscode"
}
},
{
"type": "npm",
"script": "start",
"dependsOn": "npm: watch",
"group": "build",
"isBackground": true,
"problemMatcher": [],
"presentation": {
"group": "vscode"
}
},
{
"label": "Start demo",
"dependsOn": "npm: start",
"group": {
"kind": "build",
"isDefault": true
},
"isBackground": true,
"problemMatcher": {
"owner": "typescript",
"fileLocation": [
"relative",
"${workspaceFolder}"
],
"pattern": [
{
"regexp": "^([^\\\\s].*)\\\\((\\\\d+,\\\\d+)\\\\):\\\\s*(.*)$",
"file": 1,
"location": 2,
"message": 3
}
],
"background": {
"beginsPattern": "assets by",
"endsPattern": "webpack \\d+\\.\\d+\\.\\d+ compiled successfully"
}
},
"presentation": {
"group": "vscode"
}
}
]
}
+39 -26
View File
@@ -6,11 +6,11 @@ Xterm.js is a front-end component written in TypeScript that lets applications b
## Features
- **Terminal apps just work**: Xterm.js works with most terminal apps such as `bash`, `vim` and `tmux`, this includes support for curses-based apps and mouse event support.
- **Terminal apps just work**: Xterm.js works with most terminal apps such as `bash`, `vim`, and `tmux`, including support for curses-based apps and mouse events.
- **Performant**: Xterm.js is *really* fast, it even includes a GPU-accelerated renderer.
- **Rich unicode support**: Supports CJK, emojis and IMEs.
- **Rich Unicode support**: Supports CJK, emojis, and IMEs.
- **Self-contained**: Requires zero dependencies to work.
- **Accessible**: Screen reader and minimum contrast ratio support can be turned on
- **Accessible**: Screen reader and minimum contrast ratio support can be turned on.
- **And much more**: Links, theming, addons, well documented API, etc.
## What xterm.js is not
@@ -20,13 +20,13 @@ Xterm.js is a front-end component written in TypeScript that lets applications b
## Getting Started
First you need to install the module, we ship exclusively through [npm](https://www.npmjs.com/) so you need that installed and then add xterm.js as a dependency by running:
First, you need to install the module, we ship exclusively through [npm](https://www.npmjs.com/), so you need that installed and then add xterm.js as a dependency by running:
```
```bash
npm install xterm
```
To start using xterm.js on your browser, add the `xterm.js` and `xterm.css` to the head of your html page. Then create a `<div id="terminal"></div>` onto which xterm can attach itself. Finally instantiate the `Terminal` object and then call the `open` function with the DOM object of the `div`.
To start using xterm.js on your browser, add the `xterm.js` and `xterm.css` to the head of your HTML page. Then create a `<div id="terminal"></div>` onto which xterm can attach itself. Finally, instantiate the `Terminal` object and then call the `open` function with the DOM object of the `div`.
```html
<!doctype html>
@@ -58,7 +58,7 @@ import { Terminal } from 'xterm';
⚠️ *This section describes the new addon format introduced in v3.14.0, see [here](https://github.com/xtermjs/xterm.js/blob/3.14.2/README.md#addons) for the instructions on the old format*
Addons are separate modules that extend the `Terminal` by building on the [xterm.js API](https://github.com/xtermjs/xterm.js/blob/master/typings/xterm.d.ts). To use an addon you first need to install it in your project:
Addons are separate modules that extend the `Terminal` by building on the [xterm.js API](https://github.com/xtermjs/xterm.js/blob/master/typings/xterm.d.ts). To use an addon, you first need to install it in your project:
```bash
npm i -S xterm-addon-web-links
@@ -76,7 +76,7 @@ const terminal = new Terminal();
terminal.loadAddon(new WebLinksAddon());
```
The xterm.js team maintains the following addons but they can be built by anyone:
The xterm.js team maintains the following addons, but anyone can build them:
- [`xterm-addon-attach`](https://github.com/xtermjs/xterm.js/tree/master/addons/xterm-addon-attach): Attaches to a server running a process via a websocket
- [`xterm-addon-fit`](https://github.com/xtermjs/xterm.js/tree/master/addons/xterm-addon-fit): Fits the terminal to the containing element
@@ -85,23 +85,27 @@ The xterm.js team maintains the following addons but they can be built by anyone
## Browser Support
Since xterm.js is typically implemented as a developer tool, only modern browsers are supported officially. Specifically the latest versions of *Chrome*, *Edge*, *Firefox* and *Safari*.
Since xterm.js is typically implemented as a developer tool, only modern browsers are supported officially. Specifically the latest versions of *Chrome*, *Edge*, *Firefox*, and *Safari*.
We also partially support *Internet Explorer 11*, meaning xterm.js should work for the most part, but we reserve the right to not provide workarounds specifically for it unless it's absolutely necessary to get the basic input/output flow working.
Xterm.js works seamlessly in [Electron](https://electronjs.org/) apps and may even work on earlier versions of the browsers, these are the versions we strive to keep working.
Xterm.js works seamlessly in [Electron](https://electronjs.org/) apps and may even work on earlier versions of the browsers. These are the versions we strive to keep working.
### Node.js Support
We also publish [`xterm-headless`](https://www.npmjs.com/package/xterm-headless) which is a stripped down version of xterm.js that runs in Node.js. An example use case for this is to keep track of a terminal's state where the process is running and using the serialize addon so it can get all state restored upon reconnection.
## API
The full API for xterm.js is contained within the [TypeScript declaration file](https://github.com/xtermjs/xterm.js/blob/master/typings/xterm.d.ts), use the branch/tag picker in GitHub (`w`) to navigate to the correct version of the API.
Note that some APIs are marked *experimental*, these are added to enable experimentation with new ideas without committing to support it like a normal [semver](https://semver.org/) API. Note that these APIs can change radically between versions so be sure to read release notes if you plan on using experimental APIs.
Note that some APIs are marked *experimental*, these are added to enable experimentation with new ideas without committing to support it like a normal [semver](https://semver.org/) API. Note that these APIs can change radically between versions, so be sure to read release notes if you plan on using experimental APIs.
## Real-world uses
Xterm.js is used in several world-class applications to provide great terminal experiences.
- [**SourceLair**](https://www.sourcelair.com/): In-browser IDE that provides its users with fully-featured Linux terminals based on xterm.js.
- [**Microsoft Visual Studio Code**](http://code.visualstudio.com/): Modern, versatile and powerful open source code editor that provides an integrated terminal based on xterm.js.
- [**Microsoft Visual Studio Code**](http://code.visualstudio.com/): Modern, versatile, and powerful open source code editor that provides an integrated terminal based on xterm.js.
- [**ttyd**](https://github.com/tsl0922/ttyd): A command-line tool for sharing terminal over the web, with fully-featured terminal emulation based on xterm.js.
- [**Katacoda**](https://www.katacoda.com/): Katacoda is an Interactive Learning Platform for software developers, covering the latest Cloud Native technologies.
- [**Eclipse Che**](http://www.eclipse.org/che): Developer workspace server, cloud IDE, and Eclipse next-generation IDE.
@@ -113,10 +117,10 @@ Xterm.js is used in several world-class applications to provide great terminal e
- [**Next Tech**](https://next.tech "Next Tech"): Online platform for interactive coding and web development courses. Live container-backed terminal uses xterm.js.
- [**RStudio**](https://www.rstudio.com/products/RStudio "RStudio"): RStudio is an integrated development environment (IDE) for R.
- [**Terminal for Atom**](https://github.com/jsmecham/atom-terminal-tab): A simple terminal for the Atom text editor.
- [**Eclipse Orion**](https://orionhub.org): A modern, open source software development environment that runs in the cloud. Code, deploy and run in the cloud.
- [**Eclipse Orion**](https://orionhub.org): A modern, open source software development environment that runs in the cloud. Code, deploy, and run in the cloud.
- [**Gravitational Teleport**](https://github.com/gravitational/teleport): Gravitational Teleport is a modern SSH server for remotely accessing clusters of Linux servers via SSH or HTTPS.
- [**Hexlet**](https://en.hexlet.io): Practical programming courses (JavaScript, PHP, Unix, databases, functional programming). A steady path from the first line of code to the first job.
- [**Selenoid UI**](https://github.com/aerokube/selenoid-ui): Simple UI for the scallable golang implementation of Selenium Hub named Selenoid. We use XTerm for streaming logs over websockets from docker containers.
- [**Selenoid UI**](https://github.com/aerokube/selenoid-ui): Simple UI for the scalable golang implementation of Selenium Hub named Selenoid. We use XTerm for streaming logs over websockets from docker containers.
- [**Portainer**](https://portainer.io): Simple management UI for Docker.
- [**SSHy**](https://github.com/stuicey/SSHy): HTML5 Based SSHv2 Web Client with E2E encryption utilising xterm.js, SJCL & websockets.
- [**JupyterLab**](https://github.com/jupyterlab/jupyterlab): An extensible computational environment for Jupyter, supporting interactive data science and scientific computing across all programming languages.
@@ -143,7 +147,6 @@ Xterm.js is used in several world-class applications to provide great terminal e
- [**cPanel & WHM**](https://cpanel.com): The hosting platform of choice.
- [**Nutanix**](https://github.com/nutanix): Nutanix Enterprise Cloud uses xterm in the webssh functionality within Nutanix Calm, and is also looking to move our old noserial (termjs) functionality to xterm.js.
- [**SSH Web Client**](https://github.com/roke22/PHP-SSH2-Web-Client): SSH Web Client with PHP.
- [**Shellvault**](https://www.shellvault.io): The cloud-based SSH terminal you can access from anywhere.
- [**Juno**](http://junolab.org/): A flexible Julia IDE, based on Atom.
- [**webssh**](https://github.com/huashengdun/webssh): Web based ssh client.
- [**info-beamer hosted**](https://info-beamer.com): Uses xterm.js to manage digital signage devices from the web dashboard.
@@ -163,18 +166,28 @@ Xterm.js is used in several world-class applications to provide great terminal e
- [**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.
- [**DockerStacks**](https://docker-stacks.com/): Local LAMP/LEMP development studio
- [**Codecademy**](https://codecademy.com/): Uses xterm.js in its courses on Bash.
- [**Codecademy**](https://codecademy.com/): Uses xterm.js in its courses on Bash.
- [**Laravel Ssh Web Client**](https://github.com/roke22/Laravel-ssh-client): Laravel server inventory with ssh web client to connect at server using xterm.js
- [**Repl.it**](https://repl.it): Collaborative browser based IDE with support for 50+ different languages.
- [**TeleType**](https://github.com/akshaykmr/TeleType): cli tool that allows you to share your terminal online conveniently. Show off mad cli-fu, help a colleague, teach, or troubleshoot.
- [**Intervue**](https://www.intervue.io): Pair programming for interviews. Multiple programming languages supported, with results displayed by xterm.js.
- [**TRASA**](https://trasa.io): Zero trust access to Web, SSH, RDP and Database services.
- [**Intervue**](https://www.intervue.io): Pair programming for interviews. Multiple programming languages are supported, with results displayed by xterm.js.
- [**TRASA**](https://trasa.io): Zero trust access to Web, SSH, RDP, and Database services.
- [**Commas**](https://github.com/CyanSalt/commas): Commas is a hackable terminal and command runner.
- [**Devtron**](https://github.com/devtron-labs/devtron): Software Delivery Workflow For Kubernetes.
- [**Devtron**](https://github.com/devtron-labs/devtron): Software Delivery Workflow For Kubernetes.
- [**NxShell**](https://github.com/nxshell/nxshell): An easy to use new terminal for SSH.
[And much more...](https://github.com/xtermjs/xterm.js/network/dependents)
- [**gifcast**](https://dstein64.github.io/gifcast/): Converts an asciinema cast to an animated GIF.
- [**WizardWebssh**](https://gitlab.com/mikeramsey/wizardwebssh): A terminal with Pyqt5 Widget for embedding, which can be used as an ssh client to connect to your ssh servers. It is written in Python, based on tornado, paramiko, and xterm.js.
- [**Wizard Assistant**](https://wizardassistant.com/): Wizard Assistant comes with advanced automation tools, preloaded common and special time-saving commands, and a built-in SSH terminal. Now you can remotely administer, troubleshoot, and analyze any system with ease.
- [**ucli**](https://github.com/tsadarsh/ucli): Command Line for everyone :family_man_woman_girl_boy: at [www.ucli.tech](https://www.ucli.tech).
- [**Tess**](https://github.com/SquitchYT/Tess/): Simple Terminal Fully Customizable for Everyone.
- [**HashiCorp Nomad**](https://www.nomadproject.io/): A container orchestrator with the ability to connect to remote tasks via a web interface using websockets and xterm.js.
- [**TermPair**](https://github.com/cs01/termpair): View and control terminals from your browser with end-to-end encryption
- [**gdbgui**](https://github.com/cs01/gdbgui): Browser-based frontend to gdb (gnu debugger)
- [**goormIDE**](https://ide.goorm.io/): Run almost every programming languages with real-time collaboration, live pair programming, and built-in messenger.
- [**FleetDeck**](https://fleetdeck.io): Remote desktop & virtual terminal
- [And much more...](https://github.com/xtermjs/xterm.js/network/dependents)
Do you use xterm.js in your application as well? Please [open a Pull Request](https://github.com/sourcelair/xterm.js/pulls) to include it here. We would love to have it in our list. Note: Please add any new contributions to the end of the list only.
Do you use xterm.js in your application as well? Please [open a Pull Request](https://github.com/sourcelair/xterm.js/pulls) to include it here. We would love to have it on our list. Note: Please add any new contributions to the end of the list only.
## Releases
@@ -184,21 +197,21 @@ All current and past releases are available on this repo's [Releases page](https
### Beta builds
Our CI releases beta builds to npm for every change that goes into master, install the latest beta build with:
Our CI releases beta builds to npm for every change that goes into master. Install the latest beta build with:
```
```bash
npm install -S xterm@beta
```
These should generally be stable but some bugs may slip in, we recommend using the beta build primarily to test out new features and for verifying bug fixes.
These should generally be stable, but some bugs may slip in. We recommend using the beta build primarily to test out new features and to verify bug fixes.
## Contributing
You can read the [guide on the wiki](https://github.com/xtermjs/xterm.js/wiki/Contributing) to learn how to contribute and setup xterm.js for development.
You can read the [guide on the wiki](https://github.com/xtermjs/xterm.js/wiki/Contributing) to learn how to contribute and set up xterm.js for development.
## License Agreement
If you contribute code to this project, you are implicitly allowing your code to be distributed under the MIT license. You are also implicitly verifying that all code is your original work.
If you contribute code to this project, you implicitly allow your code to be distributed under the MIT license. You are also implicitly verifying that all code is your original work.
Copyright (c) 2017-2019, [The xterm.js authors](https://github.com/xtermjs/xterm.js/graphs/contributors) (MIT License)<br>
Copyright (c) 2014-2017, SourceLair, Private Company ([www.sourcelair.com](https://www.sourcelair.com/home)) (MIT License)<br>
+29 -5
View File
@@ -1,5 +1,29 @@
**/*.api.js
**/*.api.ts
tsconfig.json
.yarnrc
webpack.config.js
# Blacklist - exclude everything except npm defaults such as LICENSE, etc
*
!*/
# Whitelist - lib/
!lib/**/*.d.ts
!lib/**/*.js
!lib/**/*.js.map
!lib/**/*.css
# Whitelist - src/
!src/**/*.ts
!src/**/*.d.ts
!src/**/*.js
!src/**/*.js.map
!src/**/*.css
# Blacklist - src/ test files
src/**/*.test.ts
src/**/*.test.d.ts
src/**/*.test.js
src/**/*.test.js.map
# Whitelist - typings/
!typings/*.d.ts
+4 -2
View File
@@ -20,7 +20,7 @@ export class AttachAddon implements ITerminalAddon {
this._socket = socket;
// always set binary type to arraybuffer, we do not handle blobs
this._socket.binaryType = 'arraybuffer';
this._bidirectional = (options && options.bidirectional === false) ? false : true;
this._bidirectional = !(options && options.bidirectional === false);
}
public activate(terminal: Terminal): void {
@@ -41,7 +41,9 @@ export class AttachAddon implements ITerminalAddon {
}
public dispose(): void {
this._disposables.forEach(d => d.dispose());
for (const d of this._disposables) {
d.dispose();
}
}
private _sendData(data: string): void {
@@ -4,10 +4,10 @@
*/
import WebSocket = require('ws');
import { openTerminal, pollFor, getBrowserType } from '../../../out-test/api/TestUtils';
import { openTerminal, pollFor, launchBrowser } from '../../../out-test/api/TestUtils';
import { Browser, Page } from 'playwright';
const APP = 'http://127.0.0.1:3000/test';
const APP = 'http://127.0.0.1:3001/test';
let browser: Browser;
let page: Page;
@@ -16,10 +16,7 @@ const height = 600;
describe('AttachAddon', () => {
before(async function(): Promise<any> {
const browserType = getBrowserType();
browser = await browserType.launch({
headless: process.argv.indexOf('--headless') !== -1
});
browser = await launchBrowser();
page = await (await browser.newContext()).newPage();
await page.setViewportSize({ width, height });
});
+29 -5
View File
@@ -1,5 +1,29 @@
**/*.api.js
**/*.api.ts
tsconfig.json
.yarnrc
webpack.config.js
# Blacklist - exclude everything except npm defaults such as LICENSE, etc
*
!*/
# Whitelist - lib/
!lib/**/*.d.ts
!lib/**/*.js
!lib/**/*.js.map
!lib/**/*.css
# Whitelist - src/
!src/**/*.ts
!src/**/*.d.ts
!src/**/*.js
!src/**/*.js.map
!src/**/*.css
# Blacklist - src/ test files
src/**/*.test.ts
src/**/*.test.d.ts
src/**/*.test.js
src/**/*.test.js.map
# Whitelist - typings/
!typings/*.d.ts
+1 -1
View File
@@ -33,7 +33,7 @@ export class FitAddon implements ITerminalAddon {
public fit(): void {
const dims = this.proposeDimensions();
if (!dims || !this._terminal) {
if (!dims || !this._terminal || isNaN(dims.cols) || isNaN(dims.rows)) {
return;
}
+5 -8
View File
@@ -4,10 +4,10 @@
*/
import { assert } from 'chai';
import { openTerminal, getBrowserType } from '../../../out-test/api/TestUtils';
import { openTerminal, launchBrowser } from '../../../out-test/api/TestUtils';
import { Browser, Page } from 'playwright';
const APP = 'http://127.0.0.1:3000/test';
const APP = 'http://127.0.0.1:3001/test';
let browser: Browser;
let page: Page;
@@ -16,10 +16,7 @@ const height = 768;
describe('FitAddon', () => {
before(async function(): Promise<any> {
const browserType = getBrowserType();
browser = await browserType.launch({
headless: process.argv.indexOf('--headless') !== -1
});
browser = await launchBrowser();
page = await (await browser.newContext()).newPage();
await page.setViewportSize({ width, height });
await page.goto(APP);
@@ -45,7 +42,7 @@ describe('FitAddon', () => {
describe('proposeDimensions', () => {
afterEach(async () => {
return unloadFit();
return await unloadFit();
});
it('default', async function(): Promise<any> {
@@ -84,7 +81,7 @@ describe('FitAddon', () => {
describe('fit', () => {
afterEach(async () => {
return unloadFit();
return await unloadFit();
});
it('default', async function(): Promise<any> {
+10 -29
View File
@@ -2,21 +2,13 @@
*
!*/
# 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
!lib/**/*.css
# Whitelist - src/
!src/**/*.ts
@@ -25,24 +17,13 @@
!src/**/*.js
!src/**/*.js.map
!src/**/*.css
# Blacklist - src/ test files
src/**/*.test.ts
src/**/*.test.d.ts
src/**/*.test.js
src/**/*.test.js.map
# Whitelist - typings/
!typings/**/*.d.ts
# 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
!typings/*.d.ts
+1
View File
@@ -51,3 +51,4 @@ This package makes use of the following fonts for testing:
[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
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "xterm-addon-ligatures",
"version": "0.4.0",
"version": "0.5.1",
"description": "Add support for programming ligatures to xterm.js",
"author": {
"name": "The xterm.js authors",
@@ -32,11 +32,11 @@
"license": "MIT",
"dependencies": {
"font-finder": "^1.1.0",
"font-ligatures": "^1.3.3"
"font-ligatures": "^1.4.0"
},
"devDependencies": {
"@types/sinon": "^5.0.1",
"axios": "^0.18.0",
"axios": "^0.21.2",
"mkdirp": "0.5.5",
"sinon": "6.3.5",
"yauzl": "^2.10.0"
+67 -6
View File
@@ -3,12 +3,28 @@
* @license MIT
*/
import * as fontFinder from 'font-finder';
import * as fontLigatures from 'font-ligatures';
import { FontList } from 'font-finder';
import { Font, loadBuffer, loadFile } from 'font-ligatures';
import parse from './parse';
let fontsPromise: Promise<fontFinder.FontList> | undefined = undefined;
interface IFontMetadata {
family: string;
fullName: string;
postscriptName: string;
blob: () => Promise<Blob>;
}
interface IFontAccessNavigator {
fonts: {
query: () => Promise<IFontMetadata[]>;
};
permissions: {
request?: (permission: { name: string }) => Promise<{state: string}>;
};
}
let fontsPromise: Promise<FontList | Record<string, IFontMetadata[]>> | undefined = undefined;
/**
* Loads the font ligature wrapper for the specified font family if it could be
@@ -16,9 +32,50 @@ let fontsPromise: Promise<fontFinder.FontList> | undefined = undefined;
* @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> {
export default async function load(fontFamily: string, cacheSize: number): Promise<Font | undefined> {
if (!fontsPromise) {
fontsPromise = fontFinder.list();
// Web environment that supports font access API
if (typeof navigator !== 'undefined' && 'fonts' in navigator) {
try {
const status = await (navigator as unknown as IFontAccessNavigator).permissions.request?.({
name: 'local-fonts'
});
if (status && status.state !== 'granted') {
throw new Error('Permission to access local fonts not granted.');
}
} catch (err: any) {
// A `TypeError` indicates the 'local-fonts'
// permission is not yet implemented, so
// only `throw` if this is _not_ the problem.
if (err.name !== 'TypeError') {
throw err;
}
}
const fonts: Record<string, IFontMetadata[]> = {};
try {
const fontsIterator = await (navigator as unknown as IFontAccessNavigator).fonts.query();
for (const metadata of fontsIterator) {
if (!fonts.hasOwnProperty(metadata.family)) {
fonts[metadata.family] = [];
}
fonts[metadata.family].push(metadata);
}
fontsPromise = Promise.resolve(fonts);
} catch (err: any) {
console.error(err.name, err.message);
}
}
// Node environment or no font access API
else {
try {
fontsPromise = (await import('font-finder')).list();
} catch (err) {
// No-op
}
}
if (!fontsPromise) {
fontsPromise = Promise.resolve({});
}
}
const fonts = await fontsPromise;
@@ -31,7 +88,11 @@ export default async function load(fontFamily: string, cacheSize: number): Promi
}
if (fonts.hasOwnProperty(family) && fonts[family].length > 0) {
return await fontLigatures.loadFile(fonts[family][0].path, { cacheSize });
const font = fonts[family][0];
if ('blob' in font) {
return loadBuffer(await (await font.blob()).arrayBuffer(), { cacheSize });
}
return await loadFile(font.path, { cacheSize });
}
}
+3 -3
View File
@@ -68,7 +68,7 @@ function parseString(context: IParseContext, quoteChar: '\'' | '"'): string {
while (context.offset < context.input.length) {
const char = context.input[context.offset++];
if (escaped) {
if (/[0-9a-fA-F]/.test(char)) {
if (/[\dA-Fa-f]/.test(char)) {
// Unicode escape
context.offset--;
str += parseUnicode(context);
@@ -107,7 +107,7 @@ function parseIdentifier(context: IParseContext): string {
while (context.offset < context.input.length) {
const char = context.input[context.offset++];
if (escaped) {
if (/[0-9a-fA-F]/.test(char)) {
if (/[\dA-Fa-f]/.test(char)) {
// Unicode escape
context.offset--;
str += parseUnicode(context);
@@ -156,7 +156,7 @@ function parseUnicode(context: IParseContext): string {
// of the escape and is swallowed.
return unicodeToString(str);
}
if (str.length >= 6 || !/[0-9a-fA-F]/.test(char)) {
if (str.length >= 6 || !/[\dA-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--;
+13 -2
View File
@@ -30,7 +30,18 @@ module.exports = {
},
mode: 'production',
externals: {
'font-finder':'font-finder',
'font-ligatures':'font-ligatures'
'font-finder': 'font-finder',
'stream': 'stream',
'os': 'os',
'util': 'util'
},
resolve: {
// The ligature modules contains fallbacks for node environments, we never want to browserify them
fallback: {
stream: false,
util: false,
os: false,
path: false
}
}
};
+27 -45
View File
@@ -45,23 +45,17 @@ array-from@^2.1.1:
resolved "https://registry.yarnpkg.com/array-from/-/array-from-2.1.1.tgz#cfe9d8c26628b9dc5aecc62a9f5d8f1f352c1195"
integrity sha1-z+nYwmYoudxa7MYqn12PHzUsEZU=
axios@^0.18.0:
version "0.18.1"
resolved "https://registry.yarnpkg.com/axios/-/axios-0.18.1.tgz#ff3f0de2e7b5d180e757ad98000f1081b87bcea3"
axios@^0.21.2:
version "0.21.2"
resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.2.tgz#21297d5084b2aeeb422f5d38e7be4fbb82239017"
integrity sha512-87otirqUw3e8CzHTMO+/9kh/FSgXt/eVDvipijwDtEuwbkySWZ9SBm6VEubmJ/kLKEoLQV/POhxXFb66bfekfg==
dependencies:
follow-redirects "1.5.10"
is-buffer "^2.0.2"
follow-redirects "^1.14.0"
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"
@@ -72,11 +66,10 @@ fd-slicer@~1.1.0:
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"
follow-redirects@^1.14.0:
version "1.14.3"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.3.tgz#6ada78118d8d24caee595595accdc0ac6abd022e"
integrity sha512-3MkHxknWMUtb23apkgz/83fDoe+y+qr0TdgacGIA7bew+QLBo3vdgEN2xEsuXNivpFy4CyDhBBZnNZOtalmenw==
font-finder@^1.0.3:
version "1.0.4"
@@ -87,19 +80,19 @@ font-finder@^1.0.3:
font-finder@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/font-finder/-/font-finder-1.1.0.tgz#2bff2b2762acba720239c8bec898a96daae90858"
resolved "https://registry.npmjs.org/font-finder/-/font-finder-1.1.0.tgz#2bff2b2762acba720239c8bec898a96daae90858"
integrity sha512-wpCL2uIbi6GurJbU7ZlQ3nGd61Ho+dSU6U83/xJT5UPFfN35EeCW/rOtS+5k+IuEZu2SYmHzDIPL9eA5tSYRAw==
dependencies:
get-system-fonts "^2.0.0"
promise-stream-reader "^1.0.1"
font-ligatures@^1.3.3:
version "1.3.3"
resolved "https://registry.yarnpkg.com/font-ligatures/-/font-ligatures-1.3.3.tgz#63fff18dc8adb3a11fe5eec1f4e8d7edfa8075b9"
integrity sha512-NSGpHgVNX81M7AWS1XylK1UZbN3QllfUIDAAuPv6TUcl5O2b781JcKS5L2RopAU0AqlTyX3ZuX/04eaMpbVrHA==
font-ligatures@^1.4.0:
version "1.4.0"
resolved "https://registry.npmjs.org/font-ligatures/-/font-ligatures-1.4.0.tgz#6a7b370d96be1358dddfad67830e82fbfd59e6dc"
integrity sha512-n7DFnnEpJ0NrVoLqZIL4tMGVs+CnFwQc92m80LWyrbgAFO4x234+t2/H9o4eOYA1eh6ta9dZAEEsJAwsBdNezA==
dependencies:
font-finder "^1.0.3"
lru-cache "^4.1.3"
lru-cache "^6.0.0"
opentype.js "^0.8.0"
get-system-fonts@^2.0.0:
@@ -110,10 +103,6 @@ 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"
@@ -128,9 +117,9 @@ lodash.get@^4.4.2:
resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99"
lodash@^4.17.15:
version "4.17.19"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.19.tgz#e48ddedbe30b3321783c5b4301fbd353bc1e4a4b"
integrity sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==
version "4.17.21"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
lolex@^2.7.5:
version "2.7.5"
@@ -144,12 +133,12 @@ lolex@^5.0.1:
dependencies:
"@sinonjs/commons" "^1.7.0"
lru-cache@^4.1.3:
version "4.1.3"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.3.tgz#a1175cf3496dfc8436c156c334b4955992bce69c"
lru-cache@^6.0.0:
version "6.0.0"
resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94"
integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==
dependencies:
pseudomap "^1.0.2"
yallist "^2.1.2"
yallist "^4.0.0"
minimist@^1.2.5:
version "1.2.5"
@@ -163,10 +152,6 @@ mkdirp@0.5.5:
dependencies:
minimist "^1.2.5"
ms@2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
nise@^1.4.5:
version "1.5.3"
resolved "https://registry.yarnpkg.com/nise/-/nise-1.5.3.tgz#9d2cfe37d44f57317766c6e9408a359c5d3ac1f7"
@@ -198,10 +183,6 @@ 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"
sinon@6.3.5:
version "6.3.5"
resolved "https://registry.yarnpkg.com/sinon/-/sinon-6.3.5.tgz#0f6d6a5b4ebaad1f6e8e019395542d1d02c144a0"
@@ -232,9 +213,10 @@ type-detect@4.0.8, 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"
yallist@^4.0.0:
version "4.0.0"
resolved "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
yauzl@^2.10.0:
version "2.10.0"
+29 -5
View File
@@ -1,5 +1,29 @@
**/*.api.js
**/*.api.ts
tsconfig.json
.yarnrc
webpack.config.js
# Blacklist - exclude everything except npm defaults such as LICENSE, etc
*
!*/
# Whitelist - lib/
!lib/**/*.d.ts
!lib/**/*.js
!lib/**/*.js.map
!lib/**/*.css
# Whitelist - src/
!src/**/*.ts
!src/**/*.d.ts
!src/**/*.js
!src/**/*.js.map
!src/**/*.css
# Blacklist - src/ test files
src/**/*.test.ts
src/**/*.test.d.ts
src/**/*.test.js
src/**/*.test.js.map
# Whitelist - typings/
!typings/*.d.ts
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "xterm-addon-search",
"version": "0.8.0",
"version": "0.8.1",
"author": {
"name": "The xterm.js authors",
"url": "https://xtermjs.org/"
+5 -5
View File
@@ -23,7 +23,7 @@ export interface ISearchResult {
row: number;
}
const NON_WORD_CHARACTERS = ' ~!@#$%^&*()+`-=[]{}|\;:"\',./<>?';
const NON_WORD_CHARACTERS = ' ~!@#$%^&*()+`-=[]{}|\\;:"\',./<>?';
const LINES_CACHE_TIME_TO_LIVE = 15 * 1000; // 15 secs
export class SearchAddon implements ITerminalAddon {
@@ -240,8 +240,8 @@ export class SearchAddon implements ITerminalAddon {
* @param term the substring that starts at searchIndex
*/
private _isWholeWord(searchIndex: number, line: string, term: string): boolean {
return (((searchIndex === 0) || (NON_WORD_CHARACTERS.indexOf(line[searchIndex - 1]) !== -1)) &&
(((searchIndex + term.length) === line.length) || (NON_WORD_CHARACTERS.indexOf(line[searchIndex + term.length]) !== -1)));
return ((searchIndex === 0) || (NON_WORD_CHARACTERS.includes(line[searchIndex - 1]))) &&
(((searchIndex + term.length) === line.length) || (NON_WORD_CHARACTERS.includes(line[searchIndex + term.length])));
}
/**
@@ -262,7 +262,7 @@ export class SearchAddon implements ITerminalAddon {
// Ignore wrapped lines, only consider on unwrapped line (first row of command string).
const firstLine = terminal.buffer.active.getLine(row);
if (firstLine && firstLine.isWrapped) {
if (firstLine?.isWrapped) {
if (isReverseSearch) {
searchPosition.startCol += terminal.cols;
return;
@@ -394,7 +394,7 @@ export class SearchAddon implements ITerminalAddon {
// If it is not in the viewport then we scroll else it just gets selected
if (result.row >= (terminal.buffer.active.viewportY + terminal.rows) || result.row < terminal.buffer.active.viewportY) {
let scroll = result.row - terminal.buffer.active.viewportY;
scroll = scroll - Math.floor(terminal.rows / 2);
scroll -= Math.floor(terminal.rows / 2);
terminal.scrollLines(scroll);
}
return true;
@@ -6,10 +6,10 @@
import { assert } from 'chai';
import { readFile } from 'fs';
import { resolve } from 'path';
import { openTerminal, writeSync, getBrowserType } from '../../../out-test/api/TestUtils';
import { openTerminal, writeSync, launchBrowser } from '../../../out-test/api/TestUtils';
import { Browser, Page } from 'playwright';
const APP = 'http://127.0.0.1:3000/test';
const APP = 'http://127.0.0.1:3001/test';
let browser: Browser;
let page: Page;
@@ -18,10 +18,7 @@ const height = 600;
describe('Search Tests', function(): void {
before(async function(): Promise<any> {
const browserType = getBrowserType();
browser = await browserType.launch({
headless: process.argv.indexOf('--headless') !== -1
});
browser = await launchBrowser();
page = await (await browser.newContext()).newPage();
await page.setViewportSize({ width, height });
await page.goto(APP);
+29 -5
View File
@@ -1,5 +1,29 @@
**/*.api.js
**/*.api.ts
tsconfig.json
.yarnrc
webpack.config.js
# Blacklist - exclude everything except npm defaults such as LICENSE, etc
*
!*/
# Whitelist - lib/
!lib/**/*.d.ts
!lib/**/*.js
!lib/**/*.js.map
!lib/**/*.css
# Whitelist - src/
!src/**/*.ts
!src/**/*.d.ts
!src/**/*.js
!src/**/*.js.map
!src/**/*.css
# Blacklist - src/ test files
src/**/*.test.ts
src/**/*.test.d.ts
src/**/*.test.js
src/**/*.test.js.map
# Whitelist - typings/
!typings/*.d.ts
@@ -35,7 +35,7 @@ perfContext('Terminal: sh -c "dd if=/dev/urandom count=40 bs=1k | hexdump | lolc
chunks.push(data as unknown as Buffer);
length += data.length;
});
await new Promise(resolve => p.on('exit', () => resolve()));
await new Promise<void>(resolve => p.on('exit', () => resolve()));
contentUtf8 = Buffer.concat(chunks, length);
// translate to content string
const buffer = new Uint32Array(contentUtf8.length);
+2 -1
View File
@@ -1,12 +1,13 @@
{
"name": "xterm-addon-serialize",
"version": "0.5.0",
"version": "0.6.0",
"author": {
"name": "The xterm.js authors",
"url": "https://xtermjs.org/"
},
"main": "lib/xterm-addon-serialize.js",
"types": "typings/xterm-addon-serialize.d.ts",
"repository": "https://github.com/xtermjs/xterm.js",
"license": "MIT",
"scripts": {
"build": "../../node_modules/.bin/tsc -p .",
@@ -7,13 +7,17 @@
import { Terminal, ITerminalAddon, IBuffer, IBufferCell } from 'xterm';
function constrain(value: number, low: number, high: number): number {
return Math.max(low, Math.min(value, high));
}
// TODO: Refine this template class later
abstract class BaseSerializeHandler {
constructor(private _buffer: IBuffer) { }
constructor(
protected readonly _buffer: IBuffer
) {
}
public serialize(startRow: number, endRow: number): string {
// we need two of them to flip between old and new cell
@@ -68,11 +72,10 @@ function equalFlags(cell1: IBufferCell, cell2: IBufferCell): boolean {
&& cell1.isBlink() === cell2.isBlink()
&& cell1.isInvisible() === cell2.isInvisible()
&& cell1.isItalic() === cell2.isItalic()
&& cell1.isDim() === cell2.isDim();
&& cell1.isDim() === cell2.isDim()
&& cell1.isStrikethrough() === cell2.isStrikethrough();
}
class StringSerializeHandler extends BaseSerializeHandler {
private _rowIndex: number = 0;
private _allRows: string[] = new Array<string>();
@@ -83,7 +86,7 @@ class StringSerializeHandler extends BaseSerializeHandler {
// we can see a full colored cell and a null cell that only have background the same style
// but the information isn't preserved by null cell itself
// so wee need to record it when required.
private _cursorStyle: IBufferCell = this._buffer1.getNullCell();
private _cursorStyle: IBufferCell = this._buffer.getNullCell();
// where exact the cursor styles comes from
// because we can't copy the cell directly
@@ -92,7 +95,7 @@ class StringSerializeHandler extends BaseSerializeHandler {
private _cursorStyleCol: number = 0;
// this is a null cell for reference for checking whether background is empty or not
private _backgroundCell: IBufferCell = this._buffer1.getNullCell();
private _backgroundCell: IBufferCell = this._buffer.getNullCell();
private _firstRow: number = 0;
private _lastCursorRow: number = 0;
@@ -100,8 +103,11 @@ class StringSerializeHandler extends BaseSerializeHandler {
private _lastContentCursorRow: number = 0;
private _lastContentCursorCol: number = 0;
constructor(private _buffer1: IBuffer, private _terminal: Terminal) {
super(_buffer1);
constructor(
buffer: IBuffer,
private readonly _terminal: Terminal
) {
super(buffer);
}
protected _beforeSerialize(rows: number, start: number, end: number): void {
@@ -111,14 +117,14 @@ class StringSerializeHandler extends BaseSerializeHandler {
this._firstRow = start;
}
private _thisRowLastChar: IBufferCell = this._buffer1.getNullCell();
private _thisRowLastSecondChar: IBufferCell = this._buffer1.getNullCell();
private _nextRowFirstChar: IBufferCell = this._buffer1.getNullCell();
private _thisRowLastChar: IBufferCell = this._buffer.getNullCell();
private _thisRowLastSecondChar: IBufferCell = this._buffer.getNullCell();
private _nextRowFirstChar: IBufferCell = this._buffer.getNullCell();
protected _rowEnd(row: number, isLastRow: boolean): void {
// if there is colorful empty cell at line end, whe must pad it back, or the the color block will missing
if (this._nullCellCount > 0 && !equalBg(this._cursorStyle, this._backgroundCell)) {
// use clear right to set background.
this._currentRow += `\x1b[${this._nullCellCount}X`;
this._currentRow += `\u001b[${this._nullCellCount}X`;
}
let rowSeparator = '';
@@ -127,13 +133,13 @@ class StringSerializeHandler extends BaseSerializeHandler {
if (!isLastRow) {
// Enable BCE
if (row - this._firstRow >= this._terminal.rows) {
this._buffer1.getLine(this._cursorStyleRow)?.getCell(this._cursorStyleCol, this._backgroundCell);
this._buffer.getLine(this._cursorStyleRow)?.getCell(this._cursorStyleCol, this._backgroundCell);
}
// Fetch current line
const currentLine = this._buffer1.getLine(row)!;
const currentLine = this._buffer.getLine(row)!;
// Fetch next line
const nextLine = this._buffer1.getLine(row + 1)!;
const nextLine = this._buffer.getLine(row + 1)!;
if (!nextLine.isWrapped) {
// just insert the line break
@@ -155,7 +161,7 @@ class StringSerializeHandler extends BaseSerializeHandler {
if (
// you must output character to cause overflow, control sequence can't do this
nextRowFirstChar.getChars() &&
isNextRowFirstCharDoubleWidth ? this._nullCellCount <= 1 : this._nullCellCount <= 0
isNextRowFirstCharDoubleWidth ? this._nullCellCount <= 1 : this._nullCellCount <= 0
) {
if (
// the last character can't be null,
@@ -187,15 +193,15 @@ class StringSerializeHandler extends BaseSerializeHandler {
// insert enough character to force the wrap
rowSeparator = '-'.repeat(this._nullCellCount + 1);
// move back and erase next line head
rowSeparator += '\x1b[1D\x1b[1X';
rowSeparator += '\u001b[1D\u001b[1X';
if (this._nullCellCount > 0) {
// do these because we filled the last several null slot, which we shouldn't
rowSeparator += '\x1b[A';
rowSeparator += `\x1b[${currentLine.length - this._nullCellCount}C`;
rowSeparator += `\x1b[${this._nullCellCount}X`;
rowSeparator += `\x1b[${currentLine.length - this._nullCellCount}D`;
rowSeparator += '\x1b[B';
rowSeparator += '\u001b[A';
rowSeparator += `\u001b[${currentLine.length - this._nullCellCount}C`;
rowSeparator += `\u001b[${this._nullCellCount}X`;
rowSeparator += `\u001b[${currentLine.length - this._nullCellCount}D`;
rowSeparator += '\u001b[B';
}
// This is content and need the be serialized even it is invisible.
@@ -254,6 +260,7 @@ class StringSerializeHandler extends BaseSerializeHandler {
if (cell.isInvisible() !== oldCell.isInvisible()) { sgrSeq.push(cell.isInvisible() ? 8 : 28); }
if (cell.isItalic() !== oldCell.isItalic()) { sgrSeq.push(cell.isItalic() ? 3 : 23); }
if (cell.isDim() !== oldCell.isDim()) { sgrSeq.push(cell.isDim() ? 2 : 22); }
if (cell.isStrikethrough() !== oldCell.isStrikethrough()) { sgrSeq.push(cell.isStrikethrough() ? 9 : 29); }
}
}
}
@@ -285,20 +292,20 @@ class StringSerializeHandler extends BaseSerializeHandler {
if (this._nullCellCount > 0) {
// use clear right to set background.
if (!equalBg(this._cursorStyle, this._backgroundCell)) {
this._currentRow += `\x1b[${this._nullCellCount}X`;
this._currentRow += `\u001b[${this._nullCellCount}X`;
}
// use move right to move cursor.
this._currentRow += `\x1b[${this._nullCellCount}C`;
this._currentRow += `\u001b[${this._nullCellCount}C`;
this._nullCellCount = 0;
}
this._lastContentCursorRow = this._lastCursorRow = row;
this._lastContentCursorCol = this._lastCursorCol = col;
this._currentRow += `\x1b[${sgrSeq.join(';')}m`;
this._currentRow += `\u001b[${sgrSeq.join(';')}m`;
// update the last cursor style
const line = this._buffer1.getLine(row);
const line = this._buffer.getLine(row);
if (line !== undefined) {
line.getCell(col, this._cursorStyle);
this._cursorStyleRow = row;
@@ -317,10 +324,10 @@ class StringSerializeHandler extends BaseSerializeHandler {
// because style change is handled by previous stage
// use move right when background is empty, use clear right when there is background.
if (equalBg(this._cursorStyle, this._backgroundCell)) {
this._currentRow += `\x1b[${this._nullCellCount}C`;
this._currentRow += `\u001b[${this._nullCellCount}C`;
} else {
this._currentRow += `\x1b[${this._nullCellCount}X`;
this._currentRow += `\x1b[${this._nullCellCount}C`;
this._currentRow += `\u001b[${this._nullCellCount}X`;
this._currentRow += `\u001b[${this._nullCellCount}C`;
}
this._nullCellCount = 0;
}
@@ -338,7 +345,7 @@ class StringSerializeHandler extends BaseSerializeHandler {
// the fixup is only required for data without scrollback
// because it will always be placed at last line otherwise
if (this._buffer1.length - this._firstRow <= this._terminal.rows) {
if (this._buffer.length - this._firstRow <= this._terminal.rows) {
rowEnd = this._lastContentCursorRow + 1 - this._firstRow;
this._lastCursorCol = this._lastContentCursorCol;
this._lastCursorRow = this._lastContentCursorRow;
@@ -354,8 +361,8 @@ class StringSerializeHandler extends BaseSerializeHandler {
}
// restore the cursor
const realCursorRow = this._buffer1.baseY + this._buffer1.cursorY;
const realCursorCol = this._buffer1.cursorX;
const realCursorRow = this._buffer.baseY + this._buffer.cursorY;
const realCursorCol = this._buffer.cursorX;
const cursorMoved = (realCursorRow !== this._lastCursorRow || realCursorCol !== this._lastCursorCol);
@@ -379,7 +386,6 @@ class StringSerializeHandler extends BaseSerializeHandler {
moveRight(realCursorCol - this._lastCursorCol);
}
return content;
}
}
@@ -393,34 +399,73 @@ export class SerializeAddon implements ITerminalAddon {
this._terminal = terminal;
}
private _getString(buffer: IBuffer, scrollback?: number): string {
private _serializeBuffer(terminal: Terminal, buffer: IBuffer, scrollback?: number): string {
const maxRows = buffer.length;
const handler = new StringSerializeHandler(buffer, this._terminal!);
const correctRows = (scrollback === undefined) ? maxRows : constrain(scrollback + this!._terminal!.rows, 0, maxRows);
const result = handler.serialize(maxRows - correctRows, maxRows);
return result;
const handler = new StringSerializeHandler(buffer, terminal);
const correctRows = (scrollback === undefined) ? maxRows : constrain(scrollback + terminal.rows, 0, maxRows);
return handler.serialize(maxRows - correctRows, maxRows);
}
public serialize(scrollback?: number): string {
private _serializeModes(terminal: Terminal): string {
let content = '';
const modes = terminal.modes;
// Default: false
if (modes.applicationCursorKeysMode) content += '\x1b[?1h';
if (modes.applicationKeypadMode) content += '\x1b[?66h';
if (modes.bracketedPasteMode) content += '\x1b[?2004h';
if (modes.insertMode) content += '\x1b[4h';
if (modes.originMode) content += '\x1b[?6h';
if (modes.reverseWraparoundMode) content += '\x1b[?45h';
if (modes.sendFocusMode) content += '\x1b[?1004h';
// Default: true
if (modes.wraparoundMode === false) content += '\x1b[?7l';
// Default: 'none'
if (modes.mouseTrackingMode !== 'none') {
switch (modes.mouseTrackingMode) {
case 'x10': content += '\x1b[?9h'; break;
case 'vt200': content += '\x1b[?1000h'; break;
case 'drag': content += '\x1b[?1002h'; break;
case 'any': content += '\x1b[?1003h'; break;
}
}
return content;
}
public serialize(options?: ISerializeOptions): string {
// TODO: Add combinedData support
if (!this._terminal) {
throw new Error('Cannot use addon until it has been loaded');
}
if (this._terminal.buffer.active.type === 'normal') {
return this._getString(this._terminal.buffer.active, scrollback);
// Normal buffer
let content = this._serializeBuffer(this._terminal, this._terminal.buffer.normal, options?.scrollback);
// Alternate buffer
if (!options?.excludeAltBuffer) {
if (this._terminal.buffer.active.type === 'alternate') {
const alternativeScreenContent = this._serializeBuffer(this._terminal, this._terminal.buffer.alternate, undefined);
content += `\u001b[?1049h\u001b[H${alternativeScreenContent}`;
}
}
const normalScreenContent = this._getString(this._terminal.buffer.normal, scrollback);
// alt screen don't have scrollback
const alternativeScreenContent = this._getString(this._terminal.buffer.alternate, undefined);
// Modes
if (!options?.excludeModes) {
content += this._serializeModes(this._terminal);
}
return normalScreenContent
+ '\u001b[?1049h\u001b[H'
+ alternativeScreenContent;
return content;
}
public dispose(): void { }
}
interface ISerializeOptions {
scrollback?: number;
excludeModes?: boolean;
excludeAltBuffer?: boolean;
}
@@ -4,10 +4,10 @@
*/
import { assert } from 'chai';
import { openTerminal, writeSync, getBrowserType } from '../../../out-test/api/TestUtils';
import { openTerminal, writeSync, launchBrowser } from '../../../out-test/api/TestUtils';
import { Browser, Page } from 'playwright';
const APP = 'http://127.0.0.1:3000/test';
const APP = 'http://127.0.0.1:3001/test';
let browser: Browser;
let page: Page;
@@ -30,12 +30,15 @@ const testNormalScreenEqual = async (page: any, str: string): Promise<void> => {
assert.equal(JSON.stringify(originalBuffer), JSON.stringify(newBuffer));
};
async function testSerializeEquals(writeContent: string, expectedSerialized: string): Promise<void> {
await writeRawSync(page, writeContent);
const result = await page.evaluate(`serializeAddon.serialize();`) as string;
assert.strictEqual(result, expectedSerialized);
}
describe('SerializeAddon', () => {
before(async function(): Promise<any> {
const browserType = getBrowserType();
browser = await browserType.launch({
headless: process.argv.indexOf('--headless') !== -1
});
browser = await launchBrowser();
page = await (await browser.newContext()).newPage();
await page.setViewportSize({ width, height });
await page.goto(APP);
@@ -89,8 +92,6 @@ describe('SerializeAddon', () => {
});
it('empty content', async function(): Promise<any> {
const rows = 10;
const cols = 10;
assert.equal(await page.evaluate(`serializeAddon.serialize();`), '');
});
@@ -145,7 +146,7 @@ describe('SerializeAddon', () => {
const cols = 10;
const lines = newArray<string>((index: number) => digitsString(cols, index), rows);
await writeSync(page, lines.join('\\r\\n'));
assert.equal(await page.evaluate(`serializeAddon.serialize(${halfScrollback});`), lines.slice(halfScrollback, rows).join('\r\n'));
assert.equal(await page.evaluate(`serializeAddon.serialize({ scrollback: ${halfScrollback} });`), lines.slice(halfScrollback, rows).join('\r\n'));
});
it('serialize 0 rows of scrollback', async function(): Promise<any> {
@@ -153,7 +154,19 @@ describe('SerializeAddon', () => {
const cols = 10;
const lines = newArray<string>((index: number) => digitsString(cols, index), rows);
await writeSync(page, lines.join('\\r\\n'));
assert.equal(await page.evaluate(`serializeAddon.serialize(0);`), lines.slice(rows - 10, rows).join('\r\n'));
assert.equal(await page.evaluate(`serializeAddon.serialize({ scrollback: 0 });`), lines.slice(rows - 10, rows).join('\r\n'));
});
it('serialize exclude modes', async () => {
await writeSync(page, 'before\\x1b[?1hafter');
assert.equal(await page.evaluate(`serializeAddon.serialize();`), 'beforeafter\x1b[?1h');
assert.equal(await page.evaluate(`serializeAddon.serialize({ excludeModes: true });`), 'beforeafter');
});
it('serialize exclude alt buffer', async () => {
await writeSync(page, 'normal\\x1b[?1049h\\x1b[Halt');
assert.equal(await page.evaluate(`serializeAddon.serialize();`), 'normal\x1b[?1049h\x1b[Halt');
assert.equal(await page.evaluate(`serializeAddon.serialize({ excludeAltBuffer: true });`), 'normal');
});
it('serialize all rows of content with color16', async function(): Promise<any> {
@@ -177,17 +190,19 @@ describe('SerializeAddon', () => {
const cols = 10;
const line = '+'.repeat(cols);
const lines: string[] = [
mkSGR(FG_P16_GREEN) + line, // Workaround: If we clear all flags a the end, serialize will use \x1b[0m to clear instead of the sepcific disable sequence
mkSGR(INVERSE) + line,
mkSGR(BOLD) + line,
mkSGR(UNDERLINED) + line,
mkSGR(BLINK) + line,
mkSGR(INVISIBLE) + line,
mkSGR(NO_INVERSE) + line,
mkSGR(NO_BOLD) + line,
mkSGR(NO_UNDERLINED) + line,
mkSGR(NO_BLINK) + line,
mkSGR(NO_INVISIBLE) + line
sgr(FG_P16_GREEN) + line, // Workaround: If we clear all flags a the end, serialize will use \x1b[0m to clear instead of the sepcific disable sequence
sgr(INVERSE) + line,
sgr(BOLD) + line,
sgr(UNDERLINED) + line,
sgr(BLINK) + line,
sgr(INVISIBLE) + line,
sgr(STRIKETHROUGH) + line,
sgr(NO_INVERSE) + line,
sgr(NO_BOLD) + line,
sgr(NO_UNDERLINED) + line,
sgr(NO_BLINK) + line,
sgr(NO_INVISIBLE) + line,
sgr(NO_STRIKETHROUGH) + line
];
const rows = lines.length;
await writeSync(page, lines.join('\\r\\n'));
@@ -209,16 +224,16 @@ describe('SerializeAddon', () => {
const cols = 10;
const line = '+'.repeat(cols);
const lines: string[] = [
mkSGR(FG_P16_RED) + line, // fg Red,
mkSGR(UNDERLINED) + line, // fg Red, Underlined
mkSGR(FG_P16_GREEN) + line, // fg Green, Underlined
mkSGR(INVERSE) + line, // fg Green, Underlined, Inverse
mkSGR(NO_INVERSE) + line, // fg Green, Underlined
mkSGR(INVERSE) + line, // fg Green, Underlined, Inverse
mkSGR(BG_P16_YELLOW) + line, // fg Green, bg Yellow, Underlined, Inverse
mkSGR(FG_RESET) + line, // bg Yellow, Underlined, Inverse
mkSGR(BG_RESET) + line, // Underlined, Inverse
mkSGR(NORMAL) + line // Back to normal
sgr(FG_P16_RED) + line, // fg Red,
sgr(UNDERLINED) + line, // fg Red, Underlined
sgr(FG_P16_GREEN) + line, // fg Green, Underlined
sgr(INVERSE) + line, // fg Green, Underlined, Inverse
sgr(NO_INVERSE) + line, // fg Green, Underlined
sgr(INVERSE) + line, // fg Green, Underlined, Inverse
sgr(BG_P16_YELLOW) + line, // fg Green, bg Yellow, Underlined, Inverse
sgr(FG_RESET) + line, // bg Yellow, Underlined, Inverse
sgr(BG_RESET) + line, // Underlined, Inverse
sgr(NORMAL) + line // Back to normal
];
await writeSync(page, lines.join('\\r\\n'));
assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n'));
@@ -228,19 +243,19 @@ describe('SerializeAddon', () => {
const cols = 10;
const line = '+'.repeat(cols);
const lines: string[] = [
mkSGR(FG_P16_RED) + line, // fg Red
mkSGR(FG_P16_GREEN, BG_P16_YELLOW) + line, // fg Green, bg Yellow
mkSGR(UNDERLINED, ITALIC) + line, // fg Green, bg Yellow, Underlined, Italic
mkSGR(NO_UNDERLINED, NO_ITALIC) + line, // fg Green, bg Yellow
mkSGR(FG_RESET, ITALIC) + line, // bg Yellow, Italic
mkSGR(BG_RESET) + line, // Italic
mkSGR(NORMAL) + line, // Back to normal
mkSGR(FG_P16_RED) + line, // fg Red
mkSGR(FG_P16_GREEN, BG_P16_YELLOW) + line, // fg Green, bg Yellow
mkSGR(UNDERLINED, ITALIC) + line, // fg Green, bg Yellow, Underlined, Italic
mkSGR(NO_UNDERLINED, NO_ITALIC) + line, // fg Green, bg Yellow
mkSGR(FG_RESET, ITALIC) + line, // bg Yellow, Italic
mkSGR(BG_RESET) + line // Italic
sgr(FG_P16_RED) + line, // fg Red
sgr(FG_P16_GREEN, BG_P16_YELLOW) + line, // fg Green, bg Yellow
sgr(UNDERLINED, ITALIC) + line, // fg Green, bg Yellow, Underlined, Italic
sgr(NO_UNDERLINED, NO_ITALIC) + line, // fg Green, bg Yellow
sgr(FG_RESET, ITALIC) + line, // bg Yellow, Italic
sgr(BG_RESET) + line, // Italic
sgr(NORMAL) + line, // Back to normal
sgr(FG_P16_RED) + line, // fg Red
sgr(FG_P16_GREEN, BG_P16_YELLOW) + line, // fg Green, bg Yellow
sgr(UNDERLINED, ITALIC) + line, // fg Green, bg Yellow, Underlined, Italic
sgr(NO_UNDERLINED, NO_ITALIC) + line, // fg Green, bg Yellow
sgr(FG_RESET, ITALIC) + line, // bg Yellow, Italic
sgr(BG_RESET) + line // Italic
];
await writeSync(page, lines.join('\\r\\n'));
assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n'));
@@ -250,16 +265,16 @@ describe('SerializeAddon', () => {
const cols = 10;
const line = '+'.repeat(cols);
const lines: string[] = [
mkSGR(FG_P256_RED) + line, // fg Red 256,
mkSGR(UNDERLINED) + line, // fg Red 256, Underlined
mkSGR(FG_P256_GREEN) + line, // fg Green 256, Underlined
mkSGR(INVERSE) + line, // fg Green 256, Underlined, Inverse
mkSGR(NO_INVERSE) + line, // fg Green 256, Underlined
mkSGR(INVERSE) + line, // fg Green 256, Underlined, Inverse
mkSGR(BG_P256_YELLOW) + line, // fg Green 256, bg Yellow 256, Underlined, Inverse
mkSGR(FG_RESET) + line, // bg Yellow 256, Underlined, Inverse
mkSGR(BG_RESET) + line, // Underlined, Inverse
mkSGR(NORMAL) + line // Back to normal
sgr(FG_P256_RED) + line, // fg Red 256,
sgr(UNDERLINED) + line, // fg Red 256, Underlined
sgr(FG_P256_GREEN) + line, // fg Green 256, Underlined
sgr(INVERSE) + line, // fg Green 256, Underlined, Inverse
sgr(NO_INVERSE) + line, // fg Green 256, Underlined
sgr(INVERSE) + line, // fg Green 256, Underlined, Inverse
sgr(BG_P256_YELLOW) + line, // fg Green 256, bg Yellow 256, Underlined, Inverse
sgr(FG_RESET) + line, // bg Yellow 256, Underlined, Inverse
sgr(BG_RESET) + line, // Underlined, Inverse
sgr(NORMAL) + line // Back to normal
];
await writeSync(page, lines.join('\\r\\n'));
assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n'));
@@ -269,19 +284,19 @@ describe('SerializeAddon', () => {
const cols = 10;
const line = '+'.repeat(cols);
const lines: string[] = [
mkSGR(FG_P256_RED) + line, // fg Red 256
mkSGR(FG_P256_GREEN, BG_P256_YELLOW) + line, // fg Green 256, bg Yellow 256
mkSGR(UNDERLINED, ITALIC) + line, // fg Green 256, bg Yellow 256, Underlined, Italic
mkSGR(NO_UNDERLINED, NO_ITALIC) + line, // fg Green 256, bg Yellow 256
mkSGR(FG_RESET, ITALIC) + line, // bg Yellow 256, Italic
mkSGR(BG_RESET) + line, // Italic
mkSGR(NORMAL) + line, // Back to normal
mkSGR(FG_P256_RED) + line, // fg Red 256
mkSGR(FG_P256_GREEN, BG_P256_YELLOW) + line, // fg Green 256, bg Yellow 256
mkSGR(UNDERLINED, ITALIC) + line, // fg Green 256, bg Yellow 256, Underlined, Italic
mkSGR(NO_UNDERLINED, NO_ITALIC) + line, // fg Green 256, bg Yellow 256
mkSGR(FG_RESET, ITALIC) + line, // bg Yellow 256, Italic
mkSGR(BG_RESET) + line // Italic
sgr(FG_P256_RED) + line, // fg Red 256
sgr(FG_P256_GREEN, BG_P256_YELLOW) + line, // fg Green 256, bg Yellow 256
sgr(UNDERLINED, ITALIC) + line, // fg Green 256, bg Yellow 256, Underlined, Italic
sgr(NO_UNDERLINED, NO_ITALIC) + line, // fg Green 256, bg Yellow 256
sgr(FG_RESET, ITALIC) + line, // bg Yellow 256, Italic
sgr(BG_RESET) + line, // Italic
sgr(NORMAL) + line, // Back to normal
sgr(FG_P256_RED) + line, // fg Red 256
sgr(FG_P256_GREEN, BG_P256_YELLOW) + line, // fg Green 256, bg Yellow 256
sgr(UNDERLINED, ITALIC) + line, // fg Green 256, bg Yellow 256, Underlined, Italic
sgr(NO_UNDERLINED, NO_ITALIC) + line, // fg Green 256, bg Yellow 256
sgr(FG_RESET, ITALIC) + line, // bg Yellow 256, Italic
sgr(BG_RESET) + line // Italic
];
await writeSync(page, lines.join('\\r\\n'));
assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n'));
@@ -291,16 +306,16 @@ describe('SerializeAddon', () => {
const cols = 10;
const line = '+'.repeat(cols);
const lines: string[] = [
mkSGR(FG_RGB_RED) + line, // fg Red RGB,
mkSGR(UNDERLINED) + line, // fg Red RGB, Underlined
mkSGR(FG_RGB_GREEN) + line, // fg Green RGB, Underlined
mkSGR(INVERSE) + line, // fg Green RGB, Underlined, Inverse
mkSGR(NO_INVERSE) + line, // fg Green RGB, Underlined
mkSGR(INVERSE) + line, // fg Green RGB, Underlined, Inverse
mkSGR(BG_RGB_YELLOW) + line, // fg Green RGB, bg Yellow RGB, Underlined, Inverse
mkSGR(FG_RESET) + line, // bg Yellow RGB, Underlined, Inverse
mkSGR(BG_RESET) + line, // Underlined, Inverse
mkSGR(NORMAL) + line // Back to normal
sgr(FG_RGB_RED) + line, // fg Red RGB,
sgr(UNDERLINED) + line, // fg Red RGB, Underlined
sgr(FG_RGB_GREEN) + line, // fg Green RGB, Underlined
sgr(INVERSE) + line, // fg Green RGB, Underlined, Inverse
sgr(NO_INVERSE) + line, // fg Green RGB, Underlined
sgr(INVERSE) + line, // fg Green RGB, Underlined, Inverse
sgr(BG_RGB_YELLOW) + line, // fg Green RGB, bg Yellow RGB, Underlined, Inverse
sgr(FG_RESET) + line, // bg Yellow RGB, Underlined, Inverse
sgr(BG_RESET) + line, // Underlined, Inverse
sgr(NORMAL) + line // Back to normal
];
await writeSync(page, lines.join('\\r\\n'));
assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n'));
@@ -310,19 +325,19 @@ describe('SerializeAddon', () => {
const cols = 10;
const line = '+'.repeat(cols);
const lines: string[] = [
mkSGR(FG_RGB_RED) + line, // fg Red RGB
mkSGR(FG_RGB_GREEN, BG_RGB_YELLOW) + line, // fg Green RGB, bg Yellow RGB
mkSGR(UNDERLINED, ITALIC) + line, // fg Green RGB, bg Yellow RGB, Underlined, Italic
mkSGR(NO_UNDERLINED, NO_ITALIC) + line, // fg Green RGB, bg Yellow RGB
mkSGR(FG_RESET, ITALIC) + line, // bg Yellow RGB, Italic
mkSGR(BG_RESET) + line, // Italic
mkSGR(NORMAL) + line, // Back to normal
mkSGR(FG_RGB_RED) + line, // fg Red RGB
mkSGR(FG_RGB_GREEN, BG_RGB_YELLOW) + line, // fg Green RGB, bg Yellow RGB
mkSGR(UNDERLINED, ITALIC) + line, // fg Green RGB, bg Yellow RGB, Underlined, Italic
mkSGR(NO_UNDERLINED, NO_ITALIC) + line, // fg Green RGB, bg Yellow RGB
mkSGR(FG_RESET, ITALIC) + line, // bg Yellow RGB, Italic
mkSGR(BG_RESET) + line // Italic
sgr(FG_RGB_RED) + line, // fg Red RGB
sgr(FG_RGB_GREEN, BG_RGB_YELLOW) + line, // fg Green RGB, bg Yellow RGB
sgr(UNDERLINED, ITALIC) + line, // fg Green RGB, bg Yellow RGB, Underlined, Italic
sgr(NO_UNDERLINED, NO_ITALIC) + line, // fg Green RGB, bg Yellow RGB
sgr(FG_RESET, ITALIC) + line, // bg Yellow RGB, Italic
sgr(BG_RESET) + line, // Italic
sgr(NORMAL) + line, // Back to normal
sgr(FG_RGB_RED) + line, // fg Red RGB
sgr(FG_RGB_GREEN, BG_RGB_YELLOW) + line, // fg Green RGB, bg Yellow RGB
sgr(UNDERLINED, ITALIC) + line, // fg Green RGB, bg Yellow RGB, Underlined, Italic
sgr(NO_UNDERLINED, NO_ITALIC) + line, // fg Green RGB, bg Yellow RGB
sgr(FG_RESET, ITALIC) + line, // bg Yellow RGB, Italic
sgr(BG_RESET) + line // Italic
];
await writeSync(page, lines.join('\\r\\n'));
assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n'));
@@ -481,15 +496,61 @@ describe('SerializeAddon', () => {
await testNormalScreenEqual(page, lines.join(''));
});
describe('handle modes', () => {
it('applicationCursorKeysMode', async () => {
await testSerializeEquals('test\u001b[?1h', 'test\u001b[?1h');
await testSerializeEquals('\u001b[?1l', 'test');
});
it('applicationKeypadMode', async () => {
await testSerializeEquals('test\u001b[?66h', 'test\u001b[?66h');
await testSerializeEquals('\u001b[?66l', 'test');
});
it('bracketedPasteMode', async () => {
await testSerializeEquals('test\u001b[?2004h', 'test\u001b[?2004h');
await testSerializeEquals('\u001b[?2004l', 'test');
});
it('insertMode', async () => {
await testSerializeEquals('test\u001b[4h', 'test\u001b[4h');
await testSerializeEquals('\u001b[4l', 'test');
});
it('mouseTrackingMode', async () => {
await testSerializeEquals('test\u001b[?9h', 'test\u001b[?9h');
await testSerializeEquals('\u001b[?9l', 'test');
await testSerializeEquals('\u001b[?1000h', 'test\u001b[?1000h');
await testSerializeEquals('\u001b[?1000l', 'test');
await testSerializeEquals('\u001b[?1002h', 'test\u001b[?1002h');
await testSerializeEquals('\u001b[?1002l', 'test');
await testSerializeEquals('\u001b[?1003h', 'test\u001b[?1003h');
await testSerializeEquals('\u001b[?1003l', 'test');
});
it('originMode', async () => {
// origin mode moves cursor to (0,0)
await testSerializeEquals('test\u001b[?6h', 'test\u001b[4D\u001b[?6h');
await testSerializeEquals('\u001b[?6l', 'test\u001b[4D');
});
it('reverseWraparoundMode', async () => {
await testSerializeEquals('test\u001b[?45h', 'test\u001b[?45h');
await testSerializeEquals('\u001b[?45l', 'test');
});
it('sendFocusMode', async () => {
await testSerializeEquals('test\u001b[?1004h', 'test\u001b[?1004h');
await testSerializeEquals('\u001b[?1004l', 'test');
});
it('wraparoundMode', async () => {
await testSerializeEquals('test\u001b[?7l', 'test\u001b[?7l');
await testSerializeEquals('\u001b[?7h', 'test');
});
});
});
function newArray<T>(initial: T | ((index: number) => T), count: number): T[] {
const array: T[] = new Array<T>(count);
for (let i = 0; i < array.length; i++) {
if (typeof initial === 'function') {
array[i] = (<(index: number) => T>initial)(i);
array[i] = (initial as (index: number) => T)(i);
} else {
array[i] = <T>initial;
array[i] = initial as T;
}
}
return array;
@@ -503,7 +564,7 @@ function digitsString(length: number, from: number = 0, sgr: string = ''): strin
return s;
}
function mkSGR(...seq: string[]): string {
function sgr(...seq: string[]): string {
return `\x1b[${seq.join(';')}m`;
}
@@ -532,20 +593,20 @@ const BG_RGB_GREEN = '48;2;0;255;0';
const BG_RGB_YELLOW = '48;2;255;255;0';
const BG_RESET = '49';
const INVERSE = '7';
const BOLD = '1';
const DIM = '2';
const ITALIC = '3';
const UNDERLINED = '4';
const BLINK = '5';
const INVERSE = '7';
const INVISIBLE = '8';
const STRIKETHROUGH = '9';
const NO_INVERSE = '27';
const NO_BOLD = '22';
const NO_DIM = '22';
const NO_ITALIC = '23';
const NO_UNDERLINED = '24';
const NO_BLINK = '25';
const NO_INVERSE = '27';
const NO_INVISIBLE = '28';
const ITALIC = '3';
const DIM = '2';
const NO_ITALIC = '23';
const NO_DIM = '22';
const NO_STRIKETHROUGH = '29';
@@ -3,36 +3,57 @@
* @license MIT
*/
import { Terminal, ITerminalAddon } from 'xterm';
declare module 'xterm-addon-serialize' {
/**
* An xterm.js addon that enables web links.
* An xterm.js addon that enables serialization of terminal contents.
*/
export class SerializeAddon implements ITerminalAddon {
constructor();
/**
* Activates the addon
* Activates the addon.
* @param terminal The terminal the addon is being loaded in.
*/
public activate(terminal: Terminal): void;
/**
* Serializes terminal rows into a string that can be written back to the terminal
* to restore the state. The cursor will also be positioned to the correct cell.
* When restoring a terminal it is best to do before `Terminal.open` is called
* to avoid wasting CPU cycles rendering incomplete frames.
* @param scrollback The number of rows in scrollback buffer to serialize, starting from the bottom of the
* scrollback buffer. This defaults to the all available rows in the scrollback buffer.
* Serializes terminal rows into a string that can be written back to the terminal to restore
* the state. The cursor will also be positioned to the correct cell. When restoring a terminal
* it is best to do before `Terminal.open` is called to avoid wasting CPU cycles rendering
* incomplete frames.
*
* It's recommended that you write the serialized data into a terminal of the same size in which
* it originated from and then resize it after if needed.
*
* @param options Custom options to allow control over what gets serialized.
*/
public serialize(scrollback?: number): string;
public serialize(options?: ISerializeOptions): string;
/**
* Disposes the addon.
*/
public dispose(): void;
}
export interface ISerializeOptions {
/**
* The number of rows in the scrollback buffer to serialize, starting from the bottom of the
* scrollback buffer. When not specified, all available rows in the scrollback buffer will be
* serialized.
*/
scrollback?: number;
/**
* Whether to exclude the terminal modes from the serialization. False by default.
*/
excludeModes?: boolean;
/**
* Whether to exclude the alt buffer from the serialization. False by default.
*/
excludeAltBuffer?: boolean;
}
}
@@ -25,7 +25,8 @@ module.exports = {
filename: mainFile,
path: path.resolve('./lib'),
library: addonName,
libraryTarget: 'umd'
libraryTarget: 'umd',
globalObject: 'this'
},
mode: 'production'
};
+29 -5
View File
@@ -1,5 +1,29 @@
**/*.api.js
**/*.api.ts
tsconfig.json
.yarnrc
webpack.config.js
# Blacklist - exclude everything except npm defaults such as LICENSE, etc
*
!*/
# Whitelist - lib/
!lib/**/*.d.ts
!lib/**/*.js
!lib/**/*.js.map
!lib/**/*.css
# Whitelist - src/
!src/**/*.ts
!src/**/*.d.ts
!src/**/*.js
!src/**/*.js.map
!src/**/*.css
# Blacklist - src/ test files
src/**/*.test.ts
src/**/*.test.d.ts
src/**/*.test.js
src/**/*.test.js.map
# Whitelist - typings/
!typings/*.d.ts
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "xterm-addon-unicode11",
"version": "0.2.0",
"version": "0.3.0",
"author": {
"name": "The xterm.js authors",
"url": "https://xtermjs.org/"
@@ -8,6 +8,7 @@
import { Terminal, ITerminalAddon } from 'xterm';
import { UnicodeV11 } from './UnicodeV11';
export class Unicode11Addon implements ITerminalAddon {
public activate(terminal: Terminal): void {
terminal.unicode.register(new UnicodeV11());
@@ -4,10 +4,10 @@
*/
import { assert } from 'chai';
import { openTerminal, getBrowserType } from '../../../out-test/api/TestUtils';
import { openTerminal, launchBrowser } from '../../../out-test/api/TestUtils';
import { Browser, Page } from 'playwright';
const APP = 'http://127.0.0.1:3000/test';
const APP = 'http://127.0.0.1:3001/test';
let browser: Browser;
let page: Page;
@@ -16,10 +16,7 @@ const height = 600;
describe('Unicode11Addon', () => {
before(async function(): Promise<any> {
const browserType = getBrowserType();
browser = await browserType.launch({
headless: process.argv.indexOf('--headless') !== -1
});
browser = await launchBrowser();
page = await (await browser.newContext()).newPage();
await page.setViewportSize({ width, height });
});
@@ -32,7 +32,8 @@ module.exports = {
filename: mainFile,
path: path.resolve('./lib'),
library: addonName,
libraryTarget: 'umd'
libraryTarget: 'umd',
globalObject: 'this'
},
mode: 'production'
};
+29 -5
View File
@@ -1,5 +1,29 @@
**/*.api.js
**/*.api.ts
tsconfig.json
.yarnrc
webpack.config.js
# Blacklist - exclude everything except npm defaults such as LICENSE, etc
*
!*/
# Whitelist - lib/
!lib/**/*.d.ts
!lib/**/*.js
!lib/**/*.js.map
!lib/**/*.css
# Whitelist - src/
!src/**/*.ts
!src/**/*.d.ts
!src/**/*.js
!src/**/*.js.map
!src/**/*.css
# Blacklist - src/ test files
src/**/*.test.ts
src/**/*.test.d.ts
src/**/*.test.js
src/**/*.test.js.map
# Whitelist - typings/
!typings/*.d.ts
@@ -41,7 +41,7 @@ export class WebLinkProvider implements ILinkProvider {
}
export class LinkComputer {
public static computeLink(y: number, regex: RegExp, terminal: Terminal, handler: (event: MouseEvent, uri: string) => void): ILink[] {
public static computeLink(y: number, regex: RegExp, terminal: Terminal, activate: (event: MouseEvent, uri: string) => void): ILink[] {
const rex = new RegExp(regex.source, (regex.flags || '') + 'g');
const [line, startLineIndex] = LinkComputer._translateBufferLineToStringWithWrap(y - 1, false, terminal);
@@ -89,7 +89,7 @@ export class LinkComputer {
}
};
result.push({ range, text, activate: handler });
result.push({ range, text, activate });
}
return result;
@@ -29,7 +29,11 @@ const strictUrlRegex = new RegExp(start + protocolClause + bodyClause + end);
function handleLink(event: MouseEvent, uri: string): void {
const newWindow = window.open();
if (newWindow) {
newWindow.opener = null;
try {
newWindow.opener = null;
} catch {
// no-op, Electron can throw
}
newWindow.location.href = uri;
} else {
console.warn('Opening link blocked as opener could not be cleared');
@@ -4,10 +4,10 @@
*/
import { assert } from 'chai';
import { openTerminal, pollFor, writeSync, getBrowserType } from '../../../out-test/api/TestUtils';
import { openTerminal, pollFor, writeSync, launchBrowser } from '../../../out-test/api/TestUtils';
import { Browser, Page } from 'playwright';
const APP = 'http://127.0.0.1:3000/test';
const APP = 'http://127.0.0.1:3001/test';
let browser: Browser;
let page: Page;
@@ -16,10 +16,7 @@ const height = 600;
describe('WebLinksAddon', () => {
before(async function(): Promise<any> {
const browserType = getBrowserType();
browser = await browserType.launch({
headless: process.argv.indexOf('--headless') !== -1
});
browser = await launchBrowser();
page = await (await browser.newContext()).newPage();
await page.setViewportSize({ width, height });
});
+29 -7
View File
@@ -1,7 +1,29 @@
**/*.api.js
**/*.api.ts
tsconfig.json
.yarnrc
webpack.config.js
out-test/
test/
# Blacklist - exclude everything except npm defaults such as LICENSE, etc
*
!*/
# Whitelist - lib/
!lib/**/*.d.ts
!lib/**/*.js
!lib/**/*.js.map
!lib/**/*.css
# Whitelist - src/
!src/**/*.ts
!src/**/*.d.ts
!src/**/*.js
!src/**/*.js.map
!src/**/*.css
# Blacklist - src/ test files
src/**/*.test.ts
src/**/*.test.d.ts
src/**/*.test.js
src/**/*.test.js.map
# Whitelist - typings/
!typings/*.d.ts
+16 -3
View File
@@ -1,8 +1,6 @@
## xterm-addon-webgl
An addon for [xterm.js](https://github.com/xtermjs/xterm.js) that enables a WebGL-based renderer. This addon requires xterm.js v4+.
⚠️ This is an experimental addon that is [missing some features and may be unstable](https://github.com/xtermjs/xterm.js/issues?q=is%3Aopen+is%3Aissue+label%3Aarea%2Faddon%2Fwebgl) ⚠️
An addon for [xterm.js](https://github.com/xtermjs/xterm.js) that enables a WebGL2-based renderer. This addon requires xterm.js v4+.
### Install
@@ -21,3 +19,18 @@ terminal.loadAddon(new WebglAddon());
```
See the full [API](https://github.com/xtermjs/xterm.js/blob/master/addons/xterm-addon-webgl/typings/xterm-addon-webgl.d.ts) for more advanced usage.
### Handling Context Loss
The browser may drop WebGL contexts for various reasons like OOM or after the system has been suspended. There is an API exposed that fires the `webglcontextlost` event fired on the canvas so embedders can handle it however they wish. An easy, but suboptimal way, to handle this is by disposing of WebglAddon when the event fires:
```ts
const terminal = new Terminal();
const addon = new WebglAddon();
addon.onContextLoss(e => {
addon.dispose();
});
terminal.loadAddon(addon);
```
Read more about handling WebGL context losses on the [Khronos wiki](https://www.khronos.org/webgl/wiki/HandlingContextLost).
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "xterm-addon-webgl",
"version": "0.10.0",
"version": "0.11.2",
"author": {
"name": "The xterm.js authors",
"url": "https://xtermjs.org/"
@@ -176,8 +176,9 @@ export class GlyphRenderer {
const i = (y * terminal.cols + x) * INDICES_PER_CELL;
// Exit early if this is a null/space character
if (code === NULL_CELL_CODE || code === WHITESPACE_CELL_CODE || code === undefined/* This is used for the right side of wide chars */) {
// Exit early if this is a null character, allow space character to continue as it may have
// underline/strikethrough styles
if (code === NULL_CELL_CODE || code === undefined/* This is used for the right side of wide chars */) {
fill(array, 0, i, i + INDICES_PER_CELL - 1 - CELL_POSITION_INDICES);
return;
}
@@ -263,7 +264,7 @@ export class GlyphRenderer {
// Get attributes from fg (excluding inverse) and resolve inverse by pullibng rgb colors
// from bg. This is needed since the inverse fg color should be based on the original bg
// color, not on the selection color
fg = (fg & ~(Attributes.CM_MASK | Attributes.RGB_MASK | FgFlags.INVERSE));
fg &= ~(Attributes.CM_MASK | Attributes.RGB_MASK | FgFlags.INVERSE);
switch (workCell.getBgColorMode()) {
case Attributes.CM_P16:
case Attributes.CM_P256:
+14 -5
View File
@@ -3,14 +3,18 @@
* @license MIT
*/
import { Terminal, ITerminalAddon } from 'xterm';
import { Terminal, ITerminalAddon, IEvent } from 'xterm';
import { WebglRenderer } from './WebglRenderer';
import { IRenderService } from 'browser/services/Services';
import { ICharacterJoinerService, IRenderService } from 'browser/services/Services';
import { IColorSet } from 'browser/Types';
import { EventEmitter } from 'common/EventEmitter';
import { isSafari } from 'common/Platform';
export class WebglAddon implements ITerminalAddon {
private _terminal?: Terminal;
private _renderer?: WebglRenderer;
private _onContextLoss = new EventEmitter<void>();
public get onContextLoss(): IEvent<void> { return this._onContextLoss.event; }
constructor(
private _preserveDrawingBuffer?: boolean
@@ -20,10 +24,15 @@ export class WebglAddon implements ITerminalAddon {
if (!terminal.element) {
throw new Error('Cannot activate WebglAddon before Terminal.open');
}
if (isSafari) {
throw new Error('Webgl is not currently supported on Safari');
}
this._terminal = terminal;
const renderService: IRenderService = (<any>terminal)._core._renderService;
const colors: IColorSet = (<any>terminal)._core._colorManager.colors;
this._renderer = new WebglRenderer(terminal, colors, this._preserveDrawingBuffer);
const renderService: IRenderService = (terminal as any)._core._renderService;
const characterJoinerService: ICharacterJoinerService = (terminal as any)._core._characterJoinerService;
const colors: IColorSet = (terminal as any)._core._colorManager.colors;
this._renderer = new WebglRenderer(terminal, colors, characterJoinerService, this._preserveDrawingBuffer);
this._renderer.onContextLoss(() => this._onContextLoss.fire());
renderService.setRenderer(this._renderer);
}
+138 -24
View File
@@ -12,13 +12,17 @@ import { RectangleRenderer } from './RectangleRenderer';
import { IWebGL2RenderingContext } from './Types';
import { RenderModel, COMBINED_CHAR_BIT_MASK, RENDER_MODEL_BG_OFFSET, RENDER_MODEL_FG_OFFSET, RENDER_MODEL_INDICIES_PER_CELL } from './RenderModel';
import { Disposable } from 'common/Lifecycle';
import { NULL_CELL_CODE } from 'common/buffer/Constants';
import { Content, NULL_CELL_CHAR, NULL_CELL_CODE } from 'common/buffer/Constants';
import { Terminal, IEvent } from 'xterm';
import { IRenderLayer } from './renderLayer/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';
import { addDisposableDomListener } from 'browser/Lifecycle';
import { ICharacterJoinerService } from 'browser/services/Services';
import { CharData, ICellData } from 'common/Types';
import { AttributeData } from 'common/buffer/AttributeData';
export class WebglRenderer extends Disposable implements IRenderer {
private _renderLayers: IRenderLayer[];
@@ -41,9 +45,13 @@ export class WebglRenderer extends Disposable implements IRenderer {
private _onRequestRedraw = new EventEmitter<IRequestRedrawEvent>();
public get onRequestRedraw(): IEvent<IRequestRedrawEvent> { return this._onRequestRedraw.event; }
private _onContextLoss = new EventEmitter<void>();
public get onContextLoss(): IEvent<void> { return this._onContextLoss.event; }
constructor(
private _terminal: Terminal,
private _colors: IColorSet,
private readonly _characterJoinerService: ICharacterJoinerService,
preserveDrawingBuffer?: boolean
) {
super();
@@ -52,7 +60,7 @@ export class WebglRenderer extends Disposable implements IRenderer {
this._renderLayers = [
new LinkRenderLayer(this._core.screenElement!, 2, this._colors, this._core),
new CursorRenderLayer(this._core.screenElement!, 3, this._colors, this._onRequestRedraw)
new CursorRenderLayer(_terminal, this._core.screenElement!, 3, this._colors, this._core, this._onRequestRedraw)
];
this.dimensions = {
scaledCharWidth: 0,
@@ -82,6 +90,9 @@ export class WebglRenderer extends Disposable implements IRenderer {
if (!this._gl) {
throw new Error('WebGL2 not supported ' + this._gl);
}
this.register(addDisposableDomListener(this._canvas, 'webglcontextlost', (e) => { this._onContextLoss.fire(e); }));
this._core.screenElement!.appendChild(this._canvas);
this._rectangleRenderer = new RectangleRenderer(this._terminal, this._colors, this._gl, this.dimensions);
@@ -94,8 +105,10 @@ export class WebglRenderer extends Disposable implements IRenderer {
}
public dispose(): void {
this._renderLayers.forEach(l => l.dispose());
this._core.screenElement!.removeChild(this._canvas);
for (const l of this._renderLayers) {
l.dispose();
}
this._canvas.parentElement?.removeChild(this._canvas);
super.dispose();
}
@@ -106,10 +119,10 @@ export class WebglRenderer extends Disposable implements IRenderer {
public setColors(colors: IColorSet): void {
this._colors = colors;
// Clear layers and force a full render
this._renderLayers.forEach(l => {
for (const l of this._renderLayers) {
l.setColors(this._terminal, this._colors);
l.reset(this._terminal);
});
}
this._rectangleRenderer.setColors();
this._glyphRenderer.setColors();
@@ -136,7 +149,9 @@ export class WebglRenderer extends Disposable implements IRenderer {
this._model.resize(this._terminal.cols, this._terminal.rows);
// Resize all render layers
this._renderLayers.forEach(l => l.resize(this._terminal, this.dimensions));
for (const l of this._renderLayers) {
l.resize(this._terminal, this.dimensions);
}
// Resize the canvas
this._canvas.width = this.dimensions.scaledCanvasWidth;
@@ -168,15 +183,21 @@ export class WebglRenderer extends Disposable implements IRenderer {
}
public onBlur(): void {
this._renderLayers.forEach(l => l.onBlur(this._terminal));
for (const l of this._renderLayers) {
l.onBlur(this._terminal);
}
}
public onFocus(): void {
this._renderLayers.forEach(l => l.onFocus(this._terminal));
for (const l of this._renderLayers) {
l.onFocus(this._terminal);
}
}
public onSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void {
this._renderLayers.forEach(l => l.onSelectionChanged(this._terminal, start, end, columnSelectMode));
for (const l of this._renderLayers) {
l.onSelectionChanged(this._terminal, start, end, columnSelectMode);
}
this._updateSelectionModel(start, end, columnSelectMode);
@@ -184,11 +205,15 @@ export class WebglRenderer extends Disposable implements IRenderer {
}
public onCursorMove(): void {
this._renderLayers.forEach(l => l.onCursorMove(this._terminal));
for (const l of this._renderLayers) {
l.onCursorMove(this._terminal);
}
}
public onOptionsChanged(): void {
this._renderLayers.forEach(l => l.onOptionsChanged(this._terminal));
for (const l of this._renderLayers) {
l.onOptionsChanged(this._terminal);
}
this._updateDimensions();
this._refreshCharAtlas();
}
@@ -205,7 +230,7 @@ export class WebglRenderer extends Disposable implements IRenderer {
return;
}
const atlas = acquireCharAtlas(this._terminal, this._colors, this.dimensions.scaledCharWidth, this.dimensions.scaledCharHeight);
const atlas = acquireCharAtlas(this._terminal, this._colors, this.dimensions.scaledCellWidth, this.dimensions.scaledCellHeight, this.dimensions.scaledCharWidth, this.dimensions.scaledCharHeight);
if (!('getRasterizedGlyph' in atlas)) {
throw new Error('The webgl renderer only works with the webgl char atlas');
}
@@ -222,7 +247,9 @@ export class WebglRenderer extends Disposable implements IRenderer {
}
public clear(): void {
this._renderLayers.forEach(l => l.reset(this._terminal));
for (const l of this._renderLayers) {
l.reset(this._terminal);
}
}
public registerCharacterJoiner(handler: (text: string) => [number, number][]): number {
@@ -245,7 +272,9 @@ export class WebglRenderer extends Disposable implements IRenderer {
}
// Update render layers
this._renderLayers.forEach(l => l.onGridChanged(this._terminal, start, end));
for (const l of this._renderLayers) {
l.onGridChanged(this._terminal, start, end);
}
// Tell renderer the frame is beginning
if (this._glyphRenderer.beginFrame()) {
@@ -263,16 +292,41 @@ export class WebglRenderer extends Disposable implements IRenderer {
private _updateModel(start: number, end: number): void {
const terminal = this._core;
let cell: ICellData = this._workCell;
for (let y = start; y <= end; y++) {
const row = y + terminal.buffer.ydisp;
const line = terminal.buffer.lines.get(row)!;
this._model.lineLengths[y] = 0;
const joinedRanges = this._characterJoinerService.getJoinedCharacters(row);
for (let x = 0; x < terminal.cols; x++) {
line.loadCell(x, this._workCell);
line.loadCell(x, cell);
const chars = this._workCell.getChars();
let code = this._workCell.getCode();
// If true, indicates that the current character(s) to draw were joined.
let isJoined = false;
let lastCharX = x;
// Process any joined character ranges as needed. Because of how the
// ranges are produced, we know that they are valid for the characters
// and attributes of our input.
if (joinedRanges.length > 0 && x === joinedRanges[0][0]) {
isJoined = true;
const range = joinedRanges.shift()!;
// We already know the exact start and end column of the joined range,
// so we get the string and width representing it directly
cell = new JoinedCellData(
cell,
line!.translateToString(true, range[0], range[1]),
range[1] - range[0]
);
// Skip over the cells occupied by this range in the loop
lastCharX = range[1] - 1;
}
const chars = cell.getChars();
let code = cell.getCode();
const i = ((y * terminal.cols) + x) * RENDER_MODEL_INDICIES_PER_CELL;
if (code !== NULL_CELL_CODE) {
@@ -281,22 +335,36 @@ export class WebglRenderer extends Disposable implements IRenderer {
// Nothing has changed, no updates needed
if (this._model.cells[i] === code &&
this._model.cells[i + RENDER_MODEL_BG_OFFSET] === this._workCell.bg &&
this._model.cells[i + RENDER_MODEL_FG_OFFSET] === this._workCell.fg) {
this._model.cells[i + RENDER_MODEL_BG_OFFSET] === cell.bg &&
this._model.cells[i + RENDER_MODEL_FG_OFFSET] === cell.fg) {
continue;
}
// Flag combined chars with a bit mask so they're easily identifiable
if (chars.length > 1) {
code = code | COMBINED_CHAR_BIT_MASK;
code |= COMBINED_CHAR_BIT_MASK;
}
// Cache the results in the model
this._model.cells[i] = code;
this._model.cells[i + RENDER_MODEL_BG_OFFSET] = this._workCell.bg;
this._model.cells[i + RENDER_MODEL_FG_OFFSET] = this._workCell.fg;
this._model.cells[i + RENDER_MODEL_BG_OFFSET] = cell.bg;
this._model.cells[i + RENDER_MODEL_FG_OFFSET] = cell.fg;
this._glyphRenderer.updateCell(x, y, code, this._workCell.bg, this._workCell.fg, chars);
this._glyphRenderer.updateCell(x, y, code, cell.bg, cell.fg, chars);
if (isJoined) {
// Restore work cell
cell = this._workCell;
// Null out non-first cells
for (x++; x < lastCharX; x++) {
const j = ((y * terminal.cols) + x) * RENDER_MODEL_INDICIES_PER_CELL;
this._glyphRenderer.updateCell(x, y, NULL_CELL_CODE, 0, 0, NULL_CELL_CHAR);
this._model.cells[j] = NULL_CELL_CODE;
this._model.cells[j + RENDER_MODEL_BG_OFFSET] = this._workCell.bg;
this._model.cells[j + RENDER_MODEL_FG_OFFSET] = this._workCell.fg;
}
}
}
}
this._rectangleRenderer.updateBackgrounds(this._model);
@@ -413,3 +481,49 @@ export class WebglRenderer extends Disposable implements IRenderer {
this.dimensions.actualCellWidth = this.dimensions.scaledCellWidth / this._devicePixelRatio;
}
}
// TODO: Share impl with core
export class JoinedCellData extends AttributeData implements ICellData {
private _width: number;
// .content carries no meaning for joined CellData, simply nullify it
// thus we have to overload all other .content accessors
public content: number = 0;
public fg: number;
public bg: number;
public combinedData: string = '';
constructor(firstCell: ICellData, chars: string, width: number) {
super();
this.fg = firstCell.fg;
this.bg = firstCell.bg;
this.combinedData = chars;
this._width = width;
}
public isCombined(): number {
// always mark joined cell data as combined
return Content.IS_COMBINED_MASK;
}
public getWidth(): number {
return this._width;
}
public getChars(): string {
return this.combinedData;
}
public getCode(): number {
// code always gets the highest possible fake codepoint (read as -1)
// this is needed as code is used by caches as identifier
return 0x1FFFFF;
}
public setFromCharData(value: CharData): void {
throw new Error('not implemented');
}
public getAsCharData(): CharData {
return [this.fg, this.getChars(), this.getWidth(), this.getCode()];
}
}
@@ -28,10 +28,12 @@ const charAtlasCache: ICharAtlasCacheEntry[] = [];
export function acquireCharAtlas(
terminal: Terminal,
colors: IColorSet,
scaledCellWidth: number,
scaledCellHeight: number,
scaledCharWidth: number,
scaledCharHeight: number
): WebglCharAtlas {
const newConfig = generateConfig(scaledCharWidth, scaledCharHeight, terminal, colors);
const newConfig = generateConfig(scaledCellWidth, scaledCellHeight, scaledCharWidth, scaledCharHeight, terminal, colors);
// Check to see if the terminal already owns this config
for (let i = 0; i < charAtlasCache.length; i++) {
@@ -13,7 +13,7 @@ const NULL_COLOR: IColor = {
rgba: 0
};
export function generateConfig(scaledCharWidth: number, scaledCharHeight: number, terminal: Terminal, colors: IColorSet): ICharAtlasConfig {
export function generateConfig(scaledCellWidth: number, scaledCellHeight: number, scaledCharWidth: number, scaledCharHeight: number, terminal: Terminal, colors: IColorSet): ICharAtlasConfig {
// null out some fields that don't matter
const clonedColors: IColorSet = {
foreground: colors.foreground,
@@ -28,7 +28,12 @@ export function generateConfig(scaledCharWidth: number, scaledCharHeight: number
contrastCache: colors.contrastCache
};
return {
customGlyphs: terminal.getOption('customGlyphs'),
devicePixelRatio: window.devicePixelRatio,
letterSpacing: terminal.getOption('letterSpacing'),
lineHeight: terminal.getOption('lineHeight'),
scaledCellWidth,
scaledCellHeight,
scaledCharWidth,
scaledCharHeight,
fontFamily: terminal.getOption('fontFamily'),
@@ -49,6 +54,9 @@ export function configEquals(a: ICharAtlasConfig, b: ICharAtlasConfig): boolean
}
}
return a.devicePixelRatio === b.devicePixelRatio &&
a.customGlyphs === b.customGlyphs &&
a.lineHeight === b.lineHeight &&
a.letterSpacing === b.letterSpacing &&
a.fontFamily === b.fontFamily &&
a.fontSize === b.fontSize &&
a.fontWeight === b.fontWeight &&
+5
View File
@@ -17,11 +17,16 @@ export interface IGlyphIdentifier {
}
export interface ICharAtlasConfig {
customGlyphs: boolean;
devicePixelRatio: number;
letterSpacing: number;
lineHeight: number;
fontSize: number;
fontFamily: string;
fontWeight: FontWeight;
fontWeightBold: FontWeight;
scaledCellWidth: number;
scaledCellHeight: number;
scaledCharWidth: number;
scaledCharHeight: number;
allowTransparency: boolean;
@@ -4,7 +4,7 @@
*/
import { ICharAtlasConfig } from './Types';
import { DIM_OPACITY } from 'browser/renderer/atlas/Constants';
import { DIM_OPACITY, TEXT_BASELINE } from 'browser/renderer/atlas/Constants';
import { IRasterizedGlyph, IBoundingBox, IRasterizedGlyphSet } from '../Types';
import { DEFAULT_COLOR, Attributes } from 'common/buffer/Constants';
import { throwIfFalsy } from '../WebglUtils';
@@ -12,6 +12,7 @@ import { IColor } from 'browser/Types';
import { IDisposable } from 'xterm';
import { AttributeData } from 'common/buffer/AttributeData';
import { channels, rgba } from 'browser/Color';
import { tryDrawCustomChar } from 'browser/renderer/CustomGlyphs';
// In practice we're probably never going to exhaust a texture this large. For debugging purposes,
// however, it can be useful to set this to a really tiny value, to verify that LRU eviction works.
@@ -80,12 +81,12 @@ export class WebglCharAtlas implements IDisposable {
// The canvas needs alpha because we use clearColor to convert the background color to alpha.
// It might also contain some characters with transparent backgrounds if allowTransparency is
// set.
this._cacheCtx = throwIfFalsy(this.cacheCanvas.getContext('2d', {alpha: true}));
this._cacheCtx = throwIfFalsy(this.cacheCanvas.getContext('2d', { alpha: true }));
this._tmpCanvas = document.createElement('canvas');
this._tmpCanvas.width = this._config.scaledCharWidth * 2 + TMP_CANVAS_GLYPH_PADDING * 2;
this._tmpCanvas.height = this._config.scaledCharHeight + TMP_CANVAS_GLYPH_PADDING * 2;
this._tmpCtx = throwIfFalsy(this._tmpCanvas.getContext('2d', {alpha: this._config.allowTransparency}));
this._tmpCanvas.width = this._config.scaledCellWidth * 4 + TMP_CANVAS_GLYPH_PADDING * 2;
this._tmpCanvas.height = this._config.scaledCellHeight + TMP_CANVAS_GLYPH_PADDING * 2;
this._tmpCtx = throwIfFalsy(this._tmpCanvas.getContext('2d', { alpha: this._config.allowTransparency }));
}
public dispose(): void {
@@ -317,6 +318,13 @@ export class WebglCharAtlas implements IDisposable {
this.hasCanvasChanged = true;
// Allow 1 cell width per character, with a minimum of 2 (CJK), plus some padding. This is used
// to draw the glyph to the canvas as well as to restrict the bounding box search to ensure
// giant ligatures (eg. =====>) don't impact overall performance.
const allowedWidth = this._config.scaledCharWidth * Math.max(chars.length, 2) + TMP_CANVAS_GLYPH_PADDING * 2;
if (this._tmpCanvas.width < allowedWidth) {
this._tmpCanvas.width = allowedWidth;
}
this._tmpCtx.save();
this._workAttributeData.fg = fg;
@@ -331,6 +339,8 @@ export class WebglCharAtlas implements IDisposable {
const inverse = !!this._workAttributeData.isInverse();
const dim = !!this._workAttributeData.isDim();
const italic = !!this._workAttributeData.isItalic();
const underline = !!this._workAttributeData.isUnderline();
const strikethrough = !!this._workAttributeData.isStrikethrough();
let fgColor = this._workAttributeData.getFgColor();
let fgColorMode = this._workAttributeData.getFgColorMode();
let bgColor = this._workAttributeData.getBgColor();
@@ -358,7 +368,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 = 'middle';
this._tmpCtx.textBaseline = TEXT_BASELINE;
this._tmpCtx.fillStyle = this._getForegroundCss(bg, bgColorMode, bgColor, fg, fgColorMode, fgColor, inverse, bold);
@@ -367,8 +377,66 @@ export class WebglCharAtlas implements IDisposable {
this._tmpCtx.globalAlpha = DIM_OPACITY;
}
// Check if the char is a powerline glyph, these will be restricted to a single cell glyph, no
// padding on either side that are allowed for other glyphs since they are designed to be pixel
// perfect but may render with "bad" anti-aliasing
let isPowerlineGlyph = false;
if (chars.length === 1) {
const code = chars.charCodeAt(0);
if (code >= 0xE0A0 && code <= 0xE0D6) {
isPowerlineGlyph = true;
}
}
// For powerline glyphs left/top padding is excluded (https://github.com/microsoft/vscode/issues/120129)
const padding = isPowerlineGlyph ? 0 : TMP_CANVAS_GLYPH_PADDING;
// Draw custom characters if applicable
let drawSuccess = false;
if (this._config.customGlyphs !== false) {
drawSuccess = tryDrawCustomChar(this._tmpCtx, chars, padding, padding, this._config.scaledCellWidth, this._config.scaledCellHeight);
}
// Draw the character
this._tmpCtx.fillText(chars, TMP_CANVAS_GLYPH_PADDING, TMP_CANVAS_GLYPH_PADDING + this._config.scaledCharHeight / 2);
if (!drawSuccess) {
this._tmpCtx.fillText(chars, padding, padding + this._config.scaledCharHeight);
}
// If this charcater is underscore and beyond the cell bounds, shift it up until it is visible,
// try for a maximum of 5 pixels.
if (chars === '_' && !this._config.allowTransparency) {
let isBeyondCellBounds = clearColor(this._tmpCtx.getImageData(padding, padding, this._config.scaledCellWidth, this._config.scaledCellHeight), backgroundColor);
if (isBeyondCellBounds) {
for (let offset = 1; offset <= 5; offset++) {
this._tmpCtx.clearRect(0, 0, this._tmpCanvas.width, this._tmpCanvas.height);
this._tmpCtx.fillText(chars, padding, padding + this._config.scaledCharHeight - offset);
isBeyondCellBounds = clearColor(this._tmpCtx.getImageData(padding, padding, this._config.scaledCellWidth, this._config.scaledCellHeight), backgroundColor);
if (!isBeyondCellBounds) {
break;
}
}
}
}
// Draw underline and strikethrough
if (underline || strikethrough) {
const lineWidth = Math.max(1, Math.floor(this._config.fontSize / 10));
const yOffset = this._tmpCtx.lineWidth % 2 === 1 ? 0.5 : 0; // When the width is odd, draw at 0.5 position
this._tmpCtx.lineWidth = lineWidth;
this._tmpCtx.strokeStyle = this._tmpCtx.fillStyle;
this._tmpCtx.beginPath();
if (underline) {
this._tmpCtx.moveTo(padding, padding + this._config.scaledCharHeight - yOffset);
this._tmpCtx.lineTo(padding + this._config.scaledCharWidth, padding + this._config.scaledCharHeight - yOffset);
}
if (strikethrough) {
this._tmpCtx.moveTo(padding, padding + Math.floor(this._config.scaledCharHeight / 2) - yOffset);
this._tmpCtx.lineTo(padding + this._config.scaledCharWidth, padding + Math.floor(this._config.scaledCharHeight / 2) - yOffset);
}
this._tmpCtx.stroke();
this._tmpCtx.closePath();
}
this._tmpCtx.restore();
// clear the background from the character to avoid issues with drawing over the previous
@@ -391,7 +459,7 @@ export class WebglCharAtlas implements IDisposable {
return NULL_RASTERIZED_GLYPH;
}
const rasterizedGlyph = this._findGlyphBoundingBox(imageData, this._workBoundingBox);
const rasterizedGlyph = this._findGlyphBoundingBox(imageData, this._workBoundingBox, allowedWidth, isPowerlineGlyph, drawSuccess);
const clippedImageData = this._clipImageData(imageData, this._workBoundingBox);
// Check if there is enough room in the current row and go to next if needed
@@ -424,11 +492,13 @@ export class WebglCharAtlas implements IDisposable {
* @param imageData The image data to read.
* @param boundingBox An IBoundingBox to put the clipped bounding box values.
*/
private _findGlyphBoundingBox(imageData: ImageData, boundingBox: IBoundingBox): IRasterizedGlyph {
private _findGlyphBoundingBox(imageData: ImageData, boundingBox: IBoundingBox, allowedWidth: number, restrictedGlyph: boolean, customGlyph: boolean): IRasterizedGlyph {
boundingBox.top = 0;
const height = restrictedGlyph ? this._config.scaledCharHeight : this._tmpCanvas.height;
const width = restrictedGlyph ? this._config.scaledCharWidth : allowedWidth;
let found = false;
for (let y = 0; y < this._tmpCanvas.height; y++) {
for (let x = 0; x < this._tmpCanvas.width; x++) {
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
const alphaOffset = y * this._tmpCanvas.width * 4 + x * 4 + 3;
if (imageData.data[alphaOffset] !== 0) {
boundingBox.top = y;
@@ -442,8 +512,8 @@ export class WebglCharAtlas implements IDisposable {
}
boundingBox.left = 0;
found = false;
for (let x = 0; x < this._tmpCanvas.width; x++) {
for (let y = 0; y < this._tmpCanvas.height; y++) {
for (let x = 0; x < width; x++) {
for (let y = 0; y < height; y++) {
const alphaOffset = y * this._tmpCanvas.width * 4 + x * 4 + 3;
if (imageData.data[alphaOffset] !== 0) {
boundingBox.left = x;
@@ -455,10 +525,10 @@ export class WebglCharAtlas implements IDisposable {
break;
}
}
boundingBox.right = this._tmpCanvas.width;
boundingBox.right = width;
found = false;
for (let x = this._tmpCanvas.width - 1; x >= 0; x--) {
for (let y = 0; y < this._tmpCanvas.height; y++) {
for (let x = width - 1; x >= 0; x--) {
for (let y = 0; y < height; y++) {
const alphaOffset = y * this._tmpCanvas.width * 4 + x * 4 + 3;
if (imageData.data[alphaOffset] !== 0) {
boundingBox.right = x;
@@ -470,10 +540,10 @@ export class WebglCharAtlas implements IDisposable {
break;
}
}
boundingBox.bottom = this._tmpCanvas.height;
boundingBox.bottom = height;
found = false;
for (let y = this._tmpCanvas.height - 1; y >= 0; y--) {
for (let x = 0; x < this._tmpCanvas.width; x++) {
for (let y = height - 1; y >= 0; y--) {
for (let x = 0; x < width; x++) {
const alphaOffset = y * this._tmpCanvas.width * 4 + x * 4 + 3;
if (imageData.data[alphaOffset] !== 0) {
boundingBox.bottom = y;
@@ -497,8 +567,8 @@ export class WebglCharAtlas implements IDisposable {
y: (boundingBox.bottom - boundingBox.top + 1) / TEXTURE_HEIGHT
},
offset: {
x: -boundingBox.left + TMP_CANVAS_GLYPH_PADDING,
y: -boundingBox.top + TMP_CANVAS_GLYPH_PADDING
x: -boundingBox.left + (restrictedGlyph ? 0 : TMP_CANVAS_GLYPH_PADDING) + (customGlyph ? Math.floor(this._config.letterSpacing / 2) : 0),
y: -boundingBox.top + (restrictedGlyph ? 0 : TMP_CANVAS_GLYPH_PADDING) + (customGlyph ? this._config.lineHeight === 1 ? 0 : Math.round((this._config.scaledCellHeight - this._config.scaledCharHeight) / 2) : 0)
}
};
}
@@ -7,6 +7,7 @@ import { IRenderLayer } from './Types';
import { acquireCharAtlas } from '../atlas/CharAtlasCache';
import { Terminal } from 'xterm';
import { IColorSet } from 'browser/Types';
import { TEXT_BASELINE } from 'browser/renderer/atlas/Constants';
import { IRenderDimensions } from 'browser/renderer/Types';
import { CellData } from 'common/buffer/CellData';
import { WebglCharAtlas } from 'atlas/WebglCharAtlas';
@@ -46,7 +47,7 @@ export abstract class BaseRenderLayer implements IRenderLayer {
}
private _initCanvas(): void {
this._ctx = throwIfFalsy(this._canvas.getContext('2d', {alpha: this._alpha}));
this._ctx = throwIfFalsy(this._canvas.getContext('2d', { alpha: this._alpha }));
// Draw the background if this is an opaque layer
if (!this._alpha) {
this._clearAll();
@@ -92,7 +93,7 @@ export abstract class BaseRenderLayer implements IRenderLayer {
if (this._scaledCharWidth <= 0 && this._scaledCharHeight <= 0) {
return;
}
this._charAtlas = acquireCharAtlas(terminal, colorSet, this._scaledCharWidth, this._scaledCharHeight);
this._charAtlas = acquireCharAtlas(terminal, colorSet, this._scaledCellWidth, this._scaledCellHeight, this._scaledCharWidth, this._scaledCharHeight);
this._charAtlas.warmUp();
}
@@ -224,12 +225,12 @@ export abstract class BaseRenderLayer implements IRenderLayer {
*/
protected _fillCharTrueColor(terminal: Terminal, cell: CellData, x: number, y: number): void {
this._ctx.font = this._getFont(terminal, false, false);
this._ctx.textBaseline = 'middle';
this._ctx.textBaseline = TEXT_BASELINE;
this._clipRow(terminal, y);
this._ctx.fillText(
cell.getChars(),
x * this._scaledCellWidth + this._scaledCharLeft,
y * this._scaledCellHeight + this._scaledCharTop + this._scaledCharHeight / 2);
y * this._scaledCellHeight + this._scaledCharTop + this._scaledCharHeight);
}
/**
@@ -7,7 +7,7 @@ import { Terminal } from 'xterm';
import { BaseRenderLayer } from './BaseRenderLayer';
import { ICellData } from 'common/Types';
import { CellData } from 'common/buffer/CellData';
import { IColorSet } from 'browser/Types';
import { IColorSet, ITerminal } from 'browser/Types';
import { IRenderDimensions, IRequestRedrawEvent } from 'browser/renderer/Types';
import { IEventEmitter } from 'common/EventEmitter';
@@ -31,9 +31,11 @@ export class CursorRenderLayer extends BaseRenderLayer {
private _cell: ICellData = new CellData();
constructor(
terminal: Terminal,
container: HTMLElement,
zIndex: number,
colors: IColorSet,
private readonly _terminal: ITerminal,
private _onRequestRefreshRowsEvent: IEventEmitter<IRequestRedrawEvent>
) {
super(container, 'cursor', zIndex, true, colors);
@@ -49,7 +51,7 @@ export class CursorRenderLayer extends BaseRenderLayer {
'block': this._renderBlockCursor.bind(this),
'underline': this._renderUnderlineCursor.bind(this)
};
// TODO: Consider initial options? Maybe onOptionsChanged should be called at the end of open?
this.onOptionsChanged(terminal);
}
public resize(terminal: Terminal, dim: IRenderDimensions): void {
@@ -66,25 +68,18 @@ export class CursorRenderLayer extends BaseRenderLayer {
public reset(terminal: Terminal): void {
this._clearCursor();
if (this._cursorBlinkStateManager) {
this._cursorBlinkStateManager.dispose();
this.onOptionsChanged(terminal);
}
this._cursorBlinkStateManager?.restartBlinkAnimation(terminal);
this.onOptionsChanged(terminal);
}
public onBlur(terminal: Terminal): void {
if (this._cursorBlinkStateManager) {
this._cursorBlinkStateManager.pause();
}
this._cursorBlinkStateManager?.pause();
this._onRequestRefreshRowsEvent.fire({ start: terminal.buffer.active.cursorY, end: terminal.buffer.active.cursorY });
}
public onFocus(terminal: Terminal): void {
if (this._cursorBlinkStateManager) {
this._cursorBlinkStateManager.resume(terminal);
} else {
this._onRequestRefreshRowsEvent.fire({ start: terminal.buffer.active.cursorY, end: terminal.buffer.active.cursorY });
}
this._cursorBlinkStateManager?.resume(terminal);
this._onRequestRefreshRowsEvent.fire({ start: terminal.buffer.active.cursorY, end: terminal.buffer.active.cursorY });
}
public onOptionsChanged(terminal: Terminal): void {
@@ -104,9 +99,7 @@ export class CursorRenderLayer extends BaseRenderLayer {
}
public onCursorMove(terminal: Terminal): void {
if (this._cursorBlinkStateManager) {
this._cursorBlinkStateManager.restartBlinkAnimation(terminal);
}
this._cursorBlinkStateManager?.restartBlinkAnimation(terminal);
}
public onGridChanged(terminal: Terminal, startRow: number, endRow: number): void {
@@ -120,7 +113,7 @@ export class CursorRenderLayer extends BaseRenderLayer {
private _render(terminal: Terminal, triggeredByAnimationFrame: boolean): void {
// Don't draw the cursor if it's hidden
// TODO: Need to expose API for this
if (!(terminal as any)._core._coreService.isCursorInitialized || (terminal as any)._core._coreService.isCursorHidden) {
if (!this._terminal.coreService.isCursorInitialized || this._terminal.coreService.isCursorHidden) {
this._clearCursor();
return;
}
@@ -193,7 +186,12 @@ export class CursorRenderLayer extends BaseRenderLayer {
private _clearCursor(): void {
if (this._state) {
this._clearCells(this._state.x, this._state.y, this._state.width, 1);
// Avoid potential rounding errors when device pixel ratio is less than 1
if (window.devicePixelRatio < 1) {
this._clearAll();
} else {
this._clearCells(this._state.x, this._state.y, this._state.width, 1);
}
this._state = {
x: 0,
y: 0,
@@ -296,6 +294,7 @@ class CursorBlinkStateManager {
// Clear any existing interval
if (this._blinkInterval) {
window.clearInterval(this._blinkInterval);
this._blinkInterval = undefined;
}
// Setup the initial timeout which will hide the cursor, this is done before
@@ -3,13 +3,13 @@
* @license MIT
*/
import { ITerminalOptions } from '../../../src/common/Types';
import { ITheme } from 'xterm';
import { assert } from 'chai';
import { openTerminal, pollFor, writeSync, getBrowserType } from '../../../out-test/api/TestUtils';
import { Browser, Page } from 'playwright';
import { ITheme } from 'xterm';
import { getBrowserType, launchBrowser, openTerminal, pollFor, writeSync } from '../../../out-test/api/TestUtils';
import { ITerminalOptions } from '../../../src/common/Types';
const APP = 'http://127.0.0.1:3000/test';
const APP = 'http://127.0.0.1:3001/test';
let browser: Browser;
let page: Page;
@@ -18,7 +18,7 @@ const height = 600;
describe('WebGL Renderer Integration Tests', async () => {
const browserType = getBrowserType();
const isHeadless = process.argv.indexOf('--headless') !== -1;
const isHeadless = process.argv.includes('--headless');
// Firefox works only in non-headless mode https://github.com/microsoft/playwright/issues/1032
const areTestsEnabled = browserType.name() === 'chromium' || (browserType.name() === 'firefox' && !isHeadless);
const itWebgl = areTestsEnabled ? it : it.skip;
@@ -890,11 +890,22 @@ async function getCellColor(col: number, row: number): Promise<number[]> {
return await page.evaluate(`Array.from(window.result)`);
}
async function getCellPixels(col: number, row: number): Promise<number[]> {
await page.evaluate(`
window.gl = window.term._core._renderService._renderer._gl;
window.result = new Uint8Array(window.d.scaledCellWidth * window.d.scaledCellHeight * 4);
window.d = window.term._core._renderService.dimensions;
window.gl.readPixels(
Math.floor(${col - 1} * window.d.scaledCellWidth),
Math.floor(window.gl.drawingBufferHeight - ${row} * window.d.scaledCellHeight),
window.d.scaledCellWidth, window.d.scaledCellHeight, window.gl.RGBA, window.gl.UNSIGNED_BYTE, window.result
);
`);
return await page.evaluate(`Array.from(window.result)`);
}
async function setupBrowser(options: ITerminalOptions = { rendererType: 'dom' }): Promise<void> {
const browserType = getBrowserType();
browser = await browserType.launch({
headless: process.argv.indexOf('--headless') !== -1
});
browser = await launchBrowser();
page = await (await browser.newContext()).newPage();
await page.setViewportSize({ width, height });
await page.goto(APP);
+6 -1
View File
@@ -3,7 +3,7 @@
* @license MIT
*/
import { Terminal, ITerminalAddon } from 'xterm';
import { Terminal, ITerminalAddon, IEvent } from 'xterm';
declare module 'xterm-addon-webgl' {
/**
@@ -29,5 +29,10 @@ declare module 'xterm-addon-webgl' {
* Clears the terminal's texture atlas and triggers a redraw.
*/
public clearTextureAtlas(): void;
/**
* Fired when the WebglRenderer loses context
*/
public get onContextLoss(): IEvent<void>;
}
}
+28 -16
View File
@@ -10,7 +10,7 @@ jobs:
steps:
- task: NodeTool@0
inputs:
versionSpec: '10.x'
versionSpec: '14.x'
displayName: 'Install Node.js'
- task: YarnInstaller@3
inputs:
@@ -43,7 +43,7 @@ jobs:
steps:
- task: NodeTool@0
inputs:
versionSpec: '10.x'
versionSpec: '14.x'
displayName: 'Install Node.js'
- task: CacheBeta@1
inputs:
@@ -59,11 +59,11 @@ jobs:
- job: Windows
pool:
vmImage: 'vs2017-win2016'
vmImage: 'windows-2019'
steps:
- task: NodeTool@0
inputs:
versionSpec: '10.x'
versionSpec: '14.x'
displayName: 'Install Node.js'
- task: CacheBeta@1
inputs:
@@ -92,7 +92,7 @@ jobs:
displayName: Install required packages
- task: NodeTool@0
inputs:
versionSpec: '10.x'
versionSpec: '14.x'
displayName: 'Install Node.js'
- task: YarnInstaller@3
inputs:
@@ -100,10 +100,6 @@ jobs:
displayName: 'Install Yarn'
- script: yarn --frozen-lockfile
displayName: 'Install dependencies and build'
- script: |
yarn start &
sleep 10
displayName: 'Start test server'
- script: yarn test-api-chromium --headless --forbid-only
displayName: 'Integration tests (Chromium)'
- script: xvfb-run --auto-servernum -- bash -c "yarn test-api-firefox --headless --forbid-only"
@@ -115,14 +111,10 @@ jobs:
steps:
- task: NodeTool@0
inputs:
versionSpec: '10.x'
versionSpec: '14.x'
displayName: 'Install Node.js'
- script: yarn --frozen-lockfile
displayName: 'Install dependencies and build'
- script: |
yarn start &
sleep 10
displayName: 'Start test server'
- script: yarn test-api-chromium --headless --forbid-only
displayName: 'Integration tests (Chromium)'
- script: yarn test-api-firefox --headless --forbid-only
@@ -130,6 +122,21 @@ jobs:
- script: yarn test-api-webkit --headless --forbid-only
displayName: 'Integration tests (Webkit)'
- job: Windows_IntegrationTests
pool:
vmImage: 'windows-2019'
steps:
- task: NodeTool@0
inputs:
versionSpec: '14.x'
displayName: 'Install Node.js'
- script: yarn --frozen-lockfile
displayName: 'Install dependencies and build'
- script: yarn test-api-chromium --headless --forbid-only
displayName: 'Integration tests (Chromium)'
- script: yarn test-api-firefox --headless --forbid-only
displayName: 'Integration tests (Firefox)'
- job: Release
dependsOn:
- Linux
@@ -137,13 +144,14 @@ jobs:
- Windows
- Linux_IntegrationTests
- macOS_IntegrationTests
- Windows_IntegrationTests
condition: and(succeeded(), or(eq(variables['Build.SourceBranch'], 'refs/heads/master'), eq(variables['FORCE_RELEASE'], 'true')))
pool:
vmImage: 'ubuntu-16.04'
vmImage: 'ubuntu-18.04'
steps:
- task: NodeTool@0
inputs:
versionSpec: '10.x'
versionSpec: '14.x'
displayName: 'Install Node.js'
- task: YarnInstaller@3
inputs:
@@ -156,5 +164,9 @@ jobs:
displayName: Cache node modules
- script: yarn --frozen-lockfile
displayName: 'Install dependencies and build'
- script: |
yarn package-headless
node ./bin/package_headless.js
displayName: 'Package xterm-headless'
- script: NPM_AUTH_TOKEN="$(NPM_AUTH_TOKEN)" node ./bin/publish.js
displayName: 'Package and publish to npm'
+57
View File
@@ -0,0 +1,57 @@
/**
* Copyright (c) 2021 The xterm.js authors. All rights reserved.
* @license MIT
*/
const { exec } = require('child_process');
const fs = require('fs');
const { join } = require('path');
const repoRoot = join(__dirname, '..');
const headlessRoot = join(repoRoot, 'headless');
console.log('> headless/package.json');
const xtermPackageJson = require('../package.json');
const xtermHeadlessPackageJson = {
...xtermPackageJson,
name: 'xterm-headless',
description: 'A headless terminal component that runs in Node.js',
main: 'lib-headless/xterm-headless.js',
types: 'typings/xterm-headless.d.ts',
};
delete xtermHeadlessPackageJson['scripts'];
delete xtermHeadlessPackageJson['devDependencies'];
delete xtermHeadlessPackageJson['style'];
fs.writeFileSync(join(headlessRoot, 'package.json'), JSON.stringify(xtermHeadlessPackageJson, null, 1));
console.log(fs.readFileSync(join(headlessRoot, 'package.json')).toString());
console.log('> headless/typings/');
mkdirF(join(headlessRoot, 'typings'));
fs.copyFileSync(
join(repoRoot, 'typings/xterm-headless.d.ts'),
join(headlessRoot, 'typings/xterm-headless.d.ts')
);
console.log('> headless/logo-full.png');
fs.copyFileSync(
join(repoRoot, 'logo-full.png'),
join(headlessRoot, 'logo-full.png')
);
function mkdirF(p) {
if (!fs.existsSync(p)) {
fs.mkdirSync(p);
}
}
console.log('> Publish dry run');
exec('npm publish --dry-run', { cwd: headlessRoot }, (error, stdout, stderr) => {
if (error) {
console.log(`error: ${error.message}`);
return;
}
if (stderr) {
console.error(`stderr:\n${stderr}`);
}
console.log(`stdout:\n${stdout}`);
});
+1
View File
@@ -22,6 +22,7 @@ const changedFiles = getChangedFilesInCommit('HEAD');
let isStableRelease = false;
if (changedFiles.some(e => e.search(/^addons\//) === -1)) {
isStableRelease = checkAndPublishPackage(path.resolve(__dirname, '..'));
checkAndPublishPackage(path.resolve(__dirname, '../headless'));
}
// Publish addons if any files were changed inside of the addon
+31 -13
View File
@@ -34,19 +34,37 @@ if (process.argv.length > 2) {
env.DEBUG = flagArgs.indexOf('--debug') >= 0 ? 'debug' : '';
env.PORT = 3001;
const run = cp.spawnSync(
npmBinScript('mocha'),
[...testFiles, ...flagArgs],
{
cwd: path.resolve(__dirname, '..'),
env,
stdio: 'inherit'
const server = cp.spawn('node', ['demo/start'], {
cwd: path.resolve(__dirname, '..'),
env,
stdio: 'pipe'
})
server.stdout.on('data', (data) => {
// await for the server to fully start
if (data.indexOf("successfully") !== -1) {
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));
}
server.kill();
process.exit(run.status);
}
);
});
function npmBinScript(script) {
return path.resolve(__dirname, `../node_modules/.bin/` + (process.platform === 'win32' ? `${script}.cmd` : script));
}
process.exit(run.status);
server.stderr.on('data', (data) => {
console.error(data.toString());
});
+4 -1
View File
@@ -36,7 +36,6 @@
*/
.xterm {
font-feature-settings: "liga" 0;
position: relative;
user-select: none;
-ms-user-select: none;
@@ -169,3 +168,7 @@
.xterm-underline {
text-decoration: underline;
}
.xterm-strikethrough {
text-decoration: line-through;
}
+118 -15
View File
@@ -16,6 +16,7 @@ import { SerializeAddon } from '../addons/xterm-addon-serialize/out/SerializeAdd
import { WebLinksAddon } from '../addons/xterm-addon-web-links/out/WebLinksAddon';
import { WebglAddon } from '../addons/xterm-addon-webgl/out/WebglAddon';
import { Unicode11Addon } from '../addons/xterm-addon-unicode11/out/Unicode11Addon';
import { LigaturesAddon } from '../addons/xterm-addon-ligatures/out/LigaturesAddon';
// Use webpacked version (yarn package)
// import { Terminal } from '../lib/xterm';
@@ -26,6 +27,7 @@ import { Unicode11Addon } from '../addons/xterm-addon-unicode11/out/Unicode11Add
// import { WebLinksAddon } from 'xterm-addon-web-links';
// import { WebglAddon } from 'xterm-addon-webgl';
// import { Unicode11Addon } from 'xterm-addon-unicode11';
// import { LigaturesAddon } from 'xterm-addon-ligatures';
// Pulling in the module's types relies on the <reference> above, it's looks a
// little weird here as we're importing "this" module
@@ -41,6 +43,7 @@ export interface IWindowWithTerminal extends Window {
WebLinksAddon?: typeof WebLinksAddon;
WebglAddon?: typeof WebglAddon;
Unicode11Addon?: typeof Unicode11Addon;
LigaturesAddon?: typeof LigaturesAddon;
}
declare let window: IWindowWithTerminal;
@@ -50,7 +53,7 @@ let socketURL;
let socket;
let pid;
type AddonType = 'attach' | 'fit' | 'search' | 'serialize' | 'unicode11' | 'web-links' | 'webgl';
type AddonType = 'attach' | 'fit' | 'search' | 'serialize' | 'unicode11' | 'web-links' | 'webgl' | 'ligatures';
interface IDemoAddon<T extends AddonType> {
name: T;
@@ -62,8 +65,9 @@ interface IDemoAddon<T extends AddonType> {
T extends 'serialize' ? typeof SerializeAddon :
T extends 'web-links' ? typeof WebLinksAddon :
T extends 'unicode11' ? typeof Unicode11Addon :
T extends 'ligatures' ? typeof LigaturesAddon :
typeof WebglAddon;
instance?:
instance?:
T extends 'attach' ? AttachAddon :
T extends 'fit' ? FitAddon :
T extends 'search' ? SearchAddon :
@@ -71,6 +75,7 @@ interface IDemoAddon<T extends AddonType> {
T extends 'web-links' ? WebLinksAddon :
T extends 'webgl' ? WebglAddon :
T extends 'unicode11' ? typeof Unicode11Addon :
T extends 'ligatures' ? typeof LigaturesAddon :
never;
}
@@ -81,7 +86,8 @@ const addons: { [T in AddonType]: IDemoAddon<T>} = {
serialize: { name: 'serialize', ctor: SerializeAddon, canChange: true },
'web-links': { name: 'web-links', ctor: WebLinksAddon, canChange: true },
webgl: { name: 'webgl', ctor: WebglAddon, canChange: true },
unicode11: { name: 'unicode11', ctor: Unicode11Addon, canChange: true }
unicode11: { name: 'unicode11', ctor: Unicode11Addon, canChange: true },
ligatures: { name: 'ligatures', ctor: LigaturesAddon, canChange: true }
};
const terminalContainer = document.getElementById('terminal-container');
@@ -117,6 +123,7 @@ const disposeRecreateButtonHandler = () => {
addons.search.instance = undefined;
addons.serialize.instance = undefined;
addons.unicode11.instance = undefined;
addons.ligatures.instance = undefined;
addons['web-links'].instance = undefined;
addons.webgl.instance = undefined;
document.getElementById('dispose').innerHTML = 'Recreate Terminal';
@@ -133,12 +140,15 @@ if (document.location.pathname === '/test') {
window.SearchAddon = SearchAddon;
window.SerializeAddon = SerializeAddon;
window.Unicode11Addon = Unicode11Addon;
window.LigaturesAddon = LigaturesAddon;
window.WebLinksAddon = WebLinksAddon;
window.WebglAddon = WebglAddon;
} else {
createTerminal();
document.getElementById('dispose').addEventListener('click', disposeRecreateButtonHandler);
document.getElementById('serialize').addEventListener('click', serializeButtonHandler);
document.getElementById('custom-glyph').addEventListener('click', writeCustomGlyphHandler);
document.getElementById('load-test').addEventListener('click', loadTest);
}
function createTerminal(): void {
@@ -149,7 +159,8 @@ function createTerminal(): void {
const isWindows = ['Windows', 'Win16', 'Win32', 'WinCE'].indexOf(navigator.platform) >= 0;
term = new Terminal({
windowsMode: isWindows
windowsMode: isWindows,
fontFamily: 'Fira Code, courier-new, courier, monospace'
} as ITerminalOptions);
// Load addons
@@ -282,11 +293,11 @@ function initOptions(term: TerminalType): void {
rendererType: ['dom', 'canvas'],
wordSeparator: null
};
const options = Object.keys((<any>term)._core.options);
const options = Object.keys(term.options);
const booleanOptions = [];
const numberOptions = [];
options.filter(o => blacklistedOptions.indexOf(o) === -1).forEach(o => {
switch (typeof term.getOption(o)) {
switch (typeof term.options[o]) {
case 'boolean':
booleanOptions.push(o);
break;
@@ -303,18 +314,18 @@ function initOptions(term: TerminalType): void {
let html = '';
html += '<div class="option-group">';
booleanOptions.forEach(o => {
html += `<div class="option"><label><input id="opt-${o}" type="checkbox" ${term.getOption(o) ? 'checked' : ''}/> ${o}</label></div>`;
html += `<div class="option"><label><input id="opt-${o}" type="checkbox" ${term.options[o] ? 'checked' : ''}/> ${o}</label></div>`;
});
html += '</div><div class="option-group">';
numberOptions.forEach(o => {
html += `<div class="option"><label>${o} <input id="opt-${o}" type="number" value="${term.getOption(o)}" step="${o === 'lineHeight' || o === 'scrollSensitivity' ? '0.1' : '1'}"/></label></div>`;
html += `<div class="option"><label>${o} <input id="opt-${o}" type="number" value="${term.options[o]}" step="${o === 'lineHeight' || o === 'scrollSensitivity' ? '0.1' : '1'}"/></label></div>`;
});
html += '</div><div class="option-group">';
Object.keys(stringOptions).forEach(o => {
if (stringOptions[o]) {
html += `<div class="option"><label>${o} <select id="opt-${o}">${stringOptions[o].map(v => `<option ${term.getOption(o) === v ? 'selected' : ''}>${v}</option>`).join('')}</select></label></div>`;
html += `<div class="option"><label>${o} <select id="opt-${o}">${stringOptions[o].map(v => `<option ${term.options[o] === v ? 'selected' : ''}>${v}</option>`).join('')}</select></label></div>`;
} else {
html += `<div class="option"><label>${o} <input id="opt-${o}" type="text" value="${term.getOption(o)}"/></label></div>`;
html += `<div class="option"><label>${o} <input id="opt-${o}" type="text" value="${term.options[o]}"/></label></div>`;
}
});
html += '</div>';
@@ -327,7 +338,7 @@ function initOptions(term: TerminalType): void {
const input = <HTMLInputElement>document.getElementById(`opt-${o}`);
addDomListener(input, 'change', () => {
console.log('change', o, input.checked);
term.setOption(o, input.checked);
term.options[o] = input.checked;
});
});
numberOptions.forEach(o => {
@@ -336,11 +347,17 @@ function initOptions(term: TerminalType): void {
console.log('change', o, input.value);
if (o === 'cols' || o === 'rows') {
updateTerminalSize();
} else if (o === 'lineHeight' || o === 'scrollSensitivity') {
term.setOption(o, parseFloat(input.value));
} else if (o === 'lineHeight') {
term.options.lineHeight = parseFloat(input.value);
updateTerminalSize();
} else if (o === 'scrollSensitivity') {
term.options.scrollSensitivity = parseFloat(input.value);
updateTerminalSize();
} else if(o === 'scrollback') {
term.options.scrollback = parseInt(input.value);
setTimeout(() => updateTerminalSize(), 5);
} else {
term.setOption(o, parseInt(input.value));
term.options[o] = parseInt(input.value);
}
});
});
@@ -348,7 +365,7 @@ function initOptions(term: TerminalType): void {
const input = <HTMLInputElement>document.getElementById(`opt-${o}`);
addDomListener(input, 'change', () => {
console.log('change', o, input.value);
term.setOption(o, input.value);
term.options[o] = input.value;
});
});
}
@@ -363,6 +380,9 @@ function initAddons(term: TerminalType): void {
if (!addon.canChange) {
checkbox.disabled = true;
}
if(name === 'unicode11' && checkbox.checked) {
term.unicode.activeVersion = '11';
}
addDomListener(checkbox, 'change', () => {
if (checkbox.checked) {
addon.instance = new addon.ctor();
@@ -371,10 +391,14 @@ function initAddons(term: TerminalType): void {
setTimeout(() => {
document.body.appendChild((addon.instance as WebglAddon).textureAtlas);
}, 0);
} else if (name === 'unicode11') {
term.unicode.activeVersion = '11';
}
} else {
if (name === 'webgl') {
document.body.removeChild((addon.instance as WebglAddon).textureAtlas);
} else if (name === 'unicode11') {
term.unicode.activeVersion = '6';
}
addon.instance!.dispose();
addon.instance = undefined;
@@ -422,3 +446,82 @@ function serializeButtonHandler(): void {
term.write(output);
}
}
function writeCustomGlyphHandler() {
term.write('\n\r');
term.write('\n\r');
term.write('Box styles: ┎┰┒┍┯┑╓╥╖╒╤╕ ┏┳┓┌┲┓┌┬┐┏┱┐\n\r');
term.write('┌─┬─┐ ┏━┳━┓ ╔═╦═╗ ┠╂┨┝┿┥╟╫╢╞╪╡ ┡╇┩├╊┫┢╈┪┣╉┤\n\r');
term.write('│ │ │ ┃ ┃ ┃ ║ ║ ║ ┖┸┚┕┷┙╙╨╜╘╧╛ └┴┘└┺┛┗┻┛┗┹┘\n\r');
term.write('├─┼─┤ ┣━╋━┫ ╠═╬═╣ ┏┱┐┌┲┓┌┬┐┌┬┐ ┏┳┓┌┮┓┌┬┐┏┭┐\n\r');
term.write('│ │ │ ┃ ┃ ┃ ║ ║ ║ ┡╃┤├╄┩├╆┪┢╅┤ ┞╀┦├┾┫┟╁┧┣┽┤\n\r');
term.write('└─┴─┘ ┗━┻━┛ ╚═╩═╝ └┴┘└┴┘└┺┛┗┹┘ └┴┘└┶┛┗┻┛┗┵┘\n\r');
term.write('\n\r');
term.write('Other:\n\r');
term.write('╭─╮ ╲ ╱ ╷╻╎╏┆┇┊┋ ╺╾╴ ╌╌╌ ┄┄┄ ┈┈┈\n\r');
term.write('│ │ ╳ ╽╿╎╏┆┇┊┋ ╶╼╸ ╍╍╍ ┅┅┅ ┉┉┉\n\r');
term.write('╰─╯ ╱ ╲ ╹╵╎╏┆┇┊┋\n\r');
term.write('\n\r');
term.write('All box drawing characters:\n\r');
term.write('─ ━ │ ┃ ┄ ┅ ┆ ┇ ┈ ┉ ┊ ┋ ┌ ┍ ┎ ┏\n\r');
term.write('┐ ┑ ┒ ┓ └ ┕ ┖ ┗ ┘ ┙ ┚ ┛ ├ ┝ ┞ ┟\n\r');
term.write('┠ ┡ ┢ ┣ ┤ ┥ ┦ ┧ ┨ ┩ ┪ ┫ ┬ ┭ ┮ ┯\n\r');
term.write('┰ ┱ ┲ ┳ ┴ ┵ ┶ ┷ ┸ ┹ ┺ ┻ ┼ ┽ ┾ ┿\n\r');
term.write('╀ ╁ ╂ ╃ ╄ ╅ ╆ ╇ ╈ ╉ ╊ ╋ ╌ ╍ ╎ ╏\n\r');
term.write('═ ║ ╒ ╓ ╔ ╕ ╖ ╗ ╘ ╙ ╚ ╛ ╜ ╝ ╞ ╟\n\r');
term.write('╠ ╡ ╢ ╣ ╤ ╥ ╦ ╧ ╨ ╩ ╪ ╫ ╬ ╭ ╮ ╯\n\r');
term.write('╰ ╱ ╲ ╳ ╴ ╵ ╶ ╷ ╸ ╹ ╺ ╻ ╼ ╽ ╾ ╿\n\r');
term.write('Box drawing alignment tests:\x1b[31m █\n\r');
term.write(' ▉\n\r');
term.write(' ╔══╦══╗ ┌──┬──┐ ╭──┬──╮ ╭──┬──╮ ┏━━┳━━┓ ┎┒┏┑ ╷ ╻ ┏┯┓ ┌┰┐ ▊ ╱╲╱╲╳╳╳\n\r');
term.write(' ║┌─╨─┐║ │╔═╧═╗│ │╒═╪═╕│ │╓─╁─╖│ ┃┌─╂─┐┃ ┗╃╄┙ ╶┼╴╺╋╸┠┼┨ ┝╋┥ ▋ ╲╱╲╱╳╳╳\n\r');
term.write(' ║│╲ ╱│║ │║ ║│ ││ │ ││ │║ ┃ ║│ ┃│ ╿ │┃ ┍╅╆┓ ╵ ╹ ┗┷┛ └┸┘ ▌ ╱╲╱╲╳╳╳\n\r');
term.write(' ╠╡ ╳ ╞╣ ├╢ ╟┤ ├┼─┼─┼┤ ├╫─╂─╫┤ ┣┿╾┼╼┿┫ ┕┛┖┚ ┌┄┄┐ ╎ ┏┅┅┓ ┋ ▍ ╲╱╲╱╳╳╳\n\r');
term.write(' ║│╱ ╲│║ │║ ║│ ││ │ ││ │║ ┃ ║│ ┃│ ╽ │┃ ░░▒▒▓▓██ ┊ ┆ ╎ ╏ ┇ ┋ ▎\n\r');
term.write(' ║└─╥─┘║ │╚═╤═╝│ │╘═╪═╛│ │╙─╀─╜│ ┃└─╂─┘┃ ░░▒▒▓▓██ ┊ ┆ ╎ ╏ ┇ ┋ ▏\n\r');
term.write(' ╚══╩══╝ └──┴──┘ ╰──┴──╯ ╰──┴──╯ ┗━━┻━━┛ └╌╌┘ ╎ ┗╍╍┛ ┋ ▁▂▃▄▅▆▇█\n\r');
term.write('Box drawing alignment tests:\x1b[32m █\n\r');
term.write(' ▉\n\r');
term.write(' ╔══╦══╗ ┌──┬──┐ ╭──┬──╮ ╭──┬──╮ ┏━━┳━━┓ ┎┒┏┑ ╷ ╻ ┏┯┓ ┌┰┐ ▊ ╱╲╱╲╳╳╳\n\r');
term.write(' ║┌─╨─┐║ │╔═╧═╗│ │╒═╪═╕│ │╓─╁─╖│ ┃┌─╂─┐┃ ┗╃╄┙ ╶┼╴╺╋╸┠┼┨ ┝╋┥ ▋ ╲╱╲╱╳╳╳\n\r');
term.write(' ║│╲ ╱│║ │║ ║│ ││ │ ││ │║ ┃ ║│ ┃│ ╿ │┃ ┍╅╆┓ ╵ ╹ ┗┷┛ └┸┘ ▌ ╱╲╱╲╳╳╳\n\r');
term.write(' ╠╡ ╳ ╞╣ ├╢ ╟┤ ├┼─┼─┼┤ ├╫─╂─╫┤ ┣┿╾┼╼┿┫ ┕┛┖┚ ┌┄┄┐ ╎ ┏┅┅┓ ┋ ▍ ╲╱╲╱╳╳╳\n\r');
term.write(' ║│╱ ╲│║ │║ ║│ ││ │ ││ │║ ┃ ║│ ┃│ ╽ │┃ ░░▒▒▓▓██ ┊ ┆ ╎ ╏ ┇ ┋ ▎\n\r');
term.write(' ║└─╥─┘║ │╚═╤═╝│ │╘═╪═╛│ │╙─╀─╜│ ┃└─╂─┘┃ ░░▒▒▓▓██ ┊ ┆ ╎ ╏ ┇ ┋ ▏\n\r');
term.write(' ╚══╩══╝ └──┴──┘ ╰──┴──╯ ╰──┴──╯ ┗━━┻━━┛ └╌╌┘ ╎ ┗╍╍┛ ┋ ▁▂▃▄▅▆▇█\n\r');
window.scrollTo(0, 0);
}
function loadTest() {
const isWebglEnabled = !!addons.webgl.instance;
const testData = [];
let byteCount = 0;
for (let i = 0; i < 50; i++) {
const count = 1 + Math.floor(Math.random() * 79);
byteCount += count + 2;
const data = new Uint8Array(count + 2);
data[0] = 0x0A; // \n
for (let i = 1; i < count + 1; i++) {
data[i] = 0x61 + Math.floor(Math.random() * (0x7A - 0x61));
}
// End each line with \r so the cursor remains constant, this is what ls/tree do and improves
// performance significantly due to the cursor DOM element not needing to change
data[data.length - 1] = 0x0D; // \r
testData.push(data);
}
const start = performance.now();
for (let i = 0; i < 1024; i++) {
for (const d of testData) {
term.write(d);
}
}
// Wait for all data to be parsed before evaluating time
term.write('', () => {
const time = Math.round(performance.now() - start);
const mbs = ((byteCount / 1024) * (1 / (time / 1000))).toFixed(2);
term.write(`\n\r\nWrote ${byteCount}kB in ${time}ms (${mbs}MB/s) using the (${isWebglEnabled ? 'webgl' : 'canvas'} renderer)`);
// Send ^C to get a new prompt
term._core._onData.fire('\x03');
});
}
+85 -35
View File
@@ -15,41 +15,91 @@
</head>
<body>
<h1 style="color: #2D2E2C">xterm.js: A terminal for the <em style="color: #5DA5D5">web</em></h1>
<div id="terminal-container"></div>
<div>
<h3>Options</h3>
<p>These options can be set in the <code>Terminal</code> constructor or by using the <code>Terminal.setOption</code> function.</p>
<div id="options-container"></div>
</div>
<div>
<h3>Addons</h3>
<p>Addons can be loaded and unloaded on a particular terminal to extend its functionality.</p>
<div id="addons-container"></div>
<h3>Addons Control</h3>
<h4>SearchAddon</h4>
<p>
<label>Find next <input id="find-next"/></label>
<label>Find previous <input id="find-previous"/></label>
<label>Use regex<input type="checkbox" id="regex"/></label>
<label>Case sensitive<input type="checkbox" id="case-sensitive"/></label>
<label>Whole word<input type="checkbox" id="whole-word"/></label>
</p>
<h4>SerializeAddon</h4>
<p>
<button id="serialize">Serialize the content of terminal</button>
<label><input type="checkbox" id="write-to-terminal">Write back to terminal</label>
<div><pre id="serialize-output"></pre></div>
</p>
</div>
<div>
<h3>Style</h3>
<div style="display: inline-block; margin-right: 16px;">
<label for="padding">Padding</label>
<input type="number" id="padding" />
<div id="container">
<div id="grid">
<div id="terminal-container"></div>
</div>
<div id="grid">
<div class="tab">
<button id= "optionsbutton" class="tabLinks" onclick="openSection(event, 'options')">Options</button>
<button id= "addonsbutton" class="tabLinks" onclick="openSection(event, 'addons')">Addons</button>
<button id= "stylebutton" class="tabLinks" onclick="openSection(event, 'style')">Style</button>
<button id= "testbutton" class="tabLinks" onclick="openSection(event, 'test')">Test</button>
</div>
<div id="options" class="tabContent">
<h3>Options</h3>
<p>These options can be set in the <code>Terminal</code> constructor or by using the <code>Terminal.setOption</code> function.</p>
<div id="options-container"></div>
</div>
<div id="addons" class="tabContent">
<h3>Addons</h3>
<p>Addons can be loaded and unloaded on a particular terminal to extend its functionality.</p>
<div id="addons-container"></div>
<h3>Addons Control</h3>
<h4>SearchAddon</h4>
<div style= "display:flex; flex-direction:column;">
<label>Find next <input id="find-next"/></label>
<label>Find previous <input id="find-previous"/></label>
<label><input type="checkbox" id="regex"/>Use regex</label>
<label><input type="checkbox" id="case-sensitive"/>Case sensitive</label>
<label><input type="checkbox" id="whole-word"/>Whole word</label>
</div>
<h4>SerializeAddon</h4>
<div>
<button id="serialize">Serialize the content of terminal</button>
<label><input type="checkbox" id="write-to-terminal">Write back to terminal</label>
<div><pre id="serialize-output"></pre></div>
</div>
</div>
<div id="style" class="tabContent">
<h3>Style</h3>
<div style="display: inline-block; margin-right: 16px;">
<label for="padding">Padding</label>
<input type="number" id="padding" />
</div>
</div>
<div id="test" class="tabContent">
<h3>Test</h3>
<div style="display: inline-block; margin-right: 16px;">
<button id="dispose" title="This is used to testing memory leaks">Dispose terminal</button>
<button id="custom-glyph" title="Write custom box drawing and block element characters to the terminal">Test custom glyphs</button>
<button id="load-test" title="Write several MB of data to simulate a lot of data coming from the process">Load test</button>
</div>
</div>
</div>
</div>
<hr/>
<button id="dispose" title="This is used to testing memory leaks">Dispose terminal</button>
<script src="dist/client-bundle.js" defer ></script>
</body>
<script src="dist/client-bundle.js" defer ></script>
<script>
var tab = localStorage.getItem("tab");
if(tab === null){
document.getElementById("options").style.display = "block";
document.getElementById("optionsbutton").classList.add("active");
}
else {
const tabContent = document.getElementsByClassName("tabContent");
let itr;
for (itr = 0; itr < tabContent.length; itr+=1) {
tabContent[itr].style.display = "none";
}
document.getElementById(""+tab+"").style.display = "block";
document.getElementById(""+tab+"button").classList.add("active");
}
function openSection(event, section) {
const tabContent = document.getElementsByClassName("tabContent");
let itr;
for (itr = 0; itr < tabContent.length; itr+=1) {
tabContent[itr].style.display = "none";
}
const tabLinks = document.getElementsByClassName("tabLinks");
for (itr = 0; itr < tabLinks.length; itr+=1) {
tabLinks[itr].className = tabLinks[itr].className.replace(" active", "");
}
document.getElementById(section).style.display = "block";
localStorage.setItem("tab", section);
event.currentTarget.className += " active";
}
</script>
</body>
</html>
+8
View File
@@ -49,6 +49,14 @@ const clientConfig = {
alias: {
common: path.resolve('./out/common'),
browser: path.resolve('./out/browser')
},
fallback: {
// The ligature modules contains fallbacks for node environments, we never want to browserify them
stream: false,
util: false,
os: false,
path: false,
fs: false
}
},
output: {
+49
View File
@@ -41,3 +41,52 @@ pre {
word-wrap: break-word;
white-space: pre-wrap;
}
#container {
display: flex;
height: 75vh;
}
#grid {
flex: 1;
/* max-height: 80vh;
overflow-y: auto; */
width: 100%;
}
.tab {
overflow: hidden;
border: 1px solid #ccc;
background-color: #f1f1f1;
}
/* Style the buttons inside the tab */
.tab button {
background-color: inherit;
float: left;
border: none;
outline: none;
cursor: pointer;
padding: 14px 16px;
transition: 0.3s;
font-size: 17px;
}
/* Change background color of buttons on hover */
.tab button:hover {
background-color: #ddd;
}
/* Create an active/current tablink class */
.tab button.active {
background-color: #ccc;
}
/* Style the tab content */
.tabContent {
display: none;
padding: 6px 12px;
border: 1px solid #ccc;
border-top: none;
max-height: 100vh;
overflow-y: auto;
}
+4
View File
@@ -0,0 +1,4 @@
lib-headless/
typings/
logo-full.png
./package.json
+29
View File
@@ -0,0 +1,29 @@
# Blacklist - exclude everything except npm defaults such as LICENSE, etc
*
!*/
# Whitelist - lib-headless/
!lib-headless/**/*.d.ts
!lib-headless/**/*.js
!lib-headless/**/*.js.map
!lib-headless/**/*.css
# Whitelist - src/
!src/**/*.ts
!src/**/*.d.ts
!src/**/*.js
!src/**/*.js.map
!src/**/*.css
# Blacklist - src/ test files
src/**/*.test.ts
src/**/*.test.d.ts
src/**/*.test.js
src/**/*.test.js.map
# Whitelist - typings/
!typings/*.d.ts
+31
View File
@@ -0,0 +1,31 @@
# [![xterm.js logo](logo-full.png)](https://xtermjs.org)
⚠ This package is experimental
`xterm-headless` is a headless terminal that can be run in node.js. This is useful in combination with the frontend [`xterm`](https://www.npmjs.com/package/xterm) for example to keep track of a terminal's state on a remote server where the process is hosted.
## Getting Started
First, you need to install the module, we ship exclusively through npm, so you need that installed and then add xterm.js as a dependency by running:
```sh
npm install xterm-headless
```
Then import as you would a regular node package. The recommended way to load `xterm-headless` is with TypeScript and the ES6 module syntax:
```javascript
import { Terminal } from 'xterm-headless';
```
## API
The full API for `xterm-headless` is contained within the [TypeScript declaration file](https://github.com/xtermjs/xterm.js/blob/master/typings/xterm-headless.d.ts), use the branch/tag picker in GitHub (`w`) to navigate to the correct version of the API.
Note that some APIs are marked *experimental*, these are added to enable experimentation with new ideas without committing to support it like a normal [semver](https://semver.org/) API. Note that these APIs can change radically between versions, so be sure to read release notes if you plan on using experimental APIs.
### Addons
Addons in `xterm-headless` work the [same as in `xterm`](https://github.com/xtermjs/xterm.js/blob/master/README.md#addons) with the one caveat being that the addon needs to be packaged for node.js and not use any DOM APIs.
Currently no official addons are packaged on npm.
+34 -29
View File
@@ -1,7 +1,7 @@
{
"name": "xterm",
"description": "Full xterm terminal, in your browser",
"version": "4.11.0",
"version": "4.14.1",
"main": "lib/xterm.js",
"style": "css/xterm.css",
"types": "typings/xterm.d.ts",
@@ -10,6 +10,8 @@
"scripts": {
"prepackage": "npm run build",
"package": "webpack",
"package-headless": "webpack --config ./webpack.config.headless.js",
"postpackage-headless": "node ./bin/package_headless.js",
"start": "node demo/start",
"start-debug": "node --inspect-brk demo/start",
"lint": "eslint -c .eslintrc.json --max-warnings 0 --ext .ts src/ addons/",
@@ -21,6 +23,7 @@
"test-api-webkit": "node ./bin/test_api.js --browser=webkit --timeout=20000",
"test-unit": "node ./bin/test.js",
"test-unit-coverage": "node ./bin/test.js --coverage",
"test-unit-dev": "cross-env NODE_PATH='./out' mocha",
"build": "tsc -b ./tsconfig.all.json",
"prepare": "npm run setup",
"setup": "npm run build",
@@ -34,37 +37,39 @@
"vtfeatures": "node bin/extract_vtfeatures.js src/**/*.ts src/*.ts"
},
"devDependencies": {
"@types/chai": "^4.2.14",
"@types/debug": "^4.1.5",
"@types/chai": "^4.2.22",
"@types/debug": "^4.1.7",
"@types/deep-equal": "^1.0.1",
"@types/glob": "^7.1.3",
"@types/jsdom": "^16.2.5",
"@types/mocha": "^8.0.3",
"@types/node": "^10.17.17",
"@types/utf8": "^2.1.6",
"@types/webpack": "^4.41.24",
"@types/ws": "^7.2.9",
"@typescript-eslint/eslint-plugin": "^4.0.0",
"@typescript-eslint/parser": "^3.10.1",
"chai": "^4.2.0",
"deep-equal": "^2.0.4",
"eslint": "^7.12.1",
"@types/glob": "^7.2.0",
"@types/jsdom": "^16.2.13",
"@types/mocha": "^9.0.0",
"@types/node": "^14.14.44",
"@types/utf8": "^3.0.0",
"@types/webpack": "^5.28.0",
"@types/ws": "^8.2.0",
"@typescript-eslint/eslint-plugin": "^5.3.0",
"@typescript-eslint/parser": "^5.3.0",
"chai": "^4.3.4",
"cross-env": "^7.0.3",
"deep-equal": "^2.0.5",
"eslint": "^8.1.0",
"express": "^4.17.1",
"express-ws": "^4.0.0",
"glob": "^7.0.5",
"jsdom": "^16.4.0",
"mocha": "^8.2.1",
"mustache": "^4.0.1",
"node-pty": "^0.9.0",
"express-ws": "^5.0.2",
"glob": "^7.2.0",
"jsdom": "^18.0.1",
"mocha": "^9.1.3",
"mustache": "^4.2.0",
"node-pty": "^0.10.1",
"nyc": "^15.1.0",
"playwright": "^1.5.2",
"source-map-loader": "^1.1.2",
"ts-loader": "^8.0.8",
"typescript": "4.0",
"playwright": "^1.16.2",
"source-map-loader": "^3.0.0",
"source-map-support": "^0.5.20",
"ts-loader": "^9.1.2",
"typescript": "^4.4.4",
"utf8": "^3.0.0",
"webpack": "^5.4.0",
"webpack-cli": "^4.2.0",
"ws": "^7.3.1",
"xterm-benchmark": "^0.1.3"
"webpack": "^5.61.0",
"webpack-cli": "^4.9.1",
"ws": "^8.2.3",
"xterm-benchmark": "^0.3.0"
}
}
+6 -5
View File
@@ -4,10 +4,10 @@
*/
import * as Strings from 'browser/LocalizableStrings';
import { ITerminal } from 'browser/Types';
import { ITerminal, IRenderDebouncer } from 'browser/Types';
import { IBuffer } from 'common/buffer/Types';
import { isMac } from 'common/Platform';
import { RenderDebouncer } from 'browser/RenderDebouncer';
import { TimeBasedDebouncer } from 'browser/TimeBasedDebouncer';
import { addDisposableDomListener } from 'browser/Lifecycle';
import { Disposable } from 'common/Lifecycle';
import { ScreenDprMonitor } from 'browser/ScreenDprMonitor';
@@ -28,7 +28,7 @@ export class AccessibilityManager extends Disposable {
private _liveRegion: HTMLElement;
private _liveRegionLineCount: number = 0;
private _renderRowsDebouncer: RenderDebouncer;
private _renderRowsDebouncer: IRenderDebouncer;
private _screenDprMonitor: ScreenDprMonitor;
private _topBoundaryFocusListener: (e: FocusEvent) => void;
@@ -53,6 +53,7 @@ export class AccessibilityManager extends Disposable {
) {
super();
this._accessibilityTreeRoot = document.createElement('div');
this._accessibilityTreeRoot.setAttribute('role', 'document');
this._accessibilityTreeRoot.classList.add('xterm-accessibility');
this._rowContainer = document.createElement('div');
@@ -72,7 +73,7 @@ export class AccessibilityManager extends Disposable {
this._refreshRowsDimensions();
this._accessibilityTreeRoot.appendChild(this._rowContainer);
this._renderRowsDebouncer = new RenderDebouncer(this._renderRows.bind(this));
this._renderRowsDebouncer = new TimeBasedDebouncer(this._renderRows.bind(this));
this._refreshRows();
this._liveRegion = document.createElement('div');
@@ -112,7 +113,7 @@ export class AccessibilityManager extends Disposable {
}
private _onBoundaryFocus(e: FocusEvent, position: BoundaryPosition): void {
const boundaryElement = <HTMLElement>e.target;
const boundaryElement = e.target as HTMLElement;
const beforeBoundaryElement = this._rowElements[position === BoundaryPosition.TOP ? 1 : this._rowElements.length - 2];
// Don't scroll if the buffer top has reached the end in that direction
+2 -2
View File
@@ -17,7 +17,7 @@ describe('ColorManager', () => {
dom = new jsdom.JSDOM('');
window = dom.window;
document = window.document;
(<any>window).HTMLCanvasElement.prototype.getContext = () => ({
(window as any).HTMLCanvasElement.prototype.getContext = () => ({
createLinearGradient(): any {
return null;
},
@@ -36,7 +36,7 @@ describe('ColorManager', () => {
for (const key of Object.keys(cm.colors)) {
if (key !== 'ansi' && key !== 'contrastCache') {
// A #rrggbb or rgba(...)
assert.ok((<any>cm.colors)[key].css.length >= 7);
assert.ok((cm.colors as any)[key].css.length >= 7);
}
}
assert.equal(cm.colors.ansi.length, 256);
+2 -2
View File
@@ -174,7 +174,7 @@ describe('Linkifier', () => {
assert.equal(mouseZoneManager.zones[0].y1, 1);
assert.equal(mouseZoneManager.zones[0].y2, 1);
// Fires done()
mouseZoneManager.zones[0].clickCallback(<any>{});
mouseZoneManager.zones[0].clickCallback({} as any);
}
});
linkifier.linkifyRows();
@@ -210,7 +210,7 @@ describe('Linkifier', () => {
let count = 0;
linkifier.registerLinkMatcher(/test/, () => assert.fail(), {
validationCallback: (url, cb) => {
count += 1;
count++;
if (count === 2) {
done();
}
+3 -1
View File
@@ -89,7 +89,9 @@ export class Linkifier implements ILinkifier {
if (this._rowsTimeoutId) {
clearTimeout(this._rowsTimeoutId);
}
this._rowsTimeoutId = <number><any>setTimeout(() => this._linkifyRows(), Linkifier._timeBeforeLatency);
// Cannot use window.setTimeout since tests need to run in node
this._rowsTimeoutId = setTimeout(() => this._linkifyRows(), Linkifier._timeBeforeLatency) as any as number;
}
/**
+2 -11
View File
@@ -3,7 +3,7 @@
* @license MIT
*/
import { ILinkifier2, ILinkProvider, IBufferCellPosition, ILink, ILinkifierEvent, ILinkDecorations } from 'browser/Types';
import { ILinkifier2, ILinkProvider, IBufferCellPosition, ILink, ILinkifierEvent, ILinkDecorations, ILinkWithState } from 'browser/Types';
import { IDisposable } from 'common/Types';
import { IMouseService, IRenderService } from './services/Services';
import { IBufferService } from 'common/services/Services';
@@ -11,21 +11,12 @@ import { EventEmitter, IEvent } from 'common/EventEmitter';
import { Disposable, getDisposeArrayDisposable, disposeArray } from 'common/Lifecycle';
import { addDisposableDomListener } from 'browser/Lifecycle';
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[] = [];
public get currentLink(): ILinkWithState | undefined { return this._currentLink; }
protected _currentLink: ILinkWithState | undefined;
private _lastMouseEvent: MouseEvent | undefined;
private _linkCacheDisposables: IDisposable[] = [];
+1 -3
View File
@@ -156,9 +156,7 @@ export class MouseZoneManager extends Disposable implements IMouseZoneManager {
private _onTooltip(e: MouseEvent): void {
this._tooltipTimeout = undefined;
const zone = this._findZoneEventAt(e);
if (zone && zone.tooltipCallback) {
zone.tooltipCallback(e);
}
zone?.tooltipCallback(e);
}
private _onMouseDown(e: MouseEvent): void {
+2 -2
View File
@@ -3,12 +3,12 @@
* @license MIT
*/
import { IDisposable } from 'common/Types';
import { IRenderDebouncer } from 'browser/Types';
/**
* Debounces calls to render terminal rows using animation frames.
*/
export class RenderDebouncer implements IDisposable {
export class RenderDebouncer implements IRenderDebouncer {
private _rowStart: number | undefined;
private _rowEnd: number | undefined;
private _rowCount: number | undefined;
File diff suppressed because it is too large Load Diff
+175 -87
View File
@@ -21,8 +21,8 @@
* http://linux.die.net/man/7/urxvt
*/
import { ICompositionHelper, ITerminal, IBrowser, CustomKeyEventHandler, ILinkifier, IMouseZoneManager, LinkMatcherHandler, ILinkMatcherOptions, IViewport, ILinkifier2 } from 'browser/Types';
import { IRenderer, CharacterJoinerHandler } from 'browser/renderer/Types';
import { ICompositionHelper, ITerminal, IBrowser, CustomKeyEventHandler, ILinkifier, IMouseZoneManager, LinkMatcherHandler, ILinkMatcherOptions, IViewport, ILinkifier2, CharacterJoinerHandler } from 'browser/Types';
import { IRenderer } from 'browser/renderer/Types';
import { CompositionHelper } from 'browser/input/CompositionHelper';
import { Viewport } from 'browser/Viewport';
import { rightClickHandler, moveTextAreaUnderMouseCursor, handlePasteEvent, copyHandler, paste } from 'browser/Clipboard';
@@ -39,13 +39,13 @@ import { MouseZoneManager } from 'browser/MouseZoneManager';
import { AccessibilityManager } from './AccessibilityManager';
import { ITheme, IMarker, IDisposable, ISelectionPosition, ILinkProvider } from 'xterm';
import { DomRenderer } from 'browser/renderer/dom/DomRenderer';
import { IKeyboardEvent, KeyboardResultType, CoreMouseEventType, CoreMouseButton, CoreMouseAction, ITerminalOptions, IAnsiColorChangeEvent } from 'common/Types';
import { IKeyboardEvent, KeyboardResultType, CoreMouseEventType, CoreMouseButton, CoreMouseAction, ITerminalOptions, ScrollSource, IAnsiColorChangeEvent } from 'common/Types';
import { evaluateKeyboardEvent } from 'common/input/Keyboard';
import { EventEmitter, IEvent, forwardEvent } from 'common/EventEmitter';
import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine';
import { ColorManager } from 'browser/ColorManager';
import { RenderService } from 'browser/services/RenderService';
import { ICharSizeService, IRenderService, IMouseService, ISelectionService, ISoundService, ICoreBrowserService } from 'browser/services/Services';
import { ICharSizeService, IRenderService, IMouseService, ISelectionService, ISoundService, ICoreBrowserService, ICharacterJoinerService } from 'browser/services/Services';
import { CharSizeService } from 'browser/services/CharSizeService';
import { IBuffer } from 'common/buffer/Types';
import { MouseService } from 'browser/services/MouseService';
@@ -54,6 +54,7 @@ import { CoreBrowserService } from 'browser/services/CoreBrowserService';
import { CoreTerminal } from 'common/CoreTerminal';
import { ITerminalOptions as IInitializedTerminalOptions } from 'common/services/Services';
import { rgba } from 'browser/Color';
import { CharacterJoinerService } from 'browser/services/CharacterJoinerService';
// Let it work inside Node.js for automated testing purposes.
const document: Document = (typeof window !== 'undefined') ? window.document : null as any;
@@ -71,7 +72,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
// private _visualBellTimer: number;
public browser: IBrowser = <any>Browser;
public browser: IBrowser = Browser as any;
// TODO: We should remove options once components adopt optionsService
public get options(): IInitializedTerminalOptions { return this.optionsService.options; }
@@ -82,6 +83,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
private _charSizeService: ICharSizeService | undefined;
private _mouseService: IMouseService | undefined;
private _renderService: IRenderService | undefined;
private _characterJoinerService: ICharacterJoinerService | undefined;
private _selectionService: ISelectionService | undefined;
private _soundService: ISoundService | undefined;
@@ -92,6 +94,20 @@ export class Terminal extends CoreTerminal implements ITerminal {
*/
private _keyDownHandled: boolean = false;
/**
* Records whether the keypress event has already been handled and triggered a data event, if so
* the input event should not trigger a data event but should still print to the textarea so
* screen readers will announce it.
*/
private _keyPressHandled: boolean = false;
/**
* Records whether there has been a keydown event for a dead key without a corresponding keydown
* event for the composed/alternative character. If we cancel the keydown event for the dead key,
* no events will be emitted for the final character.
*/
private _unprocessedDeadKey: boolean = false;
public linkifier: ILinkifier;
public linkifier2: ILinkifier2;
public viewport: IViewport | undefined;
@@ -111,6 +127,8 @@ export class Terminal extends CoreTerminal implements ITerminal {
public get onSelectionChange(): IEvent<void> { return this._onSelectionChange.event; }
private _onTitleChange = new EventEmitter<string>();
public get onTitleChange(): IEvent<string> { return this._onTitleChange.event; }
private _onBell = new EventEmitter<void>();
public get onBell(): IEvent<void> { return this._onBell.event; }
private _onFocus = new EventEmitter<void>();
public get onFocus(): IEvent<void> { return this._onFocus.event; }
@@ -134,7 +152,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
* @alias module:xterm/src/xterm
*/
constructor(
options: ITerminalOptions = {}
options: Partial<ITerminalOptions> = {}
) {
super(options);
@@ -146,8 +164,8 @@ export class Terminal extends CoreTerminal implements ITerminal {
// Setup InputHandler listeners
this.register(this._inputHandler.onRequestBell(() => this.bell()));
this.register(this._inputHandler.onRequestRefreshRows((start, end) => this.refresh(start, end)));
this.register(this._inputHandler.onRequestSendFocus(() => this._reportFocus()));
this.register(this._inputHandler.onRequestReset(() => this.reset()));
this.register(this._inputHandler.onRequestScroll((eraseAttr, isWrapped) => this.scroll(eraseAttr, isWrapped || undefined)));
this.register(this._inputHandler.onRequestWindowsOptionsReport(type => this._reportWindowsOptions(type)));
this.register(this._inputHandler.onAnsiColorChange((event) => this._changeAnsiColor(event)));
this.register(forwardEvent(this._inputHandler.onCursorMove, this._onCursorMove));
@@ -162,11 +180,11 @@ export class Terminal extends CoreTerminal implements ITerminal {
private _changeAnsiColor(event: IAnsiColorChangeEvent): void {
if (!this._colorManager) { return; }
event.colors.forEach(ansiColor => {
for (const ansiColor of event.colors) {
const color = rgba.toColor(ansiColor.red, ansiColor.green, ansiColor.blue);
this._colorManager!.colors.ansi[ansiColor.colorIndex] = color;
});
}
this._renderService?.setColors(this._colorManager!.colors);
this.viewport?.onThemeChange(this._colorManager!.colors);
@@ -221,6 +239,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
// The DOM renderer needs a row refresh to update the cursor styles
this.refresh(this.buffer.y, this.buffer.y);
break;
case 'customGlyphs':
case 'drawBoldTextInBrightColors':
case 'letterSpacing':
case 'lineHeight':
@@ -264,8 +283,8 @@ export class Terminal extends CoreTerminal implements ITerminal {
* Binds the desired focus behavior on a given terminal object.
*/
private _onTextAreaFocus(ev: KeyboardEvent): void {
if (this._coreService.decPrivateModes.sendFocus) {
this._coreService.triggerDataEvent(C0.ESC + '[I');
if (this.coreService.decPrivateModes.sendFocus) {
this.coreService.triggerDataEvent(C0.ESC + '[I');
}
this.updateCursorStyle(ev);
this.element!.classList.add('focus');
@@ -289,27 +308,34 @@ export class Terminal extends CoreTerminal implements ITerminal {
// screen readers reading it out.
this.textarea!.value = '';
this.refresh(this.buffer.y, this.buffer.y);
if (this._coreService.decPrivateModes.sendFocus) {
this._coreService.triggerDataEvent(C0.ESC + '[O');
if (this.coreService.decPrivateModes.sendFocus) {
this.coreService.triggerDataEvent(C0.ESC + '[O');
}
this.element!.classList.remove('focus');
this._onBlur.fire();
}
private _syncTextArea(): void {
if (!this.textarea || !this.buffer.isCursorInViewport || this._compositionHelper!.isComposing) {
if (!this.textarea || !this.buffer.isCursorInViewport || this._compositionHelper!.isComposing || !this._renderService) {
return;
}
const cellHeight = Math.ceil(this._charSizeService!.height * this.optionsService.options.lineHeight);
const cursorTop = this._bufferService.buffer.y * cellHeight;
const cursorLeft = this._bufferService.buffer.x * this._charSizeService!.width;
const cursorY = this.buffer.ybase + this.buffer.y;
const bufferLine = this.buffer.lines.get(cursorY);
if (!bufferLine) {
return;
}
const cursorX = Math.min(this.buffer.x, this.cols - 1);
const cellHeight = this._renderService.dimensions.actualCellHeight;
const width = bufferLine.getWidth(cursorX);
const cellWidth = this._renderService.dimensions.actualCellWidth * width;
const cursorTop = this.buffer.y * this._renderService.dimensions.actualCellHeight;
const cursorLeft = cursorX * this._renderService.dimensions.actualCellWidth;
// Sync the textarea to the exact position of the composition view so the IME knows where the
// text is.
this.textarea.style.left = cursorLeft + 'px';
this.textarea.style.top = cursorTop + 'px';
this.textarea.style.width = this._charSizeService!.width + 'px';
this.textarea.style.width = cellWidth + 'px';
this.textarea.style.height = cellHeight + 'px';
this.textarea.style.lineHeight = cellHeight + 'px';
this.textarea.style.zIndex = '-5';
@@ -330,7 +356,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
}
copyHandler(event, this._selectionService!);
}));
const pasteHandlerWrapper = (event: ClipboardEvent): void => handlePasteEvent(event, this.textarea!, this._coreService);
const pasteHandlerWrapper = (event: ClipboardEvent): void => handlePasteEvent(event, this.textarea!, this.coreService);
this.register(addDisposableDomListener(this.textarea!, 'paste', pasteHandlerWrapper));
this.register(addDisposableDomListener(this.element!, 'paste', pasteHandlerWrapper));
@@ -372,6 +398,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
this.register(addDisposableDomListener(this.textarea!, 'compositionstart', () => this._compositionHelper!.compositionstart()));
this.register(addDisposableDomListener(this.textarea!, 'compositionupdate', (e: CompositionEvent) => this._compositionHelper!.compositionupdate(e)));
this.register(addDisposableDomListener(this.textarea!, 'compositionend', () => this._compositionHelper!.compositionend()));
this.register(addDisposableDomListener(this.textarea!, 'input', (ev: InputEvent) => this._inputEvent(ev), true));
this.register(this.onRender(() => this._compositionHelper!.updateCompositionElements()));
this.register(this.onRender(e => this._queueLinkification(e.start, e.end)));
}
@@ -398,7 +425,6 @@ export class Terminal extends CoreTerminal implements ITerminal {
this.element.classList.add('terminal');
this.element.classList.add('xterm');
this.element.setAttribute('tabindex', '0');
this.element.setAttribute('role', 'document');
parent.appendChild(this.element);
// Performance: Use a document fragment to build the terminal
@@ -438,6 +464,20 @@ export class Terminal extends CoreTerminal implements ITerminal {
this._charSizeService = this._instantiationService.createInstance(CharSizeService, this._document, this._helperContainer);
this._instantiationService.setService(ICharSizeService, this._charSizeService);
this._theme = this.options.theme || this._theme;
this._colorManager = new ColorManager(document, this.options.allowTransparency);
this.register(this.optionsService.onOptionChange(e => this._colorManager!.onOptionsChange(e)));
this._colorManager.setTheme(this._theme);
this._characterJoinerService = this._instantiationService.createInstance(CharacterJoinerService);
this._instantiationService.setService(ICharacterJoinerService, this._characterJoinerService);
const renderer = this._createRenderer();
this._renderService = this.register(this._instantiationService.createInstance(RenderService, renderer, this.rows, this.screenElement));
this._instantiationService.setService(IRenderService, this._renderService);
this.register(this._renderService.onRenderedBufferChange(e => this._onRender.fire(e)));
this.onResize(e => this._renderService!.resize(e.cols, e.rows));
this._compositionView = document.createElement('div');
this._compositionView.classList.add('composition-view');
this._compositionHelper = this._instantiationService.createInstance(CompositionHelper, this.textarea, this._compositionView);
@@ -446,26 +486,16 @@ export class Terminal extends CoreTerminal implements ITerminal {
// Performance: Add viewport and helper elements from the fragment
this.element.appendChild(fragment);
this._theme = this.options.theme || this._theme;
this._colorManager = new ColorManager(document, this.options.allowTransparency);
this.register(this.optionsService.onOptionChange(e => this._colorManager!.onOptionsChange(e)));
this._colorManager.setTheme(this._theme);
const renderer = this._createRenderer();
this._renderService = this.register(this._instantiationService.createInstance(RenderService, renderer, this.rows, this.screenElement));
this._instantiationService.setService(IRenderService, this._renderService);
this.register(this._renderService.onRenderedBufferChange(e => this._onRender.fire(e)));
this.onResize(e => this._renderService!.resize(e.cols, e.rows));
this._soundService = this._instantiationService.createInstance(SoundService);
this._instantiationService.setService(ISoundService, this._soundService);
this._mouseService = this._instantiationService.createInstance(MouseService);
this._instantiationService.setService(IMouseService, this._mouseService);
this.viewport = this._instantiationService.createInstance(Viewport,
(amount: number, suppressEvent: boolean) => this.scrollLines(amount, suppressEvent),
(amount: number) => this.scrollLines(amount, true, ScrollSource.VIEWPORT),
this._viewportElement,
this._viewportScrollArea
this._viewportScrollArea,
this.element
);
this.viewport.onThemeChange(this._colorManager.colors);
this.register(this._inputHandler.onRequestSyncScrollBar(() => this.viewport!.syncScrollArea()));
@@ -482,7 +512,9 @@ export class Terminal extends CoreTerminal implements ITerminal {
this._selectionService = this.register(this._instantiationService.createInstance(SelectionService,
this.element,
this.screenElement));
this.screenElement,
this.linkifier2
));
this._instantiationService.setService(ISelectionService, this._selectionService);
this.register(this._selectionService.onRequestScrollLines(e => this.scrollLines(e.amount, e.suppressScrollEvent)));
this.register(this._selectionService.onSelectionChange(() => this._onSelectionChange.fire()));
@@ -495,7 +527,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
this.textarea!.focus();
this.textarea!.select();
}));
this.register(this.onScroll(() => {
this.register(this._onScroll.event(ev => {
this.viewport!.syncScrollArea();
this._selectionService!.refresh();
}));
@@ -511,7 +543,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
this.register(addDisposableDomListener(this.element, 'mousedown', (e: MouseEvent) => this._selectionService!.onMouseDown(e)));
// apply mouse event classes set by escape codes before terminal was attached
if (this._coreMouseService.areMouseEventsActive) {
if (this.coreMouseService.areMouseEventsActive) {
this._selectionService.disable();
this.element.classList.add('enable-mouse-events');
} else {
@@ -586,7 +618,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
let but: CoreMouseButton;
let action: CoreMouseAction | undefined;
switch ((<any>ev).overrideType || ev.type) {
switch ((ev as any).overrideType || ev.type) {
case 'mousemove':
action = CoreMouseAction.MOVE;
if (ev.buttons === undefined) {
@@ -629,7 +661,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
return false;
}
return self._coreMouseService.triggerMouseEvent({
return self.coreMouseService.triggerMouseEvent({
col: pos.x - 33, // FIXME: why -33 here?
row: pos.y - 33,
button: but,
@@ -668,8 +700,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
},
wheel: (ev: WheelEvent) => {
sendEvent(ev);
ev.preventDefault();
return this.cancel(ev);
return this.cancel(ev, true);
},
mousedrag: (ev: MouseEvent) => {
// deal only with move while a button is held
@@ -684,11 +715,11 @@ export class Terminal extends CoreTerminal implements ITerminal {
}
}
};
this.register(this._coreMouseService.onProtocolChange(events => {
this.register(this.coreMouseService.onProtocolChange(events => {
// apply global changes on events
if (events) {
if (this.optionsService.options.logLevel === 'debug') {
this._logService.debug('Binding to mouse events:', this._coreMouseService.explainEvents(events));
this._logService.debug('Binding to mouse events:', this.coreMouseService.explainEvents(events));
}
this.element!.classList.add('enable-mouse-events');
this._selectionService!.disable();
@@ -731,7 +762,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
}
}));
// force initial onProtocolChange so we dont miss early mouse requests
this._coreMouseService.activeProtocol = this._coreMouseService.activeProtocol;
this.coreMouseService.activeProtocol = this.coreMouseService.activeProtocol;
/**
* "Always on" event listeners.
@@ -743,7 +774,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
// Don't send the mouse button to the pty if mouse events are disabled or
// if the selection manager is having selection forced (ie. a modifier is
// held).
if (!this._coreMouseService.areMouseEventsActive || this._selectionService!.shouldForceSelection(ev)) {
if (!this.coreMouseService.areMouseEventsActive || this._selectionService!.shouldForceSelection(ev)) {
return;
}
@@ -764,46 +795,44 @@ export class Terminal extends CoreTerminal implements ITerminal {
}));
this.register(addDisposableDomListener(el, 'wheel', (ev: WheelEvent) => {
if (!requestedEvents.wheel) {
// do nothing, if app side handles wheel itself
if (requestedEvents.wheel) return;
if (!this.buffer.hasScrollback) {
// Convert wheel events into up/down events when the buffer does not have scrollback, this
// enables scrolling in apps hosted in the alt buffer such as vim or tmux.
if (!this.buffer.hasScrollback) {
const amount = this.viewport!.getLinesScrolled(ev);
const amount = this.viewport!.getLinesScrolled(ev);
// Do nothing if there's no vertical scroll
if (amount === 0) {
return;
}
// Construct and send sequences
const sequence = C0.ESC + (this._coreService.decPrivateModes.applicationCursorKeys ? 'O' : '[') + (ev.deltaY < 0 ? 'A' : 'B');
let data = '';
for (let i = 0; i < Math.abs(amount); i++) {
data += sequence;
}
this._coreService.triggerDataEvent(data, true);
// Do nothing if there's no vertical scroll
if (amount === 0) {
return;
}
return;
}
}, { passive: true }));
// allow wheel scrolling in
// the shell for example
this.register(addDisposableDomListener(el, 'wheel', (ev: WheelEvent) => {
if (requestedEvents.wheel) return;
// Construct and send sequences
const sequence = C0.ESC + (this.coreService.decPrivateModes.applicationCursorKeys ? 'O' : '[') + (ev.deltaY < 0 ? 'A' : 'B');
let data = '';
for (let i = 0; i < Math.abs(amount); i++) {
data += sequence;
}
this.coreService.triggerDataEvent(data, true);
return this.cancel(ev, true);
}
// normal viewport scrolling
// conditionally stop event, if the viewport still had rows to scroll within
if (!this.viewport!.onWheel(ev)) {
return this.cancel(ev);
}
}, { passive: false }));
this.register(addDisposableDomListener(el, 'touchstart', (ev: TouchEvent) => {
if (this._coreMouseService.areMouseEventsActive) return;
if (this.coreMouseService.areMouseEventsActive) return;
this.viewport!.onTouchStart(ev);
return this.cancel(ev);
}, { passive: true }));
this.register(addDisposableDomListener(el, 'touchmove', (ev: TouchEvent) => {
if (this._coreMouseService.areMouseEventsActive) return;
if (this.coreMouseService.areMouseEventsActive) return;
if (!this.viewport!.onTouchMove(ev)) {
return this.cancel(ev);
}
@@ -834,7 +863,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
* Change the cursor style for different selection modes
*/
public updateCursorStyle(ev: KeyboardEvent): void {
if (this._selectionService && this._selectionService.shouldColumnSelect(ev)) {
if (this._selectionService?.shouldColumnSelect(ev)) {
this.element!.classList.add('column-select');
} else {
this.element!.classList.remove('column-select');
@@ -845,19 +874,19 @@ export class Terminal extends CoreTerminal implements ITerminal {
* Display the cursor element
*/
private _showCursor(): void {
if (!this._coreService.isCursorInitialized) {
this._coreService.isCursorInitialized = true;
if (!this.coreService.isCursorInitialized) {
this.coreService.isCursorInitialized = true;
this.refresh(this.buffer.y, this.buffer.y);
}
}
public scrollLines(disp: number, suppressScrollEvent?: boolean): void {
super.scrollLines(disp, suppressScrollEvent);
public scrollLines(disp: number, suppressScrollEvent?: boolean, source = ScrollSource.TERMINAL): void {
super.scrollLines(disp, suppressScrollEvent, source);
this.refresh(0, this.rows - 1);
}
public paste(data: string): void {
paste(data, this.textarea!, this._coreService);
paste(data, this.textarea!, this.coreService);
}
/**
@@ -904,13 +933,19 @@ export class Terminal extends CoreTerminal implements ITerminal {
}
public registerCharacterJoiner(handler: CharacterJoinerHandler): number {
const joinerId = this._renderService!.registerCharacterJoiner(handler);
if (!this._characterJoinerService) {
throw new Error('Terminal must be opened first');
}
const joinerId = this._characterJoinerService.register(handler);
this.refresh(0, this.rows - 1);
return joinerId;
}
public deregisterCharacterJoiner(joinerId: number): void {
if (this._renderService!.deregisterCharacterJoiner(joinerId)) {
if (!this._characterJoinerService) {
throw new Error('Terminal must be opened first');
}
if (this._characterJoinerService.deregister(joinerId)) {
this.refresh(0, this.rows - 1);
}
}
@@ -999,12 +1034,16 @@ export class Terminal extends CoreTerminal implements ITerminal {
if (!this._compositionHelper!.keydown(event)) {
if (this.buffer.ybase !== this.buffer.ydisp) {
this.scrollToBottom();
this._bufferService.scrollToBottom();
}
return false;
}
const result = evaluateKeyboardEvent(event, this._coreService.decPrivateModes.applicationCursorKeys, this.browser.isMac, this.options.macOptionIsMeta);
if (event.key === 'Dead' || event.key === 'AltGraph') {
this._unprocessedDeadKey = true;
}
const result = evaluateKeyboardEvent(event, this.coreService.decPrivateModes.applicationCursorKeys, this.browser.isMac, this.options.macOptionIsMeta);
this.updateCursorStyle(event);
@@ -1031,6 +1070,11 @@ export class Terminal extends CoreTerminal implements ITerminal {
return true;
}
if (this._unprocessedDeadKey) {
this._unprocessedDeadKey = false;
return true;
}
// If ctrl+c or enter is being sent, clear out the textarea. This is done so that screen readers
// will announce deleted characters. This will not work 100% of the time but it should cover
// most scenarios.
@@ -1040,7 +1084,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
this._onKey.fire({ key: result.key, domEvent: event });
this._showCursor();
this._coreService.triggerDataEvent(result.key, true);
this.coreService.triggerDataEvent(result.key, true);
// Cancel events when not in screen reader mode so events don't get bubbled up and handled by
// other listeners. When screen reader mode is enabled, this could cause issues if the event
@@ -1053,10 +1097,11 @@ export class Terminal extends CoreTerminal implements ITerminal {
this._keyDownHandled = true;
}
private _isThirdLevelShift(browser: IBrowser, ev: IKeyboardEvent): boolean {
private _isThirdLevelShift(browser: IBrowser, ev: KeyboardEvent): boolean {
const thirdLevelKey =
(browser.isMac && !this.options.macOptionIsMeta && ev.altKey && !ev.ctrlKey && !ev.metaKey) ||
(browser.isWindows && ev.altKey && ev.ctrlKey && !ev.metaKey);
(browser.isWindows && ev.altKey && ev.ctrlKey && !ev.metaKey) ||
(browser.isWindows && ev.getModifierState('AltGraph'));
if (ev.type === 'keypress') {
return thirdLevelKey;
@@ -1076,6 +1121,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
}
this.updateCursorStyle(ev);
this._keyPressHandled = false;
}
/**
@@ -1087,6 +1133,8 @@ export class Terminal extends CoreTerminal implements ITerminal {
protected _keyPress(ev: KeyboardEvent): boolean {
let key;
this._keyPressHandled = false;
if (this._keyDownHandled) {
return false;
}
@@ -1117,20 +1165,48 @@ export class Terminal extends CoreTerminal implements ITerminal {
this._onKey.fire({ key, domEvent: ev });
this._showCursor();
this._coreService.triggerDataEvent(key, true);
this.coreService.triggerDataEvent(key, true);
this._keyPressHandled = true;
return true;
}
/**
* Handle an input event.
* Key Resources:
* - https://developer.mozilla.org/en-US/docs/Web/API/InputEvent
* @param ev The input event to be handled.
*/
protected _inputEvent(ev: InputEvent): boolean {
// Only support emoji IMEs when screen reader mode is disabled as the event must bubble up to
// support reading out character input which can doubling up input characters
if (ev.data && ev.inputType === 'insertText' && !this.optionsService.options.screenReaderMode) {
if (this._keyPressHandled) {
return false;
}
const text = ev.data;
this.coreService.triggerDataEvent(text, true);
this.cancel(ev);
return true;
}
return false;
}
/**
* Ring the bell.
* Note: We could do sweet things with webaudio here
*/
public bell(): void {
if (this._soundBell()) {
this._soundService!.playBellSound();
this._soundService?.playBellSound();
}
this._onBell.fire();
// if (this._visualBell()) {
// this.element.classList.add('visual-bell-active');
// clearTimeout(this._visualBellTimer);
@@ -1183,7 +1259,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
this.buffer.lines.push(this.buffer.getBlankLine(DEFAULT_ATTR_DATA));
}
this.refresh(0, this.rows - 1);
this._onScroll.fire(this.buffer.ydisp);
this._onScroll.fire({ position: this.buffer.ydisp, source: ScrollSource.TERMINAL });
}
/**
@@ -1215,6 +1291,18 @@ export class Terminal extends CoreTerminal implements ITerminal {
this.viewport?.syncScrollArea();
}
public clearTextureAtlas(): void {
this._renderService?.clearTextureAtlas();
}
private _reportFocus(): void {
if (this.element?.classList.contains('focus')) {
this.coreService.triggerDataEvent(C0.ESC + '[I');
} else {
this.coreService.triggerDataEvent(C0.ESC + '[O');
}
}
private _reportWindowsOptions(type: WindowsOptionsReportType): void {
if (!this._renderService) {
return;
@@ -1224,12 +1312,12 @@ export class Terminal extends CoreTerminal implements ITerminal {
case WindowsOptionsReportType.GET_WIN_SIZE_PIXELS:
const canvasWidth = this._renderService.dimensions.scaledCanvasWidth.toFixed(0);
const canvasHeight = this._renderService.dimensions.scaledCanvasHeight.toFixed(0);
this._coreService.triggerDataEvent(`${C0.ESC}[4;${canvasHeight};${canvasWidth}t`);
this.coreService.triggerDataEvent(`${C0.ESC}[4;${canvasHeight};${canvasWidth}t`);
break;
case WindowsOptionsReportType.GET_CELL_SIZE_PIXELS:
const cellWidth = this._renderService.dimensions.scaledCellWidth.toFixed(0);
const cellHeight = this._renderService.dimensions.scaledCellHeight.toFixed(0);
this._coreService.triggerDataEvent(`${C0.ESC}[6;${cellHeight};${cellWidth}t`);
this.coreService.triggerDataEvent(`${C0.ESC}[6;${cellHeight};${cellWidth}t`);
break;
}
}
+4 -3
View File
@@ -17,6 +17,7 @@ const ROWS = 25;
const TESTFILES = glob.sync('**/escape_sequence_files/*.in', { cwd: path.join(__dirname, '../..')});
const SKIP_FILES = [
't0055-EL.in', // EL/ED handle cursor at cols differently (see #3362)
't0084-CBT.in',
't0101-NLM.in',
't0103-reverse_wrap.in', // not comparable, we deviate from xterm reverse wrap on purpose
@@ -73,8 +74,8 @@ describe('Escape Sequence Files', function(): void {
// register handler to trigger viewport scraping, wait for it to finish
let content = '';
const OSC_CODE = 12345;
await new Promise(resolve => {
customHandler = term.addOscHandler(OSC_CODE, () => {
await new Promise<void>(resolve => {
customHandler = term.registerOscHandler(OSC_CODE, () => {
// grab terminal viewport content
content = terminalToString(term);
resolve();
@@ -105,7 +106,7 @@ function formatError(input: string, output: string, expected: string): string {
function addLineNumber(start: number, color: string): (s: string) => string {
let counter = start || 0;
return (s: string): string => {
counter += 1;
counter++;
return '\x1b[33m' + (' ' + counter).slice(-2) + color + s;
};
}
+34 -17
View File
@@ -5,15 +5,15 @@
import { IDisposable, IMarker, ISelectionPosition, ILinkProvider } from 'xterm';
import { IEvent, EventEmitter } from 'common/EventEmitter';
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 { ICharacterJoinerService, ICharSizeService, IMouseService, IRenderService, ISelectionService } from 'browser/services/Services';
import { IRenderDimensions, IRenderer, IRequestRedrawEvent } from 'browser/renderer/Types';
import { IColorSet, ILinkMatcherOptions, ITerminal, ILinkifier, ILinkifier2, IBrowser, IViewport, IColorManager, ICompositionHelper, CharacterJoinerHandler } 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 { IUnicodeService, IOptionsService, ICoreService, ICoreMouseService } from 'common/services/Services';
import { IFunctionIdentifier, IParams } from 'common/parser/Types';
import { AttributeData } from 'common/buffer/AttributeData';
@@ -21,6 +21,9 @@ 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); }
public writeP(data: string | Uint8Array): Promise<void> {
return new Promise(r => this.write(data, r));
}
}
export class MockTerminal implements ITerminal {
@@ -34,11 +37,14 @@ export class MockTerminal implements ITerminal {
public onData!: IEvent<string>;
public onBinary!: IEvent<string>;
public onTitleChange!: IEvent<string>;
public onBell!: IEvent<void>;
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 coreMouseService!: ICoreMouseService;
public coreService!: ICoreService;
public optionsService!: IOptionsService;
public unicodeService!: IUnicodeService;
public addMarker(cursorYOffset: number): IMarker {
@@ -75,16 +81,16 @@ export class MockTerminal implements ITerminal {
public attachCustomKeyEventHandler(customKeyEventHandler: (event: KeyboardEvent) => boolean): void {
throw new Error('Method not implemented.');
}
public addCsiHandler(id: IFunctionIdentifier, callback: (params: IParams) => boolean): IDisposable {
public registerCsiHandler(id: IFunctionIdentifier, callback: (params: IParams) => boolean | Promise<boolean>): IDisposable {
throw new Error('Method not implemented.');
}
public addDcsHandler(id: IFunctionIdentifier, callback: (data: string, param: IParams) => boolean): IDisposable {
public registerDcsHandler(id: IFunctionIdentifier, callback: (data: string, param: IParams) => boolean | Promise<boolean>): IDisposable {
throw new Error('Method not implemented.');
}
public addEscHandler(id: IFunctionIdentifier, handler: () => boolean): IDisposable {
public registerEscHandler(id: IFunctionIdentifier, handler: () => boolean | Promise<boolean>): IDisposable {
throw new Error('Method not implemented.');
}
public addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable {
public registerOscHandler(ident: number, callback: (data: string) => boolean | Promise<boolean>): IDisposable {
throw new Error('Method not implemented.');
}
public registerLinkMatcher(regex: RegExp, handler: (event: MouseEvent, uri: string) => boolean | void, options?: ILinkMatcherOptions): number {
@@ -149,7 +155,7 @@ export class MockTerminal implements ITerminal {
public textarea!: HTMLTextAreaElement;
public rows!: number;
public cols!: number;
public browser: IBrowser = <any>Browser;
public browser: IBrowser = Browser as any;
public writeBuffer!: string[];
public children!: HTMLElement[];
public cursorHidden!: boolean;
@@ -189,6 +195,9 @@ export class MockTerminal implements ITerminal {
public reset(): void {
throw new Error('Method not implemented.');
}
public clearTextureAtlas(): void {
throw new Error('Method not implemented.');
}
public refresh(start: number, end: number): void {
throw new Error('Method not implemented.');
}
@@ -281,8 +290,6 @@ export class MockRenderer implements IRenderer {
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 {
@@ -370,6 +377,9 @@ export class MockRenderService implements IRenderService {
public refreshRows(start: number, end: number): void {
throw new Error('Method not implemented.');
}
public clearTextureAtlas(): void {
throw new Error('Method not implemented.');
}
public resize(cols: number, rows: number): void {
throw new Error('Method not implemented.');
}
@@ -406,13 +416,20 @@ export class MockRenderService implements IRenderService {
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.');
}
}
export class MockCharacterJoinerService implements ICharacterJoinerService {
public serviceBrand: undefined;
public register(handler: (text: string) => [number, number][]): number {
return 0;
}
public deregister(joinerId: number): boolean {
return true;
}
public getJoinedCharacters(row: number): [number, number][] {
return [];
}
}
+86
View File
@@ -0,0 +1,86 @@
/**
* Copyright (c) 2018 The xterm.js authors. All rights reserved.
* @license MIT
*/
const RENDER_DEBOUNCE_THRESHOLD_MS = 1000; // 1 Second
import { IRenderDebouncer } from 'browser/Types';
/**
* Debounces calls to update screen readers to update at most once configurable interval of time.
*/
export class TimeBasedDebouncer implements IRenderDebouncer {
private _rowStart: number | undefined;
private _rowEnd: number | undefined;
private _rowCount: number | undefined;
// The last moment that the Terminal was refreshed at
private _lastRefreshMs = 0;
// Whether a trailing refresh should be triggered due to a refresh request that was throttled
private _additionalRefreshRequested = false;
private _refreshTimeoutID: number | undefined;
constructor(
private _renderCallback: (start: number, end: number) => void,
private readonly _debounceThresholdMS = RENDER_DEBOUNCE_THRESHOLD_MS
) {
}
public dispose(): void {
if (this._refreshTimeoutID) {
clearTimeout(this._refreshTimeoutID);
}
}
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;
rowEnd = rowEnd !== undefined ? rowEnd : this._rowCount - 1;
// Set the properties to the updated values
this._rowStart = this._rowStart !== undefined ? Math.min(this._rowStart, rowStart) : rowStart;
this._rowEnd = this._rowEnd !== undefined ? Math.max(this._rowEnd, rowEnd) : rowEnd;
// Only refresh if the time since last refresh is above a threshold, otherwise wait for
// enough time to pass before refreshing again.
const refreshRequestTime: number = Date.now();
if (refreshRequestTime - this._lastRefreshMs >= this._debounceThresholdMS) {
// Enough time has lapsed since the last refresh; refresh immediately
this._lastRefreshMs = refreshRequestTime;
this._innerRefresh();
} else if (!this._additionalRefreshRequested) {
// This is the first additional request throttled; set up trailing refresh
const elapsed = refreshRequestTime - this._lastRefreshMs;
const waitPeriodBeforeTrailingRefresh = this._debounceThresholdMS - elapsed;
this._additionalRefreshRequested = true;
this._refreshTimeoutID = window.setTimeout(() => {
this._lastRefreshMs = Date.now();
this._innerRefresh();
this._additionalRefreshRequested = false;
this._refreshTimeoutID = undefined; // No longer need to clear the timeout
}, waitPeriodBeforeTrailingRefresh);
}
}
private _innerRefresh(): void {
// Make sure values are set
if (this._rowStart === undefined || this._rowEnd === undefined || this._rowCount === undefined) {
return;
}
// Clamp values
const start = Math.max(this._rowStart, 0);
const end = Math.min(this._rowEnd, this._rowCount - 1);
// Reset debouncer (this happens before render callback as the render could trigger it again)
this._rowStart = undefined;
this._rowEnd = undefined;
// Run render callback
this._renderCallback(start, end);
}
}
+27 -5
View File
@@ -15,7 +15,6 @@ export interface ITerminal extends IPublicTerminal, ICoreTerminal {
screenElement: HTMLElement | undefined;
browser: IBrowser;
buffer: IBuffer;
buffers: IBufferSet;
viewport: IViewport | undefined;
// TODO: We should remove options once components adopt optionsService
options: ITerminalOptions;
@@ -47,15 +46,16 @@ export interface IPublicTerminal extends IDisposable {
onRender: IEvent<{ start: number, end: number }>;
onResize: IEvent<{ cols: number, rows: number }>;
onTitleChange: IEvent<string>;
onBell: IEvent<void>;
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;
registerCsiHandler(id: IFunctionIdentifier, callback: (params: IParams) => boolean | Promise<boolean>): IDisposable;
registerDcsHandler(id: IFunctionIdentifier, callback: (data: string, param: IParams) => boolean | Promise<boolean>): IDisposable;
registerEscHandler(id: IFunctionIdentifier, callback: () => boolean | Promise<boolean>): IDisposable;
registerOscHandler(ident: number, callback: (data: string) => boolean | Promise<boolean>): IDisposable;
registerLinkMatcher(regex: RegExp, handler: (event: MouseEvent, uri: string) => void, options?: ILinkMatcherOptions): number;
deregisterLinkMatcher(matcherId: number): void;
registerLinkProvider(linkProvider: ILinkProvider): IDisposable;
@@ -79,6 +79,7 @@ export interface IPublicTerminal extends IDisposable {
write(data: string | Uint8Array, callback?: () => void): void;
paste(data: string): void;
refresh(start: number, end: number): void;
clearTextureAtlas(): void;
reset(): void;
}
@@ -205,9 +206,19 @@ export interface ILinkifier {
deregisterLinkMatcher(matcherId: number): boolean;
}
interface ILinkState {
decorations: ILinkDecorations;
isHovered: boolean;
}
export interface ILinkWithState {
link: ILink;
state?: ILinkState;
}
export interface ILinkifier2 {
onShowLinkUnderline: IEvent<ILinkifierEvent>;
onHideLinkUnderline: IEvent<ILinkifierEvent>;
readonly currentLink: ILinkWithState | undefined;
attachToDom(element: HTMLElement, mouseService: IMouseService, renderService: IRenderService): void;
registerLinkProvider(linkProvider: ILinkProvider): IDisposable;
@@ -292,3 +303,14 @@ interface IBufferCellPosition {
x: number;
y: number;
}
export type CharacterJoinerHandler = (text: string) => [number, number][];
export interface ICharacterJoiner {
id: number;
handler: CharacterJoinerHandler;
}
export interface IRenderDebouncer extends IDisposable {
refresh(rowStart: number | undefined, rowEnd: number | undefined, rowCount: number): void;
}
+38 -11
View File
@@ -8,6 +8,8 @@ import { addDisposableDomListener } from 'browser/Lifecycle';
import { IColorSet, IViewport } from 'browser/Types';
import { ICharSizeService, IRenderService } from 'browser/services/Services';
import { IBufferService, IOptionsService } from 'common/services/Services';
import { IBuffer } from 'common/buffer/Types';
import { IRenderDimensions } from 'browser/renderer/Types';
const FALLBACK_SCROLL_BAR_WIDTH = 15;
@@ -18,11 +20,15 @@ const FALLBACK_SCROLL_BAR_WIDTH = 15;
export class Viewport extends Disposable implements IViewport {
public scrollBarWidth: number = 0;
private _currentRowHeight: number = 0;
private _currentScaledCellHeight: number = 0;
private _lastRecordedBufferLength: number = 0;
private _lastRecordedViewportHeight: number = 0;
private _lastRecordedBufferHeight: number = 0;
private _lastTouchY: number = 0;
private _lastScrollTop: number = 0;
private _lastHadScrollBar: boolean = false;
private _activeBuffer: IBuffer;
private _renderDimensions: IRenderDimensions;
// Stores a partial line amount when scrolling, this is used to keep track of how much of a line
// is scrolled so we can "scroll" over partial lines and feel natural on touchpads. This is a
@@ -33,9 +39,10 @@ export class Viewport extends Disposable implements IViewport {
private _ignoreNextScrollEvent: boolean = false;
constructor(
private readonly _scrollLines: (amount: number, suppressEvent: boolean) => void,
private readonly _scrollLines: (amount: number) => void,
private readonly _viewportElement: HTMLElement,
private readonly _scrollArea: HTMLElement,
private readonly _element: HTMLElement,
@IBufferService private readonly _bufferService: IBufferService,
@IOptionsService private readonly _optionsService: IOptionsService,
@ICharSizeService private readonly _charSizeService: ICharSizeService,
@@ -47,8 +54,15 @@ export class Viewport extends Disposable implements IViewport {
// Unfortunately the overlay scrollbar would be hidden underneath the screen element in that case,
// therefore we account for a standard amount to make it visible
this.scrollBarWidth = (this._viewportElement.offsetWidth - this._scrollArea.offsetWidth) || FALLBACK_SCROLL_BAR_WIDTH;
this._lastHadScrollBar = true;
this.register(addDisposableDomListener(this._viewportElement, 'scroll', this._onScroll.bind(this)));
// Track properties used in performance critical code manually to avoid using slow getters
this._activeBuffer = this._bufferService.buffer;
this.register(this._bufferService.buffers.onBufferActivate(e => this._activeBuffer = e.activeBuffer));
this._renderDimensions = this._renderService.dimensions;
this.register(this._renderService.onDimensionsChange(e => this._renderDimensions = e));
// Perform this async to ensure the ICharSizeService is ready.
setTimeout(() => this.syncScrollArea(), 0);
}
@@ -77,6 +91,7 @@ export class Viewport extends Disposable implements IViewport {
private _innerRefresh(): void {
if (this._charSizeService.height > 0) {
this._currentRowHeight = this._renderService.dimensions.scaledCellHeight / window.devicePixelRatio;
this._currentScaledCellHeight = this._renderService.dimensions.scaledCellHeight;
this._lastRecordedViewportHeight = this._viewportElement.offsetHeight;
const newBufferHeight = Math.round(this._currentRowHeight * this._lastRecordedBufferLength) + (this._lastRecordedViewportHeight - this._renderService.dimensions.canvasHeight);
if (this._lastRecordedBufferHeight !== newBufferHeight) {
@@ -94,8 +109,20 @@ export class Viewport extends Disposable implements IViewport {
this._viewportElement.scrollTop = scrollTop;
}
// Update scroll bar width
if (this._optionsService.options.scrollback === 0) {
this.scrollBarWidth = 0;
} else {
this.scrollBarWidth = (this._viewportElement.offsetWidth - this._scrollArea.offsetWidth) || FALLBACK_SCROLL_BAR_WIDTH;
}
this._lastHadScrollBar = this.scrollBarWidth > 0;
const elementStyle = window.getComputedStyle(this._element);
const elementPadding = parseInt(elementStyle.paddingLeft) + parseInt(elementStyle.paddingRight);
this._viewportElement.style.width = (this._renderService.dimensions.actualCellWidth * (this._bufferService.cols) + this.scrollBarWidth + (this._lastHadScrollBar ? elementPadding : 0)).toString() + 'px';
this._refreshAnimationFrame = null;
}
/**
* Updates dimensions and synchronizes the scroll area if necessary.
*/
@@ -114,23 +141,21 @@ export class Viewport extends Disposable implements IViewport {
}
// If the buffer position doesn't match last scroll top
const newScrollTop = this._bufferService.buffer.ydisp * this._currentRowHeight;
if (this._lastScrollTop !== newScrollTop) {
this._refresh(immediate);
return;
}
// If element's scroll top changed, this can happen when hiding the element
if (this._lastScrollTop !== this._viewportElement.scrollTop) {
if (this._lastScrollTop !== this._activeBuffer.ydisp * this._currentRowHeight) {
this._refresh(immediate);
return;
}
// If row height changed
if (this._renderService.dimensions.scaledCellHeight / window.devicePixelRatio !== this._currentRowHeight) {
if (this._renderDimensions.scaledCellHeight !== this._currentScaledCellHeight) {
this._refresh(immediate);
return;
}
// If the scroll bar visibility changed
if (this._lastHadScrollBar !== (this._optionsService.options.scrollback > 0)) {
this._refresh(immediate);
}
}
/**
@@ -151,12 +176,14 @@ export class Viewport extends Disposable implements IViewport {
// Ignore the event if it was flagged to ignore (when the source of the event is from Viewport)
if (this._ignoreNextScrollEvent) {
this._ignoreNextScrollEvent = false;
// Still trigger the scroll so lines get refreshed
this._scrollLines(0);
return;
}
const newRow = Math.round(this._lastScrollTop / this._currentRowHeight);
const diff = newRow - this._bufferService.buffer.ydisp;
this._scrollLines(diff, true);
this._scrollLines(diff);
}
/**
+26 -26
View File
@@ -5,7 +5,7 @@
import { assert } from 'chai';
import { CompositionHelper } from 'browser/input/CompositionHelper';
import { MockCharSizeService } from 'browser/TestUtils.test';
import { MockRenderService } from 'browser/TestUtils.test';
import { MockCoreService, MockBufferService, MockOptionsService } from 'common/TestUtils.test';
describe('CompositionHelper', () => {
@@ -42,14 +42,14 @@ describe('CompositionHelper', () => {
};
handledText = '';
const bufferService = new MockBufferService(10, 5);
compositionHelper = new CompositionHelper(textarea, compositionView, bufferService, new MockOptionsService(), new MockCharSizeService(10, 10), coreService);
compositionHelper = new CompositionHelper(textarea, compositionView, bufferService, new MockOptionsService(), coreService, new MockRenderService());
});
describe('Input', () => {
it('Should insert simple characters', (done) => {
// First character 'ㅇ'
compositionHelper.compositionstart();
compositionHelper.compositionupdate(<CompositionEvent><CompositionEvent>{ data: 'ㅇ' });
compositionHelper.compositionupdate({ data: 'ㅇ' });
textarea.value = 'ㅇ';
setTimeout(() => { // wait for any textarea updates
compositionHelper.compositionend();
@@ -57,7 +57,7 @@ describe('CompositionHelper', () => {
assert.equal(handledText, 'ㅇ');
// Second character 'ㅇ'
compositionHelper.compositionstart();
compositionHelper.compositionupdate(<CompositionEvent><CompositionEvent>{ data: 'ㅇ' });
compositionHelper.compositionupdate({ data: 'ㅇ' });
textarea.value = 'ㅇㅇ';
setTimeout(() => { // wait for any textarea updates
compositionHelper.compositionend();
@@ -73,13 +73,13 @@ describe('CompositionHelper', () => {
it('Should insert complex characters', (done) => {
// First character '앙'
compositionHelper.compositionstart();
compositionHelper.compositionupdate(<CompositionEvent>{ data: 'ㅇ' });
compositionHelper.compositionupdate({ data: 'ㅇ' });
textarea.value = 'ㅇ';
setTimeout(() => { // wait for any textarea updates
compositionHelper.compositionupdate(<CompositionEvent>{ data: '아' });
compositionHelper.compositionupdate({ data: '아' });
textarea.value = '아';
setTimeout(() => { // wait for any textarea updates
compositionHelper.compositionupdate(<CompositionEvent>{ data: '앙' });
compositionHelper.compositionupdate({ data: '앙' });
textarea.value = '앙';
setTimeout(() => { // wait for any textarea updates
compositionHelper.compositionend();
@@ -87,13 +87,13 @@ describe('CompositionHelper', () => {
assert.equal(handledText, '앙');
// Second character '앙'
compositionHelper.compositionstart();
compositionHelper.compositionupdate(<CompositionEvent>{ data: 'ㅇ' });
compositionHelper.compositionupdate({ data: 'ㅇ' });
textarea.value = '앙ㅇ';
setTimeout(() => { // wait for any textarea updates
compositionHelper.compositionupdate(<CompositionEvent>{ data: '아' });
compositionHelper.compositionupdate({ data: '아' });
textarea.value = '앙아';
setTimeout(() => { // wait for any textarea updates
compositionHelper.compositionupdate(<CompositionEvent>{ data: '앙' });
compositionHelper.compositionupdate({ data: '앙' });
textarea.value = '앙앙';
setTimeout(() => { // wait for any textarea updates
compositionHelper.compositionend();
@@ -113,19 +113,19 @@ describe('CompositionHelper', () => {
it('Should insert complex characters that change with following character', (done) => {
// First character '아'
compositionHelper.compositionstart();
compositionHelper.compositionupdate(<CompositionEvent>{ data: 'ㅇ' });
compositionHelper.compositionupdate({ data: 'ㅇ' });
textarea.value = 'ㅇ';
setTimeout(() => { // wait for any textarea updates
compositionHelper.compositionupdate(<CompositionEvent>{ data: '아' });
compositionHelper.compositionupdate({ data: '아' });
textarea.value = '아';
setTimeout(() => { // wait for any textarea updates
// Start second character '아' in first character
compositionHelper.compositionupdate(<CompositionEvent>{ data: '앙' });
compositionHelper.compositionupdate({ data: '앙' });
textarea.value = '앙';
setTimeout(() => { // wait for any textarea updates
compositionHelper.compositionend();
compositionHelper.compositionstart();
compositionHelper.compositionupdate(<CompositionEvent>{ data: '아' });
compositionHelper.compositionupdate({ data: '아' });
textarea.value = '아아';
setTimeout(() => { // wait for any textarea updates
compositionHelper.compositionend();
@@ -142,14 +142,14 @@ describe('CompositionHelper', () => {
it('Should insert multi-characters compositions', (done) => {
// First character 'だ'
compositionHelper.compositionstart();
compositionHelper.compositionupdate(<CompositionEvent>{ data: 'd' });
compositionHelper.compositionupdate({ data: 'd' });
textarea.value = 'd';
setTimeout(() => { // wait for any textarea updates
compositionHelper.compositionupdate(<CompositionEvent>{ data: 'だ' });
compositionHelper.compositionupdate({ data: 'だ' });
textarea.value = 'だ';
setTimeout(() => { // wait for any textarea updates
// Second character 'あ'
compositionHelper.compositionupdate(<CompositionEvent>{ data: 'だあ' });
compositionHelper.compositionupdate({ data: 'だあ' });
textarea.value = 'だあ';
setTimeout(() => { // wait for any textarea updates
compositionHelper.compositionend();
@@ -165,18 +165,18 @@ describe('CompositionHelper', () => {
it('Should insert multi-character compositions that are converted to other characters with the same length', (done) => {
// First character 'だ'
compositionHelper.compositionstart();
compositionHelper.compositionupdate(<CompositionEvent>{ data: 'd' });
compositionHelper.compositionupdate({ data: 'd' });
textarea.value = 'd';
setTimeout(() => { // wait for any textarea updates
compositionHelper.compositionupdate(<CompositionEvent>{ data: 'だ' });
compositionHelper.compositionupdate({ data: 'だ' });
textarea.value = 'だ';
setTimeout(() => { // wait for any textarea updates
// Second character 'ー'
compositionHelper.compositionupdate(<CompositionEvent>{ data: 'だー' });
compositionHelper.compositionupdate({ data: 'だー' });
textarea.value = 'だー';
setTimeout(() => { // wait for any textarea updates
// Convert to katakana 'ダー'
compositionHelper.compositionupdate(<CompositionEvent>{ data: 'ダー' });
compositionHelper.compositionupdate({ data: 'ダー' });
textarea.value = 'ダー';
setTimeout(() => { // wait for any textarea updates
compositionHelper.compositionend();
@@ -193,18 +193,18 @@ describe('CompositionHelper', () => {
it('Should insert multi-character compositions that are converted to other characters with different lengths', (done) => {
// First character 'い'
compositionHelper.compositionstart();
compositionHelper.compositionupdate(<CompositionEvent>{ data: 'い' });
compositionHelper.compositionupdate({ data: 'い' });
textarea.value = 'い';
setTimeout(() => { // wait for any textarea updates
// Second character 'ま'
compositionHelper.compositionupdate(<CompositionEvent>{ data: 'いm' });
compositionHelper.compositionupdate({ data: 'いm' });
textarea.value = 'いm';
setTimeout(() => { // wait for any textarea updates
compositionHelper.compositionupdate(<CompositionEvent>{ data: 'いま' });
compositionHelper.compositionupdate({ data: 'いま' });
textarea.value = 'いま';
setTimeout(() => { // wait for any textarea updates
// Convert to kanji '今'
compositionHelper.compositionupdate(<CompositionEvent>{ data: '今' });
compositionHelper.compositionupdate({ data: '今' });
textarea.value = '今';
setTimeout(() => { // wait for any textarea updates
compositionHelper.compositionend();
@@ -221,7 +221,7 @@ describe('CompositionHelper', () => {
it('Should insert non-composition characters input immediately after composition characters', (done) => {
// First character 'ㅇ'
compositionHelper.compositionstart();
compositionHelper.compositionupdate(<CompositionEvent>{ data: 'ㅇ' });
compositionHelper.compositionupdate({ data: 'ㅇ' });
textarea.value = 'ㅇ';
setTimeout(() => { // wait for any textarea updates
compositionHelper.compositionend();
+12 -9
View File
@@ -3,7 +3,7 @@
* @license MIT
*/
import { ICharSizeService } from 'browser/services/Services';
import { IRenderService } from 'browser/services/Services';
import { IBufferService, ICoreService, IOptionsService } from 'common/services/Services';
interface IPosition {
@@ -45,8 +45,8 @@ export class CompositionHelper {
private readonly _compositionView: HTMLElement,
@IBufferService private readonly _bufferService: IBufferService,
@IOptionsService private readonly _optionsService: IOptionsService,
@ICharSizeService private readonly _charSizeService: ICharSizeService,
@ICoreService private readonly _coreService: ICoreService
@ICoreService private readonly _coreService: ICoreService,
@IRenderService private readonly _renderService: IRenderService
) {
this._isComposing = false;
this._isSendingComposition = false;
@@ -69,7 +69,7 @@ export class CompositionHelper {
* Handles the compositionupdate event, updating the composition view.
* @param ev The event.
*/
public compositionupdate(ev: CompositionEvent): void {
public compositionupdate(ev: Pick<CompositionEvent, 'data'>): void {
this._compositionView.textContent = ev.data;
this.updateCompositionElements();
setTimeout(() => {
@@ -207,9 +207,11 @@ export class CompositionHelper {
}
if (this._bufferService.buffer.isCursorInViewport) {
const cellHeight = Math.ceil(this._charSizeService.height * this._optionsService.options.lineHeight);
const cursorTop = this._bufferService.buffer.y * cellHeight;
const cursorLeft = this._bufferService.buffer.x * this._charSizeService.width;
const cursorX = Math.min(this._bufferService.buffer.x, this._bufferService.cols - 1);
const cellHeight = this._renderService.dimensions.actualCellHeight;
const cursorTop = this._bufferService.buffer.y * this._renderService.dimensions.actualCellHeight;
const cursorLeft = cursorX * this._renderService.dimensions.actualCellWidth;
this._compositionView.style.left = cursorLeft + 'px';
this._compositionView.style.top = cursorTop + 'px';
@@ -222,8 +224,9 @@ export class CompositionHelper {
const compositionViewBounds = this._compositionView.getBoundingClientRect();
this._textarea.style.left = cursorLeft + 'px';
this._textarea.style.top = cursorTop + 'px';
this._textarea.style.width = compositionViewBounds.width + 'px';
this._textarea.style.height = compositionViewBounds.height + 'px';
// Ensure the text area is at least 1x1, otherwise certain IMEs may break
this._textarea.style.width = Math.max(compositionViewBounds.width, 1) + 'px';
this._textarea.style.height = Math.max(compositionViewBounds.height, 1) + 'px';
this._textarea.style.lineHeight = compositionViewBounds.height + 'px';
}
+42 -136
View File
@@ -3,17 +3,15 @@
* @license MIT
*/
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, FontWeight } from 'xterm';
import { Terminal as ITerminalApi, ITerminalOptions, IMarker, IDisposable, ILinkMatcherOptions, ITheme, ILocalizableStrings, ITerminalAddon, ISelectionPosition, IBufferNamespace as IBufferNamespaceApi, IParser, ILinkProvider, IUnicodeHandling, FontWeight, IModes } from 'xterm';
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 '../LocalizableStrings';
import { IEvent, EventEmitter } from 'common/EventEmitter';
import { AddonManager } from './AddonManager';
import { IParams } from 'common/parser/Types';
import { BufferSet } from 'common/buffer/BufferSet';
import { Terminal as TerminalCore } from 'browser/Terminal';
import * as Strings from 'browser/LocalizableStrings';
import { IEvent } from 'common/EventEmitter';
import { ParserApi } from 'common/public/ParserApi';
import { UnicodeApi } from 'common/public/UnicodeApi';
import { AddonManager } from 'common/public/AddonManager';
import { BufferNamespaceApi } from 'common/public/BufferNamespaceApi';
export class Terminal implements ITerminalApi {
private _core: ITerminal;
@@ -32,16 +30,17 @@ export class Terminal implements ITerminalApi {
}
}
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; }
public get onData(): IEvent<string> { return this._core.onData; }
public get onBell(): IEvent<void> { return this._core.onBell; }
public get onBinary(): IEvent<string> { return this._core.onBinary; }
public get onTitleChange(): IEvent<string> { return this._core.onTitleChange; }
public get onScroll(): IEvent<number> { return this._core.onScroll; }
public get onCursorMove(): IEvent<void> { return this._core.onCursorMove; }
public get onData(): IEvent<string> { return this._core.onData; }
public get onKey(): IEvent<{ key: string, domEvent: KeyboardEvent }> { return this._core.onKey; }
public get onLineFeed(): IEvent<void> { return this._core.onLineFeed; }
public get onRender(): IEvent<{ start: number, end: number }> { return this._core.onRender; }
public get onResize(): IEvent<{ cols: number, rows: number }> { return this._core.onResize; }
public get onScroll(): IEvent<number> { return this._core.onScroll; }
public get onSelectionChange(): IEvent<void> { return this._core.onSelectionChange; }
public get onTitleChange(): IEvent<string> { return this._core.onTitleChange; }
public get element(): HTMLElement | undefined { return this._core.element; }
public get parser(): IParser {
@@ -69,6 +68,30 @@ export class Terminal implements ITerminalApi {
this._checkProposedApi();
return this._core.markers;
}
public get modes(): IModes {
const m = this._core.coreService.decPrivateModes;
let mouseTrackingMode: 'none' | 'x10' | 'vt200' | 'drag' | 'any' = 'none';
switch (this._core.coreMouseService.activeProtocol) {
case 'X10': mouseTrackingMode = 'x10'; break;
case 'VT200': mouseTrackingMode = 'vt200'; break;
case 'DRAG': mouseTrackingMode = 'drag'; break;
case 'ANY': mouseTrackingMode = 'any'; break;
}
return {
applicationCursorKeysMode: m.applicationCursorKeys,
applicationKeypadMode: m.applicationKeypad,
bracketedPasteMode: m.bracketedPasteMode,
insertMode: this._core.coreService.modes.insertMode,
mouseTrackingMode: mouseTrackingMode,
originMode: m.origin,
reverseWraparoundMode: m.reverseWraparound,
sendFocusMode: m.sendFocus,
wraparoundMode: m.wraparound
};
}
public get options(): ITerminalOptions {
return this._core.options;
}
public blur(): void {
this._core.blur();
}
@@ -202,6 +225,9 @@ export class Terminal implements ITerminalApi {
public reset(): void {
this._core.reset();
}
public clearTextureAtlas(): void {
this._core.clearTextureAtlas();
}
public loadAddon(addon: ITerminalAddon): void {
return this._addonManager.loadAddon(this, addon);
}
@@ -217,123 +243,3 @@ export class Terminal implements ITerminalApi {
}
}
}
class BufferApiView implements IBufferApi {
constructor(
private _buffer: IBuffer,
public readonly type: 'normal' | 'alternate'
) { }
public init(buffer: IBuffer): BufferApiView {
this._buffer = buffer;
return this;
}
public get cursorY(): number { return this._buffer.y; }
public get cursorX(): number { return this._buffer.x; }
public get viewportY(): number { return this._buffer.ydisp; }
public get baseY(): number { return this._buffer.ybase; }
public get length(): number { return this._buffer.lines.length; }
public getLine(y: number): IBufferLineApi | undefined {
const line = this._buffer.lines.get(y);
if (!line) {
return undefined;
}
return new BufferLineApiView(line);
}
public getNullCell(): IBufferCellApi { return new CellData(); }
}
class BufferNamespaceApi implements IBufferNamespaceApi {
private _normal: BufferApiView;
private _alternate: BufferApiView;
private _onBufferChange = new EventEmitter<IBufferApi>();
public get onBufferChange(): IEvent<IBufferApi> { return this._onBufferChange.event; }
constructor(private _core: ITerminal) {
this._normal = new BufferApiView(this._core.buffers.normal, 'normal');
this._alternate = new BufferApiView(this._core.buffers.alt, 'alternate');
this._core.buffers.onBufferActivate(() => this._onBufferChange.fire(this.active));
}
public get active(): IBufferApi {
if (this._core.buffers.active === this._core.buffers.normal) { return this.normal; }
if (this._core.buffers.active === this._core.buffers.alt) { return this.alternate; }
throw new Error('Active buffer is neither normal nor alternate');
}
public get normal(): IBufferApi {
return this._normal.init(this._core.buffers.normal);
}
public get alternate(): IBufferApi {
return this._alternate.init(this._core.buffers.alt);
}
}
class BufferLineApiView implements IBufferLineApi {
constructor(private _line: IBufferLine) { }
public get isWrapped(): boolean { return this._line.isWrapped; }
public get length(): number { return this._line.length; }
public getCell(x: number, cell?: IBufferCellApi): IBufferCellApi | undefined {
if (x < 0 || x >= this._line.length) {
return undefined;
}
if (cell) {
this._line.loadCell(x, <ICellData>cell);
return cell;
}
return this._line.loadCell(x, new CellData());
}
public translateToString(trimRight?: boolean, startColumn?: number, endColumn?: number): string {
return this._line.translateToString(trimRight, startColumn, endColumn);
}
}
class ParserApi implements IParser {
constructor(private _core: ITerminal) { }
public registerCsiHandler(id: IFunctionIdentifier, callback: (params: (number | number[])[]) => boolean): IDisposable {
return this._core.addCsiHandler(id, (params: IParams) => callback(params.toArray()));
}
public addCsiHandler(id: IFunctionIdentifier, callback: (params: (number | number[])[]) => boolean): IDisposable {
return this.registerCsiHandler(id, callback);
}
public registerDcsHandler(id: IFunctionIdentifier, callback: (data: string, param: (number | number[])[]) => boolean): IDisposable {
return this._core.addDcsHandler(id, (data: string, params: IParams) => callback(data, params.toArray()));
}
public addDcsHandler(id: IFunctionIdentifier, callback: (data: string, param: (number | number[])[]) => boolean): IDisposable {
return this.registerDcsHandler(id, callback);
}
public registerEscHandler(id: IFunctionIdentifier, handler: () => boolean): IDisposable {
return this._core.addEscHandler(id, handler);
}
public addEscHandler(id: IFunctionIdentifier, handler: () => boolean): IDisposable {
return this.registerEscHandler(id, handler);
}
public registerOscHandler(ident: number, callback: (data: string) => boolean): IDisposable {
return this._core.addOscHandler(ident, callback);
}
public addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable {
return this.registerOscHandler(ident, callback);
}
}
class UnicodeApi implements IUnicodeHandling {
constructor(private _core: ITerminal) { }
public register(provider: IUnicodeVersionProvider): void {
this._core.unicodeService.register(provider);
}
public get versions(): string[] {
return this._core.unicodeService.versions;
}
public get activeVersion(): string {
return this._core.unicodeService.activeVersion;
}
public set activeVersion(version: string) {
this._core.unicodeService.activeVersion = version;
}
}
+53 -12
View File
@@ -7,7 +7,7 @@ import { IRenderDimensions, IRenderLayer } from 'browser/renderer/Types';
import { ICellData } from 'common/Types';
import { DEFAULT_COLOR, WHITESPACE_CELL_CHAR, WHITESPACE_CELL_CODE, Attributes } from 'common/buffer/Constants';
import { IGlyphIdentifier } from 'browser/renderer/atlas/Types';
import { DIM_OPACITY, INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants';
import { DIM_OPACITY, INVERTED_DEFAULT_COLOR, TEXT_BASELINE } from 'browser/renderer/atlas/Constants';
import { BaseCharAtlas } from 'browser/renderer/atlas/BaseCharAtlas';
import { acquireCharAtlas } from 'browser/renderer/atlas/CharAtlasCache';
import { AttributeData } from 'common/buffer/AttributeData';
@@ -17,6 +17,7 @@ import { IBufferService, IOptionsService } from 'common/services/Services';
import { throwIfFalsy } from 'browser/renderer/RendererUtils';
import { channels, color, rgba } from 'browser/Color';
import { removeElementFromParent } from 'browser/Dom';
import { tryDrawCustomChar } from 'browser/renderer/CustomGlyphs';
export abstract class BaseRenderLayer implements IRenderLayer {
private _canvas: HTMLCanvasElement;
@@ -66,7 +67,7 @@ export abstract class BaseRenderLayer implements IRenderLayer {
}
private _initCanvas(): void {
this._ctx = throwIfFalsy(this._canvas.getContext('2d', {alpha: this._alpha}));
this._ctx = throwIfFalsy(this._canvas.getContext('2d', { alpha: this._alpha }));
// Draw the background if this is an opaque layer
if (!this._alpha) {
this._clearAll();
@@ -137,6 +138,10 @@ export abstract class BaseRenderLayer implements IRenderLayer {
public abstract reset(): void;
public clearTextureAtlas(): void {
this._charAtlas?.clear();
}
/**
* Fills 1+ cells completely. This uses the existing fillStyle on the context.
* @param x The column to start at.
@@ -152,6 +157,21 @@ export abstract class BaseRenderLayer implements IRenderLayer {
height * this._scaledCellHeight);
}
/**
* Fills a 1px line (2px on HDPI) at the middle of the cell. This uses the
* existing fillStyle on the context.
* @param x The column to fill.
* @param y The row to fill.
*/
protected _fillMiddleLineAtCells(x: number, y: number, width: number = 1): void {
const cellOffset = Math.ceil(this._scaledCellHeight * 0.5);
this._ctx.fillRect(
x * this._scaledCellWidth,
(y + 1) * this._scaledCellHeight - cellOffset - window.devicePixelRatio,
width * this._scaledCellWidth,
window.devicePixelRatio);
}
/**
* Fills a 1px line (2px on HDPI) at the bottom of the cell. This uses the
* existing fillStyle on the context.
@@ -242,12 +262,22 @@ export abstract class BaseRenderLayer implements IRenderLayer {
*/
protected _fillCharTrueColor(cell: CellData, x: number, y: number): void {
this._ctx.font = this._getFont(false, false);
this._ctx.textBaseline = 'middle';
this._ctx.textBaseline = TEXT_BASELINE;
this._clipRow(y);
this._ctx.fillText(
cell.getChars(),
x * this._scaledCellWidth + this._scaledCharLeft,
y * this._scaledCellHeight + this._scaledCharTop + this._scaledCharHeight / 2);
// Draw custom characters if applicable
let drawSuccess = false;
if (this._optionsService.options.customGlyphs !== false) {
drawSuccess = tryDrawCustomChar(this._ctx, cell.getChars(), x * this._scaledCellWidth, y * this._scaledCellHeight, this._scaledCellWidth, this._scaledCellHeight);
}
// Draw the character
if (!drawSuccess) {
this._ctx.fillText(
cell.getChars(),
x * this._scaledCellWidth + this._scaledCharLeft,
y * this._scaledCellHeight + this._scaledCharTop + this._scaledCharHeight);
}
}
/**
@@ -320,7 +350,7 @@ export abstract class BaseRenderLayer implements IRenderLayer {
private _drawUncachedChars(cell: ICellData, x: number, y: number, fgOverride?: IColor): void {
this._ctx.save();
this._ctx.font = this._getFont(!!cell.isBold(), !!cell.isItalic());
this._ctx.textBaseline = 'middle';
this._ctx.textBaseline = TEXT_BASELINE;
if (cell.isInverse()) {
if (fgOverride) {
@@ -358,14 +388,25 @@ export abstract class BaseRenderLayer implements IRenderLayer {
if (cell.isDim()) {
this._ctx.globalAlpha = DIM_OPACITY;
}
// Draw custom characters if applicable
let drawSuccess = false;
if (this._optionsService.options.customGlyphs !== false) {
drawSuccess = tryDrawCustomChar(this._ctx, cell.getChars(), x * this._scaledCellWidth, y * this._scaledCellHeight, this._scaledCellWidth, this._scaledCellHeight);
}
// Draw the character
this._ctx.fillText(
cell.getChars(),
x * this._scaledCellWidth + this._scaledCharLeft,
y * this._scaledCellHeight + this._scaledCharTop + this._scaledCharHeight / 2);
if (!drawSuccess) {
this._ctx.fillText(
cell.getChars(),
x * this._scaledCellWidth + this._scaledCharLeft,
y * this._scaledCellHeight + this._scaledCharTop + this._scaledCharHeight);
}
this._ctx.restore();
}
/**
* Clips a row to ensure no pixels will be drawn outside the cells in the row.
* @param y The row to clip.
+25 -22
View File
@@ -37,10 +37,10 @@ export class CursorRenderLayer extends BaseRenderLayer {
colors: IColorSet,
rendererId: number,
private _onRequestRedraw: IEventEmitter<IRequestRedrawEvent>,
bufferService: IBufferService,
optionsService: IOptionsService,
private readonly _coreService: ICoreService,
private readonly _coreBrowserService: ICoreBrowserService
@IBufferService bufferService: IBufferService,
@IOptionsService optionsService: IOptionsService,
@ICoreService private readonly _coreService: ICoreService,
@ICoreBrowserService private readonly _coreBrowserService: ICoreBrowserService
) {
super(container, 'cursor', zIndex, true, colors, rendererId, bufferService, optionsService);
this._state = {
@@ -55,7 +55,14 @@ export class CursorRenderLayer extends BaseRenderLayer {
'block': this._renderBlockCursor.bind(this),
'underline': this._renderUnderlineCursor.bind(this)
};
// TODO: Consider initial options? Maybe onOptionsChanged should be called at the end of open?
}
public dispose(): void {
if (this._cursorBlinkStateManager) {
this._cursorBlinkStateManager.dispose();
this._cursorBlinkStateManager = undefined;
}
super.dispose();
}
public resize(dim: IRenderDimensions): void {
@@ -72,26 +79,18 @@ export class CursorRenderLayer extends BaseRenderLayer {
public reset(): void {
this._clearCursor();
if (this._cursorBlinkStateManager) {
this._cursorBlinkStateManager.dispose();
this._cursorBlinkStateManager = undefined;
this.onOptionsChanged();
}
this._cursorBlinkStateManager?.restartBlinkAnimation();
this.onOptionsChanged();
}
public onBlur(): void {
if (this._cursorBlinkStateManager) {
this._cursorBlinkStateManager.pause();
}
this._cursorBlinkStateManager?.pause();
this._onRequestRedraw.fire({ start: this._bufferService.buffer.y, end: this._bufferService.buffer.y });
}
public onFocus(): void {
if (this._cursorBlinkStateManager) {
this._cursorBlinkStateManager.resume();
} else {
this._onRequestRedraw.fire({ start: this._bufferService.buffer.y, end: this._bufferService.buffer.y });
}
this._cursorBlinkStateManager?.resume();
this._onRequestRedraw.fire({ start: this._bufferService.buffer.y, end: this._bufferService.buffer.y });
}
public onOptionsChanged(): void {
@@ -111,9 +110,7 @@ export class CursorRenderLayer extends BaseRenderLayer {
}
public onCursorMove(): void {
if (this._cursorBlinkStateManager) {
this._cursorBlinkStateManager.restartBlinkAnimation();
}
this._cursorBlinkStateManager?.restartBlinkAnimation();
}
public onGridChanged(startRow: number, endRow: number): void {
@@ -197,7 +194,12 @@ export class CursorRenderLayer extends BaseRenderLayer {
private _clearCursor(): void {
if (this._state) {
this._clearCells(this._state.x, this._state.y, this._state.width, 1);
// Avoid potential rounding errors when device pixel ratio is less than 1
if (window.devicePixelRatio < 1) {
this._clearAll();
} else {
this._clearCells(this._state.x, this._state.y, this._state.width, 1);
}
this._state = {
x: 0,
y: 0,
@@ -300,6 +302,7 @@ class CursorBlinkStateManager {
// Clear any existing interval
if (this._blinkInterval) {
window.clearInterval(this._blinkInterval);
this._blinkInterval = undefined;
}
// Setup the initial timeout which will hide the cursor, this is done before
+563
View File
@@ -0,0 +1,563 @@
/**
* Copyright (c) 2021 The xterm.js authors. All rights reserved.
* @license MIT
*/
import { throwIfFalsy } from 'browser/renderer/RendererUtils';
interface IBlockVector {
x: number;
y: number;
w: number;
h: number;
}
export const blockElementDefinitions: { [index: string]: IBlockVector[] | undefined } = {
// Block elements (0x2580-0x2590)
'▀': [{ x: 0, y: 0, w: 8, h: 4 }], // UPPER HALF BLOCK
'▁': [{ x: 0, y: 7, w: 8, h: 1 }], // LOWER ONE EIGHTH BLOCK
'▂': [{ x: 0, y: 6, w: 8, h: 2 }], // LOWER ONE QUARTER BLOCK
'▃': [{ x: 0, y: 5, w: 8, h: 3 }], // LOWER THREE EIGHTHS BLOCK
'▄': [{ x: 0, y: 4, w: 8, h: 4 }], // LOWER HALF BLOCK
'▅': [{ x: 0, y: 3, w: 8, h: 5 }], // LOWER FIVE EIGHTHS BLOCK
'▆': [{ x: 0, y: 2, w: 8, h: 6 }], // LOWER THREE QUARTERS BLOCK
'▇': [{ x: 0, y: 1, w: 8, h: 7 }], // LOWER SEVEN EIGHTHS BLOCK
'█': [{ x: 0, y: 0, w: 8, h: 8 }], // FULL BLOCK
'▉': [{ x: 0, y: 0, w: 7, h: 8 }], // LEFT SEVEN EIGHTHS BLOCK
'▊': [{ x: 0, y: 0, w: 6, h: 8 }], // LEFT THREE QUARTERS BLOCK
'▋': [{ x: 0, y: 0, w: 5, h: 8 }], // LEFT FIVE EIGHTHS BLOCK
'▌': [{ x: 0, y: 0, w: 4, h: 8 }], // LEFT HALF BLOCK
'▍': [{ x: 0, y: 0, w: 3, h: 8 }], // LEFT THREE EIGHTHS BLOCK
'▎': [{ x: 0, y: 0, w: 2, h: 8 }], // LEFT ONE QUARTER BLOCK
'▏': [{ x: 0, y: 0, w: 1, h: 8 }], // LEFT ONE EIGHTH BLOCK
'▐': [{ x: 4, y: 0, w: 4, h: 8 }], // RIGHT HALF BLOCK
// Block elements (0x2594-0x2595)
'▔': [{ x: 0, y: 0, w: 9, h: 1 }], // UPPER ONE EIGHTH BLOCK
'▕': [{ x: 7, y: 0, w: 1, h: 8 }], // RIGHT ONE EIGHTH BLOCK
// Terminal graphic characters (0x2596-0x259F)
'▖': [{ x: 0, y: 4, w: 4, h: 4 }], // QUADRANT LOWER LEFT
'▗': [{ x: 4, y: 4, w: 4, h: 4 }], // QUADRANT LOWER RIGHT
'▘': [{ x: 0, y: 0, w: 4, h: 4 }], // QUADRANT UPPER LEFT
'▙': [{ x: 0, y: 0, w: 4, h: 8 }, { x: 0, y: 4, w: 8, h: 4 }], // QUADRANT UPPER LEFT AND LOWER LEFT AND LOWER RIGHT
'▚': [{ x: 0, y: 0, w: 4, h: 4 }, { x: 4, y: 4, w: 4, h: 4 }], // QUADRANT UPPER LEFT AND LOWER RIGHT
'▛': [{ x: 0, y: 0, w: 4, h: 8 }, { x: 0, y: 0, w: 4, h: 8 }], // QUADRANT UPPER LEFT AND UPPER RIGHT AND LOWER LEFT
'▜': [{ x: 0, y: 0, w: 8, h: 4 }, { x: 4, y: 0, w: 4, h: 8 }], // QUADRANT UPPER LEFT AND UPPER RIGHT AND LOWER RIGHT
'▝': [{ x: 4, y: 0, w: 4, h: 4 }], // QUADRANT UPPER RIGHT
'▞': [{ x: 4, y: 0, w: 4, h: 4 }, { x: 0, y: 4, w: 4, h: 4 }], // QUADRANT UPPER RIGHT AND LOWER LEFT
'▟': [{ x: 4, y: 0, w: 4, h: 8 }, { x: 0, y: 4, w: 8, h: 4 }], // QUADRANT UPPER RIGHT AND LOWER LEFT AND LOWER RIGHT
// VERTICAL ONE EIGHTH BLOCK-2 through VERTICAL ONE EIGHTH BLOCK-7
'\u{1FB70}': [{ x: 1, y: 0, w: 1, h: 8 }],
'\u{1FB71}': [{ x: 2, y: 0, w: 1, h: 8 }],
'\u{1FB72}': [{ x: 3, y: 0, w: 1, h: 8 }],
'\u{1FB73}': [{ x: 4, y: 0, w: 1, h: 8 }],
'\u{1FB74}': [{ x: 5, y: 0, w: 1, h: 8 }],
'\u{1FB75}': [{ x: 6, y: 0, w: 1, h: 8 }],
// HORIZONTAL ONE EIGHTH BLOCK-2 through HORIZONTAL ONE EIGHTH BLOCK-7
'\u{1FB76}': [{ x: 0, y: 1, w: 8, h: 1 }],
'\u{1FB77}': [{ x: 0, y: 2, w: 8, h: 1 }],
'\u{1FB78}': [{ x: 0, y: 3, w: 8, h: 1 }],
'\u{1FB79}': [{ x: 0, y: 4, w: 8, h: 1 }],
'\u{1FB7A}': [{ x: 0, y: 5, w: 8, h: 1 }],
'\u{1FB7B}': [{ x: 0, y: 6, w: 8, h: 1 }],
// LEFT AND LOWER ONE EIGHTH BLOCK
'\u{1FB7C}': [{ x: 0, y: 0, w: 1, h: 8 }, { x: 0, y: 7, w: 8, h: 1 }],
// LEFT AND UPPER ONE EIGHTH BLOCK
'\u{1FB7D}': [{ x: 0, y: 0, w: 1, h: 8 }, { x: 0, y: 0, w: 8, h: 1 }],
// RIGHT AND UPPER ONE EIGHTH BLOCK
'\u{1FB7E}': [{ x: 7, y: 0, w: 1, h: 8 }, { x: 0, y: 0, w: 8, h: 1 }],
// RIGHT AND LOWER ONE EIGHTH BLOCK
'\u{1FB7F}': [{ x: 7, y: 0, w: 1, h: 8 }, { x: 0, y: 7, w: 8, h: 1 }],
// UPPER AND LOWER ONE EIGHTH BLOCK
'\u{1FB80}': [{ x: 0, y: 0, w: 8, h: 1 }, { x: 0, y: 7, w: 8, h: 1 }],
// HORIZONTAL ONE EIGHTH BLOCK-1358
'\u{1FB81}': [{ x: 0, y: 0, w: 8, h: 1 }, { x: 0, y: 2, w: 8, h: 1 }, { x: 0, y: 4, w: 8, h: 1 }, { x: 0, y: 7, w: 8, h: 1 }],
// UPPER ONE QUARTER BLOCK
'\u{1FB82}': [{ x: 0, y: 0, w: 8, h: 2 }],
// UPPER THREE EIGHTHS BLOCK
'\u{1FB83}': [{ x: 0, y: 0, w: 8, h: 3 }],
// UPPER FIVE EIGHTHS BLOCK
'\u{1FB84}': [{ x: 0, y: 0, w: 8, h: 5 }],
// UPPER THREE QUARTERS BLOCK
'\u{1FB85}': [{ x: 0, y: 0, w: 8, h: 6 }],
// UPPER SEVEN EIGHTHS BLOCK
'\u{1FB86}': [{ x: 0, y: 0, w: 8, h: 7 }],
// RIGHT ONE QUARTER BLOCK
'\u{1FB87}': [{ x: 6, y: 0, w: 2, h: 8 }],
// RIGHT THREE EIGHTHS B0OCK
'\u{1FB88}': [{ x: 5, y: 0, w: 3, h: 8 }],
// RIGHT FIVE EIGHTHS BL0CK
'\u{1FB89}': [{ x: 3, y: 0, w: 5, h: 8 }],
// RIGHT THREE QUARTERS 0LOCK
'\u{1FB8A}': [{ x: 2, y: 0, w: 6, h: 8 }],
// RIGHT SEVEN EIGHTHS B0OCK
'\u{1FB8B}': [{ x: 1, y: 0, w: 7, h: 8 }],
// CHECKER BOARD FILL
'\u{1FB95}': [
{ x: 0, y: 0, w: 2, h: 2 }, { x: 4, y: 0, w: 2, h: 2 },
{ x: 2, y: 2, w: 2, h: 2 }, { x: 6, y: 2, w: 2, h: 2 },
{ x: 0, y: 4, w: 2, h: 2 }, { x: 4, y: 4, w: 2, h: 2 },
{ x: 2, y: 6, w: 2, h: 2 }, { x: 6, y: 6, w: 2, h: 2 }
],
// INVERSE CHECKER BOARD FILL
'\u{1FB96}': [
{ x: 2, y: 0, w: 2, h: 2 }, { x: 6, y: 0, w: 2, h: 2 },
{ x: 0, y: 2, w: 2, h: 2 }, { x: 4, y: 2, w: 2, h: 2 },
{ x: 2, y: 4, w: 2, h: 2 }, { x: 6, y: 4, w: 2, h: 2 },
{ x: 0, y: 6, w: 2, h: 2 }, { x: 4, y: 6, w: 2, h: 2 }
],
// HEAVY HORIZONTAL FILL (upper middle and lower one quarter block)
'\u{1FB97}': [{ x: 0, y: 2, w: 8, h: 2 }, { x: 0, y: 6, w: 8, h: 2 }]
};
type PatternDefinition = number[][];
/**
* Defines the repeating pattern used by special characters, the pattern is made up of a 2d array of
* pixel values to be filled (1) or not filled (0).
*/
const patternCharacterDefinitions: { [key: string]: PatternDefinition | undefined } = {
// Shade characters (0x2591-0x2593)
'░': [ // LIGHT SHADE (25%)
[1, 0, 0, 0],
[0, 0, 0, 0],
[0, 0, 1, 0],
[0, 0, 0, 0]
],
'▒': [ // MEDIUM SHADE (50%)
[1, 0],
[0, 0],
[0, 1],
[0, 0]
],
'▓': [ // DARK SHADE (75%)
[0, 1],
[1, 1],
[1, 0],
[1, 1]
]
};
const enum Shapes {
/** │ */ TOP_TO_BOTTOM = 'M.5,0 L.5,1',
/** ─ */ LEFT_TO_RIGHT = 'M0,.5 L1,.5',
/** └ */ TOP_TO_RIGHT = 'M.5,0 L.5,.5 L1,.5',
/** ┘ */ TOP_TO_LEFT = 'M.5,0 L.5,.5 L0,.5',
/** ┐ */ LEFT_TO_BOTTOM = 'M0,.5 L.5,.5 L.5,1',
/** ┌ */ RIGHT_TO_BOTTOM = 'M0.5,1 L.5,.5 L1,.5',
/** ╵ */ MIDDLE_TO_TOP = 'M.5,.5 L.5,0',
/** ╴ */ MIDDLE_TO_LEFT = 'M.5,.5 L0,.5',
/** ╶ */ MIDDLE_TO_RIGHT = 'M.5,.5 L1,.5',
/** ╷ */ MIDDLE_TO_BOTTOM = 'M.5,.5 L.5,1',
/** ┴ */ T_TOP = 'M0,.5 L1,.5 M.5,.5 L.5,0',
/** ┤ */ T_LEFT = 'M.5,0 L.5,1 M.5,.5 L0,.5',
/** ├ */ T_RIGHT = 'M.5,0 L.5,1 M.5,.5 L1,.5',
/** ┬ */ T_BOTTOM = 'M0,.5 L1,.5 M.5,.5 L.5,1',
/** ┼ */ CROSS = 'M0,.5 L1,.5 M.5,0 L.5,1',
/** ╌ */ TWO_DASHES_HORIZONTAL = 'M.1,.5 L.4,.5 M.6,.5 L.9,.5', // .2 empty, .3 filled
/** ┄ */ THREE_DASHES_HORIZONTAL = 'M.0667,.5 L.2667,.5 M.4,.5 L.6,.5 M.7333,.5 L.9333,.5', // .1333 empty, .2 filled
/** ┉ */ FOUR_DASHES_HORIZONTAL = 'M.05,.5 L.2,.5 M.3,.5 L.45,.5 M.55,.5 L.7,.5 M.8,.5 L.95,.5', // .1 empty, .15 filled
/** ╎ */ TWO_DASHES_VERTICAL = 'M.5,.1 L.5,.4 M.5,.6 L.5,.9',
/** ┆ */ THREE_DASHES_VERTICAL = 'M.5,.0667 L.5,.2667 M.5,.4 L.5,.6 M.5,.7333 L.5,.9333',
/** ┊ */ FOUR_DASHES_VERTICAL = 'M.5,.05 L.5,.2 M.5,.3 L.5,.45 L.5,.55 M.5,.7 L.5,.95',
}
const enum Style {
NORMAL = 1,
BOLD = 3
}
/**
* This contains the definitions of all box drawing characters in the format of SVG paths (ie. the
* svg d attribute).
*/
export const boxDrawingDefinitions: { [character: string]: { [fontWeight: number]: string | ((xp: number, yp: number) => string) } | undefined } = {
// Uniform normal and bold
'─': { [Style.NORMAL]: Shapes.LEFT_TO_RIGHT },
'━': { [Style.BOLD]: Shapes.LEFT_TO_RIGHT },
'│': { [Style.NORMAL]: Shapes.TOP_TO_BOTTOM },
'┃': { [Style.BOLD]: Shapes.TOP_TO_BOTTOM },
'┌': { [Style.NORMAL]: Shapes.RIGHT_TO_BOTTOM },
'┏': { [Style.BOLD]: Shapes.RIGHT_TO_BOTTOM },
'┐': { [Style.NORMAL]: Shapes.LEFT_TO_BOTTOM },
'┓': { [Style.BOLD]: Shapes.LEFT_TO_BOTTOM },
'└': { [Style.NORMAL]: Shapes.TOP_TO_RIGHT },
'┗': { [Style.BOLD]: Shapes.TOP_TO_RIGHT },
'┘': { [Style.NORMAL]: Shapes.TOP_TO_LEFT },
'┛': { [Style.BOLD]: Shapes.TOP_TO_LEFT },
'├': { [Style.NORMAL]: Shapes.T_RIGHT },
'┣': { [Style.BOLD]: Shapes.T_RIGHT },
'┤': { [Style.NORMAL]: Shapes.T_LEFT },
'┫': { [Style.BOLD]: Shapes.T_LEFT },
'┬': { [Style.NORMAL]: Shapes.T_BOTTOM },
'┳': { [Style.BOLD]: Shapes.T_BOTTOM },
'┴': { [Style.NORMAL]: Shapes.T_TOP },
'┻': { [Style.BOLD]: Shapes.T_TOP },
'┼': { [Style.NORMAL]: Shapes.CROSS },
'╋': { [Style.BOLD]: Shapes.CROSS },
'╴': { [Style.NORMAL]: Shapes.MIDDLE_TO_LEFT },
'╸': { [Style.BOLD]: Shapes.MIDDLE_TO_LEFT },
'╵': { [Style.NORMAL]: Shapes.MIDDLE_TO_TOP },
'╹': { [Style.BOLD]: Shapes.MIDDLE_TO_TOP },
'╶': { [Style.NORMAL]: Shapes.MIDDLE_TO_RIGHT },
'╺': { [Style.BOLD]: Shapes.MIDDLE_TO_RIGHT },
'╷': { [Style.NORMAL]: Shapes.MIDDLE_TO_BOTTOM },
'╻': { [Style.BOLD]: Shapes.MIDDLE_TO_BOTTOM },
// Double border
'═': { [Style.NORMAL]: (xp, yp) => `M0,${.5 - yp} L1,${.5 - yp} M0,${.5 + yp} L1,${.5 + yp}` },
'║': { [Style.NORMAL]: (xp, yp) => `M${.5 - xp},0 L${.5 - xp},1 M${.5 + xp},0 L${.5 + xp},1` },
'╒': { [Style.NORMAL]: (xp, yp) => `M.5,1 L.5,${.5 - yp} L1,${.5 - yp} M.5,${.5 + yp} L1,${.5 + yp}` },
'╓': { [Style.NORMAL]: (xp, yp) => `M${.5 - xp},1 L${.5 - xp},.5 L1,.5 M${.5 + xp},.5 L${.5 + xp},1` },
'╔': { [Style.NORMAL]: (xp, yp) => `M1,${.5 - yp} L${.5 - xp},${.5 - yp} L${.5 - xp},1 M1,${.5 + yp} L${.5 + xp},${.5 + yp} L${.5 + xp},1` },
'╕': { [Style.NORMAL]: (xp, yp) => `M0,${.5 - yp} L.5,${.5 - yp} L.5,1 M0,${.5 + yp} L.5,${.5 + yp}` },
'╖': { [Style.NORMAL]: (xp, yp) => `M${.5 + xp},1 L${.5 + xp},.5 L0,.5 M${.5 - xp},.5 L${.5 - xp},1` },
'╗': { [Style.NORMAL]: (xp, yp) => `M0,${.5 + yp} L${.5 - xp},${.5 + yp} L${.5 - xp},1 M0,${.5 - yp} L${.5 + xp},${.5 - yp} L${.5 + xp},1` },
'╘': { [Style.NORMAL]: (xp, yp) => `M.5,0 L.5,${.5 + yp} L1,${.5 + yp} M.5,${.5 - yp} L1,${.5 - yp}` },
'╙': { [Style.NORMAL]: (xp, yp) => `M1,.5 L${.5 - xp},.5 L${.5 - xp},0 M${.5 + xp},.5 L${.5 + xp},0` },
'╚': { [Style.NORMAL]: (xp, yp) => `M1,${.5 - yp} L${.5 + xp},${.5 - yp} L${.5 + xp},0 M1,${.5 + yp} L${.5 - xp},${.5 + yp} L${.5 - xp},0` },
'╛': { [Style.NORMAL]: (xp, yp) => `M0,${.5 + yp} L.5,${.5 + yp} L.5,0 M0,${.5 - yp} L.5,${.5 - yp}` },
'╜': { [Style.NORMAL]: (xp, yp) => `M0,.5 L${.5 + xp},.5 L${.5 + xp},0 M${.5 - xp},.5 L${.5 - xp},0` },
'╝': { [Style.NORMAL]: (xp, yp) => `M0,${.5 - yp} L${.5 - xp},${.5 - yp} L${.5 - xp},0 M0,${.5 + yp} L${.5 + xp},${.5 + yp} L${.5 + xp},0` },
'╞': { [Style.NORMAL]: (xp, yp) => `${Shapes.TOP_TO_BOTTOM} M.5,${.5 - yp} L1,${.5 - yp} M.5,${.5 + yp} L1,${.5 + yp}` },
'╟': { [Style.NORMAL]: (xp, yp) => `M${.5 - xp},0 L${.5 - xp},1 M${.5 + xp},0 L${.5 + xp},1 M${.5 + xp},.5 L1,.5` },
'╠': { [Style.NORMAL]: (xp, yp) => `M${.5 - xp},0 L${.5 - xp},1 M1,${.5 + yp} L${.5 + xp},${.5 + yp} L${.5 + xp},1 M1,${.5 - yp} L${.5 + xp},${.5 - yp} L${.5 + xp},0` },
'╡': { [Style.NORMAL]: (xp, yp) => `${Shapes.TOP_TO_BOTTOM} M0,${.5 - yp} L.5,${.5 - yp} M0,${.5 + yp} L.5,${.5 + yp}` },
'╢': { [Style.NORMAL]: (xp, yp) => `M0,.5 L${.5 - xp},.5 M${.5 - xp},0 L${.5 - xp},1 M${.5 + xp},0 L${.5 + xp},1` },
'╣': { [Style.NORMAL]: (xp, yp) => `M${.5 + xp},0 L${.5 + xp},1 M0,${.5 + yp} L${.5 - xp},${.5 + yp} L${.5 - xp},1 M0,${.5 - yp} L${.5 - xp},${.5 - yp} L${.5 - xp},0` },
'╤': { [Style.NORMAL]: (xp, yp) => `M0,${.5 - yp} L1,${.5 - yp} M0,${.5 + yp} L1,${.5 + yp} M.5,${.5 + yp} L.5,1` },
'╥': { [Style.NORMAL]: (xp, yp) => `${Shapes.LEFT_TO_RIGHT} M${.5 - xp},.5 L${.5 - xp},1 M${.5 + xp},.5 L${.5 + xp},1` },
'╦': { [Style.NORMAL]: (xp, yp) => `M0,${.5 - yp} L1,${.5 - yp} M0,${.5 + yp} L${.5 - xp},${.5 + yp} L${.5 - xp},1 M1,${.5 + yp} L${.5 + xp},${.5 + yp} L${.5 + xp},1` },
'╧': { [Style.NORMAL]: (xp, yp) => `M.5,0 L.5,${.5 - yp} M0,${.5 - yp} L1,${.5 - yp} M0,${.5 + yp} L1,${.5 + yp}` },
'╨': { [Style.NORMAL]: (xp, yp) => `${Shapes.LEFT_TO_RIGHT} M${.5 - xp},.5 L${.5 - xp},0 M${.5 + xp},.5 L${.5 + xp},0` },
'╩': { [Style.NORMAL]: (xp, yp) => `M0,${.5 + yp} L1,${.5 + yp} M0,${.5 - yp} L${.5 - xp},${.5 - yp} L${.5 - xp},0 M1,${.5 - yp} L${.5 + xp},${.5 - yp} L${.5 + xp},0` },
'╪': { [Style.NORMAL]: (xp, yp) => `${Shapes.TOP_TO_BOTTOM} M0,${.5 - yp} L1,${.5 - yp} M0,${.5 + yp} L1,${.5 + yp}` },
'╫': { [Style.NORMAL]: (xp, yp) => `${Shapes.LEFT_TO_RIGHT} M${.5 - xp},0 L${.5 - xp},1 M${.5 + xp},0 L${.5 + xp},1` },
'╬': { [Style.NORMAL]: (xp, yp) => `M0,${.5 + yp} L${.5 - xp},${.5 + yp} L${.5 - xp},1 M1,${.5 + yp} L${.5 + xp},${.5 + yp} L${.5 + xp},1 M0,${.5 - yp} L${.5 - xp},${.5 - yp} L${.5 - xp},0 M1,${.5 - yp} L${.5 + xp},${.5 - yp} L${.5 + xp},0` },
// Diagonal
'╱': { [Style.NORMAL]: 'M1,0 L0,1' },
'╲': { [Style.NORMAL]: 'M0,0 L1,1' },
'╳': { [Style.NORMAL]: 'M1,0 L0,1 M0,0 L1,1' },
// Mixed weight
'╼': { [Style.NORMAL]: Shapes.MIDDLE_TO_LEFT, [Style.BOLD]: Shapes.MIDDLE_TO_RIGHT },
'╽': { [Style.NORMAL]: Shapes.MIDDLE_TO_TOP, [Style.BOLD]: Shapes.MIDDLE_TO_BOTTOM },
'╾': { [Style.NORMAL]: Shapes.MIDDLE_TO_RIGHT, [Style.BOLD]: Shapes.MIDDLE_TO_LEFT },
'╿': { [Style.NORMAL]: Shapes.MIDDLE_TO_BOTTOM, [Style.BOLD]: Shapes.MIDDLE_TO_TOP },
'┍': { [Style.NORMAL]: Shapes.MIDDLE_TO_BOTTOM, [Style.BOLD]: Shapes.MIDDLE_TO_RIGHT },
'┎': { [Style.NORMAL]: Shapes.MIDDLE_TO_RIGHT, [Style.BOLD]: Shapes.MIDDLE_TO_BOTTOM },
'┑': { [Style.NORMAL]: Shapes.MIDDLE_TO_BOTTOM, [Style.BOLD]: Shapes.MIDDLE_TO_LEFT },
'┒': { [Style.NORMAL]: Shapes.MIDDLE_TO_LEFT, [Style.BOLD]: Shapes.MIDDLE_TO_BOTTOM },
'┕': { [Style.NORMAL]: Shapes.MIDDLE_TO_TOP, [Style.BOLD]: Shapes.MIDDLE_TO_RIGHT },
'┖': { [Style.NORMAL]: Shapes.MIDDLE_TO_RIGHT, [Style.BOLD]: Shapes.MIDDLE_TO_TOP },
'┙': { [Style.NORMAL]: Shapes.MIDDLE_TO_TOP, [Style.BOLD]: Shapes.MIDDLE_TO_LEFT },
'┚': { [Style.NORMAL]: Shapes.MIDDLE_TO_LEFT, [Style.BOLD]: Shapes.MIDDLE_TO_TOP },
'┝': { [Style.NORMAL]: Shapes.TOP_TO_BOTTOM, [Style.BOLD]: Shapes.MIDDLE_TO_RIGHT },
'┞': { [Style.NORMAL]: Shapes.RIGHT_TO_BOTTOM, [Style.BOLD]: Shapes.MIDDLE_TO_TOP },
'┟': { [Style.NORMAL]: Shapes.TOP_TO_RIGHT, [Style.BOLD]: Shapes.MIDDLE_TO_BOTTOM },
'┠': { [Style.NORMAL]: Shapes.MIDDLE_TO_RIGHT, [Style.BOLD]: Shapes.TOP_TO_BOTTOM },
'┡': { [Style.NORMAL]: Shapes.MIDDLE_TO_BOTTOM, [Style.BOLD]: Shapes.TOP_TO_RIGHT },
'┢': { [Style.NORMAL]: Shapes.MIDDLE_TO_TOP, [Style.BOLD]: Shapes.RIGHT_TO_BOTTOM },
'┥': { [Style.NORMAL]: Shapes.TOP_TO_BOTTOM, [Style.BOLD]: Shapes.MIDDLE_TO_LEFT },
'┦': { [Style.NORMAL]: Shapes.LEFT_TO_BOTTOM, [Style.BOLD]: Shapes.MIDDLE_TO_TOP },
'┧': { [Style.NORMAL]: Shapes.TOP_TO_LEFT, [Style.BOLD]: Shapes.MIDDLE_TO_BOTTOM },
'┨': { [Style.NORMAL]: Shapes.MIDDLE_TO_LEFT, [Style.BOLD]: Shapes.TOP_TO_BOTTOM },
'┩': { [Style.NORMAL]: Shapes.MIDDLE_TO_BOTTOM, [Style.BOLD]: Shapes.TOP_TO_LEFT },
'┪': { [Style.NORMAL]: Shapes.MIDDLE_TO_TOP, [Style.BOLD]: Shapes.LEFT_TO_BOTTOM },
'┭': { [Style.NORMAL]: Shapes.RIGHT_TO_BOTTOM, [Style.BOLD]: Shapes.MIDDLE_TO_LEFT },
'┮': { [Style.NORMAL]: Shapes.LEFT_TO_BOTTOM, [Style.BOLD]: Shapes.MIDDLE_TO_RIGHT },
'┯': { [Style.NORMAL]: Shapes.MIDDLE_TO_BOTTOM, [Style.BOLD]: Shapes.LEFT_TO_RIGHT },
'┰': { [Style.NORMAL]: Shapes.LEFT_TO_RIGHT, [Style.BOLD]: Shapes.MIDDLE_TO_BOTTOM },
'┱': { [Style.NORMAL]: Shapes.MIDDLE_TO_RIGHT, [Style.BOLD]: Shapes.LEFT_TO_BOTTOM },
'┲': { [Style.NORMAL]: Shapes.MIDDLE_TO_LEFT, [Style.BOLD]: Shapes.RIGHT_TO_BOTTOM },
'┵': { [Style.NORMAL]: Shapes.TOP_TO_RIGHT, [Style.BOLD]: Shapes.MIDDLE_TO_LEFT },
'┶': { [Style.NORMAL]: Shapes.TOP_TO_LEFT, [Style.BOLD]: Shapes.MIDDLE_TO_RIGHT },
'┷': { [Style.NORMAL]: Shapes.MIDDLE_TO_TOP, [Style.BOLD]: Shapes.LEFT_TO_RIGHT },
'┸': { [Style.NORMAL]: Shapes.LEFT_TO_RIGHT, [Style.BOLD]: Shapes.MIDDLE_TO_TOP },
'┹': { [Style.NORMAL]: Shapes.MIDDLE_TO_RIGHT, [Style.BOLD]: Shapes.TOP_TO_LEFT },
'┺': { [Style.NORMAL]: Shapes.MIDDLE_TO_LEFT, [Style.BOLD]: Shapes.TOP_TO_RIGHT },
'┽': { [Style.NORMAL]: `${Shapes.TOP_TO_BOTTOM} ${Shapes.MIDDLE_TO_RIGHT}`, [Style.BOLD]: Shapes.MIDDLE_TO_LEFT },
'┾': { [Style.NORMAL]: `${Shapes.TOP_TO_BOTTOM} ${Shapes.MIDDLE_TO_LEFT}`, [Style.BOLD]: Shapes.MIDDLE_TO_RIGHT },
'┿': { [Style.NORMAL]: Shapes.TOP_TO_BOTTOM, [Style.BOLD]: Shapes.LEFT_TO_RIGHT },
'╀': { [Style.NORMAL]: `${Shapes.LEFT_TO_RIGHT} ${Shapes.MIDDLE_TO_BOTTOM}`, [Style.BOLD]: Shapes.MIDDLE_TO_TOP },
'╁': { [Style.NORMAL]: `${Shapes.MIDDLE_TO_TOP} ${Shapes.LEFT_TO_RIGHT}`, [Style.BOLD]: Shapes.MIDDLE_TO_BOTTOM },
'╂': { [Style.NORMAL]: Shapes.LEFT_TO_RIGHT, [Style.BOLD]: Shapes.TOP_TO_BOTTOM },
'╃': { [Style.NORMAL]: Shapes.RIGHT_TO_BOTTOM, [Style.BOLD]: Shapes.TOP_TO_LEFT },
'╄': { [Style.NORMAL]: Shapes.LEFT_TO_BOTTOM, [Style.BOLD]: Shapes.TOP_TO_RIGHT },
'╅': { [Style.NORMAL]: Shapes.TOP_TO_RIGHT, [Style.BOLD]: Shapes.LEFT_TO_BOTTOM },
'╆': { [Style.NORMAL]: Shapes.TOP_TO_LEFT, [Style.BOLD]: Shapes.RIGHT_TO_BOTTOM },
'╇': { [Style.NORMAL]: Shapes.MIDDLE_TO_BOTTOM, [Style.BOLD]: `${Shapes.MIDDLE_TO_TOP} ${Shapes.LEFT_TO_RIGHT}` },
'╈': { [Style.NORMAL]: Shapes.MIDDLE_TO_TOP, [Style.BOLD]: `${Shapes.LEFT_TO_RIGHT} ${Shapes.MIDDLE_TO_BOTTOM}` },
'╉': { [Style.NORMAL]: Shapes.MIDDLE_TO_RIGHT, [Style.BOLD]: `${Shapes.TOP_TO_BOTTOM} ${Shapes.MIDDLE_TO_LEFT}` },
'╊': { [Style.NORMAL]: Shapes.MIDDLE_TO_LEFT, [Style.BOLD]: `${Shapes.TOP_TO_BOTTOM} ${Shapes.MIDDLE_TO_RIGHT}` },
// Dashed
'╌': { [Style.NORMAL]: Shapes.TWO_DASHES_HORIZONTAL },
'╍': { [Style.BOLD]: Shapes.TWO_DASHES_HORIZONTAL },
'┄': { [Style.NORMAL]: Shapes.THREE_DASHES_HORIZONTAL },
'┅': { [Style.BOLD]: Shapes.THREE_DASHES_HORIZONTAL },
'┈': { [Style.NORMAL]: Shapes.FOUR_DASHES_HORIZONTAL },
'┉': { [Style.BOLD]: Shapes.FOUR_DASHES_HORIZONTAL },
'╎': { [Style.NORMAL]: Shapes.TWO_DASHES_VERTICAL },
'╏': { [Style.BOLD]: Shapes.TWO_DASHES_VERTICAL },
'┆': { [Style.NORMAL]: Shapes.THREE_DASHES_VERTICAL },
'┇': { [Style.BOLD]: Shapes.THREE_DASHES_VERTICAL },
'┊': { [Style.NORMAL]: Shapes.FOUR_DASHES_VERTICAL },
'┋': { [Style.BOLD]: Shapes.FOUR_DASHES_VERTICAL },
// Curved
'╭': { [Style.NORMAL]: 'C.5,1,.5,.5,1,.5' },
'╮': { [Style.NORMAL]: 'C.5,1,.5,.5,0,.5' },
'╯': { [Style.NORMAL]: 'C.5,0,.5,.5,0,.5' },
'╰': { [Style.NORMAL]: 'C.5,0,.5,.5,1,.5' }
};
/**
* Try drawing a custom block element or box drawing character, returning whether it was
* successfully drawn.
*/
export function tryDrawCustomChar(
ctx: CanvasRenderingContext2D,
c: string,
xOffset: number,
yOffset: number,
scaledCellWidth: number,
scaledCellHeight: number
): boolean {
const blockElementDefinition = blockElementDefinitions[c];
if (blockElementDefinition) {
drawBlockElementChar(ctx, blockElementDefinition, xOffset, yOffset, scaledCellWidth, scaledCellHeight);
return true;
}
const patternDefinition = patternCharacterDefinitions[c];
if (patternDefinition) {
drawPatternChar(ctx, patternDefinition, xOffset, yOffset, scaledCellWidth, scaledCellHeight);
return true;
}
const boxDrawingDefinition = boxDrawingDefinitions[c];
if (boxDrawingDefinition) {
drawBoxDrawingChar(ctx, boxDrawingDefinition, xOffset, yOffset, scaledCellWidth, scaledCellHeight);
return true;
}
return false;
}
function drawBlockElementChar(
ctx: CanvasRenderingContext2D,
charDefinition: IBlockVector[],
xOffset: number,
yOffset: number,
scaledCellWidth: number,
scaledCellHeight: number
): void {
for (let i = 0; i < charDefinition.length; i++) {
const box = charDefinition[i];
const xEighth = scaledCellWidth / 8;
const yEighth = scaledCellHeight / 8;
ctx.fillRect(
xOffset + box.x * xEighth,
yOffset + box.y * yEighth,
box.w * xEighth,
box.h * yEighth
);
}
}
const cachedPatterns: Map<PatternDefinition, Map</* fillStyle */string, CanvasPattern>> = new Map();
function drawPatternChar(
ctx: CanvasRenderingContext2D,
charDefinition: number[][],
xOffset: number,
yOffset: number,
scaledCellWidth: number,
scaledCellHeight: number
): void {
let patternSet = cachedPatterns.get(charDefinition);
if (!patternSet) {
patternSet = new Map();
cachedPatterns.set(charDefinition, patternSet);
}
const fillStyle = ctx.fillStyle;
if (typeof fillStyle !== 'string') {
throw new Error(`Unexpected fillStyle type "${fillStyle}"`);
}
let pattern = patternSet.get(fillStyle);
if (!pattern) {
const width = charDefinition[0].length;
const height = charDefinition.length;
const tmpCanvas = document.createElement('canvas');
tmpCanvas.width = width;
tmpCanvas.height = height;
const tmpCtx = throwIfFalsy(tmpCanvas.getContext('2d'));
const imageData = new ImageData(width, height);
// Extract rgba from fillStyle
let r: number;
let g: number;
let b: number;
let a: number;
if (fillStyle.startsWith('#')) {
r = parseInt(fillStyle.substr(1, 2), 16);
g = parseInt(fillStyle.substr(3, 2), 16);
b = parseInt(fillStyle.substr(5, 2), 16);
a = fillStyle.length > 7 && parseInt(fillStyle.substr(7, 2), 16) || 1;
} else if (fillStyle.startsWith('rgba')) {
([r, g, b, a] = fillStyle.substring(5, fillStyle.length - 1).split(',').map(e => parseFloat(e)));
} else {
throw new Error(`Unexpected fillStyle color format "${fillStyle}" when drawing pattern glyph`);
}
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
imageData.data[(y * width + x) * 4 ] = r;
imageData.data[(y * width + x) * 4 + 1] = g;
imageData.data[(y * width + x) * 4 + 2] = b;
imageData.data[(y * width + x) * 4 + 3] = charDefinition[y][x] * (a * 255);
}
}
tmpCtx.putImageData(imageData, 0, 0);
pattern = throwIfFalsy(ctx.createPattern(tmpCanvas, null));
patternSet.set(fillStyle, pattern);
}
ctx.fillStyle = pattern;
ctx.fillRect(xOffset, yOffset, scaledCellWidth, scaledCellHeight);
}
/**
* Draws the following box drawing characters by mapping a subset of SVG d attribute instructions to
* canvas draw calls.
*
* Box styles: ┎┰┒┍┯┑╓╥╖╒╤╕ ┏┳┓┌┲┓┌┬┐┏┱┐
* ┌─┬─┐ ┏━┳━┓ ╔═╦═╗ ┠╂┨┝┿┥╟╫╢╞╪╡ ┡╇┩├╊┫┢╈┪┣╉┤
* │ │ │ ┃ ┃ ┃ ║ ║ ║ ┖┸┚┕┷┙╙╨╜╘╧╛ └┴┘└┺┛┗┻┛┗┹┘
* ├─┼─┤ ┣━╋━┫ ╠═╬═╣ ┏┱┐┌┲┓┌┬┐┌┬┐ ┏┳┓┌┮┓┌┬┐┏┭┐
* │ │ │ ┃ ┃ ┃ ║ ║ ║ ┡╃┤├╄┩├╆┪┢╅┤ ┞╀┦├┾┫┟╁┧┣┽┤
* └─┴─┘ ┗━┻━┛ ╚═╩═╝ └┴┘└┴┘└┺┛┗┹┘ └┴┘└┶┛┗┻┛┗┵┘
*
* Other:
* ╭─╮ ╲ ╱ ╷╻╎╏┆┇┊┋ ╺╾╴ ╌╌╌ ┄┄┄ ┈┈┈
* │ │ ╳ ╽╿╎╏┆┇┊┋ ╶╼╸ ╍╍╍ ┅┅┅ ┉┉┉
* ╰─╯ ╱ ╲ ╹╵╎╏┆┇┊┋
*
* All box drawing characters:
* ─ ━ │ ┃ ┄ ┅ ┆ ┇ ┈ ┉ ┊ ┋ ┌ ┍ ┎ ┏
* ┐ ┑ ┒ ┓ └ ┕ ┖ ┗ ┘ ┙ ┚ ┛ ├ ┝ ┞ ┟
* ┠ ┡ ┢ ┣ ┤ ┥ ┦ ┧ ┨ ┩ ┪ ┫ ┬ ┭ ┮ ┯
* ┰ ┱ ┲ ┳ ┴ ┵ ┶ ┷ ┸ ┹ ┺ ┻ ┼ ┽ ┾ ┿
* ╀ ╁ ╂ ╃ ╄ ╅ ╆ ╇ ╈ ╉ ╊ ╋ ╌ ╍ ╎ ╏
* ═ ║ ╒ ╓ ╔ ╕ ╖ ╗ ╘ ╙ ╚ ╛ ╜ ╝ ╞ ╟
* ╠ ╡ ╢ ╣ ╤ ╥ ╦ ╧ ╨ ╩ ╪ ╫ ╬ ╭ ╮ ╯
* ╰ ╱ ╲ ╳ ╴ ╵ ╶ ╷ ╸ ╹ ╺ ╻ ╼ ╽ ╾ ╿
*
* ---
*
* Box drawing alignment tests: █
* ▉
* ╔══╦══╗ ┌──┬──┐ ╭──┬──╮ ╭──┬──╮ ┏━━┳━━┓ ┎┒┏┑ ╷ ╻ ┏┯┓ ┌┰┐ ▊ ╱╲╱╲╳╳╳
* ║┌─╨─┐║ │╔═╧═╗│ │╒═╪═╕│ │╓─╁─╖│ ┃┌─╂─┐┃ ┗╃╄┙ ╶┼╴╺╋╸┠┼┨ ┝╋┥ ▋ ╲╱╲╱╳╳╳
* ║│╲ ╱│║ │║ ║│ ││ │ ││ │║ ┃ ║│ ┃│ ╿ │┃ ┍╅╆┓ ╵ ╹ ┗┷┛ └┸┘ ▌ ╱╲╱╲╳╳╳
* ╠╡ ╳ ╞╣ ├╢ ╟┤ ├┼─┼─┼┤ ├╫─╂─╫┤ ┣┿╾┼╼┿┫ ┕┛┖┚ ┌┄┄┐ ╎ ┏┅┅┓ ┋ ▍ ╲╱╲╱╳╳╳
* ║│╱ ╲│║ │║ ║│ ││ │ ││ │║ ┃ ║│ ┃│ ╽ │┃ ░░▒▒▓▓██ ┊ ┆ ╎ ╏ ┇ ┋ ▎
* ║└─╥─┘║ │╚═╤═╝│ │╘═╪═╛│ │╙─╀─╜│ ┃└─╂─┘┃ ░░▒▒▓▓██ ┊ ┆ ╎ ╏ ┇ ┋ ▏
* ╚══╩══╝ └──┴──┘ ╰──┴──╯ ╰──┴──╯ ┗━━┻━━┛ └╌╌┘ ╎ ┗╍╍┛ ┋ ▁▂▃▄▅▆▇█
*
* Source: https://www.w3.org/2001/06/utf-8-test/UTF-8-demo.html
*/
function drawBoxDrawingChar(
ctx: CanvasRenderingContext2D,
charDefinition: { [fontWeight: number]: string | ((xp: number, yp: number) => string) },
xOffset: number,
yOffset: number,
scaledCellWidth: number,
scaledCellHeight: number
): void {
ctx.strokeStyle = ctx.fillStyle;
for (const [fontWeight, instructions] of Object.entries(charDefinition)) {
ctx.beginPath();
ctx.lineWidth = window.devicePixelRatio * Number.parseInt(fontWeight);
let actualInstructions: string;
if (typeof instructions === 'function') {
const xp = .15;
const yp = .15 / scaledCellHeight * scaledCellWidth;
actualInstructions = instructions(xp, yp);
} else {
actualInstructions = instructions;
}
for (const instruction of actualInstructions.split(' ')) {
const type = instruction[0];
const f = svgToCanvasInstructionMap[type];
if (!f) {
console.error(`Could not find drawing instructions for "${type}"`);
continue;
}
const args: string[] = instruction.substring(1).split(',');
if (!args[0] || !args[1]) {
continue;
}
f(ctx, translateArgs(args, scaledCellWidth, scaledCellHeight, xOffset, yOffset));
}
ctx.stroke();
ctx.closePath();
}
}
function clamp(value: number, max: number, min: number = 0): number {
return Math.max(Math.min(value, max), min);
}
const svgToCanvasInstructionMap: { [index: string]: any } = {
'C': (ctx: CanvasRenderingContext2D, args: number[]) => ctx.bezierCurveTo(args[0], args[1], args[2], args[3], args[4], args[5]),
'L': (ctx: CanvasRenderingContext2D, args: number[]) => ctx.lineTo(args[0], args[1]),
'M': (ctx: CanvasRenderingContext2D, args: number[]) => ctx.moveTo(args[0], args[1])
};
function translateArgs(args: string[], cellWidth: number, cellHeight: number, xOffset: number, yOffset: number): number[] {
const result = args.map(e => parseFloat(e) || parseInt(e));
if (result.length < 2) {
throw new Error('Too few arguments for instruction');
}
for (let x = 0; x < result.length; x += 2) {
// Translate from 0-1 to 0-cellWidth
result[x] *= cellWidth;
// Ensure coordinate doesn't escape cell bounds and round to the nearest 0.5 to ensure a crisp
// line at 100% devicePixelRatio
if (result[x] !== 0) {
result[x] = clamp(Math.round(result[x] + 0.5) - 0.5, cellWidth, 0);
}
// Apply the cell's offset (ie. x*cellWidth)
result[x] += xOffset;
}
for (let y = 1; y < result.length; y += 2) {
// Translate from 0-1 to 0-cellHeight
result[y] *= cellHeight;
// Ensure coordinate doesn't escape cell bounds and round to the nearest 0.5 to ensure a crisp
// line at 100% devicePixelRatio
if (result[y] !== 0) {
result[y] = clamp(Math.round(result[y] + 0.5) - 0.5, cellHeight, 0);
}
// Apply the cell's offset (ie. x*cellHeight)
result[y] += yOffset;
}
return result;
}
+2 -2
View File
@@ -20,8 +20,8 @@ export class LinkRenderLayer extends BaseRenderLayer {
rendererId: number,
linkifier: ILinkifier,
linkifier2: ILinkifier2,
bufferService: IBufferService,
optionsService: IOptionsService
@IBufferService bufferService: IBufferService,
@IOptionsService optionsService: IOptionsService
) {
super(container, 'link', zIndex, true, colors, rendererId, bufferService, optionsService);
linkifier.onShowLinkUnderline(e => this._onShowLinkUnderline(e));
+14 -20
View File
@@ -6,13 +6,12 @@
import { TextRenderLayer } from 'browser/renderer/TextRenderLayer';
import { SelectionRenderLayer } from 'browser/renderer/SelectionRenderLayer';
import { CursorRenderLayer } from 'browser/renderer/CursorRenderLayer';
import { IRenderLayer, IRenderer, IRenderDimensions, CharacterJoinerHandler, ICharacterJoinerRegistry, IRequestRedrawEvent } from 'browser/renderer/Types';
import { IRenderLayer, IRenderer, IRenderDimensions, IRequestRedrawEvent } from 'browser/renderer/Types';
import { LinkRenderLayer } from 'browser/renderer/LinkRenderLayer';
import { CharacterJoinerRegistry } from 'browser/renderer/CharacterJoinerRegistry';
import { Disposable } from 'common/Lifecycle';
import { IColorSet, ILinkifier, ILinkifier2 } from 'browser/Types';
import { ICharSizeService, ICoreBrowserService } from 'browser/services/Services';
import { IBufferService, IOptionsService, ICoreService } from 'common/services/Services';
import { IBufferService, IOptionsService, ICoreService, IInstantiationService } from 'common/services/Services';
import { removeTerminalFromCache } from 'browser/renderer/atlas/CharAtlasCache';
import { EventEmitter, IEvent } from 'common/EventEmitter';
@@ -23,7 +22,6 @@ export class Renderer extends Disposable implements IRenderer {
private _renderLayers: IRenderLayer[];
private _devicePixelRatio: number;
private _characterJoinerRegistry: ICharacterJoinerRegistry;
public dimensions: IRenderDimensions;
@@ -35,20 +33,18 @@ export class Renderer extends Disposable implements IRenderer {
private readonly _screenElement: HTMLElement,
linkifier: ILinkifier,
linkifier2: ILinkifier2,
@IInstantiationService instantiationService: IInstantiationService,
@IBufferService private readonly _bufferService: IBufferService,
@ICharSizeService private readonly _charSizeService: ICharSizeService,
@IOptionsService private readonly _optionsService: IOptionsService,
@ICoreService coreService: ICoreService,
@ICoreBrowserService coreBrowserService: ICoreBrowserService
@IOptionsService private readonly _optionsService: IOptionsService
) {
super();
const allowTransparency = this._optionsService.options.allowTransparency;
this._characterJoinerRegistry = new CharacterJoinerRegistry(this._bufferService);
this._renderLayers = [
new TextRenderLayer(this._screenElement, 0, this._colors, this._characterJoinerRegistry, allowTransparency, this._id, this._bufferService, _optionsService),
new SelectionRenderLayer(this._screenElement, 1, this._colors, this._id, this._bufferService, _optionsService),
new LinkRenderLayer(this._screenElement, 2, this._colors, this._id, linkifier, linkifier2, this._bufferService, _optionsService),
new CursorRenderLayer(this._screenElement, 3, this._colors, this._id, this._onRequestRedraw, this._bufferService, _optionsService, coreService, coreBrowserService)
instantiationService.createInstance(TextRenderLayer, this._screenElement, 0, this._colors, allowTransparency, this._id),
instantiationService.createInstance(SelectionRenderLayer, this._screenElement, 1, this._colors, this._id),
instantiationService.createInstance(LinkRenderLayer, this._screenElement, 2, this._colors, this._id, linkifier, linkifier2),
instantiationService.createInstance(CursorRenderLayer, this._screenElement, 3, this._colors, this._id, this._onRequestRedraw)
];
this.dimensions = {
scaledCharWidth: 0,
@@ -153,6 +149,12 @@ export class Renderer extends Disposable implements IRenderer {
}
}
public clearTextureAtlas(): void {
for (const layer of this._renderLayers) {
layer.clearTextureAtlas();
}
}
/**
* Recalculates the character and canvas dimensions.
*/
@@ -210,12 +212,4 @@ export class Renderer extends Disposable implements IRenderer {
this.dimensions.actualCellHeight = this.dimensions.canvasHeight / this._bufferService.rows;
this.dimensions.actualCellWidth = this.dimensions.canvasWidth / this._bufferService.cols;
}
public registerCharacterJoiner(handler: CharacterJoinerHandler): number {
return this._characterJoinerRegistry.registerCharacterJoiner(handler);
}
public deregisterCharacterJoiner(joinerId: number): boolean {
return this._characterJoinerRegistry.deregisterCharacterJoiner(joinerId);
}
}
+2 -2
View File
@@ -23,8 +23,8 @@ export class SelectionRenderLayer extends BaseRenderLayer {
zIndex: number,
colors: IColorSet,
rendererId: number,
bufferService: IBufferService,
optionsService: IOptionsService
@IBufferService bufferService: IBufferService,
@IOptionsService optionsService: IOptionsService
) {
super(container, 'selection', zIndex, true, colors, rendererId, bufferService, optionsService);
this._clearState();
+16 -14
View File
@@ -3,16 +3,17 @@
* @license MIT
*/
import { ICharacterJoinerRegistry, IRenderDimensions } from 'browser/renderer/Types';
import { IRenderDimensions } from 'browser/renderer/Types';
import { CharData, ICellData } from 'common/Types';
import { GridCache } from 'browser/renderer/GridCache';
import { BaseRenderLayer } from 'browser/renderer/BaseRenderLayer';
import { AttributeData } from 'common/buffer/AttributeData';
import { NULL_CELL_CODE, Content } from 'common/buffer/Constants';
import { JoinedCellData } from 'browser/renderer/CharacterJoinerRegistry';
import { IColorSet } from 'browser/Types';
import { CellData } from 'common/buffer/CellData';
import { IOptionsService, IBufferService } from 'common/services/Services';
import { ICharacterJoinerService } from 'browser/services/Services';
import { JoinedCellData } from 'browser/services/CharacterJoinerService';
/**
* This CharData looks like a null character, which will forc a clear and render
@@ -26,22 +27,20 @@ export class TextRenderLayer extends BaseRenderLayer {
private _characterWidth: number = 0;
private _characterFont: string = '';
private _characterOverlapCache: { [key: string]: boolean } = {};
private _characterJoinerRegistry: ICharacterJoinerRegistry;
private _workCell = new CellData();
constructor(
container: HTMLElement,
zIndex: number,
colors: IColorSet,
characterJoinerRegistry: ICharacterJoinerRegistry,
alpha: boolean,
rendererId: number,
bufferService: IBufferService,
optionsService: IOptionsService
@IBufferService bufferService: IBufferService,
@IOptionsService optionsService: IOptionsService,
@ICharacterJoinerService private readonly _characterJoinerService: ICharacterJoinerService
) {
super(container, 'text', zIndex, alpha, colors, rendererId, bufferService, optionsService);
this._state = new GridCache<CharData>();
this._characterJoinerRegistry = characterJoinerRegistry;
}
public resize(dim: IRenderDimensions): void {
@@ -67,7 +66,6 @@ export class TextRenderLayer extends BaseRenderLayer {
private _forEachCell(
firstRow: number,
lastRow: number,
joinerRegistry: ICharacterJoinerRegistry | null,
callback: (
cell: ICellData,
x: number,
@@ -77,7 +75,7 @@ export class TextRenderLayer extends BaseRenderLayer {
for (let y = firstRow; y <= lastRow; y++) {
const row = y + this._bufferService.buffer.ydisp;
const line = this._bufferService.buffer.lines.get(row);
const joinedRanges = joinerRegistry ? joinerRegistry.getJoinedCharacters(row) : [];
const joinedRanges = this._characterJoinerService.getJoinedCharacters(row);
for (let x = 0; x < this._bufferService.cols; x++) {
line!.loadCell(x, this._workCell);
let cell = this._workCell;
@@ -101,7 +99,6 @@ export class TextRenderLayer extends BaseRenderLayer {
// We already know the exact start and end column of the joined range,
// so we get the string and width representing it directly
cell = new JoinedCellData(
this._workCell,
line!.translateToString(true, range[0], range[1]),
@@ -160,7 +157,7 @@ export class TextRenderLayer extends BaseRenderLayer {
ctx.save();
this._forEachCell(firstRow, lastRow, null, (cell, x, y) => {
this._forEachCell(firstRow, lastRow, (cell, x, y) => {
// libvte and xterm both draw the background (but not foreground) of invisible characters,
// so we should too.
let nextFillStyle = null; // null represents default background color
@@ -213,12 +210,12 @@ export class TextRenderLayer extends BaseRenderLayer {
}
private _drawForeground(firstRow: number, lastRow: number): void {
this._forEachCell(firstRow, lastRow, this._characterJoinerRegistry, (cell, x, y) => {
this._forEachCell(firstRow, lastRow, (cell, x, y) => {
if (cell.isInvisible()) {
return;
}
this._drawChars(cell, x, y);
if (cell.isUnderline()) {
if (cell.isUnderline() || cell.isStrikethrough()) {
this._ctx.save();
if (cell.isInverse()) {
@@ -247,7 +244,12 @@ export class TextRenderLayer extends BaseRenderLayer {
}
}
this._fillBottomLineAtCells(x, y, cell.getWidth());
if (cell.isStrikethrough()) {
this._fillMiddleLineAtCells(x, y, cell.getWidth());
}
if (cell.isUnderline()) {
this._fillBottomLineAtCells(x, y, cell.getWidth());
}
this._ctx.restore();
}
});
+6 -25
View File
@@ -7,8 +7,6 @@ import { IDisposable } from 'common/Types';
import { IColorSet } from 'browser/Types';
import { IEvent } from 'common/EventEmitter';
export type CharacterJoinerHandler = (text: string) => [number, number][];
export interface IRenderDimensions {
scaledCharWidth: number;
scaledCharHeight: number;
@@ -54,19 +52,7 @@ export interface IRenderer extends IDisposable {
onOptionsChanged(): void;
clear(): void;
renderRows(start: number, end: number): void;
registerCharacterJoiner(handler: CharacterJoinerHandler): number;
deregisterCharacterJoiner(joinerId: number): boolean;
}
export interface ICharacterJoiner {
id: number;
handler: CharacterJoinerHandler;
}
export interface ICharacterJoinerRegistry {
registerCharacterJoiner(handler: (text: string) => [number, number][]): number;
deregisterCharacterJoiner(joinerId: number): boolean;
getJoinedCharacters(row: number): [number, number][];
clearTextureAtlas?(): void;
}
export interface IRenderLayer extends IDisposable {
@@ -106,16 +92,6 @@ export interface IRenderLayer extends IDisposable {
*/
onSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void;
/**
* Registers a handler to join characters to render as a group
*/
registerCharacterJoiner?(joiner: ICharacterJoiner): void;
/**
* Deregisters the specified character joiner handler
*/
deregisterCharacterJoiner?(joinerId: number): void;
/**
* Resize the render layer.
*/
@@ -125,4 +101,9 @@ export interface IRenderLayer extends IDisposable {
* Clear the state of the render layer.
*/
reset(): void;
/**
* Clears the texture atlas.
*/
clearTextureAtlas(): void;
}
@@ -28,6 +28,8 @@ export abstract class BaseCharAtlas implements IDisposable {
*/
private _doWarmUp(): void { }
public clear(): void { }
/**
* Called when we start drawing a new frame.
*
+1 -1
View File
@@ -10,7 +10,7 @@ import { ITerminalOptions } from 'common/services/Services';
export function generateConfig(scaledCharWidth: number, scaledCharHeight: number, options: ITerminalOptions, colors: IColorSet): ICharAtlasConfig {
// null out some fields that don't matter
const clonedColors = <IPartialColorSet>{
const clonedColors: IPartialColorSet = {
foreground: colors.foreground,
background: colors.background,
cursor: undefined,
+6
View File
@@ -3,7 +3,13 @@
* @license MIT
*/
import { isFirefox } from 'common/Platform';
export const INVERTED_DEFAULT_COLOR = 257;
export const DIM_OPACITY = 0.5;
// The text baseline is set conditionally by browser. Using 'ideographic' for Firefox would
// result in truncated text (Issue 3353). Using 'bottom' for Chrome would result in slightly
// unaligned Powerline fonts (PR 3356#issuecomment-850928179).
export const TEXT_BASELINE: CanvasTextBaseline = isFirefox ? 'bottom' : 'ideographic';
export const CHAR_ATLAS_CELL_SPACING = 1;
+37 -7
View File
@@ -3,7 +3,7 @@
* @license MIT
*/
import { DIM_OPACITY, INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants';
import { DIM_OPACITY, INVERTED_DEFAULT_COLOR, TEXT_BASELINE } from 'browser/renderer/atlas/Constants';
import { IGlyphIdentifier, ICharAtlasConfig } from 'browser/renderer/atlas/Types';
import { BaseCharAtlas } from 'browser/renderer/atlas/BaseCharAtlas';
import { DEFAULT_ANSI_COLORS } from 'browser/ColorManager';
@@ -91,12 +91,12 @@ export class DynamicCharAtlas extends BaseCharAtlas {
// The canvas needs alpha because we use clearColor to convert the background color to alpha.
// It might also contain some characters with transparent backgrounds if allowTransparency is
// set.
this._cacheCtx = throwIfFalsy(this._cacheCanvas.getContext('2d', {alpha: true}));
this._cacheCtx = throwIfFalsy(this._cacheCanvas.getContext('2d', { alpha: true }));
const tmpCanvas = document.createElement('canvas');
tmpCanvas.width = this._config.scaledCharWidth;
tmpCanvas.height = this._config.scaledCharHeight;
this._tmpCtx = throwIfFalsy(tmpCanvas.getContext('2d', {alpha: this._config.allowTransparency}));
this._tmpCtx = throwIfFalsy(tmpCanvas.getContext('2d', { alpha: this._config.allowTransparency }));
this._width = Math.floor(TEXTURE_WIDTH / this._config.scaledCharWidth);
this._height = Math.floor(TEXTURE_HEIGHT / this._config.scaledCharHeight);
@@ -119,6 +119,16 @@ export class DynamicCharAtlas extends BaseCharAtlas {
this._drawToCacheCount = 0;
}
public clear(): void {
if (this._cacheMap.size > 0) {
const capacity = this._width * this._height;
this._cacheMap = new LRUMap(capacity);
this._cacheMap.prealloc(capacity);
}
this._cacheCtx.clearRect(0, 0, TEXTURE_WIDTH, TEXTURE_HEIGHT);
this._tmpCtx.clearRect(0, 0, this._config.scaledCharWidth, this._config.scaledCharHeight);
}
public draw(
ctx: CanvasRenderingContext2D,
glyph: IGlyphIdentifier,
@@ -256,7 +266,7 @@ export class DynamicCharAtlas extends BaseCharAtlas {
const fontStyle = glyph.italic ? 'italic' : '';
this._tmpCtx.font =
`${fontStyle} ${fontWeight} ${this._config.fontSize * this._config.devicePixelRatio}px ${this._config.fontFamily}`;
this._tmpCtx.textBaseline = 'middle';
this._tmpCtx.textBaseline = TEXT_BASELINE;
this._tmpCtx.fillStyle = this._getForegroundColor(glyph).css;
@@ -265,12 +275,11 @@ export class DynamicCharAtlas extends BaseCharAtlas {
this._tmpCtx.globalAlpha = DIM_OPACITY;
}
// Draw the character
this._tmpCtx.fillText(glyph.chars, 0, this._config.scaledCharHeight / 2);
this._tmpCtx.restore();
this._tmpCtx.fillText(glyph.chars, 0, this._config.scaledCharHeight);
// clear the background from the character to avoid issues with drawing over the previous
// character if it extends past it's bounds
const imageData = this._tmpCtx.getImageData(
let imageData = this._tmpCtx.getImageData(
0, 0, this._config.scaledCharWidth, this._config.scaledCharHeight
);
let isEmpty = false;
@@ -278,6 +287,27 @@ export class DynamicCharAtlas extends BaseCharAtlas {
isEmpty = clearColor(imageData, backgroundColor);
}
// If this charcater is underscore and empty, shift it up until it is visible, try for a maximum
// of 5 pixels.
if (isEmpty && glyph.chars === '_' && !this._config.allowTransparency) {
for (let offset = 1; offset <= 5; offset++) {
// Draw the character
this._tmpCtx.fillText(glyph.chars, 0, this._config.scaledCharHeight - offset);
// clear the background from the character to avoid issues with drawing over the previous
// character if it extends past it's bounds
imageData = this._tmpCtx.getImageData(
0, 0, this._config.scaledCharWidth, this._config.scaledCharHeight
);
isEmpty = clearColor(imageData, backgroundColor);
if (!isEmpty) {
break;
}
}
}
this._tmpCtx.restore();
// copy the data from imageData to _cacheCanvas
const x = this._toCoordinateX(index);
const y = this._toCoordinateY(index);
+6 -8
View File
@@ -3,13 +3,13 @@
* @license MIT
*/
import { IRenderer, IRenderDimensions, CharacterJoinerHandler, IRequestRedrawEvent } from 'browser/renderer/Types';
import { IRenderer, IRenderDimensions, IRequestRedrawEvent } from 'browser/renderer/Types';
import { BOLD_CLASS, ITALIC_CLASS, CURSOR_CLASS, CURSOR_STYLE_BLOCK_CLASS, CURSOR_BLINK_CLASS, CURSOR_STYLE_BAR_CLASS, CURSOR_STYLE_UNDERLINE_CLASS, DomRendererRowFactory } from 'browser/renderer/dom/DomRendererRowFactory';
import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants';
import { Disposable } from 'common/Lifecycle';
import { IColorSet, ILinkifierEvent, ILinkifier, ILinkifier2 } from 'browser/Types';
import { ICharSizeService } from 'browser/services/Services';
import { IOptionsService, IBufferService } from 'common/services/Services';
import { IOptionsService, IBufferService, IInstantiationService } from 'common/services/Services';
import { EventEmitter, IEvent } from 'common/EventEmitter';
import { color } from 'browser/Color';
import { removeElementFromParent } from 'browser/Dom';
@@ -49,6 +49,7 @@ export class DomRenderer extends Disposable implements IRenderer {
private readonly _viewportElement: HTMLElement,
private readonly _linkifier: ILinkifier,
private readonly _linkifier2: ILinkifier2,
@IInstantiationService instantiationService: IInstantiationService,
@ICharSizeService private readonly _charSizeService: ICharSizeService,
@IOptionsService private readonly _optionsService: IOptionsService,
@IBufferService private readonly _bufferService: IBufferService
@@ -80,7 +81,7 @@ export class DomRenderer extends Disposable implements IRenderer {
this._updateDimensions();
this._injectCss();
this._rowFactory = new DomRendererRowFactory(document, this._optionsService, this._colors);
this._rowFactory = instantiationService.createInstance(DomRendererRowFactory, document, this._colors);
this._element.classList.add(TERMINAL_CLASS_PREFIX + this._terminalClass);
this._screenElement.appendChild(this._rowContainer);
@@ -364,7 +365,7 @@ export class DomRenderer extends Disposable implements IRenderer {
const row = y + this._bufferService.buffer.ydisp;
const lineData = this._bufferService.buffer.lines.get(row);
const cursorStyle = this._optionsService.options.cursorStyle;
rowElement.appendChild(this._rowFactory.createRow(lineData!, row === cursorAbsoluteY, cursorStyle, cursorX, cursorBlink, this.dimensions.actualCellWidth, this._bufferService.cols));
rowElement.appendChild(this._rowFactory.createRow(lineData!, row, row === cursorAbsoluteY, cursorStyle, cursorX, cursorBlink, this.dimensions.actualCellWidth, this._bufferService.cols));
}
}
@@ -372,9 +373,6 @@ export class DomRenderer extends Disposable implements IRenderer {
return `.${TERMINAL_CLASS_PREFIX}${this._terminalClass}`;
}
public registerCharacterJoiner(handler: CharacterJoinerHandler): number { return -1; }
public deregisterCharacterJoiner(joinerId: number): boolean { return false; }
private _onLinkHover(e: ILinkifierEvent): void {
this._setCellUnderline(e.x1, e.x2, e.y1, e.y2, e.cols, true);
}
@@ -389,7 +387,7 @@ export class DomRenderer extends Disposable implements IRenderer {
if (!row) {
return;
}
const span = <HTMLElement>row.children[x];
const span = row.children[x] as HTMLElement;
if (span) {
span.style.textDecoration = enabled ? 'underline' : 'none';
}
@@ -10,8 +10,9 @@ import { NULL_CELL_CODE, NULL_CELL_WIDTH, NULL_CELL_CHAR, DEFAULT_ATTR, FgFlags,
import { BufferLine, DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine';
import { IBufferLine } from 'common/Types';
import { CellData } from 'common/buffer/CellData';
import { MockOptionsService } from 'common/TestUtils.test';
import { MockCoreService, MockOptionsService } from 'common/TestUtils.test';
import { css } from 'browser/Color';
import { MockCharacterJoinerService } from 'browser/TestUtils.test';
describe('DomRendererRowFactory', () => {
let dom: jsdom.JSDOM;
@@ -20,36 +21,42 @@ describe('DomRendererRowFactory', () => {
beforeEach(() => {
dom = new jsdom.JSDOM('');
rowFactory = new DomRendererRowFactory(dom.window.document, new MockOptionsService({ drawBoldTextInBrightColors: true }), {
background: css.toColor('#010101'),
foreground: css.toColor('#020202'),
ansi: [
// dark:
css.toColor('#2e3436'),
css.toColor('#cc0000'),
css.toColor('#4e9a06'),
css.toColor('#c4a000'),
css.toColor('#3465a4'),
css.toColor('#75507b'),
css.toColor('#06989a'),
css.toColor('#d3d7cf'),
// bright:
css.toColor('#555753'),
css.toColor('#ef2929'),
css.toColor('#8ae234'),
css.toColor('#fce94f'),
css.toColor('#729fcf'),
css.toColor('#ad7fa8'),
css.toColor('#34e2e2'),
css.toColor('#eeeeec')
]
} as any);
rowFactory = new DomRendererRowFactory(
dom.window.document,
{
background: css.toColor('#010101'),
foreground: css.toColor('#020202'),
ansi: [
// dark:
css.toColor('#2e3436'),
css.toColor('#cc0000'),
css.toColor('#4e9a06'),
css.toColor('#c4a000'),
css.toColor('#3465a4'),
css.toColor('#75507b'),
css.toColor('#06989a'),
css.toColor('#d3d7cf'),
// bright:
css.toColor('#555753'),
css.toColor('#ef2929'),
css.toColor('#8ae234'),
css.toColor('#fce94f'),
css.toColor('#729fcf'),
css.toColor('#ad7fa8'),
css.toColor('#34e2e2'),
css.toColor('#eeeeec')
]
} as any,
new MockCharacterJoinerService(),
new MockOptionsService({ drawBoldTextInBrightColors: true }),
new MockCoreService()
);
lineData = createEmptyLineData(2);
});
describe('createRow', () => {
it('should not create anything for an empty row', () => {
const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20);
const fragment = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, 20);
assert.equal(getFragmentHtml(fragment),
''
);
@@ -59,7 +66,7 @@ describe('DomRendererRowFactory', () => {
lineData.setCell(0, CellData.fromCharData([DEFAULT_ATTR, '語', 2, '語'.charCodeAt(0)]));
// There should be no element for the following "empty" cell
lineData.setCell(1, CellData.fromCharData([DEFAULT_ATTR, '', 0, 0]));
const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20);
const fragment = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, 20);
assert.equal(getFragmentHtml(fragment),
'<span style="width: 10px;">語</span>'
);
@@ -67,7 +74,7 @@ describe('DomRendererRowFactory', () => {
it('should add class for cursor and cursor style', () => {
for (const style of ['block', 'bar', 'underline']) {
const fragment = rowFactory.createRow(lineData, true, style, 0, false, 5, 20);
const fragment = rowFactory.createRow(lineData, 0, true, style, 0, false, 5, 20);
assert.equal(getFragmentHtml(fragment),
`<span class="xterm-cursor xterm-cursor-${style}"> </span>`
);
@@ -75,7 +82,7 @@ describe('DomRendererRowFactory', () => {
});
it('should add class for cursor blink', () => {
const fragment = rowFactory.createRow(lineData, true, 'block', 0, true, 5, 20);
const fragment = rowFactory.createRow(lineData, 0, true, 'block', 0, true, 5, 20);
assert.equal(getFragmentHtml(fragment),
`<span class="xterm-cursor xterm-cursor-blink xterm-cursor-block"> </span>`
);
@@ -84,7 +91,7 @@ describe('DomRendererRowFactory', () => {
it('should not render cells that go beyond the terminal\'s columns', () => {
lineData.setCell(0, CellData.fromCharData([DEFAULT_ATTR, 'a', 1, 'a'.charCodeAt(0)]));
lineData.setCell(1, CellData.fromCharData([DEFAULT_ATTR, 'b', 1, 'b'.charCodeAt(0)]));
const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 1);
const fragment = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, 1);
assert.equal(getFragmentHtml(fragment),
'<span>a</span>'
);
@@ -95,7 +102,7 @@ describe('DomRendererRowFactory', () => {
const cell = CellData.fromCharData([0, 'a', 1, 'a'.charCodeAt(0)]);
cell.fg = DEFAULT_ATTR_DATA.fg | FgFlags.BOLD;
lineData.setCell(0, cell);
const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20);
const fragment = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, 20);
assert.equal(getFragmentHtml(fragment),
'<span class="xterm-bold">a</span>'
);
@@ -105,7 +112,7 @@ describe('DomRendererRowFactory', () => {
const cell = CellData.fromCharData([0, 'a', 1, 'a'.charCodeAt(0)]);
cell.bg = DEFAULT_ATTR_DATA.bg | BgFlags.ITALIC;
lineData.setCell(0, cell);
const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20);
const fragment = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, 20);
assert.equal(getFragmentHtml(fragment),
'<span class="xterm-italic">a</span>'
);
@@ -115,7 +122,7 @@ describe('DomRendererRowFactory', () => {
const cell = CellData.fromCharData([0, 'a', 1, 'a'.charCodeAt(0)]);
cell.bg = DEFAULT_ATTR_DATA.bg | BgFlags.DIM;
lineData.setCell(0, cell);
const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20);
const fragment = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, 20);
assert.equal(getFragmentHtml(fragment),
'<span class="xterm-dim">a</span>'
);
@@ -125,12 +132,22 @@ describe('DomRendererRowFactory', () => {
const cell = CellData.fromCharData([0, 'a', 1, 'a'.charCodeAt(0)]);
cell.fg = DEFAULT_ATTR_DATA.fg | FgFlags.UNDERLINE;
lineData.setCell(0, cell);
const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20);
const fragment = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, 20);
assert.equal(getFragmentHtml(fragment),
'<span class="xterm-underline">a</span>'
);
});
it('should add class for strikethrough', () => {
const cell = CellData.fromCharData([0, 'a', 1, 'a'.charCodeAt(0)]);
cell.fg = DEFAULT_ATTR_DATA.fg | FgFlags.STRIKETHROUGH;
lineData.setCell(0, cell);
const fragment = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, 20);
assert.equal(getFragmentHtml(fragment),
'<span class="xterm-strikethrough">a</span>'
);
});
it('should add classes for 256 foreground colors', () => {
const cell = CellData.fromCharData([0, 'a', 1, 'a'.charCodeAt(0)]);
cell.fg |= Attributes.CM_P256;
@@ -138,7 +155,7 @@ describe('DomRendererRowFactory', () => {
cell.fg &= ~Attributes.PCOLOR_MASK;
cell.fg |= i;
lineData.setCell(0, cell);
const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20);
const fragment = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, 20);
assert.equal(getFragmentHtml(fragment),
`<span class="xterm-fg-${i}">a</span>`
);
@@ -152,7 +169,7 @@ describe('DomRendererRowFactory', () => {
cell.bg &= ~Attributes.PCOLOR_MASK;
cell.bg |= i;
lineData.setCell(0, cell);
const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20);
const fragment = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, 20);
assert.equal(getFragmentHtml(fragment),
`<span class="xterm-bg-${i}">a</span>`
);
@@ -164,7 +181,7 @@ describe('DomRendererRowFactory', () => {
cell.fg |= Attributes.CM_P16 | 2 | FgFlags.INVERSE;
cell.bg |= Attributes.CM_P16 | 1;
lineData.setCell(0, cell);
const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20);
const fragment = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, 20);
assert.equal(getFragmentHtml(fragment),
'<span class="xterm-fg-1 xterm-bg-2">a</span>'
);
@@ -175,7 +192,7 @@ describe('DomRendererRowFactory', () => {
cell.fg |= FgFlags.INVERSE;
cell.bg |= Attributes.CM_P16 | 1;
lineData.setCell(0, cell);
const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20);
const fragment = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, 20);
assert.equal(getFragmentHtml(fragment),
'<span class="xterm-fg-1 xterm-bg-257">a</span>'
);
@@ -185,7 +202,7 @@ describe('DomRendererRowFactory', () => {
const cell = CellData.fromCharData([0, 'a', 1, 'a'.charCodeAt(0)]);
cell.fg |= Attributes.CM_P16 | 1 | FgFlags.INVERSE;
lineData.setCell(0, cell);
const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20);
const fragment = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, 20);
assert.equal(getFragmentHtml(fragment),
'<span class="xterm-fg-257 xterm-bg-1">a</span>'
);
@@ -198,7 +215,7 @@ describe('DomRendererRowFactory', () => {
cell.fg &= ~Attributes.PCOLOR_MASK;
cell.fg |= i;
lineData.setCell(0, cell);
const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20);
const fragment = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, 20);
assert.equal(getFragmentHtml(fragment),
`<span class="xterm-bold xterm-fg-${i + 8}">a</span>`
);
@@ -210,7 +227,7 @@ describe('DomRendererRowFactory', () => {
cell.fg |= Attributes.CM_RGB | 1 << 16 | 2 << 8 | 3;
cell.bg |= Attributes.CM_RGB | 4 << 16 | 5 << 8 | 6;
lineData.setCell(0, cell);
const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20);
const fragment = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, 20);
assert.equal(getFragmentHtml(fragment),
'<span style="color:#010203;background-color:#040506;">a</span>'
);
@@ -221,7 +238,7 @@ describe('DomRendererRowFactory', () => {
cell.fg |= Attributes.CM_RGB | 1 << 16 | 2 << 8 | 3 | FgFlags.INVERSE;
cell.bg |= Attributes.CM_RGB | 4 << 16 | 5 << 8 | 6;
lineData.setCell(0, cell);
const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20);
const fragment = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, 20);
assert.equal(getFragmentHtml(fragment),
'<span style="color:#040506;background-color:#010203;">a</span>'
);

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