Compare commits

..
437 Commits
Author SHA1 Message Date
Daniel Imms 4a61642b81 Merge pull request #3879 from Tyriar/r4_19
v4.19.0
2022-06-29 10:05:04 -07:00
Daniel Imms 0a0ebb68b5 v4.19.0 2022-06-29 09:40:07 -07:00
Megan Rogge e77a0ec886 return 0 instead of -1 for no result find count (#3874) 2022-06-27 14:42:46 -04:00
Daniel Imms 1bf2ccc572 Merge pull request #3876 from yankay/add-cloudtty-to-list-of-xterm-users
Add CloudTTY to list of xterm users
2022-06-27 05:52:33 -07:00
kay.yan 5f680c2185 add-cloudtty-to-list-of-xterm-users 2022-06-27 17:46:08 +08:00
Daniel Imms 38450082ac Merge pull request #3728 from serkodev/fix-caps-lock-ime
Fix Caps lock bug when using some IME on macOS
2022-06-26 09:31:36 -07:00
Daniel Imms 1801403ce2 Move off deprecated keyCode -> key 2022-06-26 09:22:39 -07:00
Daniel Imms 4c7d89e67d Merge branch 'master' into fix-caps-lock-ime 2022-06-26 09:09:43 -07:00
Daniel Imms 5d3a9e864d Merge pull request #3726 from Eugeny/fix-macos-alt-n
Fix Alt-N/E/U handling in macOS, handle Alt-Shift-Letter
2022-06-26 09:04:33 -07:00
Daniel Imms 911d92200a Merge pull request #3763 from robinfai/master
Send the record separator for C-@, like some other keyboards
2022-06-26 07:46:38 -07:00
Daniel Imms 1a788f6363 Merge pull request #3869 from pfitzseb/sp/wheel-mouse-event-per-line
Only send wheel mouse event when scrolling a line or more
2022-06-26 07:43:06 -07:00
Daniel Imms a354f36e6f Merge pull request #3871 from crcastle/patch-1
Add Render to list of xterm.js users
2022-06-24 13:11:08 -07:00
Chris Castle 343e1c8a86 Add Render to list of xterm.js users 2022-06-24 12:59:26 -07:00
Sebastian Pfitzner 783722c2f3 Add missing semicolons 2022-06-22 11:58:32 +02:00
Sebastian Pfitzner 44e4b56ced Only send wheel mouse event when scrolling a line or more 2022-06-22 11:52:00 +02:00
Megan Rogge 52044e9aad clear all markers (#3868) 2022-06-21 18:26:02 +00:00
Daniel Imms 4cdf308c2f Merge pull request #3866 from Tyriar/vsc_152478
Fix clipping of non-powerline chars
2022-06-17 08:12:13 -07:00
Daniel Imms d24a27d93e Fix clipping of non-powerline chars
See microsoft/vscode#152478
2022-06-17 08:01:10 -07:00
Daniel Imms 55beec8b96 Merge pull request #3865 from mmis1000/fix/serialize-addon-test-typo
Fix a typo in serialize addon test
2022-06-16 14:16:06 -07:00
mmis1000 81dce7a4ba Fix typo in serialize addon test 2022-06-17 01:09:50 +08:00
Daniel Imms ddf3f08a6c Merge pull request #3862 from Tyriar/3861
Fix webgl clipping powerline y axis
2022-06-14 18:03:11 -07:00
Daniel Imms 8c6140c2e3 Merge pull request #3863 from Tyriar/3847
Fix webgl min contrast ratio api tests
2022-06-14 18:01:01 -07:00
Daniel Imms 1639c0a031 Fix webgl min contrast ratio api tests
Fixes #3847
2022-06-14 16:45:19 -07:00
Daniel Imms fb950fcbe5 Fix webgl clipping powerline y axis
Fixes #3861
2022-06-14 15:09:00 -07:00
Daniel Imms 1bb465d6b0 Merge pull request #3860 from Tyriar/3855
Fix column selection issues
2022-06-14 09:40:41 -07:00
Daniel Imms efb939d171 Fix column selection issues
The following parts were not respecting the column selection edge case where the
start column was greater than the end column while the start row was less than the
end row:

- Webgl renderer
- DOM renderer
- Selection service (ie. getSelection(), I don't think this was a
  regression)

Fixes #3855
2022-06-14 06:51:54 -07:00
Daniel Imms fe99422ca4 Merge pull request #3856 from Tyriar/custom_powerline_glyphs
Custom common powerline glyph rendering
2022-06-14 06:24:47 -07:00
Daniel Imms d28819de41 Add horizontal padding to powerline outline chars 2022-06-14 06:12:05 -07:00
Daniel Imms f8e9f1813a Custom common powerline glyph rendering
Fixes #3827
2022-06-11 16:15:04 -07:00
Daniel Imms 48b8e31794 Merge pull request #3852 from umaxyon/issue/3851-organize_demo_test_buttons
organize demo test buttons
2022-06-10 12:12:39 -07:00
Umaxyon 58b657bc3b organaize demo test buttons 2022-06-04 12:52:43 +09:00
Megan RoggeandDaniel Imms bc9c9dd6fd fix #3846 (#3849)
Co-authored-by: Daniel Imms <2193314+Tyriar@users.noreply.github.com>
2022-06-02 18:02:36 +00:00
Megan Rogge 6b16732192 exclude box and block glyphs from contrast ratio demands (#3844) 2022-06-01 17:05:27 -07:00
Daniel Imms 5aaa8dafc5 Merge pull request #3841 from Tyriar/3840
Allow up to double the size for overview ruler decorations
2022-05-27 12:03:21 -07:00
Daniel Imms 7a652c4169 Allow up to double the size for overview ruler decorations
Fixes #3840
2022-05-27 11:56:54 -07:00
Daniel Imms 1980f675f9 Merge pull request #3839 from Tyriar/3838
Support opaque selections in DOM renderer
2022-05-27 10:58:16 -07:00
Daniel Imms 98c40e3f8e Add opaque selection dom renderer tests 2022-05-27 10:46:23 -07:00
Daniel Imms 6ced3014cf Support opaque selections in DOM renderer
Brings the DOM renderer closer to the webgl renderer which draws the text
on top of the selection. This is done by leveraging the override system
similar to in webgl and as well as forcing the element above the selection.
This also improves contrast color caching for the DOM renderer.

This should result in a slight performance hit but only when a selection
is being made. The improved contrast seems worth it.

Fixes #3838
2022-05-27 10:38:32 -07:00
Daniel Imms 3e8aecaea3 Merge pull request #3837 from lp1dev/patch-1
Update of README to add hack.courses
2022-05-27 07:37:04 -07:00
Jérémie e3c008ba38 Update of README to add hack.courses
Update of README to add hack.courses as a project using xterm.js (https://hack.courses)
2022-05-27 15:52:40 +02:00
Daniel Imms d2083c5e8c Merge pull request #3835 from Tyriar/3834
Fix search when the search line contains a null character
2022-05-26 15:15:19 -07:00
Daniel Imms 0c0a34d888 Fix search when the search line contains a null character
Fixes #3834
2022-05-26 15:09:01 -07:00
Daniel Imms cd975d22cc Merge pull request #3831 from Tyriar/vscode148109
Test search event, fix returning -1 for no results
2022-05-26 11:58:47 -07:00
Daniel Imms b5f53b341d Test search event, fix returning -1 for no results
Fixes #3830
2022-05-26 11:37:54 -07:00
Daniel Imms 221db55680 Add find result count/index to demo
Part of #148109
2022-05-26 10:32:53 -07:00
Daniel Imms 5cc1114594 Merge pull request #3828 from Tyriar/powerlines
Add powerline symbol test button to demo
2022-05-25 06:35:51 -07:00
SerKo 6ad553483d Merge branch 'master' into fix-caps-lock-ime 2022-05-24 22:49:52 +08:00
Daniel Imms 49872f9377 Merge pull request #3826 from Tyriar/vscode148061
Take padding into account when converting mouse coord to cell
2022-05-23 12:10:48 -07:00
Daniel Imms 1e17ba7a80 Add some NF icons 2022-05-23 12:08:28 -07:00
Daniel Imms 1c8b08d55c Add test button to print all powerline symbols
Part of #3807
2022-05-23 10:42:31 -07:00
Daniel Imms ca083f257c Pass window in to fix tests 2022-05-23 07:23:15 -07:00
Daniel Imms 8b53b8c255 Take padding into account when converting mouse coord to cell
Part of microsoft/vscode#148061
2022-05-23 06:49:52 -07:00
Daniel Imms ba078ca1f9 Merge pull request #3824 from Tyriar/toColor_standard
Standardize css.toColor output to #rrggbb[aa]
2022-05-20 12:48:16 -07:00
Daniel ImmsandMegan Rogge 4ededf5b06 Update src/common/Color.test.ts
Co-authored-by: Megan Rogge <megan.rogge@microsoft.com>
2022-05-20 12:35:51 -07:00
Daniel Imms 384cd9db30 Standardize css.toColor output to #rrggbb[aa]
This will help reduce the chance of runtime exceptions that would
break the render loop.
2022-05-20 11:06:19 -07:00
Daniel Imms 55b420b016 Merge pull request #3823 from Tyriar/3814_2
Correct rgba() parsing to use float alpha channel
2022-05-20 06:50:03 -07:00
Daniel Imms ddd43e0f09 Correct rgba() parsing to use float alpha channel
Fixes #3814
2022-05-20 06:34:17 -07:00
Daniel Imms 41fdb96b68 Merge pull request #3822 from Tyriar/3821
Deflake registerLinkProvider tests
2022-05-19 15:51:58 -07:00
Daniel ImmsandMegan Rogge e5fb255b79 Update package.json
Co-authored-by: Megan Rogge <megan.rogge@microsoft.com>
2022-05-19 15:51:31 -07:00
Daniel ImmsandMegan Rogge 7f14bfc7d9 Update package.json
Co-authored-by: Megan Rogge <megan.rogge@microsoft.com>
2022-05-19 15:51:28 -07:00
Daniel Imms 8f97a5a786 Deflake registerLinkProvider tests
The root cause of this flakiness problem was related to the click event
handler. For some reason it would not go off on some links but it would
when clicking elsewhere in the terminal. I tried a bunch of things to
keep the click handler to no avail, instead opting to move to using
mousedown and mouseup events for activating links which comes with the
nice benefit of ensuring that the link on mouseup is the same one on
mousedown.

Fixes #3821
2022-05-19 15:30:12 -07:00
Daniel Imms 5353808437 Update playwright 2022-05-19 15:26:52 -07:00
Daniel Imms 38f8bc5f3c Merge pull request #3820 from Tyriar/3818
Fix decoration positioning
2022-05-19 10:24:42 -07:00
Daniel Imms 48c21dc3ae Merge pull request #3819 from Tyriar/3814
Support rgb() and rgba() in css.toColor
2022-05-19 10:24:32 -07:00
Daniel Imms bcb71e854f Fix decoration positioning
In _createElement the element wasn't set on the decoration yet.

Fixes 3818
2022-05-19 09:32:57 -07:00
Daniel Imms e3c361327c Support rgb() and rgba()
This is used for default theme colors and decoration bg/fg.

Fixes #3814
2022-05-19 09:23:36 -07:00
Daniel Imms 51ef27894e Merge pull request #3815 from ChaseKnowlden/ChaseKnowlden/issue3814
Add #rgba format
2022-05-19 09:07:48 -07:00
Daniel Imms 7edc11bd04 Correct #rgba, add #rgb and tests 2022-05-19 09:00:50 -07:00
ChaseKnowlden ebe55e5b5f Add rgba format 2022-05-18 20:28:59 -04:00
Daniel Imms 3d541dd323 Merge pull request #3813 from Tyriar/3810
Support selectionForeground theme color
2022-05-18 04:14:34 -07:00
Daniel Imms 53f05de3e7 Add selectionForeground webgl unit test 2022-05-17 12:12:01 -07:00
Daniel Imms d1a87facfd Fix selectionForeground column select mode on dom/canvas 2022-05-17 12:10:52 -07:00
Daniel Imms 18a2751471 Fix selectionForeground handling in test 2022-05-17 11:55:14 -07:00
Daniel Imms 59e1bddb6e Merge branch 'master' into 3810 2022-05-17 11:43:06 -07:00
Daniel Imms d6ad4a4fc3 Remove unneeded dep 2022-05-17 11:41:13 -07:00
Daniel Imms f9bd0b8fed Fix setting selection foreground on webgl 2022-05-17 11:39:14 -07:00
Daniel Imms ec26820ea6 Fix circular dependency mouseservice <-> renderservice 2022-05-17 11:23:33 -07:00
Daniel Imms d7fdf17eab Ensure color mode is set for selection foreground overrides 2022-05-17 11:20:20 -07:00
Megan Rogge a5f2ea336e update search results onBufferContentsChange (#3811) 2022-05-17 10:00:55 -07:00
Megan Rogge de5df69333 share animation frame with renderService (#3796) 2022-05-17 09:52:46 -07:00
Daniel Imms f2b90d690f Selection foreground for canvas (circular dep issue) 2022-05-16 15:17:03 -07:00
Daniel Imms eec53261e0 Merge pull request #3779 from Tyriar/3773
Layout dimension left/right after renderer dims change
2022-05-16 14:29:16 -07:00
Daniel Imms eb96187801 Support selectionForeground in dom
Part of #3810
2022-05-16 10:08:51 -07:00
Daniel Imms c502272373 Support selection foreground in webgl
Part of #3810
2022-05-16 09:40:07 -07:00
Daniel Imms 87bc48f5bc Merge pull request #3808 from Tyriar/trigger_webgl
Whitespace change to trigger build
2022-05-13 12:32:36 -07:00
Daniel Imms d7fb0141d2 Whitespace change to trigger build
The fix for #3720 didn't touch any file in the webgl addon so it didn't end
up releasing.
2022-05-13 12:32:17 -07:00
Daniel Imms 2fa125904a Merge pull request #3806 from Tyriar/3720
Allow ensureContrastRatio to change luminance the other way
2022-05-13 11:31:37 -07:00
Daniel Imms de03391c2d Allow ensureContrastRatio to change luminance the other way
The higher ratio of the two results will be picked.

Fixes #3720
2022-05-13 11:19:38 -07:00
Daniel Imms a38e97f1ec Merge pull request #3805 from Tyriar/3804
Fire onSelectionChange when Terminal.select is called
2022-05-13 09:51:28 -07:00
Daniel Imms 5d98304249 Fire onSelectionChange when Terminal.select is called
Fixes #3804
2022-05-13 09:42:39 -07:00
Megan Rogge 82a6144fd1 reset search result index when buffer changes/ terminal is resized (#3793) 2022-05-13 08:47:56 -07:00
Daniel Imms 995080c40c Merge pull request #3802 from Tyriar/3801
Allow styling active result decoration via .xterm-find-active-result-decoration
2022-05-13 08:47:11 -07:00
Daniel Imms 5ad12dff8d Merge pull request #3800 from Tyriar/3799
Dom renderer: top decorations above selection
2022-05-13 08:46:47 -07:00
Daniel ImmsandMegan Rogge a458bae36d Update src/browser/renderer/dom/DomRendererRowFactory.ts
Co-authored-by: Megan Rogge <megan.rogge@microsoft.com>
2022-05-13 08:46:40 -07:00
Daniel Imms 900200b871 Merge remote-tracking branch 'upstream/master' into 3773 2022-05-13 08:46:05 -07:00
Daniel Imms 77d5d52e71 Allow styling active result decoration via .xterm-find-active-result-decoration
Fixes #3801
2022-05-13 08:10:35 -07:00
Daniel Imms 1a50cfabe0 Dom renderer: top decorations above selection
Fixes #3799
2022-05-13 07:56:45 -07:00
Daniel Imms 3abbaa9ba9 Merge pull request #3678 from silamon/scrollbar-impro
Another way to turn off the scrollbar
2022-05-13 06:07:48 -07:00
Daniel Imms 5a9f2635d3 Merge branch 'master' into scrollbar-impro 2022-05-13 05:29:57 -07:00
SerKo d24c473172 Merge branch 'master' into fix-caps-lock-ime 2022-05-13 15:38:02 +08:00
Daniel Imms da93a35671 Merge pull request #3785 from Tyriar/layers
Support setting the layer of a decoration
2022-05-12 13:36:40 -07:00
Daniel Imms 7d4b61413e Merge pull request #3786 from Tyriar/default_highlight
Highlight all matches in demo by default
2022-05-12 13:25:23 -07:00
Daniel Imms 13e33a8da4 Merge pull request #3787 from Tyriar/3780
Merge .xterm css rules
2022-05-12 13:25:07 -07:00
Daniel Imms ede3d10528 Merge pull request #3788 from Tyriar/3762
Disable pointer events on the overview ruler
2022-05-12 13:24:53 -07:00
Daniel Imms 64ea96cc13 Merge pull request #3789 from Tyriar/3760
Add warning in demo code about flow control
2022-05-12 13:24:40 -07:00
Daniel Imms 47e3dc7bcd Merge pull request #3792 from Tyriar/casing_fix2
Fix missed decorations casing
2022-05-12 11:50:33 -07:00
Daniel Imms 748d0cc74f Fix missed decorations casing 2022-05-12 11:49:57 -07:00
Daniel Imms 343ea9bcd0 Merge pull request #3791 from Tyriar/decorations_lowercase2
Fix case of Decorations folder (take 2)
2022-05-12 11:20:10 -07:00
Daniel Imms cd01157e55 Rename decorations2 -> decorations to fix casing 2022-05-12 11:19:07 -07:00
Daniel Imms 4cc4b2cd42 Rename Decorations -> decoration2 to fix casing 2022-05-12 11:18:49 -07:00
Daniel Imms 29c1264b42 Add warning in demo code about flow control
Fixes #3760
2022-05-12 11:04:48 -07:00
Daniel Imms d84b71d4c6 Disable pointer events on the overview ruler
Fixes #3762
2022-05-12 10:59:57 -07:00
Daniel Imms f18528d2bb Clear _dimensionsChanged variable 2022-05-12 10:58:50 -07:00
Daniel Imms ddde738326 Merge .xterm css rules
Fixes #3780
2022-05-12 10:57:12 -07:00
Daniel Imms 24919e36f0 Highlight all matches in demo by default 2022-05-12 10:54:36 -07:00
Daniel Imms f5223d30b8 Fix typo 2022-05-12 10:52:16 -07:00
Daniel Imms 7b4423819e Support setting the layer of a decoration
Fixes #3778
2022-05-12 10:51:11 -07:00
Daniel Imms 372c2970e6 Merge pull request #3782 from Tyriar/3774
Correct positioning of decoration elements
2022-05-12 10:16:06 -07:00
Daniel Imms bc4158c9b8 Merge pull request #3783 from Tyriar/bg_decorations__selection_refactor
Remove selection-specific render pass, use bg overrides instead
2022-05-11 12:52:49 -07:00
Daniel Imms 8164c638e5 Merge branch 'master' into bg_decorations__selection_refactor 2022-05-11 12:34:28 -07:00
Daniel Imms 8b9fa324f4 Merge pull request #3775 from Tyriar/bg_decorations
Support for decorations to change cell foreground and background
2022-05-11 12:34:08 -07:00
Daniel Imms 0976b15709 Fix lint 2022-05-11 11:57:13 -07:00
Daniel Imms 181fb1cfe5 Remove selection-specific render pass, use bg overrides instead
Part of #3778
2022-05-11 11:46:44 -07:00
Daniel Imms 09aed4d339 Correct positioning of decoration elements
Fixes #3774
2022-05-11 10:27:35 -07:00
Daniel Imms aeb4fa0fe9 Fix compile after bad merge 2022-05-11 10:15:18 -07:00
Daniel Imms 0ae5aedcf9 Merge branch 'master' into 3773 2022-05-11 10:13:57 -07:00
Daniel Imms 3d41f028e4 Layout dimension left/right after renderer dims change
Fixes #3773
2022-05-11 10:12:48 -07:00
Daniel Imms 324ea2ecb7 Clean up/resolve todos 2022-05-11 09:54:40 -07:00
Daniel Imms 999f255893 Fix sorted list edge case 2022-05-11 09:49:23 -07:00
Daniel Imms 931ee9e89e Maintain decorations sorted by line 2022-05-11 09:34:51 -07:00
Daniel Imms c0b4edaf31 Reduce duplicate with getDecorationsAtCell method 2022-05-11 08:37:42 -07:00
Daniel Imms abdce874db Fix cases where only one override was with tests 2022-05-11 08:31:09 -07:00
Daniel Imms edba006045 Clear all decorations on reset 2022-05-10 12:32:06 -07:00
Daniel Imms 6b4df216d3 Remove unneeded code 2022-05-10 12:08:42 -07:00
Daniel Imms f502f55ae1 Merge branch 'master' into bg_decorations 2022-05-10 11:50:01 -07:00
Daniel Imms afe1d7ed6f Fix webgl contrast tests since a bug in luminance was fixed 2022-05-10 11:48:37 -07:00
Daniel Imms 1628795557 Fix fg/bg flags being set by negative ints 2022-05-10 09:42:12 -07:00
Daniel Imms e9643f5936 Ensure texture atlas isn't for overrides used in canvas renderer 2022-05-10 09:24:09 -07:00
Daniel Imms ba61509fdd Remove react to changes idea 2022-05-10 06:20:48 -07:00
Daniel Imms a67e7286d3 Use correct row for override in webgl renderer 2022-05-10 06:16:32 -07:00
Daniel Imms b85f5aacd6 Use correct row for override in canvas renderer 2022-05-10 06:14:53 -07:00
Daniel Imms de574a27fc Full re-render when there are decoration changes 2022-05-10 05:44:00 -07:00
Daniel Imms 7c87f5b47a Start to use new API in search addon 2022-05-10 05:31:49 -07:00
Daniel Imms 0a26cee08b Pull override color logic into a function 2022-05-10 05:06:20 -07:00
Daniel Imms 40716b8ca5 Consolidate override logic into model/WebglRenderer 2022-05-10 05:00:52 -07:00
Daniel Imms d29388394c Get Webgl rendering fg/bg overrides 2022-05-10 04:53:31 -07:00
Daniel Imms 714d4b1cd6 Fix fg color when there is a decoration override in canvas renderer 2022-05-10 04:06:06 -07:00
Daniel Imms 9f1db12b1b bg/fg decorations mostly working in canvas renderer 2022-05-09 16:19:58 -07:00
Megan Rogge 56c7c5c4ed check if undefined instead of index directly to fix search addon issue (#3767) 2022-05-09 15:55:33 -07:00
Daniel Imms 13abd2aa96 Support min contrast ratio in decoration fg/bg 2022-05-09 15:39:02 -07:00
Daniel Imms 10598462d0 Fix color channel used by luminance functions 2022-05-09 15:36:54 -07:00
Daniel Imms 0c6877f46f Move color to common, work with IColor
IColor is needed for the minimum contrast ratio function currently.
2022-05-09 14:00:39 -07:00
Daniel Imms 0c4ee0db65 Merge pull request #3769 from Tyriar/145751
Only create decoration elements when they are actually rendered
2022-05-09 12:45:19 -07:00
Daniel Imms 5d09d3b217 Merge pull request #3768 from Tyriar/clean_up
Correct param names/jsdoc in SearchAddon
2022-05-09 12:45:03 -07:00
Daniel Imms 63eb9a4555 Initial bg/fg renderer decoration proof of concept
Part of #3770
2022-05-09 12:44:51 -07:00
Daniel Imms b7c0332626 Only create decoration elements when they are actually rendered
Part of microsoft/vscode#145751
2022-05-09 10:52:18 -07:00
Daniel Imms 95979c4a7d Correct param names/jsdoc in SearchAddon 2022-05-09 10:34:40 -07:00
binghuiluo 5019e14753 Send the record separator for C-@, like some other keyboards 2022-05-07 17:19:04 +08:00
Daniel Imms 85e0625078 Merge pull request #3761 from Tyriar/vscode148685
Send ctrl modifier in page up/down sequence
2022-05-06 07:45:43 -07:00
Daniel Imms 873e3745ec Send ctrl modifier in page up/down sequence
Part of microsoft/vscode#148685
2022-05-06 07:36:22 -07:00
SerKo 9b19187307 Merge branch 'master' into fix-caps-lock-ime 2022-05-05 19:55:56 +08:00
Megan Rogge 218745ab85 refine name for clearDecorations param (#3757) 2022-05-02 14:20:25 -07:00
Megan Rogge 1687d06f1c use correct powerline glyph range (#3756) 2022-05-02 20:50:11 +00:00
jerch 261209cab6 Merge pull request #3754 from xtermjs/dependabot/npm_and_yarn/ansi-regex-5.0.1
Bump ansi-regex from 5.0.0 to 5.0.1
2022-05-01 18:45:27 +02:00
dependabot[bot] bb55cd2881 Bump ansi-regex from 5.0.0 to 5.0.1
Bumps [ansi-regex](https://github.com/chalk/ansi-regex) from 5.0.0 to 5.0.1.
- [Release notes](https://github.com/chalk/ansi-regex/releases)
- [Commits](https://github.com/chalk/ansi-regex/compare/v5.0.0...v5.0.1)

---
updated-dependencies:
- dependency-name: ansi-regex
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-05-01 16:05:46 +00:00
jerch 7b38283db0 Merge pull request #3753 from jerch/fix_deps_warning
update benchmark version to silence vulnerability warning
2022-05-01 18:03:36 +02:00
Jörg Breitbart 908e84a1f8 update benchmark version to silence vul warning 2022-05-01 17:55:04 +02:00
Megan Rogge 656912c934 on data/ buffer change, update the match count (#3752) 2022-04-27 19:10:00 +00:00
Megan Rogge 7fa32af051 clear cachedSearchTerm when decorations are cleared (#3750) 2022-04-27 18:20:00 +00:00
Megan Rogge fa92e6074d search fixes (#3748) 2022-04-26 15:45:30 -07:00
Megan Rogge 239f669041 add max find result count for which to show decorations (#3745) 2022-04-21 16:04:08 -07:00
SerKo 214555085a Merge branch 'master' into fix-caps-lock-ime 2022-04-21 20:02:49 +08:00
Megan Rogge da33543ffb align isPowerlineGlyph across renderers (#3743) 2022-04-20 14:25:46 -07:00
SerKo e643286cd5 Merge branch 'master' into fix-caps-lock-ime 2022-04-20 17:34:38 +08:00
Megan Rogge 38f0dd9be2 apply to dom and webgl too (#3742) 2022-04-19 18:46:39 -07:00
Megan Rogge 6d801311cd exclude powerline char range from contrast demands (#3740) 2022-04-19 15:23:26 -07:00
Megan Rogge 3bd37115d1 queue refresh when deco is removed (#3738) 2022-04-19 08:49:16 -07:00
Megan Rogge 0785a1bf24 fix #3735 (#3736) 2022-04-19 08:25:17 -07:00
SerKo 413a2b22fa Merge branch 'master' into fix-caps-lock-ime 2022-04-15 15:23:22 +08:00
Megan Rogge e98b6315e4 always update overview ruler decorations (#3733) 2022-04-13 18:19:22 +00:00
SerKo d9a33d080b Merge branch 'master' into fix-caps-lock-ime 2022-04-10 07:02:52 +08:00
Megan Rogge 85b5161380 don't return if results are the same bc color might have changed (#3731) 2022-04-08 21:07:46 +00:00
Megan Rogge 2b916a24aa get overview ruler to update when color is changed (#3730) 2022-04-08 13:35:55 -07:00
SerKo ccaf0a71c6 fix: caps lock bug on macOS IME 2022-04-07 06:07:28 +08:00
Eugene Pankov b3fd901699 lint 2022-04-03 17:44:15 +02:00
Eugene Pankov a32e3958d8 fixed macOptionIsMeta on macOS and added Alt-Shift-Letter key handling 2022-04-03 17:39:18 +02:00
Daniel Imms dbd999c4a4 Merge pull request #3723 from Tyriar/3703_ruler_perf
Improve performance of overview ruler by computing and rendering color zones
2022-04-01 09:58:28 -07:00
Daniel Imms 8158a16629 Remove unused _decorationElements 2022-04-01 07:07:13 -07:00
Daniel Imms 7eace4d479 Add tests for ColorZoneStore 2022-04-01 07:05:05 -07:00
Daniel Imms e98a20ba9e Fix typo in test name 2022-04-01 07:04:52 -07:00
Daniel Imms d072a628ca Object pool, correct merging of zones 2022-04-01 06:25:55 -07:00
Daniel Imms 6b26a1bcf8 Stick to integers for padding and render full after 2022-04-01 06:05:41 -07:00
Daniel Imms 8524c23deb Render overview ruler using color zones
Fixes #3703
2022-04-01 05:55:57 -07:00
Daniel Imms 7c5853bf00 Create color zone system
Part of #3703
2022-04-01 05:30:01 -07:00
Daniel Imms 23afd53301 Merge pull request #3718 from LuckyGeck/fixweblink
Fix WebLinkProvider to handle wrapped lines properly.
2022-03-31 09:23:06 -07:00
Daniel Imms 1d728f7f87 Merge branch 'master' into fixweblink 2022-03-31 09:12:15 -07:00
Daniel Imms 5030dc3d6a Merge pull request #3669 from silamon/tripleselect
Fix triple select edge case
2022-03-31 09:10:31 -07:00
Daniel Imms 47fe7b2318 Merge pull request #3667 from childrentime/dev_wlw
Replace all getOption/setOption usage with options
2022-03-31 09:09:49 -07:00
Daniel Imms 51d18e533f Merge branch 'master' into dev_wlw 2022-03-31 09:00:09 -07:00
Megan Rogge 842f4c2088 Add search result count to SearchAddon (#3716) 2022-03-30 20:08:55 -04:00
Pavel Sychev c8b0ddbc72 Merge branch 'master' into fixweblink 2022-03-30 17:10:29 +02:00
Daniel Imms 4319df0eeb Merge pull request #3719 from LuckyGeck/addregex
Expose urlRegex in the public .d.ts file.
2022-03-30 07:59:17 -07:00
Daniel Imms bd3bd7fda8 Remove duplicate interface 2022-03-30 07:58:35 -07:00
Pavel Sychev 7f87b46328 Expose urlRegex in the public .d.ts file. 2022-03-30 14:30:56 +02:00
Pavel Sychev 32ae29be37 Fix WebLinkProvider to handle wrapped lines properly. 2022-03-30 14:27:02 +02:00
Daniel Imms 89af5b1098 Merge pull request #3713 from CommanderRoot/refactor/rm-deprecated-substr
refactor: replace deprecated String.prototype.substr()
2022-03-28 06:58:38 -07:00
Daniel Imms 5aaed8cf0b Merge pull request #3715 from xtermjs/dependabot/npm_and_yarn/minimist-1.2.6
Bump minimist from 1.2.5 to 1.2.6
2022-03-28 06:40:41 -07:00
dependabot[bot] 06f3f8f267 Bump minimist from 1.2.5 to 1.2.6
Bumps [minimist](https://github.com/substack/minimist) from 1.2.5 to 1.2.6.
- [Release notes](https://github.com/substack/minimist/releases)
- [Commits](https://github.com/substack/minimist/compare/1.2.5...1.2.6)

---
updated-dependencies:
- dependency-name: minimist
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-28 13:30:37 +00:00
Daniel Imms 14d22a2002 Merge pull request #3714 from xtermjs/dependabot/npm_and_yarn/addons/xterm-addon-ligatures/minimist-1.2.6
Bump minimist from 1.2.5 to 1.2.6 in /addons/xterm-addon-ligatures
2022-03-28 06:29:42 -07:00
dependabot[bot] 4337326169 Bump minimist from 1.2.5 to 1.2.6 in /addons/xterm-addon-ligatures
Bumps [minimist](https://github.com/substack/minimist) from 1.2.5 to 1.2.6.
- [Release notes](https://github.com/substack/minimist/releases)
- [Commits](https://github.com/substack/minimist/compare/1.2.5...1.2.6)

---
updated-dependencies:
- dependency-name: minimist
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-26 15:33:57 +00:00
Tobias Speicher 28dcd38a53 refactor: replace deprecated String.prototype.substr()
.substr() is deprecated so we replace it with .slice() which works similarily but isn't deprecated

Signed-off-by: Tobias Speicher <rootcommander@gmail.com>
2022-03-25 20:41:06 +01:00
Daniel Imms bb2367ab4a Merge pull request #3711 from Tyriar/3705_2
Don't change overview ruler position based on decorations
2022-03-24 08:06:04 -07:00
Daniel Imms 02795f8803 Don't change overview ruler position based on decorations
The original fix for #3705 ended up revealing this other bug; the canvas
shouldn't be set based on the anchor of its decorations.

Fixes #3705
2022-03-24 08:02:52 -07:00
Daniel Imms 8528e8e83b Merge pull request #3710 from Tyriar/3705
Don't override decoration positions
2022-03-24 07:07:03 -07:00
Daniel Imms 1f690c7703 Merge remote-tracking branch 'upstream/master' into 3705 2022-03-24 07:06:36 -07:00
Daniel Imms 24fb498757 Don't override decoration positions
This also fixes an exception that was throwing where we only allowed links
within the terminal's 'rows', which should have been buffer length

Fixes #3705
Part of microsoft/vscode#145808
2022-03-24 07:05:16 -07:00
Megan Rogge 7836097e56 Merge pull request #3709 from meganrogge/rows
don't throw if row > terminal.rows
2022-03-23 19:27:18 -04:00
meganrogge 86f80b344b don't throw if row > terminal.rows 2022-03-23 19:20:16 -04:00
Daniel Imms 49bcb969b6 Merge pull request #3708 from Tyriar/search_colors
Allow configuring search overview ruler and border color
2022-03-23 15:10:58 -07:00
Daniel Imms b55c7e342a Improve setting names 2022-03-23 15:09:35 -07:00
Daniel Imms 23ed3327ce Allow configuring search overview ruler and border color
Part of microsoft/vscode#145744
Part of microsoft/vscode#145742
Part of microsoft/vscode#145746
2022-03-23 15:04:13 -07:00
Megan Rogge d57329f7e5 Merge pull request #3704 from meganrogge/emacs
add ST sequence, fix emacs
2022-03-23 17:46:38 -04:00
meganroggeandjerch f6a384b401 fix tests
Co-authored-by: jerch <jerch@rockborn.de>
2022-03-23 17:39:10 -04:00
meganrogge 79c990d73e Revert "fix most of tests"
This reverts commit becab08b6a.
2022-03-23 15:43:12 -04:00
meganrogge ecfc2d4b40 Merge branch 'emacs' of https://github.com/meganrogge/xterm.js into emacs 2022-03-23 15:12:07 -04:00
meganrogge becab08b6a fix most of tests 2022-03-23 15:12:00 -04:00
Megan Rogge 8ad0bd1638 Merge branch 'master' into emacs 2022-03-23 14:44:01 -04:00
Megan Rogge 544c10009c Merge pull request #3707 from meganrogge/findImprovements
use correct start col/row in next search
2022-03-23 14:43:47 -04:00
meganrogge 3457fb4872 Merge branch 'emacs' of https://github.com/meganrogge/xterm.js into emacs 2022-03-23 14:18:02 -04:00
meganroggeandjerch 8e17cace6f use escaped format
Co-authored-by: jerch <jerch@rockborn.de>
2022-03-23 14:17:54 -04:00
meganrogge 93f7ad86da Merge branch 'findImprovements' of https://github.com/meganrogge/xterm.js into findImprovements 2022-03-23 14:03:24 -04:00
meganrogge 3a0f64545e throw for invalid row or col 2022-03-23 14:01:56 -04:00
Megan Rogge 5af5fe319b Merge branch 'master' into findImprovements 2022-03-23 13:57:22 -04:00
meganrogge b9d10f1453 use correct start col/row 2022-03-23 13:54:01 -04:00
Megan Rogge 14bfc0bf0e Merge branch 'master' into emacs 2022-03-23 12:54:50 -04:00
meganrogge 078d88fbbe pass in escape sequence with ESC notation" 2022-03-23 12:54:19 -04:00
meganrogge dfe35a93d5 add ST sequence 2022-03-23 11:48:02 -04:00
Megan Rogge 7ff27240f0 Merge pull request #3702 from meganrogge/overviewrulerFix 2022-03-22 20:04:00 -04:00
meganrogge 550b19f334 refactor 2022-03-22 16:41:50 -04:00
meganrogge 74409da947 move resetting of var 2022-03-22 15:09:54 -04:00
meganrogge 1f60a398b2 on render, fire when height has changed (was 0) 2022-03-22 15:04:22 -04:00
meganrogge 21e1c5ef58 refresh canvas dimensions on refresh 2022-03-22 01:53:10 -04:00
Daniel Imms dddde8ec23 Merge pull request #3698 from meganrogge/findAll
Show all matches when searching
2022-03-21 17:01:18 -07:00
Daniel Imms 43a014e8c1 Fix decoration lifecycle issues 2022-03-21 16:57:49 -07:00
Daniel Imms b39477d81a Fix full decorations 2022-03-21 16:45:48 -07:00
Daniel Imms 6ef1bdd7ce Debounce onData listener 2022-03-21 16:37:46 -07:00
Daniel Imms f9deb2f840 Don't set overview ruler width when using find
An embedder may not want to enable it
2022-03-21 16:34:48 -07:00
Daniel Imms 23076ba6cc Fix duplicate decorations getting created 2022-03-21 16:24:34 -07:00
Daniel Imms fe530d92ec Ensure marker is disposed when decoration is 2022-03-21 16:11:31 -07:00
Daniel Imms efda0e77a9 Fix indentation properly this time 2022-03-21 16:04:23 -07:00
Daniel Imms 87dd275a51 Undo change to Terminal.ts 2022-03-21 16:03:59 -07:00
Daniel Imms ef3695f727 Move setting opacity into js 2022-03-21 16:03:10 -07:00
Daniel Imms 09d1a01126 Add ISearchDecorationOptions to the d.ts 2022-03-21 16:01:34 -07:00
Daniel Imms 7a70db709c Fix API indentation 2022-03-21 15:51:51 -07:00
meganrogge 7f131aa19a fix error 2022-03-21 18:16:35 -04:00
meganrogge 4645ef706e refactor 2022-03-21 18:13:08 -04:00
meganrogge 866ee23df9 add disposeDecorations helper 2022-03-21 15:45:45 -04:00
meganrogge fefdd8a4e9 clearDecorations 2022-03-21 15:44:17 -04:00
meganrogge 7a502c6b83 use const enum 2022-03-21 15:43:08 -04:00
meganrogge 90dca7d650 Merge branch 'findAll' of https://github.com/meganrogge/xterm.js into findAll 2022-03-21 15:37:45 -04:00
meganrogge e8aa4eba75 IDecorationColor -> ISearchDecorationOptions 2022-03-21 15:36:39 -04:00
Megan RoggeandDaniel Imms 64d5af38f5 Update addons/xterm-addon-search/src/SearchAddon.ts
Co-authored-by: Daniel Imms <2193314+Tyriar@users.noreply.github.com>
2022-03-21 15:35:01 -04:00
meganrogge 4cea6b3f41 revert a breaking change 2022-03-21 13:35:56 -04:00
meganrogge b9331ec810 Merge branch 'findAll' of https://github.com/meganrogge/xterm.js into findAll 2022-03-21 13:07:34 -04:00
meganrogge 97e4af3707 only highlight all matches when decorations are requested 2022-03-21 13:07:25 -04:00
Megan Rogge faa3769ed1 Merge branch 'master' into findAll 2022-03-21 12:58:05 -04:00
meganrogge 1098173908 use set timeout 2022-03-21 11:45:45 -04:00
meganrogge 8ff34a6aee update decorations dynamically when the buffer changes 2022-03-21 11:17:24 -04:00
meganrogge bec4cb37d4 set data changed to false if true 2022-03-21 10:58:44 -04:00
meganrogge 12a999dfe0 get rid of start row/col code 2022-03-21 10:54:01 -04:00
meganrogge 8225ecd3ba refactor from highlightAllMatches -> decorations 2022-03-21 10:24:46 -04:00
Megan Rogge 31c11c7a65 Merge pull request #3697 from Tyriar/3692
Polish rendering of overview ruler decorations
2022-03-20 12:34:10 -04:00
meganrogge a0aae62cb7 get rid of start col end col 2022-03-18 19:43:00 -04:00
meganrogge e79c21d4b2 get it to work for previous too 2022-03-18 19:24:27 -04:00
meganrogge 61e5c10fd3 pull apart and refactor findNext 2022-03-18 19:07:39 -04:00
meganrogge fef30bdba4 revert row register marker changes 2022-03-18 15:38:31 -04:00
meganrogge 61c2ca71d8 Metrge branch 'findAll' of https://github.com/meganrogge/xterm.js into findAll 2022-03-18 15:32:31 -04:00
meganrogge 8328fcac52 track disposable 2022-03-18 15:32:24 -04:00
Megan RoggeandDaniel Imms dd503785be Update css/xterm.css
Co-authored-by: Daniel Imms <2193314+Tyriar@users.noreply.github.com>
2022-03-18 15:28:16 -04:00
meganrogge 85683a457d add to col, not row 2022-03-18 15:27:50 -04:00
meganrogge 15c2432b6f jsdoc 2022-03-18 15:23:49 -04:00
meganrogge 66dcd1694c clean up jsdoc 2022-03-18 15:13:49 -04:00
meganrogge 8e68c67a0c applyStyles 2022-03-18 15:07:33 -04:00
Megan Rogge d2913d607d Merge branch 'master' into findAll 2022-03-18 14:47:51 -04:00
meganrogge 01fd148673 clean up 2022-03-18 14:46:49 -04:00
meganrogge 47ec31361c support colors 2022-03-18 14:14:02 -04:00
Daniel Imms 39d1546753 Draw full decorations on top
Fixes #3696
2022-03-18 10:59:10 -07:00
Daniel Imms b5f208294d Clean up 2022-03-18 10:56:49 -07:00
Daniel Imms 77622f8919 Align full with other decorations
Fixes #3692
2022-03-18 10:54:11 -07:00
Daniel Imms 816220ebd2 Round dpr for more crisp decorations 2022-03-18 10:51:02 -07:00
Daniel Imms 4a661da57b Use object properties for fast access to precalculated draw args 2022-03-18 10:50:28 -07:00
Daniel Imms f43afde8df Respect device pixel ratio in overview ruler 2022-03-18 10:42:26 -07:00
meganrogge 6c4f8b42dd append to results 2022-03-18 13:29:59 -04:00
meganrogge a1468d0f58 use start row and col 2022-03-18 13:12:32 -04:00
meganrogge c8dc679086 Merge branch 'master' into findAll 2022-03-18 12:05:13 -04:00
meganrogge 9bc5090bac use a map 2022-03-18 11:47:26 -04:00
Megan Rogge 29e9685839 Merge pull request #3695 from meganrogge/fixes
queue refresh on dispose of decoration
2022-03-17 16:38:55 -04:00
meganrogge 02e2727fb5 fix typos 2022-03-17 16:36:34 -04:00
meganrogge a05cdbd48e Remove accidental import 2022-03-17 16:30:55 -04:00
meganrogge 8f1dcecb50 dispose of decoration 2022-03-17 16:23:37 -04:00
meganrogge bb2193676b fix problems 2022-03-17 16:20:47 -04:00
Megan Rogge f5b5716582 Merge pull request #3693 from meganrogge/clearRuler
clear overview ruler canvas when decoration is disposed
2022-03-17 15:57:56 -04:00
Megan Rogge 38ccf1d5c7 Merge pull request #3694 from meganrogge/full
Add overviewRulerOptions position 'full'
2022-03-17 15:41:14 -04:00
meganrogge ba4252bc0b fix merge result 2022-03-17 15:21:29 -04:00
Megan Rogge 900d0132cb Merge branch 'master' into full 2022-03-17 15:20:00 -04:00
meganrogge 5a6a275a53 use interface 2022-03-17 15:18:21 -04:00
meganrogge 731432a852 set fallback 2022-03-17 15:16:28 -04:00
meganrogge c70be89bc2 fix #3690 2022-03-17 15:14:35 -04:00
meganrogge 03bc29e0ed fix #3691 2022-03-17 15:02:57 -04:00
Daniel Imms eb562b5d89 Merge pull request #3680 from Eugeny/electron-13-sogou-fix
IME: handle missing compositionend events for Sogou IME
2022-03-17 09:23:06 -07:00
Daniel Imms d60431f4bf Merge branch 'master' into electron-13-sogou-fix 2022-03-17 09:20:46 -07:00
meganrogge de56d04ae5 return decoration from show result 2022-03-17 11:08:40 -04:00
meganrogge c2145d5125 on dispose, clear the canvas at that position 2022-03-17 10:38:44 -04:00
meganrogge 90f64ba6cc get it to work in the demo 2022-03-17 09:52:41 -04:00
meganrogge 26f8fce538 update color when select next happens 2022-03-16 17:23:33 -04:00
Megan Rogge 19399066f1 Merge branch 'xtermjs:master' into findAll 2022-03-16 16:55:49 -04:00
Megan Rogge f38d7d86ac Merge pull request #3689 from meganrogge/polish
allow changing the color for overviewRuler decorations
2022-03-16 16:09:31 -04:00
meganrogge 450ee0c6a5 tweak api 2022-03-16 15:58:06 -04:00
meganrogge 6899f9dc4d tweak jsdoc 2022-03-16 15:55:15 -04:00
meganrogge f92d1f46a6 only allow updating color 2022-03-16 15:36:03 -04:00
meganrogge 12efc1a455 Merge branch 'polish' of https://github.com/meganrogge/xterm.js into polish 2022-03-16 15:20:56 -04:00
meganrogge f4593b76df use options 2022-03-16 15:20:13 -04:00
Megan Rogge 9e8e6159cf Merge branch 'master' into polish 2022-03-16 14:51:56 -04:00
Daniel Imms b5d0a46ccb Merge pull request #3688 from meganrogge/onRender
don't fire onRender for overviewRuler decorations
2022-03-16 11:47:26 -07:00
meganrogge fb13f812f7 tweak description 2022-03-16 14:37:12 -04:00
meganrogge 6162ae9ffe allow changing the color of overview decorations 2022-03-16 14:33:51 -04:00
Megan Rogge b6e1925ba8 Merge branch 'master' into onRender 2022-03-16 14:07:12 -04:00
Megan Rogge fd3b22e8ab Merge pull request #3687 from meganrogge/master
Use proper height and fix alignment for positioned overview ruler decorations
2022-03-16 14:07:00 -04:00
meganrogge a4212e98d2 fix #3686 2022-03-16 13:40:06 -04:00
meganrogge e293d29fd3 add a comment 2022-03-16 13:31:03 -04:00
meganrogge 1a9965ef01 fix #3683 2022-03-16 13:20:48 -04:00
meganrogge 05eb12e6dd start work on selection color 2022-03-16 13:03:21 -04:00
meganrogge 40b8854df4 rm bad decoration service 2022-03-16 12:47:46 -04:00
meganrogge 1bab173e99 fix problems 2022-03-16 12:45:18 -04:00
meganrogge f678f0824b merge 2022-03-16 11:39:34 -04:00
Daniel Imms 8b32d0c8ba Merge pull request #3685 from xtermjs/Tyriar-patch-1
Remove unwanted console.log
2022-03-16 08:08:28 -07:00
Daniel Imms 8b55497adf Remove unwanted console.log 2022-03-16 07:50:01 -07:00
Megan Rogge 3870507305 Merge pull request #3676 from meganrogge/merogge/scroll-bar-decorations
add overview ruler
2022-03-15 21:13:52 -04:00
meganrogge a0a7abbf49 add overview options to decoration in demo 2022-03-15 21:05:14 -04:00
meganrogge c277fb742d add overviewRuler to addDecoration 2022-03-15 20:50:13 -04:00
meganrogge 7f8592282f Merge branch 'merogge/scroll-bar-decorations' of https://github.com/meganrogge/xterm.js into merogge/scroll-bar-decorations 2022-03-15 19:29:43 -04:00
meganrogge 963ed59459 tweak demo 2022-03-15 19:29:33 -04:00
meganrogge 22d889ab07 use this._width instead of options 2022-03-15 19:15:59 -04:00
Megan RoggeandDaniel Imms a4f830aa5d Update typings/xterm.d.ts
Co-authored-by: Daniel Imms <2193314+Tyriar@users.noreply.github.com>
2022-03-15 18:13:36 -04:00
Megan RoggeandDaniel Imms 6f5008dba6 Update typings/xterm.d.ts
Co-authored-by: Daniel Imms <2193314+Tyriar@users.noreply.github.com>
2022-03-15 18:13:23 -04:00
Megan RoggeandDaniel Imms 5777fbc608 Update src/browser/tsconfig.json
Co-authored-by: Daniel Imms <2193314+Tyriar@users.noreply.github.com>
2022-03-15 18:13:10 -04:00
Megan RoggeandDaniel Imms 035a7f710e Update src/browser/Terminal.ts
Co-authored-by: Daniel Imms <2193314+Tyriar@users.noreply.github.com>
2022-03-15 18:12:58 -04:00
Megan RoggeandDaniel Imms d92e636482 Update src/browser/Terminal.ts
Co-authored-by: Daniel Imms <2193314+Tyriar@users.noreply.github.com>
2022-03-15 18:12:48 -04:00
meganrogge b05db37ac1 adjust size based on position 2022-03-15 18:12:19 -04:00
meganrogge 53c432df27 add condition for when to update anchor 2022-03-15 17:58:44 -04:00
meganrogge 8ccbef850e only update canvas dimensions when needed 2022-03-15 17:38:15 -04:00
meganrogge 19b212d570 Merge branch 'merogge/scroll-bar-decorations' of https://github.com/meganrogge/xterm.js into merogge/scroll-bar-decorations 2022-03-15 17:08:07 -04:00
meganrogge a9a6920b13 more cleanup 2022-03-15 17:08:01 -04:00
Megan Rogge 9f6d4f52f3 Merge branch 'master' into merogge/scroll-bar-decorations 2022-03-15 17:07:34 -04:00
Megan RoggeandDaniel Imms e7baccdbcd Update src/browser/Terminal.ts
Co-authored-by: Daniel Imms <2193314+Tyriar@users.noreply.github.com>
2022-03-15 17:07:01 -04:00
Megan RoggeandDaniel Imms 4cc0db4273 Update src/browser/Decorations/OverviewRulerRenderer.ts
Co-authored-by: Daniel Imms <2193314+Tyriar@users.noreply.github.com>
2022-03-15 17:06:10 -04:00
Daniel Imms 3e283e9168 Merge pull request #3682 from Tyriar/3677
Restore cursor style too during serialize
2022-03-15 12:52:22 -07:00
Daniel Imms a39facfe28 Restore cursor style too during serialize
Fixes #3677
2022-03-15 12:42:47 -07:00
meganrogge 1c38f859b7 Merge branch 'merogge/scroll-bar-decorations' of https://github.com/meganrogge/xterm.js into merogge/scroll-bar-decorations 2022-03-15 15:01:16 -04:00
meganrogge cee2411fb4 more cleanup 2022-03-15 15:00:05 -04:00
Megan RoggeandDaniel Imms fdc2bc4960 Update typings/xterm.d.ts
Co-authored-by: Daniel Imms <2193314+Tyriar@users.noreply.github.com>
2022-03-15 14:59:48 -04:00
Megan RoggeandDaniel Imms b146ffd30f Update src/browser/Decorations/OverviewRulerRenderer.ts
Co-authored-by: Daniel Imms <2193314+Tyriar@users.noreply.github.com>
2022-03-15 14:59:43 -04:00
Megan RoggeandDaniel Imms 45ecc837e4 Update typings/xterm.d.ts
Co-authored-by: Daniel Imms <2193314+Tyriar@users.noreply.github.com>
2022-03-15 14:59:23 -04:00
Megan RoggeandDaniel Imms 5b8777267a Update typings/xterm.d.ts
Co-authored-by: Daniel Imms <2193314+Tyriar@users.noreply.github.com>
2022-03-15 14:59:17 -04:00
Megan RoggeandDaniel Imms 8488f1ad63 Update src/browser/Decorations/OverviewRulerRenderer.ts
Co-authored-by: Daniel Imms <2193314+Tyriar@users.noreply.github.com>
2022-03-15 14:59:09 -04:00
Megan RoggeandDaniel Imms a93f81c270 Update typings/xterm.d.ts
Co-authored-by: Daniel Imms <2193314+Tyriar@users.noreply.github.com>
2022-03-15 14:57:34 -04:00
meganrogge 21263a5f60 cleanup 2022-03-15 14:54:37 -04:00
meganrogge 1905d7d8ca add overview ruler tests 2022-03-15 13:28:50 -04:00
meganrogge adaee5e495 use poll for instead 2022-03-15 13:23:09 -04:00
meganrogge 71649f5e40 fix tests 2022-03-15 13:21:34 -04:00
meganrogge bd21c6afa5 adjust test 2022-03-15 13:15:43 -04:00
meganrogge df929f2dd5 add position 2022-03-15 13:07:49 -04:00
meganrogge e7e9d5ca4c remove test 2022-03-15 12:56:34 -04:00
meganrogge 80e7963dbe fix tests and add one 2022-03-15 12:43:03 -04:00
meganrogge f98b4ce1ba properly dispose of buffer decoration 2022-03-15 12:16:33 -04:00
meganrogge 520d5dd9eb delete unused css 2022-03-15 11:37:20 -04:00
meganrogge 4ef5f6ae86 add listener for on option change 2022-03-15 10:53:32 -04:00
meganrogge 39f9c3507b rename css, fix background color getting applied 2022-03-15 10:37:38 -04:00
meganrogge 3e47c96445 get demo to work the right way 2022-03-15 10:31:01 -04:00
meganrogge 079bbc8242 get it working in demo with overviewRulerwidth not considered" 2022-03-15 10:27:01 -04:00
meganrogge bfb048110f register service 2022-03-15 08:57:15 -04:00
Daniel Imms fc09730ffb Tidy up DecorationService 2022-03-14 16:07:30 -07:00
Daniel Imms 3dc4f1a7f2 Remove old decoration elements 2022-03-14 15:56:27 -07:00
Daniel Imms 65b9a6cfc4 Fix dependency injection for decoration service 2022-03-14 15:52:32 -07:00
Daniel Imms 4b615f48ff Reduce state in buffer decorations 2022-03-14 15:48:09 -07:00
Daniel Imms c8d1266f0a Fix buffer decoration onRender, start using internal decoration 2022-03-14 15:24:53 -07:00
meganrogge 7dc3332b05 commit before reverting 2022-03-14 15:50:40 -04:00
meganrogge ced06629d4 large refactor, broken 2022-03-14 15:11:20 -04:00
meganrogge 3b279e4a4d round to the nearest pixel 2022-03-14 09:52:12 -04:00
meganrogge 9e598d9fba allow setting width 2022-03-14 09:30:30 -04:00
Eugene Pankov f025c0cf4e ime: handle missing compositionend events for Sogou IME 2022-03-12 20:56:02 +01:00
Simon Lamon 8adf9473b7 check scrollback in fit addon 2022-03-12 09:36:06 +00:00
meganrogge 692df151e5 part 1 of massive refactor 2022-03-11 22:34:17 -05:00
meganrogge 4022277cec scroll -> overviewRuler 2022-03-11 21:52:16 -05:00
meganrogge 1250ab637a insert before 2022-03-11 17:52:04 -05:00
meganrogge a14248d710 Merge branch 'merogge/scroll-bar-decorations' of https://github.com/meganrogge/xterm.js into merogge/scroll-bar-decorations 2022-03-11 17:15:40 -05:00
meganrogge 98a03dd477 re-arrange dom structure 2022-03-11 17:15:11 -05:00
Megan RoggeandDaniel Imms 2f85448e51 Update css/xterm.css
Co-authored-by: Daniel Imms <2193314+Tyriar@users.noreply.github.com>
2022-03-11 16:53:59 -05:00
meganrogge b4de2787c7 hide decorations >= bufferService rows 2022-03-11 13:13:05 -05:00
meganrogge e7bbc4151a start at cursor position and mandate a width gt 0 2022-03-11 13:10:26 -05:00
meganrogge 453cb00807 use a class 2022-03-11 12:43:09 -05:00
meganrogge da2c59d1d0 remove unused variable 2022-03-11 12:24:36 -05:00
meganrogge 372b6b633b set incremental search to false 2022-03-11 12:13:43 -05:00
meganrogge fa4479c797 use decoration width, find all and select next 2022-03-11 12:01:19 -05:00
meganrogge 96574425c7 get it to sort of work 2022-03-11 00:39:18 -05:00
meganrogge 60c11e87f6 start work 2022-03-10 23:40:00 -05:00
meganrogge bdfc82d1b7 delete unused import 2022-03-10 10:25:34 -05:00
meganrogge 167a6fdbf6 use position sticky 2022-03-09 12:24:37 -06:00
meganrogge 89a0cee5a4 when alt buffer is active, hide decorations 2022-03-09 11:17:26 -06:00
meganrogge 724bbebc14 assign element in render call 2022-03-09 08:46:09 -06:00
meganrogge e797070121 call on render 2022-03-09 08:42:12 -06:00
meganrogge cfd7912afa enable registering a decoration before attach to dom has happened 2022-03-09 08:40:40 -06:00
meganrogge 1948ec886f only clear relevant part of canvas on marker dispose 2022-03-09 08:27:50 -06:00
meganrogge 249e73cd25 delete unused code 2022-03-08 21:35:31 -06:00
meganrogge 89112fb1ae refactor to return / store IDecorations 2022-03-08 21:31:42 -06:00
meganrogge d46a685925 more cleanup 2022-03-08 20:49:32 -06:00
meganrogge 785a8190a6 clean up 2022-03-08 20:41:49 -06:00
meganrogge 3a499fc35d clear on dispose 2022-03-08 20:31:40 -06:00
meganrogge 4350c7ffcd get it to work 2022-03-08 16:23:51 -06:00
meganrogge a5a9c3b368 add scroll decorations 2022-03-08 14:48:15 -06:00
Daniel Imms 19367a6042 Merge pull request #3674 from coderaiser/feature/lint-using-putout
Lint using 🐊Putout: part 6
2022-03-08 12:43:52 -08:00
coderaiser 2263a1579e chore: lint using 🐊Putout 2022-03-08 20:13:40 +02:00
Megan Rogge 622a5bb9c3 Merge pull request #3671 from meganrogge/master
during buffer clear, don't dispose of markers on first line
2022-03-02 20:55:15 -06:00
meganrogge 82706e77db add to mockbuffer 2022-03-02 20:19:49 -06:00
meganrogge e75b47ca01 use clearAllMarkers 2022-03-02 15:21:17 -06:00
Megan RoggeandDaniel Imms 1af22b24c0 Update src/common/buffer/Buffer.ts
Co-authored-by: Daniel Imms <2193314+Tyriar@users.noreply.github.com>
2022-03-02 15:18:29 -06:00
Megan RoggeandDaniel Imms 85dd6a9e87 Update src/common/buffer/Buffer.ts
Co-authored-by: Daniel Imms <2193314+Tyriar@users.noreply.github.com>
2022-03-02 15:18:24 -06:00
Megan Rogge 7bf12f9123 break into two methods 2022-03-02 20:29:31 +00:00
Megan Rogge 798b805fe0 Merge branch 'xtermjs:master' into master 2022-03-02 12:37:01 -06:00
meganrogge f5b66775db during buffer clear, don't dispose of markers on first line 2022-03-02 12:36:35 -06:00
Megan Rogge ff6fa739c4 update addons 2022-03-02 17:58:27 +00:00
Simon Lamon 4799832a33 Keep the initial wrapped line upon dragging 2022-03-02 16:17:31 +00:00
Daniel Imms c10a451381 Bring back getOption/setOption api test 2022-03-02 06:59:57 -08:00
Simon Lamon 78c2f06107 Fix triple select edge case 2022-03-02 13:19:53 +00:00
儿时 1e8901630c Replace all getOption/setOption usage with options 2022-03-02 11:25:20 +08:00
Megan Rogge f4da8af2e9 Merge pull request #3666 from xtermjs/release
v4.18.0
2022-02-28 11:30:38 -06:00
meganrogge 83cb7c3184 update version number 2022-02-28 11:14:42 -06:00
99 changed files with 3332 additions and 1460 deletions
+3
View File
@@ -189,6 +189,9 @@ Xterm.js is used in several world-class applications to provide great terminal e
- [**KubeSail**](https://kubesail.com): The Self-Hosting Company - uses xterm to allow users to exec into kubernetes pods and build github apps
- [**WiTTY**](https://github.com/syssecfsu/witty): Web-based interactive terminal emulator that allows users to easily record, share, and replay console sessions.
- [**libv86 Terminal Forwarding**](https://github.com/hello-smile6/libv86-terminal-forwarding): Peer-to-peer SSH for the web, using WebRTC via [Bugout](https://github.com/chr15m/bugout) for data transfer and [v86](https://github.com/copy/v86) for web-based virtualization.
- [**hack.courses**](https://hack.courses): Interactive Linux and command-line classes using xterm.js to expose a real terminal available for everyone.
- [**Render**](https://render.com): Platform-as-a-service for your apps, websites, and databases using xterm.js to provide a command prompt for user containers and for streaming build and runtime logs.
- [**CloudTTY**](https://github.com/cloudtty/cloudtty): A Friendly Kubernetes CloudShell (Web Terminal).
- [And much more...](https://github.com/xtermjs/xterm.js/network/dependents?package_id=UGFja2FnZS0xNjYzMjc4OQ%3D%3D)
Do you use xterm.js in your application as well? Please [open a Pull Request](https://github.com/sourcelair/xterm.js/pulls) to include it here. We would love to have it on our list. Note: Please add any new contributions to the end of the list only.
+4 -1
View File
@@ -63,6 +63,9 @@ export class FitAddon implements ITerminalAddon {
return undefined;
}
const scrollbarWidth = this._terminal.options.scrollback === 0 ?
0 : core.viewport.scrollBarWidth;
const parentElementStyle = window.getComputedStyle(this._terminal.element.parentElement);
const parentElementHeight = parseInt(parentElementStyle.getPropertyValue('height'));
const parentElementWidth = Math.max(0, parseInt(parentElementStyle.getPropertyValue('width')));
@@ -76,7 +79,7 @@ export class FitAddon implements ITerminalAddon {
const elementPaddingVer = elementPadding.top + elementPadding.bottom;
const elementPaddingHor = elementPadding.right + elementPadding.left;
const availableHeight = parentElementHeight - elementPaddingVer;
const availableWidth = parentElementWidth - elementPaddingHor - core.viewport.scrollBarWidth;
const availableWidth = parentElementWidth - elementPaddingHor - scrollbarWidth;
const geometry = {
cols: Math.max(MINIMUM_COLS, Math.floor(availableWidth / core._renderService.dimensions.actualCellWidth)),
rows: Math.max(MINIMUM_ROWS, Math.floor(availableHeight / core._renderService.dimensions.actualCellHeight))
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "xterm-addon-ligatures",
"version": "0.5.2",
"version": "0.5.3",
"description": "Add support for programming ligatures to xterm.js",
"author": {
"name": "The xterm.js authors",
+18 -18
View File
@@ -78,7 +78,7 @@ describe('xterm-addon-ligatures', () => {
});
it('handles quoted font names', done => {
term.setOption('fontFamily', '"Fira Code", monospace');
term.options.fontFamily = '"Fira Code", monospace';
assert.deepEqual(term.joiner!(input), []);
onRefresh.callsFake(() => {
assert.deepEqual(term.joiner!(input), [[2, 4], [7, 10]]);
@@ -87,7 +87,7 @@ describe('xterm-addon-ligatures', () => {
});
it('falls back to later fonts if earlier ones are not present', done => {
term.setOption('fontFamily', 'notinstalled, Fira Code, monospace');
term.options.fontFamily = 'notinstalled, Fira Code, monospace';
assert.deepEqual(term.joiner!(input), []);
onRefresh.callsFake(() => {
assert.deepEqual(term.joiner!(input), [[2, 4], [7, 10]]);
@@ -98,17 +98,17 @@ describe('xterm-addon-ligatures', () => {
it('uses the current font value', done => {
// The first three calls are all synchronous so that we don't allow time for
// any fonts to load while we're switching things around
term.setOption('fontFamily', 'Fira Code');
term.options.fontFamily = 'Fira Code';
assert.deepEqual(term.joiner!(input), []);
term.setOption('fontFamily', 'notinstalled');
term.options.fontFamily = 'notinstalled';
assert.deepEqual(term.joiner!(input), []);
term.setOption('fontFamily', 'Iosevka');
term.options.fontFamily = 'Iosevka';
assert.deepEqual(term.joiner!(input), []);
onRefresh.callsFake(() => {
assert.deepEqual(term.joiner!(input), [[2, 4]]);
// And switch it back to Fira Code for good measure
term.setOption('fontFamily', 'Fira Code');
term.options.fontFamily = 'Fira Code';
// At this point, we haven't loaded the new font, so the result reverts
// back to empty until that happens
@@ -124,7 +124,7 @@ describe('xterm-addon-ligatures', () => {
it('allows multiple terminal instances that use different fonts', done => {
const onRefresh2 = sinon.stub();
const term2 = new MockTerminal(onRefresh2);
term2.setOption('fontFamily', 'Iosevka');
term2.options.fontFamily = 'Iosevka';
ligatureSupport.enableLigatures(term2 as any);
assert.deepEqual(term.joiner!(input), []);
@@ -140,7 +140,7 @@ describe('xterm-addon-ligatures', () => {
});
it('fails if it finds but cannot load the font', async () => {
term.setOption('fontFamily', 'Nonexistant Font, monospace');
term.options.fontFamily = 'Nonexistant Font, monospace';
assert.deepEqual(term.joiner!(input), []);
await delay(500);
assert.isTrue(onRefresh.notCalled);
@@ -148,7 +148,7 @@ describe('xterm-addon-ligatures', () => {
});
it('returns nothing if the font is not present on the system', async () => {
term.setOption('fontFamily', 'notinstalled');
term.options.fontFamily = 'notinstalled';
assert.deepEqual(term.joiner!(input), []);
await delay(500);
assert.isTrue(onRefresh.notCalled);
@@ -156,7 +156,7 @@ describe('xterm-addon-ligatures', () => {
});
it('returns nothing if no specific font is specified', async () => {
term.setOption('fontFamily', 'monospace');
term.options.fontFamily = 'monospace';
assert.deepEqual(term.joiner!(input), []);
await delay(500);
assert.isTrue(onRefresh.notCalled);
@@ -164,7 +164,7 @@ describe('xterm-addon-ligatures', () => {
});
it('returns nothing if no fonts are provided', async () => {
term.setOption('fontFamily', '');
term.options.fontFamily = '';
assert.deepEqual(term.joiner!(input), []);
await delay(500);
assert.isTrue(onRefresh.notCalled);
@@ -172,7 +172,7 @@ describe('xterm-addon-ligatures', () => {
});
it('fails when given malformed inputs', async () => {
term.setOption('fontFamily', {} as any);
term.options.fontFamily = {} as any;
assert.deepEqual(term.joiner!(input), []);
await delay(500);
assert.isTrue(onRefresh.notCalled);
@@ -181,7 +181,7 @@ describe('xterm-addon-ligatures', () => {
it('ensures no empty errors are thrown', async () => {
sinon.stub(fontLigatures, 'loadFile').callsFake(async () => { throw undefined; });
term.setOption('fontFamily', 'Iosevka');
term.options.fontFamily = 'Iosevka';
assert.deepEqual(term.joiner!(input), []);
await delay(500);
assert.isTrue(onRefresh.notCalled);
@@ -209,11 +209,11 @@ class MockTerminal {
public deregisterCharacterJoiner(id: number): void {
this.joiner = undefined;
}
public setOption(name: string, value: string | number): void {
this._options[name] = value;
}
public getOption(name: string): string | number {
return this._options[name];
public get options(): { [name: string]: string | number } { return this._options; }
public set options(options: { [name: string]: string | number }) {
for (const key in this._options) {
this._options[key] = options[key];
}
}
}
+4 -4
View File
@@ -34,7 +34,7 @@ export function enableLigatures(term: Terminal): void {
term.registerCharacterJoiner((text: string): [number, number][] => {
// If the font hasn't been loaded yet, load it and return an empty result
const termFont = term.getOption('fontFamily');
const termFont = term.options.fontFamily;
if (
termFont &&
(loadingState === LoadingState.UNLOADED || currentFontName !== termFont)
@@ -48,20 +48,20 @@ export function enableLigatures(term: Terminal): void {
.then(f => {
// Another request may have come in while we were waiting, so make
// sure our font is still vaild.
if (currentCallFontName === term.getOption('fontFamily')) {
if (currentCallFontName === term.options.fontFamily) {
loadingState = LoadingState.LOADED;
font = f;
// Only refresh things if we actually found a font
if (f) {
term.refresh(0, term.getOption('rows') - 1);
term.refresh(0, term.options.rows! - 1);
}
}
})
.catch(e => {
// Another request may have come in while we were waiting, so make
// sure our font is still vaild.
if (currentCallFontName === term.getOption('fontFamily')) {
if (currentCallFontName === term.options.fontFamily) {
loadingState = LoadingState.FAILED;
font = undefined;
loadError = e;
+3 -3
View File
@@ -141,9 +141,9 @@ lru-cache@^6.0.0:
yallist "^4.0.0"
minimist@^1.2.5:
version "1.2.5"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
version "1.2.6"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44"
integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==
mkdirp@0.5.5:
version "0.5.5"
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "xterm-addon-search",
"version": "0.8.2",
"version": "0.9.0",
"author": {
"name": "The xterm.js authors",
"url": "https://xtermjs.org/"
+306 -20
View File
@@ -3,13 +3,25 @@
* @license MIT
*/
import { Terminal, IBufferLine, IDisposable, ITerminalAddon, ISelectionPosition } from 'xterm';
import { Terminal, IDisposable, ITerminalAddon, ISelectionPosition, IDecoration } from 'xterm';
import { EventEmitter } from 'common/EventEmitter';
export interface ISearchOptions {
regex?: boolean;
wholeWord?: boolean;
caseSensitive?: boolean;
incremental?: boolean;
decorations?: ISearchDecorationOptions;
noScroll?: boolean;
}
interface ISearchDecorationOptions {
matchBackground?: string;
matchBorder?: string;
matchOverviewRuler: string;
activeMatchBackground?: string;
activeMatchBorder?: string;
activeMatchColorOverviewRuler: string;
}
export interface ISearchPosition {
@@ -40,7 +52,14 @@ const LINES_CACHE_TIME_TO_LIVE = 15 * 1000; // 15 secs
export class SearchAddon implements ITerminalAddon {
private _terminal: Terminal | undefined;
private _cachedSearchTerm: string | undefined;
private _selectedDecoration: IDecoration | undefined;
private _resultDecorations: Map<number, IDecoration[]> | undefined;
private _searchResults: Map<string, ISearchResult> | undefined;
private _onDataDisposable: IDisposable | undefined;
private _onResizeDisposable: IDisposable | undefined;
private _lastSearchOptions: ISearchOptions | undefined;
private _highlightTimeout: number | undefined;
/**
* translateBufferLineToStringWithWrap is a fairly expensive call.
* We memoize the calls into an array that has a time based ttl.
@@ -51,11 +70,56 @@ export class SearchAddon implements ITerminalAddon {
private _cursorMoveListener: IDisposable | undefined;
private _resizeListener: IDisposable | undefined;
private _resultIndex: number | undefined;
private readonly _onDidChangeResults = new EventEmitter<{ resultIndex: number, resultCount: number } | undefined>();
public readonly onDidChangeResults = this._onDidChangeResults.event;
public activate(terminal: Terminal): void {
this._terminal = terminal;
this._onDataDisposable = this._terminal.onWriteParsed(() => this._updateMatches());
this._onResizeDisposable = this._terminal.onResize(() => this._updateMatches());
}
public dispose(): void { }
private _updateMatches(): void {
if (this._highlightTimeout) {
window.clearTimeout(this._highlightTimeout);
}
if (this._cachedSearchTerm && this._lastSearchOptions?.decorations) {
this._highlightTimeout = setTimeout(() => {
this.findPrevious(this._cachedSearchTerm!, { ...this._lastSearchOptions, incremental: true, noScroll: true });
this._resultIndex = this._searchResults ? this._searchResults.size - 1 : -1;
this._onDidChangeResults.fire({ resultIndex: this._resultIndex, resultCount: this._searchResults?.size ?? -1 });
}, 200);
}
}
public dispose(): void {
this.clearDecorations();
this._onDataDisposable?.dispose();
this._onResizeDisposable?.dispose();
}
public clearDecorations(retainCachedSearchTerm?: boolean): void {
this._selectedDecoration?.dispose();
this._searchResults?.clear();
this._resultDecorations?.forEach(decorations => {
for (const d of decorations) {
d.dispose();
}
});
this._resultDecorations?.clear();
this._searchResults = undefined;
this._resultDecorations = undefined;
if (!retainCachedSearchTerm) {
this._cachedSearchTerm = undefined;
}
}
public clearActiveDecoration(): void {
this._selectedDecoration?.dispose();
this._selectedDecoration = undefined;
}
/**
* Find the next instance of the term, then scroll to and select it. If it
@@ -68,12 +132,107 @@ export class SearchAddon implements ITerminalAddon {
if (!this._terminal) {
throw new Error('Cannot use addon until it has been loaded');
}
this._lastSearchOptions = searchOptions;
if (searchOptions?.decorations) {
if (this._resultIndex !== undefined || this._cachedSearchTerm === undefined || term !== this._cachedSearchTerm) {
this._highlightAllMatches(term, searchOptions);
}
}
return this._fireResults(term, this._findNextAndSelect(term, searchOptions), searchOptions);
}
private _highlightAllMatches(term: string, searchOptions: ISearchOptions): void {
if (!this._terminal) {
throw new Error('Cannot use addon until it has been loaded');
}
if (!term || term.length === 0) {
this._terminal.clearSelection();
this.clearDecorations();
return;
}
searchOptions = searchOptions || {};
// new search, clear out the old decorations
this.clearDecorations(true);
this._searchResults = new Map<string, ISearchResult>();
this._resultDecorations = new Map<number, IDecoration[]>();
const resultDecorations = this._resultDecorations;
let result = this._find(term, 0, 0, searchOptions);
while (result && !this._searchResults.get(`${result.row}-${result.col}`)) {
this._searchResults.set(`${result.row}-${result.col}`, result);
result = this._find(
term,
result.col + result.term.length >= this._terminal.cols ? result.row + 1 : result.row,
result.col + result.term.length >= this._terminal.cols ? 0 : result.col + 1,
searchOptions
);
if (this._searchResults.size > 1000) {
this.clearDecorations();
this._resultIndex = undefined;
return;
}
}
this._searchResults.forEach(result => {
const resultDecoration = this._createResultDecoration(result, searchOptions.decorations!);
if (resultDecoration) {
const decorationsForLine = resultDecorations.get(resultDecoration.marker.line) || [];
decorationsForLine.push(resultDecoration);
resultDecorations.set(resultDecoration.marker.line, decorationsForLine);
}
});
}
private _find(term: string, startRow: number, startCol: number, searchOptions?: ISearchOptions): ISearchResult | undefined {
if (!this._terminal || !term || term.length === 0) {
this._terminal?.clearSelection();
this.clearDecorations();
return undefined;
}
if (startCol > this._terminal.cols) {
throw new Error(`Invalid col: ${startCol} to search in terminal of ${this._terminal.cols} cols`);
}
let result: ISearchResult | undefined = undefined;
this._initLinesCache();
const searchPosition: ISearchPosition = {
startRow,
startCol
};
// Search startRow
result = this._findInLine(term, searchPosition, searchOptions);
// Search from startRow + 1 to end
if (!result) {
for (let y = startRow + 1; y < this._terminal.buffer.active.baseY + this._terminal.rows; y++) {
searchPosition.startRow = y;
searchPosition.startCol = 0;
// If the current line is wrapped line, increase index of column to ignore the previous scan
// Otherwise, reset beginning column index to zero with set new unwrapped line index
result = this._findInLine(term, searchPosition, searchOptions);
if (result) {
break;
}
}
}
return result;
}
private _findNextAndSelect(term: string, searchOptions?: ISearchOptions): boolean {
if (!this._terminal || !term || term.length === 0) {
this._terminal?.clearSelection();
this.clearDecorations();
this._cachedSearchTerm = undefined;
this._resultIndex = -1;
return false;
}
if (this._cachedSearchTerm !== term) {
this._resultIndex = undefined;
this._terminal.clearSelection();
}
let startCol = 0;
let startRow = 0;
let currentSelection: ISelectionPosition | undefined;
@@ -95,7 +254,6 @@ export class SearchAddon implements ITerminalAddon {
// Search startRow
let result = this._findInLine(term, searchPosition, searchOptions);
// Search from startRow + 1 to end
if (!result) {
@@ -129,10 +287,21 @@ export class SearchAddon implements ITerminalAddon {
result = this._findInLine(term, searchPosition, searchOptions);
}
if (this._searchResults) {
if (this._searchResults.size === 0) {
this._resultIndex = -1;
} else if (this._resultIndex === undefined) {
this._resultIndex = 0;
} else {
this._resultIndex++;
if (this._resultIndex >= this._searchResults.size) {
this._resultIndex = 0;
}
}
}
// Set selection and scroll if a result was found
return this._selectResult(result);
return this._selectResult(result, searchOptions?.decorations, searchOptions?.noScroll);
}
/**
* Find the previous instance of the term, then scroll to and select it. If it
* doesn't exist, do nothing.
@@ -144,16 +313,49 @@ export class SearchAddon implements ITerminalAddon {
if (!this._terminal) {
throw new Error('Cannot use addon until it has been loaded');
}
this._lastSearchOptions = searchOptions;
if (searchOptions?.decorations) {
if (this._resultIndex !== undefined || this._cachedSearchTerm === undefined || term !== this._cachedSearchTerm) {
this._highlightAllMatches(term, searchOptions);
}
}
return this._fireResults(term, this._findPreviousAndSelect(term, searchOptions), searchOptions);
}
if (!term || term.length === 0) {
this._terminal.clearSelection();
private _fireResults(term: string, found: boolean, searchOptions?: ISearchOptions): boolean {
if (searchOptions?.decorations) {
if (this._resultIndex !== undefined && this._searchResults?.size !== undefined) {
this._onDidChangeResults.fire({ resultIndex: this._resultIndex, resultCount: this._searchResults.size });
} else {
this._onDidChangeResults.fire(undefined);
}
}
this._cachedSearchTerm = term;
return found;
}
private _findPreviousAndSelect(term: string, searchOptions?: ISearchOptions): boolean {
if (!this._terminal) {
throw new Error('Cannot use addon until it has been loaded');
}
let result: ISearchResult | undefined;
if (!this._terminal || !term || term.length === 0) {
result = undefined;
this._terminal?.clearSelection();
this.clearDecorations();
this._resultIndex = -1;
return false;
}
const isReverseSearch = true;
if (this._cachedSearchTerm !== term) {
this._resultIndex = undefined;
this._terminal.clearSelection();
}
let startRow = this._terminal.buffer.active.baseY + this._terminal.rows;
let startCol = this._terminal.cols;
let result: ISearchResult | undefined;
const isReverseSearch = true;
const incremental = searchOptions ? searchOptions.incremental : false;
let currentSelection: ISelectionPosition | undefined;
if (this._terminal.hasSelection()) {
@@ -207,11 +409,24 @@ export class SearchAddon implements ITerminalAddon {
}
}
if (this._searchResults) {
if (this._searchResults.size === 0) {
this._resultIndex = -1;
} else if (this._resultIndex === undefined || this._resultIndex < 0) {
this._resultIndex = this._searchResults.size - 1;
} else {
this._resultIndex--;
if (this._resultIndex === -1) {
this._resultIndex = this._searchResults.size - 1;
}
}
}
// If there is only one result, return true.
if (!result && currentSelection) return true;
// Set selection and scroll if a result was found
return this._selectResult(result);
return this._selectResult(result, searchOptions?.decorations, searchOptions?.noScroll);
}
/**
@@ -393,7 +608,8 @@ export class SearchAddon implements ITerminalAddon {
break;
}
if (cell.getWidth()) {
offset += cell.getChars().length;
// Treat null characters as whitespace to align with the translateToString API
offset += cell.getCode() === 0 ? 1 : cell.getChars().length;
}
}
lineIndex++;
@@ -446,21 +662,91 @@ export class SearchAddon implements ITerminalAddon {
/**
* Selects and scrolls to a result.
* @param result The result to select.
* @return Whethera result was selected.
* @return Whether a result was selected.
*/
private _selectResult(result: ISearchResult | undefined): boolean {
private _selectResult(result: ISearchResult | undefined, options?: ISearchDecorationOptions, noScroll?: boolean): boolean {
const terminal = this._terminal!;
this.clearActiveDecoration();
if (!result) {
terminal.clearSelection();
return false;
}
terminal.select(result.col, result.row, result.size);
// 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 -= Math.floor(terminal.rows / 2);
terminal.scrollLines(scroll);
if (options) {
const marker = terminal.registerMarker(-terminal.buffer.active.baseY - terminal.buffer.active.cursorY + result.row);
if (marker) {
this._selectedDecoration = terminal.registerDecoration({
marker,
x: result.col,
width: result.size,
backgroundColor: options.activeMatchBackground,
layer: 'top',
overviewRulerOptions: {
color: options.activeMatchColorOverviewRuler
}
});
this._selectedDecoration?.onRender((e) => this._applyStyles(e, options.activeMatchBorder, true));
this._selectedDecoration?.onDispose(() => marker.dispose());
}
}
if (!noScroll) {
// 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 -= Math.floor(terminal.rows / 2);
terminal.scrollLines(scroll);
}
}
return true;
}
/**
* Applies styles to the decoration when it is rendered
* @param element the decoration's element
* @param backgroundColor the background color to apply
* @param borderColor the border color to apply
* @returns
*/
private _applyStyles(element: HTMLElement, borderColor: string | undefined, isActiveResult: boolean): void {
if (element.clientWidth <= 0) {
return;
}
if (!element.classList.contains('xterm-find-result-decoration')) {
element.classList.add('xterm-find-result-decoration');
if (borderColor) {
element.style.outline = `1px solid ${borderColor}`;
}
}
if (isActiveResult) {
element.classList.add('xterm-find-active-result-decoration');
}
}
/**
* Creates a decoration for the result and applies styles
* @param result the search result for which to create the decoration
* @param options the options for the decoration
* @returns the {@link IDecoration} or undefined if the marker has already been disposed of
*/
private _createResultDecoration(result: ISearchResult, options: ISearchDecorationOptions): IDecoration | undefined {
const terminal = this._terminal!;
const marker = terminal.registerMarker(-terminal.buffer.active.baseY - terminal.buffer.active.cursorY + result.row);
if (!marker) {
return undefined;
}
const findResultDecoration = terminal.registerDecoration({
marker,
x: result.col,
width: result.size,
backgroundColor: options.matchBackground,
overviewRulerOptions: this._resultDecorations?.get(marker.line) ? undefined : {
color: options.matchOverviewRuler,
position: 'center'
}
});
findResultDecoration?.onRender((e) => this._applyStyles(e, options.matchBorder, false));
findResultDecoration?.onDispose(() => marker.dispose());
return findResultDecoration;
}
}
+11 -1
View File
@@ -13,10 +13,20 @@
"strict": true,
"types": [
"../../../node_modules/@types/mocha"
]
],
"paths": {
"common/*": [
"../../../src/common/*"
]
}
},
"include": [
"./**/*",
"../../../typings/xterm.d.ts"
],
"references": [
{
"path": "../../../src/common"
}
]
}
@@ -6,7 +6,7 @@
import { assert } from 'chai';
import { readFile } from 'fs';
import { resolve } from 'path';
import { openTerminal, writeSync, launchBrowser } from '../../../out-test/api/TestUtils';
import { openTerminal, writeSync, launchBrowser, timeout } from '../../../out-test/api/TestUtils';
import { Browser, Page } from 'playwright';
const APP = 'http://127.0.0.1:3001/test';
@@ -16,15 +16,13 @@ let page: Page;
const width = 800;
const height = 600;
describe('Search Tests', function(): void {
before(async function(): Promise<any> {
describe('Search Tests', function (): void {
before(async function (): Promise<any> {
browser = await launchBrowser();
page = await (await browser.newContext()).newPage();
await page.setViewportSize({ width, height });
await page.goto(APP);
await openTerminal(page);
await page.evaluate(`window.search = new SearchAddon();`);
await page.evaluate(`window.term.loadAddon(window.search);`);
});
after(() => {
@@ -32,7 +30,12 @@ describe('Search Tests', function(): void {
});
beforeEach(async () => {
await page.evaluate(`window.term.reset()`);
await page.evaluate(`
window.term.reset()
window.search?.dispose();
window.search = new SearchAddon();
window.term.loadAddon(window.search);
`);
});
it('Simple Search', async () => {
@@ -120,6 +123,176 @@ describe('Search Tests', function(): void {
});
});
describe('onDidChangeResults', async () => {
describe('findNext', () => {
it('should not fire unless the decorations option is set', async () => {
await page.evaluate(`
window.calls = [];
window.search.onDidChangeResults(e => window.calls.push(e));
`);
await writeSync(page, 'abc');
assert.strictEqual(await page.evaluate(`window.search.findNext('a')`), true);
assert.strictEqual(await page.evaluate('window.calls.length'), 0);
assert.strictEqual(await page.evaluate(`window.search.findNext('b', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
assert.strictEqual(await page.evaluate('window.calls.length'), 1);
});
it('should fire with correct event values', async () => {
await page.evaluate(`
window.calls = [];
window.search.onDidChangeResults(e => window.calls.push(e));
`);
await writeSync(page, 'abc bc c');
assert.strictEqual(await page.evaluate(`window.search.findNext('a', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
assert.deepStrictEqual(await page.evaluate('window.calls'), [
{ resultCount: 1, resultIndex: 0 }
]);
assert.strictEqual(await page.evaluate(`window.search.findNext('b', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
assert.deepStrictEqual(await page.evaluate('window.calls'), [
{ resultCount: 1, resultIndex: 0 },
{ resultCount: 2, resultIndex: 0 }
]);
assert.strictEqual(await page.evaluate(`window.search.findNext('d', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), false);
assert.deepStrictEqual(await page.evaluate('window.calls'), [
{ resultCount: 1, resultIndex: 0 },
{ resultCount: 2, resultIndex: 0 },
{ resultCount: 0, resultIndex: -1 }
]);
assert.strictEqual(await page.evaluate(`window.search.findNext('c', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
assert.strictEqual(await page.evaluate(`window.search.findNext('c', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
assert.strictEqual(await page.evaluate(`window.search.findNext('c', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
assert.deepStrictEqual(await page.evaluate('window.calls'), [
{ resultCount: 1, resultIndex: 0 },
{ resultCount: 2, resultIndex: 0 },
{ resultCount: 0, resultIndex: -1 },
{ resultCount: 3, resultIndex: 0 },
{ resultCount: 3, resultIndex: 1 },
{ resultCount: 3, resultIndex: 2 }
]);
});
it('should fire with correct event values (incremental)', async () => {
await page.evaluate(`
window.calls = [];
window.search.onDidChangeResults(e => window.calls.push(e));
`);
await writeSync(page, 'abc aabc');
assert.deepStrictEqual(await page.evaluate(`window.search.findNext('a', { incremental: true, decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
assert.deepStrictEqual(await page.evaluate('window.calls'), [
{ resultCount: 3, resultIndex: 0 }
]);
assert.deepStrictEqual(await page.evaluate(`window.search.findNext('ab', { incremental: true, decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
assert.deepStrictEqual(await page.evaluate('window.calls'), [
{ resultCount: 3, resultIndex: 0 },
{ resultCount: 2, resultIndex: 0 }
]);
assert.deepStrictEqual(await page.evaluate(`window.search.findNext('abc', { incremental: true, decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
assert.deepStrictEqual(await page.evaluate('window.calls'), [
{ resultCount: 3, resultIndex: 0 },
{ resultCount: 2, resultIndex: 0 },
{ resultCount: 2, resultIndex: 0 }
]);
assert.deepStrictEqual(await page.evaluate(`window.search.findNext('abc', { incremental: true, decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
assert.deepStrictEqual(await page.evaluate('window.calls'), [
{ resultCount: 3, resultIndex: 0 },
{ resultCount: 2, resultIndex: 0 },
{ resultCount: 2, resultIndex: 0 },
{ resultCount: 2, resultIndex: 1 }
]);
assert.deepStrictEqual(await page.evaluate(`window.search.findNext('abcd', { incremental: true, decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), false);
assert.deepStrictEqual(await page.evaluate('window.calls'), [
{ resultCount: 3, resultIndex: 0 },
{ resultCount: 2, resultIndex: 0 },
{ resultCount: 2, resultIndex: 0 },
{ resultCount: 2, resultIndex: 1 },
{ resultCount: 0, resultIndex: -1 }
]);
});
});
describe('findPrevious', () => {
it('should not fire unless the decorations option is set', async () => {
await page.evaluate(`
window.calls = [];
window.search.onDidChangeResults(e => window.calls.push(e));
`);
await writeSync(page, 'abc');
assert.strictEqual(await page.evaluate(`window.search.findPrevious('a')`), true);
assert.strictEqual(await page.evaluate('window.calls.length'), 0);
assert.strictEqual(await page.evaluate(`window.search.findPrevious('b', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
assert.strictEqual(await page.evaluate('window.calls.length'), 1);
});
it('should fire with correct event values', async () => {
await page.evaluate(`
window.calls = [];
window.search.onDidChangeResults(e => window.calls.push(e));
`);
await writeSync(page, 'abc bc c');
assert.strictEqual(await page.evaluate(`window.search.findPrevious('a', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
assert.deepStrictEqual(await page.evaluate('window.calls'), [
{ resultCount: 1, resultIndex: 0 }
]);
assert.strictEqual(await page.evaluate(`window.search.findPrevious('b', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
assert.deepStrictEqual(await page.evaluate('window.calls'), [
{ resultCount: 1, resultIndex: 0 },
{ resultCount: 2, resultIndex: 1 }
]);
await timeout(2000);
assert.strictEqual(await page.evaluate(`debugger; window.search.findPrevious('d', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), false);
assert.deepStrictEqual(await page.evaluate('window.calls'), [
{ resultCount: 1, resultIndex: 0 },
{ resultCount: 2, resultIndex: 1 },
{ resultCount: 0, resultIndex: -1 }
]);
assert.strictEqual(await page.evaluate(`window.search.findPrevious('c', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
assert.strictEqual(await page.evaluate(`window.search.findPrevious('c', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
assert.strictEqual(await page.evaluate(`window.search.findPrevious('c', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
assert.deepStrictEqual(await page.evaluate('window.calls'), [
{ resultCount: 1, resultIndex: 0 },
{ resultCount: 2, resultIndex: 1 },
{ resultCount: 0, resultIndex: -1 },
{ resultCount: 3, resultIndex: 2 },
{ resultCount: 3, resultIndex: 1 },
{ resultCount: 3, resultIndex: 0 }
]);
});
it('should fire with correct event values (incremental)', async () => {
await page.evaluate(`
window.calls = [];
window.search.onDidChangeResults(e => window.calls.push(e));
`);
await writeSync(page, 'abc aabc');
assert.deepStrictEqual(await page.evaluate(`window.search.findPrevious('a', { incremental: true, decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
assert.deepStrictEqual(await page.evaluate('window.calls'), [
{ resultCount: 3, resultIndex: 2 }
]);
assert.deepStrictEqual(await page.evaluate(`window.search.findPrevious('ab', { incremental: true, decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
assert.deepStrictEqual(await page.evaluate('window.calls'), [
{ resultCount: 3, resultIndex: 2 },
{ resultCount: 2, resultIndex: 1 }
]);
assert.deepStrictEqual(await page.evaluate(`window.search.findPrevious('abc', { incremental: true, decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
assert.deepStrictEqual(await page.evaluate('window.calls'), [
{ resultCount: 3, resultIndex: 2 },
{ resultCount: 2, resultIndex: 1 },
{ resultCount: 2, resultIndex: 1 }
]);
assert.deepStrictEqual(await page.evaluate(`window.search.findPrevious('abc', { incremental: true, decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
assert.deepStrictEqual(await page.evaluate('window.calls'), [
{ resultCount: 3, resultIndex: 2 },
{ resultCount: 2, resultIndex: 1 },
{ resultCount: 2, resultIndex: 1 },
{ resultCount: 2, resultIndex: 0 }
]);
assert.deepStrictEqual(await page.evaluate(`window.search.findPrevious('abcd', { incremental: true, decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), false);
assert.deepStrictEqual(await page.evaluate('window.calls'), [
{ resultCount: 3, resultIndex: 2 },
{ resultCount: 2, resultIndex: 1 },
{ resultCount: 2, resultIndex: 1 },
{ resultCount: 2, resultIndex: 0 },
{ resultCount: 0, resultIndex: -1 }
]);
});
});
});
describe('Regression tests', () => {
describe('#2444 wrapped line content not being found', () => {
let fixture: string;
@@ -134,7 +307,7 @@ describe('Search Tests', function(): void {
.replace(/\n/g, '\\n\\r');
}
fixture = fixture
.replace(/'/g, '\\\'');
.replace(/'/g, `\\'`);
});
it('should find all occurrences using findNext', async () => {
await writeSync(page, fixture);
@@ -206,6 +379,21 @@ describe('Search Tests', function(): void {
});
});
});
describe('#3834 lines with null characters before search terms', () => {
// This case can be triggered by the prompt when using starship under conpty
it('should find all matches on a line containing null characters', async () => {
await page.evaluate(`
window.calls = [];
window.search.onDidChangeResults(e => window.calls.push(e));
`);
// Move cursor forward 1 time to create a null character, as opposed to regular whitespace
await writeSync(page, '\\x1b[CHi Hi');
assert.strictEqual(await page.evaluate(`window.search.findPrevious('h', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
assert.deepStrictEqual(await page.evaluate('window.calls'), [
{ resultCount: 2, resultIndex: 1 }
]);
});
});
});
function makeData(length: number): string {
+63 -1
View File
@@ -3,7 +3,7 @@
* @license MIT
*/
import { Terminal, ILinkMatcherOptions, IDisposable, ITerminalAddon } from 'xterm';
import { Terminal, ITerminalAddon, IEvent } from 'xterm';
declare module 'xterm-addon-search' {
/**
@@ -32,6 +32,47 @@ declare module 'xterm-addon-search' {
* `findNext`, not `findPrevious`.
*/
incremental?: boolean;
/**
* When set, will highlight all instances of the word on search and show
* them in the overview ruler if it's enabled.
*/
decorations?: ISearchDecorationOptions;
}
/**
* Options for showing decorations when searching.
*/
interface ISearchDecorationOptions {
/**
* The background color of a match, this must use #RRGGBB format.
*/
matchBackground?: string;
/**
* The border color of a match.
*/
matchBorder?: string;
/**
* The overview ruler color of a match.
*/
matchOverviewRuler: string;
/**
* The background color for the currently active match, this must use #RRGGBB format.
*/
activeMatchBackground?: string;
/**
* The border color of the currently active match.
*/
activeMatchBorder?: string;
/**
* The overview ruler color of the currently active match.
*/
activeMatchColorOverviewRuler: string;
}
/**
@@ -64,5 +105,26 @@ declare module 'xterm-addon-search' {
* @param searchOptions The options for the search.
*/
public findPrevious(term: string, searchOptions?: ISearchOptions): boolean;
/**
* Clears the decorations and selection
*/
public clearDecorations(): void;
/**
* Clears the active result decoration, this decoration is applied on top of the selection so
* removing it will reveal the selection underneath. This is intended to be called on the search
* textarea's `blur` event.
*/
public clearActiveDecoration(): void;
/**
* When decorations are enabled, fires when
* the search results change.
* @returns -1 for resultIndex for a resultCount of 0
* and @returns undefined when the threshold of 1k results
* is exceeded and decorations are disposed of.
*/
readonly onDidChangeResults: IEvent<{ resultIndex: number, resultCount: number } | undefined>;
}
}
@@ -21,6 +21,13 @@ module.exports = {
}
]
},
resolve: {
modules: ['./node_modules'],
extensions: [ '.js' ],
alias: {
common: path.resolve('../../out/common')
}
},
output: {
filename: mainFile,
path: path.resolve('./lib'),
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "xterm-addon-serialize",
"version": "0.6.1",
"version": "0.7.0",
"author": {
"name": "The xterm.js authors",
"url": "https://xtermjs.org/"
@@ -43,7 +43,7 @@ class TestSelectionService {
}
}
describe('xterm-addon-serialize html', () => {
describe('xterm-addon-serialize', () => {
let cm: ColorManager;
let dom: jsdom.JSDOM;
let document: Document;
@@ -83,123 +83,132 @@ describe('xterm-addon-serialize html', () => {
(terminal as any)._core._selectionService = selectionService;
});
it('empty terminal with selection turned off', () => {
const output = serializeAddon.serializeAsHTML();
assert.notEqual(output, '');
assert.equal((output.match(new RegExp('<div><span> {10}</span><\/div>', 'g')) || []).length, 2);
});
it('empty terminal with no selection', () => {
const output = serializeAddon.serializeAsHTML({
onlySelection: true
describe('text', () => {
it('restoring cursor styles', async () => {
await writeP(terminal, sgr('32') + '> ' + sgr('0'));
assert.equal(serializeAddon.serialize(), '\u001b[32m> \u001b[0m');
});
assert.equal(output, '');
});
it('basic terminal with selection', async () => {
await writeP(terminal, ' terminal ');
terminal.select(1, 0, 8);
const output = serializeAddon.serializeAsHTML({
onlySelection: true
describe('html', () => {
it('empty terminal with selection turned off', () => {
const output = serializeAddon.serializeAsHTML();
assert.notEqual(output, '');
assert.equal((output.match(/<div><span> {10}<\/span><\/div>/g) || []).length, 2);
});
assert.equal((output.match(new RegExp('<div><span>terminal<\/span><\/div>', 'g')) || []).length, 1, output);
});
it('cells with bold styling', async () => {
await writeP(terminal, ' ' + sgr('1') + 'terminal' + sgr('22') + ' ');
const output = serializeAddon.serializeAsHTML();
assert.equal((output.match(new RegExp('<span style=\'font-weight: bold;\'>terminal<\/span>', 'g')) || []).length, 1, output);
});
it('cells with italic styling', async () => {
await writeP(terminal, ' ' + sgr('3') + 'terminal' + sgr('23') + ' ');
const output = serializeAddon.serializeAsHTML();
assert.equal((output.match(new RegExp('<span style=\'font-style: italic;\'>terminal<\/span>', 'g')) || []).length, 1, output);
});
it('cells with inverse styling', async () => {
await writeP(terminal, ' ' + sgr('7') + 'terminal' + sgr('27') + ' ');
const output = serializeAddon.serializeAsHTML();
assert.equal((output.match(new RegExp('<span style=\'color: #000000; background-color: #BFBFBF;\'>terminal<\/span>', 'g')) || []).length, 1, output);
});
it('cells with underline styling', async () => {
await writeP(terminal, ' ' + sgr('4') + 'terminal' + sgr('24') + ' ');
const output = serializeAddon.serializeAsHTML();
assert.equal((output.match(new RegExp('<span style=\'text-decoration: underline;\'>terminal<\/span>', 'g')) || []).length, 1, output);
});
it('cells with invisible styling', async () => {
await writeP(terminal, ' ' + sgr('8') + 'terminal' + sgr('28') + ' ');
const output = serializeAddon.serializeAsHTML();
assert.equal((output.match(new RegExp('<span style=\'visibility: hidden;\'>terminal<\/span>', 'g')) || []).length, 1, output);
});
it('cells with dim styling', async () => {
await writeP(terminal, ' ' + sgr('2') + 'terminal' + sgr('22') + ' ');
const output = serializeAddon.serializeAsHTML();
assert.equal((output.match(new RegExp('<span style=\'opacity: 0.5;\'>terminal<\/span>', 'g')) || []).length, 1, output);
});
it('cells with strikethrough styling', async () => {
await writeP(terminal, ' ' + sgr('9') + 'terminal' + sgr('29') + ' ');
const output = serializeAddon.serializeAsHTML();
assert.equal((output.match(new RegExp('<span style=\'text-decoration: line-through;\'>terminal<\/span>', 'g')) || []).length, 1, output);
});
it('cells with combined styling', async () => {
await writeP(terminal, sgr('1') + ' ' + sgr('9') + 'termi' + sgr('22') + 'nal' + sgr('29') + ' ');
const output = serializeAddon.serializeAsHTML();
assert.equal((output.match(new RegExp('<span style=\'font-weight: bold;\'> <\/span>', 'g')) || []).length, 1, output);
assert.equal((output.match(new RegExp('<span style=\'font-weight: bold; text-decoration: line-through;\'>termi<\/span>', 'g')) || []).length, 1, output);
assert.equal((output.match(new RegExp('<span style=\'text-decoration: line-through;\'>nal<\/span>', 'g')) || []).length, 1, output);
});
it('cells with color styling', async () => {
await writeP(terminal, ' ' + sgr('38;5;46') + 'terminal' + sgr('39') + ' ');
const output = serializeAddon.serializeAsHTML();
assert.equal((output.match(new RegExp('<span style=\'color: #00ff00;\'>terminal<\/span>', 'g')) || []).length, 1, output);
});
it('cells with background styling', async () => {
await writeP(terminal, ' ' + sgr('48;5;46') + 'terminal' + sgr('49') + ' ');
const output = serializeAddon.serializeAsHTML();
assert.equal((output.match(new RegExp('<span style=\'background-color: #00ff00;\'>terminal<\/span>', 'g')) || []).length, 1, output);
});
it('empty terminal with default options', async () => {
const output = serializeAddon.serializeAsHTML();
assert.equal((output.match(new RegExp('color: #000000; background-color: #ffffff; font-family: courier-new, courier, monospace; font-size: 15px;', 'g')) || []).length, 1, output);
});
it('empty terminal with custom options', async () => {
terminal.options.fontFamily = 'verdana';
terminal.options.fontSize = 20;
terminal.options.theme = {
foreground: '#ff00ff',
background: '#00ff00'
};
const output = serializeAddon.serializeAsHTML({
includeGlobalBackground: true
it('empty terminal with no selection', () => {
const output = serializeAddon.serializeAsHTML({
onlySelection: true
});
assert.equal(output, '');
});
assert.equal((output.match(new RegExp('color: #ff00ff; background-color: #00ff00; font-family: verdana; font-size: 20px;', 'g')) || []).length, 1, output);
});
it('empty terminal with background included', async () => {
const output = serializeAddon.serializeAsHTML({
includeGlobalBackground: true
it('basic terminal with selection', async () => {
await writeP(terminal, ' terminal ');
terminal.select(1, 0, 8);
const output = serializeAddon.serializeAsHTML({
onlySelection: true
});
assert.equal((output.match(/<div><span>terminal<\/span><\/div>/g) || []).length, 1, output);
});
it('cells with bold styling', async () => {
await writeP(terminal, ' ' + sgr('1') + 'terminal' + sgr('22') + ' ');
const output = serializeAddon.serializeAsHTML();
assert.equal((output.match(/<span style='font-weight: bold;'>terminal<\/span>/g) || []).length, 1, output);
});
it('cells with italic styling', async () => {
await writeP(terminal, ' ' + sgr('3') + 'terminal' + sgr('23') + ' ');
const output = serializeAddon.serializeAsHTML();
assert.equal((output.match(/<span style='font-style: italic;'>terminal<\/span>/g) || []).length, 1, output);
});
it('cells with inverse styling', async () => {
await writeP(terminal, ' ' + sgr('7') + 'terminal' + sgr('27') + ' ');
const output = serializeAddon.serializeAsHTML();
assert.equal((output.match(/<span style='color: #000000; background-color: #BFBFBF;'>terminal<\/span>/g) || []).length, 1, output);
});
it('cells with underline styling', async () => {
await writeP(terminal, ' ' + sgr('4') + 'terminal' + sgr('24') + ' ');
const output = serializeAddon.serializeAsHTML();
assert.equal((output.match(/<span style='text-decoration: underline;'>terminal<\/span>/g) || []).length, 1, output);
});
it('cells with invisible styling', async () => {
await writeP(terminal, ' ' + sgr('8') + 'terminal' + sgr('28') + ' ');
const output = serializeAddon.serializeAsHTML();
assert.equal((output.match(/<span style='visibility: hidden;'>terminal<\/span>/g) || []).length, 1, output);
});
it('cells with dim styling', async () => {
await writeP(terminal, ' ' + sgr('2') + 'terminal' + sgr('22') + ' ');
const output = serializeAddon.serializeAsHTML();
assert.equal((output.match(/<span style='opacity: 0.5;'>terminal<\/span>/g) || []).length, 1, output);
});
it('cells with strikethrough styling', async () => {
await writeP(terminal, ' ' + sgr('9') + 'terminal' + sgr('29') + ' ');
const output = serializeAddon.serializeAsHTML();
assert.equal((output.match(/<span style='text-decoration: line-through;'>terminal<\/span>/g) || []).length, 1, output);
});
it('cells with combined styling', async () => {
await writeP(terminal, sgr('1') + ' ' + sgr('9') + 'termi' + sgr('22') + 'nal' + sgr('29') + ' ');
const output = serializeAddon.serializeAsHTML();
assert.equal((output.match(/<span style='font-weight: bold;'> <\/span>/g) || []).length, 1, output);
assert.equal((output.match(/<span style='font-weight: bold; text-decoration: line-through;'>termi<\/span>/g) || []).length, 1, output);
assert.equal((output.match(/<span style='text-decoration: line-through;'>nal<\/span>/g) || []).length, 1, output);
});
it('cells with color styling', async () => {
await writeP(terminal, ' ' + sgr('38;5;46') + 'terminal' + sgr('39') + ' ');
const output = serializeAddon.serializeAsHTML();
assert.equal((output.match(/<span style='color: #00ff00;'>terminal<\/span>/g) || []).length, 1, output);
});
it('cells with background styling', async () => {
await writeP(terminal, ' ' + sgr('48;5;46') + 'terminal' + sgr('49') + ' ');
const output = serializeAddon.serializeAsHTML();
assert.equal((output.match(/<span style='background-color: #00ff00;'>terminal<\/span>/g) || []).length, 1, output);
});
it('empty terminal with default options', async () => {
const output = serializeAddon.serializeAsHTML();
assert.equal((output.match(/color: #000000; background-color: #ffffff; font-family: courier-new, courier, monospace; font-size: 15px;/g) || []).length, 1, output);
});
it('empty terminal with custom options', async () => {
terminal.options.fontFamily = 'verdana';
terminal.options.fontSize = 20;
terminal.options.theme = {
foreground: '#ff00ff',
background: '#00ff00'
};
const output = serializeAddon.serializeAsHTML({
includeGlobalBackground: true
});
assert.equal((output.match(/color: #ff00ff; background-color: #00ff00; font-family: verdana; font-size: 20px;/g) || []).length, 1, output);
});
it('empty terminal with background included', async () => {
const output = serializeAddon.serializeAsHTML({
includeGlobalBackground: true
});
assert.equal((output.match(/color: #ffffff; background-color: #000000; font-family: courier-new, courier, monospace; font-size: 15px;/g) || []).length, 1, output);
});
assert.equal((output.match(new RegExp('color: #ffffff; background-color: #000000; font-family: courier-new, courier, monospace; font-size: 15px;', 'g')) || []).length, 1, output);
});
});
@@ -7,6 +7,7 @@
import { Terminal, ITerminalAddon, IBuffer, IBufferCell, IBufferRange } from 'xterm';
import { IColorSet } from 'browser/Types';
import { IAttributeData } from 'common/Types';
function constrain(value: number, low: number, high: number): number {
return Math.max(low, Math.min(value, high));
@@ -62,17 +63,17 @@ abstract class BaseSerializeHandler {
protected _serializeString(): string { return ''; }
}
function equalFg(cell1: IBufferCell, cell2: IBufferCell): boolean {
function equalFg(cell1: IBufferCell | IAttributeData, cell2: IBufferCell): boolean {
return cell1.getFgColorMode() === cell2.getFgColorMode()
&& cell1.getFgColor() === cell2.getFgColor();
}
function equalBg(cell1: IBufferCell, cell2: IBufferCell): boolean {
function equalBg(cell1: IBufferCell | IAttributeData, cell2: IBufferCell): boolean {
return cell1.getBgColorMode() === cell2.getBgColorMode()
&& cell1.getBgColor() === cell2.getBgColor();
}
function equalFlags(cell1: IBufferCell, cell2: IBufferCell): boolean {
function equalFlags(cell1: IBufferCell | IAttributeData, cell2: IBufferCell): boolean {
return cell1.isInverse() === cell2.isInverse()
&& cell1.isBold() === cell2.isBold()
&& cell1.isUnderline() === cell2.isUnderline()
@@ -229,7 +230,7 @@ class StringSerializeHandler extends BaseSerializeHandler {
this._nullCellCount = 0;
}
private _diffStyle(cell: IBufferCell, oldCell: IBufferCell): number[] {
private _diffStyle(cell: IBufferCell | IAttributeData, oldCell: IBufferCell): number[] {
const sgrSeq: number[] = [];
const fgChanged = !equalFg(cell, oldCell);
const bgChanged = !equalBg(cell, oldCell);
@@ -393,6 +394,15 @@ class StringSerializeHandler extends BaseSerializeHandler {
moveRight(realCursorCol - this._lastCursorCol);
}
// Restore the cursor's current style, see https://github.com/xtermjs/xterm.js/issues/3677
// HACK: Internal API access since it's awkward to expose this in the API and serialize will
// likely be the only consumer
const curAttrData: IAttributeData = (this._terminal as any)._core._inputHandler._curAttrData;
const sgrSeq = this._diffStyle(curAttrData, this._cursorStyle);
if (sgrSeq.length > 0) {
content += `\u001b[${sgrSeq.join(';')}m`;
}
return content;
}
}
@@ -544,7 +554,7 @@ export class HTMLSerializeHandler extends BaseSerializeHandler {
return target;
}
targetLength = targetLength - target.length;
targetLength -= target.length;
if (targetLength > padString.length) {
padString += padString.repeat(targetLength / padString.length);
}
@@ -14,7 +14,7 @@ let page: Page;
const width = 800;
const height = 600;
const writeRawSync = (page: any, str: string): Promise<void> => writeSync(page, '\' +' + JSON.stringify(str) + '+ \'');
const writeRawSync = (page: any, str: string): Promise<void> => writeSync(page, `' +` + JSON.stringify(str) + `+ '`);
const testNormalScreenEqual = async (page: any, str: string): Promise<void> => {
await writeRawSync(page, str);
@@ -83,7 +83,7 @@ describe('SerializeAddon', () => {
const buffer3 = await page.evaluate(`inspectBuffer(term.buffer.normal);`);
await page.evaluate(`term.reset();`);
await writeRawSync(page, '1234567890n12345');
await writeRawSync(page, '123456789012345');
const buffer4 = await page.evaluate(`inspectBuffer(term.buffer.normal);`);
assert.throw(() => {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "xterm-addon-web-links",
"version": "0.5.1",
"version": "0.6.0",
"author": {
"name": "The xterm.js authors",
"url": "https://xtermjs.org/"
@@ -5,9 +5,10 @@
import { ILinkProvider, ILink, Terminal, IViewportRange } from 'xterm';
interface ILinkProviderOptions {
export interface ILinkProviderOptions {
hover?(event: MouseEvent, text: string, location: IViewportRange): void;
leave?(event: MouseEvent, text: string): void;
urlRegex?: RegExp;
}
export class WebLinkProvider implements ILinkProvider {
@@ -78,10 +79,17 @@ export class LinkComputer {
endY++;
}
let startX = stringIndex + 1;
let startY = startLineIndex + 1;
while (startX > terminal.cols) {
startX -= terminal.cols;
startY++;
}
const range = {
start: {
x: stringIndex + 1,
y: startLineIndex + 1
x: startX,
y: startY
},
end: {
x: endX,
@@ -3,8 +3,8 @@
* @license MIT
*/
import { Terminal, ILinkMatcherOptions, ITerminalAddon, IDisposable, IViewportRange } from 'xterm';
import { WebLinkProvider } from './WebLinkProvider';
import { Terminal, ILinkMatcherOptions, ITerminalAddon, IDisposable } from 'xterm';
import { ILinkProviderOptions, WebLinkProvider } from './WebLinkProvider';
const protocolClause = '(https?:\\/\\/)';
const domainCharacterSet = '[\\da-z\\.-]+';
@@ -40,12 +40,6 @@ function handleLink(event: MouseEvent, uri: string): void {
}
}
interface ILinkProviderOptions {
hover?(event: MouseEvent, text: string, location: IViewportRange): void;
leave?(event: MouseEvent, text: string): void;
urlRegex?: RegExp;
}
export class WebLinksAddon implements ITerminalAddon {
private _linkMatcherId: number | undefined;
private _terminal: Terminal | undefined;
@@ -12,8 +12,7 @@
"strict": true,
"types": [
"../../../node_modules/@types/mocha",
"../../../node_modules/@types/node",
"../../../out-test/api/TestUtils"
"../../../node_modules/@types/node"
]
},
"include": [
@@ -49,5 +49,10 @@ declare module 'xterm-addon-web-links' {
* happen even when tooltipCallback hasn't fired for the link yet.
*/
leave?(event: MouseEvent, text: string): void;
/**
* A callback to use instead of the default one.
*/
urlRegex?: RegExp;
}
}
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "xterm-addon-webgl",
"version": "0.11.4",
"version": "0.12.0",
"author": {
"name": "The xterm.js authors",
"url": "https://xtermjs.org/"
+7 -95
View File
@@ -6,14 +6,11 @@
import { createProgram, PROJECTION_MATRIX, throwIfFalsy } from './WebglUtils';
import { WebglCharAtlas } from './atlas/WebglCharAtlas';
import { IWebGL2RenderingContext, IWebGLVertexArrayObject, IRenderModel, IRasterizedGlyph } from './Types';
import { COMBINED_CHAR_BIT_MASK, RENDER_MODEL_INDICIES_PER_CELL, RENDER_MODEL_FG_OFFSET, RENDER_MODEL_BG_OFFSET } from './RenderModel';
import { fill } from 'common/TypedArrayUtils';
import { slice } from './TypedArray';
import { NULL_CELL_CODE, WHITESPACE_CELL_CODE, Attributes, FgFlags } from 'common/buffer/Constants';
import { NULL_CELL_CODE } from 'common/buffer/Constants';
import { Terminal, IBufferLine } from 'xterm';
import { IColorSet, IColor } from 'browser/Types';
import { IColorSet } from 'browser/Types';
import { IRenderDimensions } from 'browser/renderer/Types';
import { AttributeData } from 'common/buffer/AttributeData';
interface IVertices {
attributes: Float32Array;
@@ -24,7 +21,6 @@ interface IVertices {
* working on the next frame.
*/
attributesBuffers: Float32Array[];
selectionAttributes: Float32Array;
count: number;
}
@@ -91,8 +87,7 @@ export class GlyphRenderer {
attributesBuffers: [
new Float32Array(0),
new Float32Array(0)
],
selectionAttributes: new Float32Array(0)
]
};
constructor(
@@ -187,6 +182,8 @@ export class GlyphRenderer {
if (!this._atlas) {
return;
}
// Get the glyph
if (chars && chars.length > 1) {
rasterizedGlyph = this._atlas.getRasterizedGlyphCombinedChar(chars, bg, fg);
} else {
@@ -214,91 +211,6 @@ export class GlyphRenderer {
// a_cellpos only changes on resize
}
public updateSelection(model: IRenderModel): void {
const terminal = this._terminal;
this._vertices.selectionAttributes = slice(this._vertices.attributes, 0);
const bg = (this._colors.selectionOpaque.rgba >>> 8) | Attributes.CM_RGB;
if (model.selection.columnSelectMode) {
const startCol = model.selection.startCol;
const width = model.selection.endCol - startCol;
const height = model.selection.viewportCappedEndRow - model.selection.viewportCappedStartRow + 1;
for (let y = model.selection.viewportCappedStartRow; y < model.selection.viewportCappedStartRow + height; y++) {
this._updateSelectionRange(startCol, startCol + width, y, model, bg);
}
} else {
// Draw first row
const startCol = model.selection.viewportStartRow === model.selection.viewportCappedStartRow ? model.selection.startCol : 0;
const startRowEndCol = model.selection.viewportCappedStartRow === model.selection.viewportCappedEndRow ? model.selection.endCol : terminal.cols;
this._updateSelectionRange(startCol, startRowEndCol, model.selection.viewportCappedStartRow, model, bg);
// Draw middle rows
const middleRowsCount = Math.max(model.selection.viewportCappedEndRow - model.selection.viewportCappedStartRow - 1, 0);
for (let y = model.selection.viewportCappedStartRow + 1; y <= model.selection.viewportCappedStartRow + middleRowsCount; y++) {
this._updateSelectionRange(0, startRowEndCol, y, model, bg);
}
// Draw final row
if (model.selection.viewportCappedStartRow !== model.selection.viewportCappedEndRow) {
// Only draw viewportEndRow if it's not the same as viewportStartRow
const endCol = model.selection.viewportEndRow === model.selection.viewportCappedEndRow ? model.selection.endCol : terminal.cols;
this._updateSelectionRange(0, endCol, model.selection.viewportCappedEndRow, model, bg);
}
}
}
private _updateSelectionRange(startCol: number, endCol: number, y: number, model: IRenderModel, bg: number): void {
const terminal = this._terminal;
const row = y + terminal.buffer.active.viewportY;
let line: IBufferLine | undefined;
for (let x = startCol; x < endCol; x++) {
const offset = (y * this._terminal.cols + x) * RENDER_MODEL_INDICIES_PER_CELL;
const code = model.cells[offset];
let fg = model.cells[offset + RENDER_MODEL_FG_OFFSET];
if (fg & FgFlags.INVERSE) {
const workCell = new AttributeData();
workCell.fg = fg;
workCell.bg = model.cells[offset + RENDER_MODEL_BG_OFFSET];
// 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 &= ~(Attributes.CM_MASK | Attributes.RGB_MASK | FgFlags.INVERSE);
switch (workCell.getBgColorMode()) {
case Attributes.CM_P16:
case Attributes.CM_P256:
const c = this._getColorFromAnsiIndex(workCell.getBgColor()).rgba;
fg |= (c >> 8) & Attributes.RED_MASK | (c >> 8) & Attributes.GREEN_MASK | (c >> 8) & Attributes.BLUE_MASK;
case Attributes.CM_RGB:
const arr = AttributeData.toColorRGB(workCell.getBgColor());
fg |= arr[0] << Attributes.RED_SHIFT | arr[1] << Attributes.GREEN_SHIFT | arr[2] << Attributes.BLUE_SHIFT;
case Attributes.CM_DEFAULT:
default:
const c2 = this._colors.background.rgba;
fg |= (c2 >> 8) & Attributes.RED_MASK | (c2 >> 8) & Attributes.GREEN_MASK | (c2 >> 8) & Attributes.BLUE_MASK;
}
fg |= Attributes.CM_RGB;
}
if (code & COMBINED_CHAR_BIT_MASK) {
if (!line) {
line = terminal.buffer.active.getLine(row);
}
const chars = line!.getCell(x)!.getChars();
this._updateCell(this._vertices.selectionAttributes, x, y, model.cells[offset], bg, fg, chars);
} else {
this._updateCell(this._vertices.selectionAttributes, x, y, model.cells[offset], bg, fg);
}
}
}
private _getColorFromAnsiIndex(idx: number): IColor {
if (idx >= this._colors.ansi.length) {
throw new Error('No color found for idx ' + idx);
}
return this._colors.ansi[idx];
}
public clear(force?: boolean): void {
const terminal = this._terminal;
const newCount = terminal.cols * terminal.rows * INDICES_PER_CELL;
@@ -333,7 +245,7 @@ export class GlyphRenderer {
public setColors(): void {
}
public render(renderModel: IRenderModel, isSelectionVisible: boolean): void {
public render(renderModel: IRenderModel): void {
if (!this._atlas) {
return;
}
@@ -357,7 +269,7 @@ export class GlyphRenderer {
let bufferLength = 0;
for (let y = 0; y < renderModel.lineLengths.length; y++) {
const si = y * this._terminal.cols * INDICES_PER_CELL;
const sub = (isSelectionVisible ? this._vertices.selectionAttributes : this._vertices.attributes).subarray(si, si + renderModel.lineLengths[y] * INDICES_PER_CELL);
const sub = this._vertices.attributes.subarray(si, si + renderModel.lineLengths[y] * INDICES_PER_CELL);
activeBuffer.set(sub, bufferLength);
bufferLength += sub.length;
}
@@ -4,11 +4,11 @@
*/
import { createProgram, expandFloat32Array, PROJECTION_MATRIX, throwIfFalsy } from './WebglUtils';
import { IRenderModel, IWebGLVertexArrayObject, IWebGL2RenderingContext, ISelectionRenderModel } from './Types';
import { fill } from 'common/TypedArrayUtils';
import { IRenderModel, IWebGLVertexArrayObject, IWebGL2RenderingContext } from './Types';
import { Attributes, FgFlags } from 'common/buffer/Constants';
import { Terminal } from 'xterm';
import { IColorSet, IColor } from 'browser/Types';
import { IColor } from 'common/Types';
import { IColorSet } from 'browser/Types';
import { IRenderDimensions } from 'browser/renderer/Types';
import { RENDER_MODEL_BG_OFFSET, RENDER_MODEL_FG_OFFSET, RENDER_MODEL_INDICIES_PER_CELL } from './RenderModel';
@@ -49,7 +49,6 @@ void main() {
interface IVertices {
attributes: Float32Array;
selection: Float32Array;
count: number;
}
@@ -66,12 +65,10 @@ export class RectangleRenderer {
private _attributesBuffer: WebGLBuffer;
private _projectionLocation: WebGLUniformLocation;
private _bgFloat!: Float32Array;
private _selectionFloat!: Float32Array;
private _vertices: IVertices = {
count: 0,
attributes: new Float32Array(INITIAL_BUFFER_RECTANGLE_CAPACITY),
selection: new Float32Array(3 * INDICES_PER_RECTANGLE)
attributes: new Float32Array(INITIAL_BUFFER_RECTANGLE_CAPACITY)
};
constructor(
@@ -137,11 +134,6 @@ export class RectangleRenderer {
gl.bindBuffer(gl.ARRAY_BUFFER, this._attributesBuffer);
gl.bufferData(gl.ARRAY_BUFFER, this._vertices.attributes, gl.DYNAMIC_DRAW);
gl.drawElementsInstanced(this._gl.TRIANGLES, 6, gl.UNSIGNED_BYTE, 0, this._vertices.count);
// Bind selection buffer and draw
gl.bindBuffer(gl.ARRAY_BUFFER, this._attributesBuffer);
gl.bufferData(gl.ARRAY_BUFFER, this._vertices.selection, gl.DYNAMIC_DRAW);
gl.drawElementsInstanced(this._gl.TRIANGLES, 6, gl.UNSIGNED_BYTE, 0, 3);
}
public onResize(): void {
@@ -155,7 +147,6 @@ export class RectangleRenderer {
private _updateCachedColors(): void {
this._bgFloat = this._colorToFloat32Array(this._colors.background);
this._selectionFloat = this._colorToFloat32Array(this._colors.selectionOpaque);
}
private _updateViewportRectangle(): void {
@@ -171,73 +162,6 @@ export class RectangleRenderer {
);
}
public updateSelection(model: ISelectionRenderModel): void {
const terminal = this._terminal;
if (!model.hasSelection) {
fill(this._vertices.selection, 0, 0);
return;
}
if (model.columnSelectMode) {
const startCol = model.startCol;
const width = model.endCol - startCol;
const height = model.viewportCappedEndRow - model.viewportCappedStartRow + 1;
this._addRectangleFloat(
this._vertices.selection,
0,
startCol * this._dimensions.scaledCellWidth,
model.viewportCappedStartRow * this._dimensions.scaledCellHeight,
width * this._dimensions.scaledCellWidth,
height * this._dimensions.scaledCellHeight,
this._selectionFloat
);
fill(this._vertices.selection, 0, INDICES_PER_RECTANGLE);
} else {
// Draw first row
const startCol = model.viewportStartRow === model.viewportCappedStartRow ? model.startCol : 0;
const startRowEndCol = model.viewportCappedStartRow === model.viewportEndRow ? model.endCol : terminal.cols;
this._addRectangleFloat(
this._vertices.selection,
0,
startCol * this._dimensions.scaledCellWidth,
model.viewportCappedStartRow * this._dimensions.scaledCellHeight,
(startRowEndCol - startCol) * this._dimensions.scaledCellWidth,
this._dimensions.scaledCellHeight,
this._selectionFloat
);
// Draw middle rows
const middleRowsCount = Math.max(model.viewportCappedEndRow - model.viewportCappedStartRow - 1, 0);
this._addRectangleFloat(
this._vertices.selection,
INDICES_PER_RECTANGLE,
0,
(model.viewportCappedStartRow + 1) * this._dimensions.scaledCellHeight,
terminal.cols * this._dimensions.scaledCellWidth,
middleRowsCount * this._dimensions.scaledCellHeight,
this._selectionFloat
);
// Draw final row
if (model.viewportCappedStartRow !== model.viewportCappedEndRow) {
// Only draw viewportEndRow if it's not the same as viewportStartRow
const endCol = model.viewportEndRow === model.viewportCappedEndRow ? model.endCol : terminal.cols;
this._addRectangleFloat(
this._vertices.selection,
INDICES_PER_RECTANGLE * 2,
0,
model.viewportCappedEndRow * this._dimensions.scaledCellHeight,
endCol * this._dimensions.scaledCellWidth,
this._dimensions.scaledCellHeight,
this._selectionFloat
);
} else {
fill(this._vertices.selection, 0, INDICES_PER_RECTANGLE * 2);
}
}
}
public updateBackgrounds(model: IRenderModel): void {
const terminal = this._terminal;
const vertices = this._vertices;
+3 -1
View File
@@ -9,6 +9,7 @@ import { ICharacterJoinerService, IRenderService } from 'browser/services/Servic
import { IColorSet } from 'browser/Types';
import { EventEmitter } from 'common/EventEmitter';
import { isSafari } from 'common/Platform';
import { IDecorationService } from 'common/services/Services';
export class WebglAddon implements ITerminalAddon {
private _terminal?: Terminal;
@@ -30,8 +31,9 @@ export class WebglAddon implements ITerminalAddon {
this._terminal = terminal;
const renderService: IRenderService = (terminal as any)._core._renderService;
const characterJoinerService: ICharacterJoinerService = (terminal as any)._core._characterJoinerService;
const decorationService: IDecorationService = (terminal as any)._core._decorationService;
const colors: IColorSet = (terminal as any)._core._colorManager.colors;
this._renderer = new WebglRenderer(terminal, colors, characterJoinerService, this._preserveDrawingBuffer);
this._renderer = new WebglRenderer(terminal, colors, characterJoinerService, decorationService, this._preserveDrawingBuffer);
this._renderer.onContextLoss(() => this._onContextLoss.fire());
renderService.setRenderer(this._renderer);
}
+121 -29
View File
@@ -12,7 +12,7 @@ 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 { Content, NULL_CELL_CHAR, NULL_CELL_CODE } from 'common/buffer/Constants';
import { Attributes, Content, FgFlags, 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';
@@ -23,6 +23,7 @@ import { addDisposableDomListener } from 'browser/Lifecycle';
import { ICharacterJoinerService } from 'browser/services/Services';
import { CharData, ICellData } from 'common/Types';
import { AttributeData } from 'common/buffer/AttributeData';
import { IDecorationService } from 'common/services/Services';
export class WebglRenderer extends Disposable implements IRenderer {
private _renderLayers: IRenderLayer[];
@@ -31,6 +32,7 @@ export class WebglRenderer extends Disposable implements IRenderer {
private _model: RenderModel = new RenderModel();
private _workCell: CellData = new CellData();
private _workColors: { fg: number, bg: number } = { fg: 0, bg: 0 };
private _canvas: HTMLCanvasElement;
private _gl: IWebGL2RenderingContext;
@@ -52,6 +54,7 @@ export class WebglRenderer extends Disposable implements IRenderer {
private _terminal: Terminal,
private _colors: IColorSet,
private readonly _characterJoinerService: ICharacterJoinerService,
private readonly _decorationService: IDecorationService,
preserveDrawingBuffer?: boolean
) {
super();
@@ -164,11 +167,6 @@ export class WebglRenderer extends Disposable implements IRenderer {
this._core.screenElement!.style.height = `${this.dimensions.canvasHeight}px`;
this._rectangleRenderer.onResize();
if (this._model.selection.hasSelection) {
// Update selection as dimensions have changed
this._rectangleRenderer.updateSelection(this._model.selection);
}
this._glyphRenderer.setDimensions(this.dimensions);
this._glyphRenderer.onResize();
@@ -198,10 +196,8 @@ export class WebglRenderer extends Disposable implements IRenderer {
for (const l of this._renderLayers) {
l.onSelectionChanged(this._terminal, start, end, columnSelectMode);
}
this._updateSelectionModel(start, end, columnSelectMode);
this._onRequestRedraw.fire({ start: 0, end: this._terminal.rows - 1 });
this._requestRedrawViewport();
}
public onCursorMove(): void {
@@ -243,7 +239,7 @@ export class WebglRenderer extends Disposable implements IRenderer {
this._charAtlas?.clearTexture();
this._model.clear();
this._updateModel(0, this._terminal.rows - 1);
this._onRequestRedraw.fire({ start: 0, end: this._terminal.rows - 1 });
this._requestRedrawViewport();
}
public clear(): void {
@@ -289,7 +285,7 @@ export class WebglRenderer extends Disposable implements IRenderer {
// Render
this._rectangleRenderer.render();
this._glyphRenderer.render(this._model, this._model.selection.hasSelection);
this._glyphRenderer.render(this._model);
}
private _updateModel(start: number, end: number): void {
@@ -331,14 +327,17 @@ export class WebglRenderer extends Disposable implements IRenderer {
let code = cell.getCode();
const i = ((y * terminal.cols) + x) * RENDER_MODEL_INDICIES_PER_CELL;
// Load colors/resolve overrides into work colors
this._loadColorsForCell(x, row);
if (code !== NULL_CELL_CODE) {
this._model.lineLengths[y] = x + 1;
}
// Nothing has changed, no updates needed
if (this._model.cells[i] === code &&
this._model.cells[i + RENDER_MODEL_BG_OFFSET] === cell.bg &&
this._model.cells[i + RENDER_MODEL_FG_OFFSET] === cell.fg) {
this._model.cells[i + RENDER_MODEL_BG_OFFSET] === this._workColors.bg &&
this._model.cells[i + RENDER_MODEL_FG_OFFSET] === this._workColors.fg) {
continue;
}
@@ -349,10 +348,10 @@ export class WebglRenderer extends Disposable implements IRenderer {
// Cache the results in the model
this._model.cells[i] = code;
this._model.cells[i + RENDER_MODEL_BG_OFFSET] = cell.bg;
this._model.cells[i + RENDER_MODEL_FG_OFFSET] = cell.fg;
this._model.cells[i + RENDER_MODEL_BG_OFFSET] = this._workColors.bg;
this._model.cells[i + RENDER_MODEL_FG_OFFSET] = this._workColors.fg;
this._glyphRenderer.updateCell(x, y, code, cell.bg, cell.fg, chars);
this._glyphRenderer.updateCell(x, y, code, this._workColors.bg, this._workColors.fg, chars);
if (isJoined) {
// Restore work cell
@@ -363,17 +362,110 @@ export class WebglRenderer extends Disposable implements IRenderer {
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._model.cells[j + RENDER_MODEL_BG_OFFSET] = this._workColors.bg;
this._model.cells[j + RENDER_MODEL_FG_OFFSET] = this._workColors.fg;
}
}
}
}
this._rectangleRenderer.updateBackgrounds(this._model);
if (this._model.selection.hasSelection) {
// Model could be updated but the selection is unchanged
this._glyphRenderer.updateSelection(this._model);
}
/**
* Loads colors for the cell into the work colors object. This resolves overrides/inverse if
* necessary which is why the work cell object is not used.
*/
private _loadColorsForCell(x: number, y: number): void {
this._workColors.bg = this._workCell.bg;
this._workColors.fg = this._workCell.fg;
// Get any foreground/background overrides, this happens on the model to avoid spreading
// override logic throughout the different sub-renderers
let bgOverride: number | undefined;
let fgOverride: number | undefined;
// Apply decorations on the bottom layer
for (const d of this._decorationService.getDecorationsAtCell(x, y, 'bottom')) {
if (d.backgroundColorRGB) {
bgOverride = d.backgroundColorRGB.rgba >> 8 & 0xFFFFFF;
}
if (d.foregroundColorRGB) {
fgOverride = d.foregroundColorRGB.rgba >> 8 & 0xFFFFFF;
}
}
// Apply the selection color if needed
if (this._isCellSelected(x, y)) {
bgOverride = this._colors.selectionOpaque.rgba >> 8 & 0xFFFFFF;
if (this._colors.selectionForeground) {
fgOverride = this._colors.selectionForeground.rgba >> 8 & 0xFFFFFF;
}
}
// Apply decorations on the top layer
for (const d of this._decorationService.getDecorationsAtCell(x, y, 'top')) {
if (d.backgroundColorRGB) {
bgOverride = d.backgroundColorRGB.rgba >> 8 & 0xFFFFFF;
}
if (d.foregroundColorRGB) {
fgOverride = d.foregroundColorRGB.rgba >> 8 & 0xFFFFFF;
}
}
// Convert any overrides from rgba to the fg/bg packed format. This resolves the inverse flag
// ahead of time in order to use the correct cache key
if (bgOverride !== undefined) {
// Non-RGB attributes from model + override + force RGB color mode
bgOverride = (this._workCell.bg & ~Attributes.RGB_MASK) | bgOverride | Attributes.CM_RGB;
}
if (fgOverride !== undefined) {
// Non-RGB attributes from model + force disable inverse + override + force RGB color mode
fgOverride = (this._workCell.fg & ~Attributes.RGB_MASK & ~FgFlags.INVERSE) | fgOverride | Attributes.CM_RGB;
}
// Handle case where inverse was specified by only one of bgOverride or fgOverride was set,
// resolving the other inverse color and setting the inverse flag if needed.
if (this._workColors.fg & FgFlags.INVERSE) {
if (bgOverride !== undefined && fgOverride === undefined) {
// Resolve bg color type (default color has a different meaning in fg vs bg)
if ((this._workColors.bg & Attributes.CM_MASK) === Attributes.CM_DEFAULT) {
fgOverride = (this._workColors.fg & ~(Attributes.RGB_MASK | FgFlags.INVERSE | Attributes.CM_MASK)) | ((this._colors.background.rgba >> 8 & 0xFFFFFF) & Attributes.RGB_MASK) | Attributes.CM_RGB;
} else {
fgOverride = (this._workColors.fg & ~(Attributes.RGB_MASK | FgFlags.INVERSE | Attributes.CM_MASK)) | this._workColors.bg & (Attributes.RGB_MASK | Attributes.CM_MASK);
}
}
if (bgOverride === undefined && fgOverride !== undefined) {
// Resolve bg color type (default color has a different meaning in fg vs bg)
if ((this._workColors.fg & Attributes.CM_MASK) === Attributes.CM_DEFAULT) {
bgOverride = (this._workColors.bg & ~(Attributes.RGB_MASK | Attributes.CM_MASK)) | ((this._colors.foreground.rgba >> 8 & 0xFFFFFF) & Attributes.RGB_MASK) | Attributes.CM_RGB;
} else {
bgOverride = (this._workColors.bg & ~(Attributes.RGB_MASK | Attributes.CM_MASK)) | this._workColors.fg & (Attributes.RGB_MASK | Attributes.CM_MASK);
}
}
}
// Use the override if it exists
this._workColors.bg = bgOverride ?? this._workColors.bg;
this._workColors.fg = fgOverride ?? this._workColors.fg;
}
private _isCellSelected(x: number, y: number): boolean {
if (!this._model.selection.hasSelection) {
return false;
}
y -= this._terminal.buffer.active.viewportY;
if (this._model.selection.columnSelectMode) {
if (this._model.selection.startCol <= this._model.selection.endCol) {
return x >= this._model.selection.startCol && y >= this._model.selection.viewportCappedStartRow &&
x < this._model.selection.endCol && y <= this._model.selection.viewportCappedEndRow;
}
return x < this._model.selection.startCol && y >= this._model.selection.viewportCappedStartRow &&
x >= this._model.selection.endCol && y <= this._model.selection.viewportCappedEndRow;
}
return (y > this._model.selection.viewportStartRow && y < this._model.selection.viewportEndRow) ||
(this._model.selection.viewportStartRow === this._model.selection.viewportEndRow && y === this._model.selection.viewportStartRow && x >= this._model.selection.startCol && x < this._model.selection.endCol) ||
(this._model.selection.viewportStartRow < this._model.selection.viewportEndRow && y === this._model.selection.viewportEndRow && x < this._model.selection.endCol) ||
(this._model.selection.viewportStartRow < this._model.selection.viewportEndRow && y === this._model.selection.viewportStartRow && x >= this._model.selection.startCol);
}
private _updateSelectionModel(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean = false): void {
@@ -382,7 +474,6 @@ export class WebglRenderer extends Disposable implements IRenderer {
// Selection does not exist
if (!start || !end || (start[0] === end[0] && start[1] === end[1])) {
this._model.clearSelection();
this._rectangleRenderer.updateSelection(this._model.selection);
return;
}
@@ -395,7 +486,6 @@ export class WebglRenderer extends Disposable implements IRenderer {
// No need to draw the selection
if (viewportCappedStartRow >= terminal.rows || viewportCappedEndRow < 0) {
this._model.clearSelection();
this._rectangleRenderer.updateSelection(this._model.selection);
return;
}
@@ -407,8 +497,6 @@ export class WebglRenderer extends Disposable implements IRenderer {
this._model.selection.viewportCappedEndRow = viewportCappedEndRow;
this._model.selection.startCol = start[0];
this._model.selection.endCol = end[0];
this._rectangleRenderer.updateSelection(this._model.selection);
}
/**
@@ -440,18 +528,18 @@ export class WebglRenderer extends Disposable implements IRenderer {
// will be floored because since lineHeight can never be lower then 1, there
// is a guarentee that the scaled line height will always be larger than
// scaled char height.
this.dimensions.scaledCellHeight = Math.floor(this.dimensions.scaledCharHeight * this._terminal.getOption('lineHeight'));
this.dimensions.scaledCellHeight = Math.floor(this.dimensions.scaledCharHeight * this._terminal.options.lineHeight!);
// Calculate the y coordinate within a cell that text should draw from in
// order to draw in the center of a cell.
this.dimensions.scaledCharTop = this._terminal.getOption('lineHeight') === 1 ? 0 : Math.round((this.dimensions.scaledCellHeight - this.dimensions.scaledCharHeight) / 2);
this.dimensions.scaledCharTop = this._terminal.options.lineHeight === 1 ? 0 : Math.round((this.dimensions.scaledCellHeight - this.dimensions.scaledCharHeight) / 2);
// Calculate the scaled cell width, taking the letterSpacing into account.
this.dimensions.scaledCellWidth = this.dimensions.scaledCharWidth + Math.round(this._terminal.getOption('letterSpacing'));
this.dimensions.scaledCellWidth = this.dimensions.scaledCharWidth + Math.round(this._terminal.options.letterSpacing!);
// Calculate the x coordinate with a cell that text should draw from in
// order to draw in the center of a cell.
this.dimensions.scaledCharLeft = Math.floor(this._terminal.getOption('letterSpacing') / 2);
this.dimensions.scaledCharLeft = Math.floor(this._terminal.options.letterSpacing! / 2);
// Recalculate the canvas dimensions; scaled* define the actual number of
// pixel in the canvas
@@ -482,6 +570,10 @@ export class WebglRenderer extends Disposable implements IRenderer {
this.dimensions.actualCellHeight = this.dimensions.scaledCellHeight / this._devicePixelRatio;
this.dimensions.actualCellWidth = this.dimensions.scaledCellWidth / this._devicePixelRatio;
}
private _requestRedrawViewport(): void {
this._onRequestRedraw.fire({ start: 0, end: this._terminal.rows - 1 });
}
}
// TODO: Share impl with core
@@ -6,7 +6,8 @@
import { ICharAtlasConfig } from './Types';
import { Attributes } from 'common/buffer/Constants';
import { Terminal, FontWeight } from 'xterm';
import { IColorSet, IColor } from 'browser/Types';
import { IColorSet } from 'browser/Types';
import { IColor } from 'common/Types';
const NULL_COLOR: IColor = {
css: '',
@@ -22,27 +23,28 @@ export function generateConfig(scaledCellWidth: number, scaledCellHeight: number
cursorAccent: NULL_COLOR,
selectionTransparent: NULL_COLOR,
selectionOpaque: NULL_COLOR,
selectionForeground: NULL_COLOR,
// For the static char atlas, we only use the first 16 colors, but we need all 256 for the
// dynamic character atlas.
ansi: colors.ansi.slice(),
contrastCache: colors.contrastCache
};
return {
customGlyphs: terminal.getOption('customGlyphs'),
customGlyphs: terminal.options.customGlyphs!,
devicePixelRatio: window.devicePixelRatio,
letterSpacing: terminal.getOption('letterSpacing'),
lineHeight: terminal.getOption('lineHeight'),
letterSpacing: terminal.options.letterSpacing!,
lineHeight: terminal.options.lineHeight!,
scaledCellWidth,
scaledCellHeight,
scaledCharWidth,
scaledCharHeight,
fontFamily: terminal.getOption('fontFamily'),
fontSize: terminal.getOption('fontSize'),
fontWeight: terminal.getOption('fontWeight') as FontWeight,
fontWeightBold: terminal.getOption('fontWeightBold') as FontWeight,
allowTransparency: terminal.getOption('allowTransparency'),
drawBoldTextInBrightColors: terminal.getOption('drawBoldTextInBrightColors'),
minimumContrastRatio: terminal.getOption('minimumContrastRatio'),
fontFamily: terminal.options.fontFamily!,
fontSize: terminal.options.fontSize!,
fontWeight: terminal.options.fontWeight as FontWeight,
fontWeightBold: terminal.options.fontWeightBold as FontWeight,
allowTransparency: terminal.options.allowTransparency!,
drawBoldTextInBrightColors: terminal.options.drawBoldTextInBrightColors!,
minimumContrastRatio: terminal.options.minimumContrastRatio!,
colors: clonedColors
};
}
@@ -8,11 +8,12 @@ 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';
import { IColor } from 'browser/Types';
import { IColor } from 'common/Types';
import { IDisposable } from 'xterm';
import { AttributeData } from 'common/buffer/AttributeData';
import { channels, rgba } from 'browser/Color';
import { channels, rgba } from 'common/Color';
import { tryDrawCustomChar } from 'browser/renderer/CustomGlyphs';
import { excludeFromContrastRatioDemands, isPowerlineGlyph } from 'browser/renderer/RendererUtils';
// For debugging purposes, it can be useful to set this to a really tiny value,
// to verify that LRU eviction works.
@@ -216,8 +217,8 @@ export class WebglCharAtlas implements IDisposable {
}
}
private _getForegroundCss(bg: number, bgColorMode: number, bgColor: number, fg: number, fgColorMode: number, fgColor: number, inverse: boolean, bold: boolean): string {
const minimumContrastCss = this._getMinimumContrastCss(bg, bgColorMode, bgColor, fg, fgColorMode, fgColor, inverse, bold);
private _getForegroundCss(bg: number, bgColorMode: number, bgColor: number, fg: number, fgColorMode: number, fgColor: number, inverse: boolean, bold: boolean, excludeFromContrastRatioDemands: boolean): string {
const minimumContrastCss = this._getMinimumContrastCss(bg, bgColorMode, bgColor, fg, fgColorMode, fgColor, inverse, bold, excludeFromContrastRatioDemands);
if (minimumContrastCss) {
return minimumContrastCss;
}
@@ -238,7 +239,7 @@ export class WebglCharAtlas implements IDisposable {
const bg = this._config.colors.background.css;
if (bg.length === 9) {
// Remove bg alpha channel if present
return bg.substr(0, 7);
return bg.slice(0, 7);
}
return bg;
}
@@ -281,8 +282,8 @@ export class WebglCharAtlas implements IDisposable {
}
}
private _getMinimumContrastCss(bg: number, bgColorMode: number, bgColor: number, fg: number, fgColorMode: number, fgColor: number, inverse: boolean, bold: boolean): string | undefined {
if (this._config.minimumContrastRatio === 1) {
private _getMinimumContrastCss(bg: number, bgColorMode: number, bgColor: number, fg: number, fgColorMode: number, fgColor: number, inverse: boolean, bold: boolean, excludeFromContrastRatioDemands: boolean): string | undefined {
if (this._config.minimumContrastRatio === 1 || excludeFromContrastRatioDemands) {
return undefined;
}
@@ -321,10 +322,15 @@ export class WebglCharAtlas implements IDisposable {
// 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;
const allowedWidth = this._config.scaledCellWidth * Math.max(chars.length, 2) + TMP_CANVAS_GLYPH_PADDING * 2;
if (this._tmpCanvas.width < allowedWidth) {
this._tmpCanvas.width = allowedWidth;
}
// Include line height when drawing glyphs
const allowedHeight = this._config.scaledCellHeight + TMP_CANVAS_GLYPH_PADDING * 2;
if (this._tmpCanvas.height < allowedHeight) {
this._tmpCanvas.height = allowedHeight;
}
this._tmpCtx.save();
this._workAttributeData.fg = fg;
@@ -370,26 +376,16 @@ export class WebglCharAtlas implements IDisposable {
`${fontStyle} ${fontWeight} ${this._config.fontSize * this._config.devicePixelRatio}px ${this._config.fontFamily}`;
this._tmpCtx.textBaseline = TEXT_BASELINE;
this._tmpCtx.fillStyle = this._getForegroundCss(bg, bgColorMode, bgColor, fg, fgColorMode, fgColor, inverse, bold);
const powerLineGlyph = chars.length === 1 && isPowerlineGlyph(chars.charCodeAt(0));
this._tmpCtx.fillStyle = this._getForegroundCss(bg, bgColorMode, bgColor, fg, fgColorMode, fgColor, inverse, bold, excludeFromContrastRatioDemands(chars.charCodeAt(0)));
// Apply alpha to dim the character
if (dim) {
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;
const padding = powerLineGlyph ? 0 : TMP_CANVAS_GLYPH_PADDING;
// Draw custom characters if applicable
let drawSuccess = false;
@@ -459,7 +455,7 @@ export class WebglCharAtlas implements IDisposable {
return NULL_RASTERIZED_GLYPH;
}
const rasterizedGlyph = this._findGlyphBoundingBox(imageData, this._workBoundingBox, allowedWidth, isPowerlineGlyph, drawSuccess);
const rasterizedGlyph = this._findGlyphBoundingBox(imageData, this._workBoundingBox, allowedWidth, powerLineGlyph, drawSuccess);
const clippedImageData = this._clipImageData(imageData, this._workBoundingBox);
// Check if there is enough room in the current row and go to next if needed
@@ -494,7 +490,7 @@ export class WebglCharAtlas implements IDisposable {
*/
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 height = restrictedGlyph ? this._config.scaledCellHeight : this._tmpCanvas.height;
const width = restrictedGlyph ? this._config.scaledCharWidth : allowedWidth;
let found = false;
for (let y = 0; y < height; y++) {
@@ -254,10 +254,10 @@ export abstract class BaseRenderLayer implements IRenderLayer {
* @param isBold If we should use the bold fontWeight.
*/
protected _getFont(terminal: Terminal, isBold: boolean, isItalic: boolean): string {
const fontWeight = isBold ? terminal.getOption('fontWeightBold') : terminal.getOption('fontWeight');
const fontWeight = isBold ? terminal.options.fontWeightBold : terminal.options.fontWeight;
const fontStyle = isItalic ? 'italic' : '';
return `${fontStyle} ${fontWeight} ${terminal.getOption('fontSize') * window.devicePixelRatio}px ${terminal.getOption('fontFamily')}`;
return `${fontStyle} ${fontWeight} ${terminal.options.fontSize! * window.devicePixelRatio}px ${terminal.options.fontFamily}`;
}
}
@@ -83,7 +83,7 @@ export class CursorRenderLayer extends BaseRenderLayer {
}
public onOptionsChanged(terminal: Terminal): void {
if (terminal.getOption('cursorBlink')) {
if (terminal.options.cursorBlink) {
if (!this._cursorBlinkStateManager) {
this._cursorBlinkStateManager = new CursorBlinkStateManager(terminal, () => {
this._render(terminal, true);
@@ -140,7 +140,7 @@ export class CursorRenderLayer extends BaseRenderLayer {
this._clearCursor();
this._ctx.save();
this._ctx.fillStyle = this._colors.cursor.css;
const cursorStyle = terminal.getOption('cursorStyle');
const cursorStyle = terminal.options.cursorStyle;
if (cursorStyle && cursorStyle !== 'block') {
this._cursorRenderers[cursorStyle](terminal, cursorX, viewportRelativeCursorY, this._cell);
} else {
@@ -150,7 +150,7 @@ export class CursorRenderLayer extends BaseRenderLayer {
this._state.x = cursorX;
this._state.y = viewportRelativeCursorY;
this._state.isFocused = false;
this._state.style = cursorStyle;
this._state.style = cursorStyle!;
this._state.width = this._cell.getWidth();
return;
}
@@ -166,7 +166,7 @@ export class CursorRenderLayer extends BaseRenderLayer {
if (this._state.x === cursorX &&
this._state.y === viewportRelativeCursorY &&
this._state.isFocused === isTerminalFocused(terminal) &&
this._state.style === terminal.getOption('cursorStyle') &&
this._state.style === terminal.options.cursorStyle &&
this._state.width === this._cell.getWidth()) {
return;
}
@@ -174,13 +174,13 @@ export class CursorRenderLayer extends BaseRenderLayer {
}
this._ctx.save();
this._cursorRenderers[terminal.getOption('cursorStyle') || 'block'](terminal, cursorX, viewportRelativeCursorY, this._cell);
this._cursorRenderers[terminal.options.cursorStyle || 'block'](terminal, cursorX, viewportRelativeCursorY, this._cell);
this._ctx.restore();
this._state.x = cursorX;
this._state.y = viewportRelativeCursorY;
this._state.isFocused = false;
this._state.style = terminal.getOption('cursorStyle');
this._state.style = terminal.options.cursorStyle!;
this._state.width = this._cell.getWidth();
}
@@ -205,7 +205,7 @@ export class CursorRenderLayer extends BaseRenderLayer {
private _renderBarCursor(terminal: Terminal, x: number, y: number, cell: ICellData): void {
this._ctx.save();
this._ctx.fillStyle = this._colors.cursor.css;
this._fillLeftLineAtCell(x, y, terminal.getOption('cursorWidth'));
this._fillLeftLineAtCell(x, y, terminal.options.cursorWidth!);
this._ctx.restore();
}
@@ -20,6 +20,7 @@
]
},
"strict": true,
"downlevelIteration": true,
"types": [
"../../../node_modules/@types/mocha"
]
@@ -6,7 +6,7 @@
import { assert } from 'chai';
import { Browser, Page } from 'playwright';
import { ITheme } from 'xterm';
import { getBrowserType, launchBrowser, openTerminal, pollFor, writeSync } from '../../../out-test/api/TestUtils';
import { getBrowserType, launchBrowser, openTerminal, pollFor, timeout, writeSync } from '../../../out-test/api/TestUtils';
import { ITerminalOptions } from '../../../src/common/Types';
const APP = 'http://127.0.0.1:3001/test';
@@ -49,7 +49,7 @@ describe('WebGL Renderer Integration Tests', async () => {
cyan: '#131415',
white: '#161718'
};
await page.evaluate(`window.term.setOption('theme', ${JSON.stringify(theme)});`);
await page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`);
await writeSync(page, `\\x1b[30m█\\x1b[31m█\\x1b[32m█\\x1b[33m█\\x1b[34m█\\x1b[35m█\\x1b[36m█\\x1b[37m█`);
await pollFor(page, () => getCellColor(1, 1), [1, 2, 3, 255]);
await pollFor(page, () => getCellColor(2, 1), [4, 5, 6, 255]);
@@ -73,8 +73,8 @@ describe('WebGL Renderer Integration Tests', async () => {
brightWhite: '#161718'
};
await page.evaluate(`
window.term.setOption('theme', ${JSON.stringify(theme)});
window.term.setOption('drawBoldTextInBrightColors', true);
window.term.options.theme = ${JSON.stringify(theme)};
window.term.options.drawBoldTextInBrightColors = true;
`);
await writeSync(page, `\\x1b[1;30m█\\x1b[1;31m█\\x1b[1;32m█\\x1b[1;33m█\\x1b[1;34m█\\x1b[1;35m█\\x1b[1;36m█\\x1b[1;37m█`);
await pollFor(page, () => getCellColor(1, 1), [1, 2, 3, 255]);
@@ -98,7 +98,7 @@ describe('WebGL Renderer Integration Tests', async () => {
cyan: '#131415',
white: '#161718'
};
await page.evaluate(`window.term.setOption('theme', ${JSON.stringify(theme)});`);
await page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`);
await writeSync(page, `\\x1b[40m \\x1b[41m \\x1b[42m \\x1b[43m \\x1b[44m \\x1b[45m \\x1b[46m \\x1b[47m `);
await pollFor(page, () => getCellColor(1, 1), [1, 2, 3, 255]);
await pollFor(page, () => getCellColor(2, 1), [4, 5, 6, 255]);
@@ -121,7 +121,7 @@ describe('WebGL Renderer Integration Tests', async () => {
cyan: '#131415',
white: '#161718'
};
await page.evaluate(`window.term.setOption('theme', ${JSON.stringify(theme)});`);
await page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`);
await writeSync(page, `\\x1b[7;30m \\x1b[7;31m \\x1b[7;32m \\x1b[7;33m \\x1b[7;34m \\x1b[7;35m \\x1b[7;36m \\x1b[7;37m `);
await pollFor(page, () => getCellColor(1, 1), [1, 2, 3, 255]);
await pollFor(page, () => getCellColor(2, 1), [4, 5, 6, 255]);
@@ -144,7 +144,7 @@ describe('WebGL Renderer Integration Tests', async () => {
cyan: '#131415',
white: '#161718'
};
await page.evaluate(`window.term.setOption('theme', ${JSON.stringify(theme)});`);
await page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`);
await writeSync(page, `\\x1b[7;40m█\\x1b[7;41m█\\x1b[7;42m█\\x1b[7;43m█\\x1b[7;44m█\\x1b[7;45m█\\x1b[7;46m█\\x1b[7;47m█`);
await pollFor(page, () => getCellColor(1, 1), [1, 2, 3, 255]);
await pollFor(page, () => getCellColor(2, 1), [4, 5, 6, 255]);
@@ -167,7 +167,7 @@ describe('WebGL Renderer Integration Tests', async () => {
cyan: '#131415',
white: '#161718'
};
await page.evaluate(`window.term.setOption('theme', ${JSON.stringify(theme)});`);
await page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`);
await writeSync(page, `\\x1b[8;30m \\x1b[8;31m \\x1b[8;32m \\x1b[8;33m \\x1b[8;34m \\x1b[8;35m \\x1b[8;36m \\x1b[8;37m `);
await pollFor(page, () => getCellColor(1, 1), [0, 0, 0, 255]);
await pollFor(page, () => getCellColor(2, 1), [0, 0, 0, 255]);
@@ -190,7 +190,7 @@ describe('WebGL Renderer Integration Tests', async () => {
cyan: '#131415',
white: '#161718'
};
await page.evaluate(`window.term.setOption('theme', ${JSON.stringify(theme)});`);
await page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`);
await writeSync(page, `\\x1b[8;40m█\\x1b[8;41m█\\x1b[8;42m█\\x1b[8;43m█\\x1b[8;44m█\\x1b[8;45m█\\x1b[8;46m█\\x1b[8;47m█`);
await pollFor(page, () => getCellColor(1, 1), [1, 2, 3, 255]);
await pollFor(page, () => getCellColor(2, 1), [4, 5, 6, 255]);
@@ -213,7 +213,7 @@ describe('WebGL Renderer Integration Tests', async () => {
brightCyan: '#131415',
brightWhite: '#161718'
};
await page.evaluate(`window.term.setOption('theme', ${JSON.stringify(theme)});`);
await page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`);
await writeSync(page, `\\x1b[90m█\\x1b[91m█\\x1b[92m█\\x1b[93m█\\x1b[94m█\\x1b[95m█\\x1b[96m█\\x1b[97m█`);
await pollFor(page, () => getCellColor(1, 1), [1, 2, 3, 255]);
await pollFor(page, () => getCellColor(2, 1), [4, 5, 6, 255]);
@@ -236,7 +236,7 @@ describe('WebGL Renderer Integration Tests', async () => {
brightCyan: '#131415',
brightWhite: '#161718'
};
await page.evaluate(`window.term.setOption('theme', ${JSON.stringify(theme)});`);
await page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`);
await writeSync(page, `\\x1b[100m \\x1b[101m \\x1b[102m \\x1b[103m \\x1b[104m \\x1b[105m \\x1b[106m \\x1b[107m `);
await pollFor(page, () => getCellColor(1, 1), [1, 2, 3, 255]);
await pollFor(page, () => getCellColor(2, 1), [4, 5, 6, 255]);
@@ -260,9 +260,9 @@ describe('WebGL Renderer Integration Tests', async () => {
for (let y = 0; y < 240 / 16; y++) {
for (let x = 0; x < 16; x++) {
const cssColor = COLORS_16_TO_255[y * 16 + x];
const r = parseInt(cssColor.substr(1, 2), 16);
const g = parseInt(cssColor.substr(3, 2), 16);
const b = parseInt(cssColor.substr(5, 2), 16);
const r = parseInt(cssColor.slice(1, 3), 16);
const g = parseInt(cssColor.slice(3, 5), 16);
const b = parseInt(cssColor.slice(5, 7), 16);
await pollFor(page, () => getCellColor(x + 1, y + 1), [r, g, b, 255]);
}
}
@@ -280,9 +280,9 @@ describe('WebGL Renderer Integration Tests', async () => {
for (let y = 0; y < 240 / 16; y++) {
for (let x = 0; x < 16; x++) {
const cssColor = COLORS_16_TO_255[y * 16 + x];
const r = parseInt(cssColor.substr(1, 2), 16);
const g = parseInt(cssColor.substr(3, 2), 16);
const b = parseInt(cssColor.substr(5, 2), 16);
const r = parseInt(cssColor.slice(1, 3), 16);
const g = parseInt(cssColor.slice(3, 5), 16);
const b = parseInt(cssColor.slice(5, 7), 16);
await pollFor(page, () => getCellColor(x + 1, y + 1), [r, g, b, 255]);
}
}
@@ -300,9 +300,9 @@ describe('WebGL Renderer Integration Tests', async () => {
for (let y = 0; y < 240 / 16; y++) {
for (let x = 0; x < 16; x++) {
const cssColor = COLORS_16_TO_255[y * 16 + x];
const r = parseInt(cssColor.substr(1, 2), 16);
const g = parseInt(cssColor.substr(3, 2), 16);
const b = parseInt(cssColor.substr(5, 2), 16);
const r = parseInt(cssColor.slice(1, 3), 16);
const g = parseInt(cssColor.slice(3, 5), 16);
const b = parseInt(cssColor.slice(5, 7), 16);
await pollFor(page, () => getCellColor(x + 1, y + 1), [r, g, b, 255]);
}
}
@@ -320,9 +320,9 @@ describe('WebGL Renderer Integration Tests', async () => {
for (let y = 0; y < 240 / 16; y++) {
for (let x = 0; x < 16; x++) {
const cssColor = COLORS_16_TO_255[y * 16 + x];
const r = parseInt(cssColor.substr(1, 2), 16);
const g = parseInt(cssColor.substr(3, 2), 16);
const b = parseInt(cssColor.substr(5, 2), 16);
const r = parseInt(cssColor.slice(1, 3), 16);
const g = parseInt(cssColor.slice(3, 5), 16);
const b = parseInt(cssColor.slice(5, 7), 16);
await pollFor(page, () => getCellColor(x + 1, y + 1), [r, g, b, 255]);
}
}
@@ -356,9 +356,9 @@ describe('WebGL Renderer Integration Tests', async () => {
for (let y = 0; y < 240 / 16; y++) {
for (let x = 0; x < 16; x++) {
const cssColor = COLORS_16_TO_255[y * 16 + x];
const r = parseInt(cssColor.substr(1, 2), 16);
const g = parseInt(cssColor.substr(3, 2), 16);
const b = parseInt(cssColor.substr(5, 2), 16);
const r = parseInt(cssColor.slice(1, 3), 16);
const g = parseInt(cssColor.slice(3, 5), 16);
const b = parseInt(cssColor.slice(5, 7), 16);
await pollFor(page, () => getCellColor(x + 1, y + 1), [r, g, b, 255]);
}
}
@@ -715,12 +715,13 @@ describe('WebGL Renderer Integration Tests', async () => {
brightWhite: '#eeeeec'
};
await page.evaluate(`
window.term.setOption('theme', ${JSON.stringify(theme)});
window.term.setOption('minimumContrastRatio', 1);
window.term.options.theme = ${JSON.stringify(theme)};
window.term.options.minimumContrastRatio = 1;
`);
// Block characters ignore block elements so a different char is used here
await writeSync(page,
`\\x1b[30m█\\x1b[31m█\\x1b[32m█\\x1b[33m█\\x1b[34m█\\x1b[35m█\\x1b[36m█\\x1b[37m█\\r\\n` +
`\\x1b[90m█\\x1b[91m█\\x1b[92m█\\x1b[93m█\\x1b[94m█\\x1b[95m█\\x1b[96m█\\x1b[97m█`
`\\x1b[30m■\\x1b[31m■\\x1b[32m■\\x1b[33m■\\x1b[34m■\\x1b[35m■\\x1b[36m■\\x1b[37m■\\r\\n` +
`\\x1b[90m■\\x1b[91m■\\x1b[92m■\\x1b[93m■\\x1b[94m■\\x1b[95m■\\x1b[96m■\\x1b[97m■`
);
// Validate before minimumContrastRatio is applied
await pollFor(page, () => getCellColor(1, 1), [0x2e, 0x34, 0x36, 255]);
@@ -742,21 +743,21 @@ describe('WebGL Renderer Integration Tests', async () => {
// Setting and check for minimum contrast values, note that these are note
// exact to the contrast ratio, if the increase luminance algorithm
// changes then these will probably fail
await page.evaluate(`window.term.setOption('minimumContrastRatio', 10);`);
await page.evaluate(`window.term.options.minimumContrastRatio = 10;`);
await pollFor(page, () => getCellColor(1, 1), [176, 180, 180, 255]);
await pollFor(page, () => getCellColor(2, 1), [238, 158, 158, 255]);
await pollFor(page, () => getCellColor(3, 1), [197, 223, 171, 255]);
await pollFor(page, () => getCellColor(4, 1), [235, 221, 158, 255]);
await pollFor(page, () => getCellColor(5, 1), [124, 156, 198, 255]);
await pollFor(page, () => getCellColor(6, 1), [183, 165, 187, 255]);
await pollFor(page, () => getCellColor(3, 1), [152, 198, 110, 255]);
await pollFor(page, () => getCellColor(4, 1), [208, 179, 49, 255]);
await pollFor(page, () => getCellColor(5, 1), [161, 183, 215, 255]);
await pollFor(page, () => getCellColor(6, 1), [191, 174, 194, 255]);
await pollFor(page, () => getCellColor(7, 1), [110, 197, 198, 255]);
await pollFor(page, () => getCellColor(8, 1), [211, 215, 207, 255]);
await pollFor(page, () => getCellColor(1, 2), [183, 185, 183, 255]);
await pollFor(page, () => getCellColor(2, 2), [249, 156, 156, 255]);
await pollFor(page, () => getCellColor(3, 2), [138, 226, 52, 255]);
await pollFor(page, () => getCellColor(4, 2), [252, 233, 79, 255]);
await pollFor(page, () => getCellColor(5, 2), [114, 159, 207, 255]);
await pollFor(page, () => getCellColor(6, 2), [190, 152, 185, 255]);
await pollFor(page, () => getCellColor(5, 2), [154, 186, 221, 255]);
await pollFor(page, () => getCellColor(6, 2), [203, 173, 199, 255]);
// Unchanged
await pollFor(page, () => getCellColor(7, 2), [0x34, 0xe2, 0xe2, 255]);
await pollFor(page, () => getCellColor(8, 2), [0xee, 0xee, 0xec, 255]);
@@ -783,12 +784,13 @@ describe('WebGL Renderer Integration Tests', async () => {
brightWhite: '#eeeeec'
};
await page.evaluate(`
window.term.setOption('theme', ${JSON.stringify(theme)});
window.term.setOption('minimumContrastRatio', 1);
window.term.options.theme = ${JSON.stringify(theme)};
window.term.options.minimumContrastRatio = 1;
`);
// Block characters ignore block elements so a different char is used here
await writeSync(page,
`\\x1b[30m█\\x1b[31m█\\x1b[32m█\\x1b[33m█\\x1b[34m█\\x1b[35m█\\x1b[36m█\\x1b[37m█\\r\\n` +
`\\x1b[90m█\\x1b[91m█\\x1b[92m█\\x1b[93m█\\x1b[94m█\\x1b[95m█\\x1b[96m█\\x1b[97m█`
`\\x1b[30m■\\x1b[31m■\\x1b[32m■\\x1b[33m■\\x1b[34m■\\x1b[35m■\\x1b[36m■\\x1b[37m■\\r\\n` +
`\\x1b[90m■\\x1b[91m■\\x1b[92m■\\x1b[93m■\\x1b[94m■\\x1b[95m■\\x1b[96m■\\x1b[97m■`
);
// Validate before minimumContrastRatio is applied
await pollFor(page, () => getCellColor(1, 1), [0x2e, 0x34, 0x36, 255]);
@@ -810,21 +812,21 @@ describe('WebGL Renderer Integration Tests', async () => {
// Setting and check for minimum contrast values, note that these are note
// exact to the contrast ratio, if the increase luminance algorithm
// changes then these will probably fail
await page.evaluate(`window.term.setOption('minimumContrastRatio', 10);`);
await page.evaluate(`window.term.options.minimumContrastRatio = 10;`);
await pollFor(page, () => getCellColor(1, 1), [46, 52, 54, 255]);
await pollFor(page, () => getCellColor(2, 1), [132, 0, 0, 255]);
await pollFor(page, () => getCellColor(3, 1), [78, 154, 6, 255]);
await pollFor(page, () => getCellColor(4, 1), [114, 93, 0, 255]);
await pollFor(page, () => getCellColor(5, 1), [19, 40, 68, 255]);
await pollFor(page, () => getCellColor(6, 1), [60, 40, 64, 255]);
await pollFor(page, () => getCellColor(3, 1), [36, 72, 0, 255]);
await pollFor(page, () => getCellColor(4, 1), [72, 59, 0, 255]);
await pollFor(page, () => getCellColor(5, 1), [32, 64, 106, 255]);
await pollFor(page, () => getCellColor(6, 1), [75, 51, 80, 255]);
await pollFor(page, () => getCellColor(7, 1), [0, 71, 72, 255]);
await pollFor(page, () => getCellColor(8, 1), [64, 64, 63, 255]);
await pollFor(page, () => getCellColor(1, 2), [61, 63, 59, 255]);
await pollFor(page, () => getCellColor(2, 2), [125, 19, 19, 255]);
await pollFor(page, () => getCellColor(3, 2), [89, 146, 32, 255]);
await pollFor(page, () => getCellColor(4, 2), [105, 98, 32, 255]);
await pollFor(page, () => getCellColor(5, 2), [36, 52, 70, 255]);
await pollFor(page, () => getCellColor(6, 2), [64, 45, 63, 255]);
await pollFor(page, () => getCellColor(3, 2), [40, 67, 13, 255]);
await pollFor(page, () => getCellColor(4, 2), [67, 63, 19, 255]);
await pollFor(page, () => getCellColor(5, 2), [45, 65, 87, 255]);
await pollFor(page, () => getCellColor(6, 2), [81, 57, 78, 255]);
await pollFor(page, () => getCellColor(7, 2), [13, 67, 67, 255]);
await pollFor(page, () => getCellColor(8, 2), [64, 64, 64, 255]);
});
@@ -843,7 +845,7 @@ describe('WebGL Renderer Integration Tests', async () => {
background: '#00FF00',
selection: '#0000FF'
};
await page.evaluate(`window.term.setOption('theme', ${JSON.stringify(theme)});`);
await page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`);
await writeSync(page, ` █\\x1b[7m█\\x1b[0m`);
await pollFor(page, () => getCellColor(1, 1), [0, 255, 0, 255]);
await pollFor(page, () => getCellColor(2, 1), [255, 0, 0, 255]);
@@ -867,13 +869,121 @@ describe('WebGL Renderer Integration Tests', async () => {
const theme: ITheme = {
background: '#ff000080'
};
await page.evaluate(`window.term.setOption('theme', ${JSON.stringify(theme)});`);
await page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`);
const data = `\\x1b[7m█\x1b[0m`;
await writeSync(page, data);
// Inverse background should be opaque
await pollFor(page, () => getCellColor(1, 1), [255, 0, 0, 255]);
});
});
describe('selectionForeground', () => {
if (areTestsEnabled) {
before(async () => setupBrowser({ rendererType: 'dom' }));
after(async () => browser.close());
beforeEach(async () => page.evaluate(`window.term.reset()`));
}
itWebgl('transparent background inverse', async () => {
const theme: ITheme = {
selectionForeground: '#ff0000'
};
await page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`);
const data = `\\x1b[7m█\x1b[0m`;
await writeSync(page, data);
await page.evaluate(`window.term.selectAll()`);
await pollFor(page, () => getCellColor(1, 1), [255, 0, 0, 255]);
});
});
describe('decoration color overrides', async () => {
if (areTestsEnabled) {
before(async () => setupBrowser({ rendererType: 'dom' }));
after(async () => browser.close());
beforeEach(async () => page.evaluate(`window.term.reset()`));
}
itWebgl('foregroundColor', async () => {
await page.evaluate(`
const marker = window.term.registerMarker(-window.term.buffer.active.cursorY);
window.term.registerDecoration({
marker,
foregroundColor: '#ff0000',
backgroundColor: '#0000ff'
});
`);
const data = `█`;
await writeSync(page, data);
await pollFor(page, () => getCellColor(1, 1), [255, 0, 0, 255]);
});
itWebgl('foregroundColor should ignore inverse', async () => {
await page.evaluate(`
const marker = window.term.registerMarker(-window.term.buffer.active.cursorY);
window.term.registerDecoration({
marker,
foregroundColor: '#ff0000',
backgroundColor: '#0000ff'
});
`);
const data = `\\x1b[7m█\\x1b[0m`;
await writeSync(page, data);
await pollFor(page, () => getCellColor(1, 1), [255, 0, 0, 255]);
});
itWebgl('foregroundColor should ignore inverse (only fg on decoration)', async () => {
await page.evaluate(`
const marker = window.term.registerMarker(-window.term.buffer.active.cursorY);
window.term.registerDecoration({
marker,
width: 2,
foregroundColor: '#ff0000'
});
`);
const data = `\\x1b[7m█ \\x1b[0m`;
await writeSync(page, data);
await pollFor(page, () => getCellColor(1, 1), [255, 0, 0, 255]); // inverse foreground of '█' should be decoration fg override
await pollFor(page, () => getCellColor(2, 1), [255, 255, 255, 255]); // inverse background of ' ' should be default foreground
});
itWebgl('backgroundColor', async () => {
await page.evaluate(`
const marker = window.term.registerMarker(-window.term.buffer.active.cursorY);
window.term.registerDecoration({
marker,
foregroundColor: '#ff0000',
backgroundColor: '#0000ff'
});
`);
const data = ` `;
await writeSync(page, data);
await pollFor(page, () => getCellColor(1, 1), [0, 0, 255, 255]);
});
itWebgl('backgroundColor should ignore inverse', async () => {
await page.evaluate(`
const marker = window.term.registerMarker(-window.term.buffer.active.cursorY);
window.term.registerDecoration({
marker,
foregroundColor: '#ff0000',
backgroundColor: '#0000ff'
});
`);
const data = `\\x1b[7m \\x1b[0m`;
await writeSync(page, data);
await pollFor(page, () => getCellColor(1, 1), [0, 0, 255, 255]);
});
itWebgl('backgroundColor should ignore inverse (only bg on decoration)', async () => {
const data = `\\x1b[7m█ \\x1b[0m`;
await writeSync(page, data);
await page.evaluate(`
const marker = window.term.registerMarker(-window.term.buffer.active.cursorY);
window.term.registerDecoration({
marker,
width: 2,
backgroundColor: '#0000ff'
});
`);
await pollFor(page, () => getCellColor(1, 1), [0, 0, 0, 255]); // inverse foreground of '█' should be default
await pollFor(page, () => getCellColor(2, 1), [0, 0, 255, 255]); // inverse background of ' ' should be decoration bg override
});
});
});
async function getCellColor(col: number, row: number): Promise<number[]> {
+3 -3
View File
@@ -104,11 +104,11 @@ function getNextBetaVersion(packageJson) {
return `${nextStableVersion}-${tag}.1`;
}
const latestPublishedVersion = publishedVersions.sort((a, b) => {
const aVersion = parseInt(a.substr(a.search(/\d+$/)));
const bVersion = parseInt(b.substr(b.search(/\d+$/)));
const aVersion = parseInt(a.slice(a.search(/\d+$/)));
const bVersion = parseInt(b.slice(b.search(/\d+$/)));
return aVersion > bVersion ? -1 : 1;
})[0];
const latestTagVersion = parseInt(latestPublishedVersion.substr(latestPublishedVersion.search(/\d+$/)), 10);
const latestTagVersion = parseInt(latestPublishedVersion.slice(latestPublishedVersion.search(/\d+$/)), 10);
return `${nextStableVersion}-${tag}.${latestTagVersion + 1}`;
}
+14 -4
View File
@@ -36,6 +36,7 @@
*/
.xterm {
cursor: text;
position: relative;
user-select: none;
-ms-user-select: none;
@@ -124,10 +125,6 @@
line-height: normal;
}
.xterm {
cursor: text;
}
.xterm.enable-mouse-events {
/* When mouse events are enabled (eg. tmux), revert to the standard pointer cursor */
cursor: default;
@@ -178,3 +175,16 @@
z-index: 6;
position: absolute;
}
.xterm-decoration-overview-ruler {
z-index: 7;
position: absolute;
top: 0;
right: 0;
pointer-events: none;
}
.xterm-decoration-top {
z-index: 2;
position: relative;
}
+107 -7
View File
@@ -92,8 +92,10 @@ const addons: { [T in AddonType]: IDemoAddon<T>} = {
const terminalContainer = document.getElementById('terminal-container');
const actionElements = {
find: <HTMLInputElement>document.querySelector('#find'),
findNext: <HTMLInputElement>document.querySelector('#find-next'),
findPrevious: <HTMLInputElement>document.querySelector('#find-previous')
findPrevious: <HTMLInputElement>document.querySelector('#find-previous'),
findResults: document.querySelector('#find-results')
};
const paddingElement = <HTMLInputElement>document.getElementById('padding');
@@ -107,7 +109,15 @@ function getSearchOptions(e: KeyboardEvent): ISearchOptions {
regex: (document.getElementById('regex') as HTMLInputElement).checked,
wholeWord: (document.getElementById('whole-word') as HTMLInputElement).checked,
caseSensitive: (document.getElementById('case-sensitive') as HTMLInputElement).checked,
incremental: e.key !== `Enter`
incremental: e.key !== `Enter`,
decorations: (document.getElementById('highlight-all-matches') as HTMLInputElement).checked ? {
matchBackground: '#232422',
matchBorder: '#555753',
matchOverviewRuler: '#555753',
activeMatchBackground: '#ef2929',
activeMatchBorder: '#ffffff',
activeMatchColorOverviewRuler: '#ef2929'
} : undefined
};
}
@@ -150,7 +160,9 @@ if (document.location.pathname === '/test') {
document.getElementById('htmlserialize').addEventListener('click', htmlSerializeButtonHandler);
document.getElementById('custom-glyph').addEventListener('click', writeCustomGlyphHandler);
document.getElementById('load-test').addEventListener('click', loadTest);
document.getElementById('powerline-symbol-test').addEventListener('click', powerlineSymbolTest);
document.getElementById('add-decoration').addEventListener('click', addDecoration);
document.getElementById('add-overview-ruler').addEventListener('click', addOverviewRuler);
}
function createTerminal(): void {
@@ -202,10 +214,15 @@ function createTerminal(): void {
addDomListener(actionElements.findNext, 'keyup', (e) => {
addons.search.instance.findNext(actionElements.findNext.value, getSearchOptions(e));
});
addDomListener(actionElements.findPrevious, 'keyup', (e) => {
addons.search.instance.findPrevious(actionElements.findPrevious.value, getSearchOptions(e));
});
addDomListener(actionElements.findNext, 'blur', (e) => {
addons.search.instance.clearActiveDecoration();
});
addDomListener(actionElements.findPrevious, 'blur', (e) => {
addons.search.instance.clearActiveDecoration();
});
// fit is called within a setTimeout, cols and rows need this.
setTimeout(() => {
@@ -382,9 +399,12 @@ function initAddons(term: TerminalType): void {
if (!addon.canChange) {
checkbox.disabled = true;
}
if(name === 'unicode11' && checkbox.checked) {
if (name === 'unicode11' && checkbox.checked) {
term.unicode.activeVersion = '11';
}
if (name === 'search' && checkbox.checked) {
addon.instance.onDidChangeResults(e => updateFindResults(e));
}
addDomListener(checkbox, 'change', () => {
if (checkbox.checked) {
addon.instance = new addon.ctor();
@@ -395,6 +415,8 @@ function initAddons(term: TerminalType): void {
}, 0);
} else if (name === 'unicode11') {
term.unicode.activeVersion = '11';
} else if (name === 'search') {
addon.instance.onDidChangeResults(e => updateFindResults(e));
}
} else {
if (name === 'webgl') {
@@ -423,6 +445,16 @@ function initAddons(term: TerminalType): void {
container.appendChild(fragment);
}
function updateFindResults(e: { resultIndex: number, resultCount: number } | undefined) {
let content: string;
if (e === undefined) {
content = 'undefined';
} else {
content = `index: ${e.resultIndex}, count: ${e.resultCount}`;
}
actionElements.findResults.textContent = content;
}
function addDomListener(element: HTMLElement, type: string, handler: (...args: any[]) => any): void {
element.addEventListener(type, handler);
term._core.register({ dispose: () => element.removeEventListener(type, handler) });
@@ -543,10 +575,78 @@ function loadTest() {
});
}
function powerlineSymbolTest() {
function s(char: string): string {
return `${char} \x1b[7m${char}\x1b[0m `;
}
term.write('\n\n\r');
term.writeln('Standard powerline symbols:');
term.writeln(' 0 1 2 3 4 5 6 7 8 9 A B C D E F');
term.writeln(`0xA_ ${s('\ue0a0')}${s('\ue0a1')}${s('\ue0a2')}`);
term.writeln(`0xB_ ${s('\ue0b0')}${s('\ue0b1')}${s('\ue0b2')}${s('\ue0b3')}`);
term.writeln('');
term.writeln(
`\x1b[7m` +
` inverse \ue0b1 \x1b[0;40m\ue0b0` +
` 0 \ue0b1 \x1b[30;41m\ue0b0\x1b[39m` +
` 1 \ue0b1 \x1b[31;42m\ue0b0\x1b[39m` +
` 2 \ue0b1 \x1b[32;43m\ue0b0\x1b[39m` +
` 3 \ue0b1 \x1b[33;44m\ue0b0\x1b[39m` +
` 4 \ue0b1 \x1b[34;45m\ue0b0\x1b[39m` +
` 5 \ue0b1 \x1b[35;46m\ue0b0\x1b[39m` +
` 6 \ue0b1 \x1b[36;47m\ue0b0\x1b[39m` +
` 7 \ue0b1 \x1b[37;49m\ue0b0\x1b[0m`
);
term.writeln('');
term.writeln(
`\x1b[7m` +
` inverse \ue0b3 \x1b[0;7;40m\ue0b2\x1b[27m` +
` 0 \ue0b3 \x1b[7;30;41m\ue0b2\x1b[27;39m` +
` 1 \ue0b3 \x1b[7;31;42m\ue0b2\x1b[27;39m` +
` 2 \ue0b3 \x1b[7;32;43m\ue0b2\x1b[27;39m` +
` 3 \ue0b3 \x1b[7;33;44m\ue0b2\x1b[27;39m` +
` 4 \ue0b3 \x1b[7;34;45m\ue0b2\x1b[27;39m` +
` 5 \ue0b3 \x1b[7;35;46m\ue0b2\x1b[27;39m` +
` 6 \ue0b3 \x1b[7;36;47m\ue0b2\x1b[27;39m` +
` 7 \ue0b3 \x1b[7;37;49m\ue0b2\x1b[0m`
);
term.writeln('');
term.writeln('Powerline extra symbols:');
term.writeln(' 0 1 2 3 4 5 6 7 8 9 A B C D E F');
term.writeln(`0xA_ ${s('\ue0a3')}`);
term.writeln(`0xB_ ${s('\ue0b4')}${s('\ue0b5')}${s('\ue0b6')}${s('\ue0b7')}${s('\ue0b8')}${s('\ue0b9')}${s('\ue0ba')}${s('\ue0bb')}${s('\ue0bc')}${s('\ue0bd')}${s('\ue0be')}${s('\ue0bf')}`);
term.writeln(`0xC_ ${s('\ue0c0')}${s('\ue0c1')}${s('\ue0c2')}${s('\ue0c3')}${s('\ue0c4')}${s('\ue0c5')}${s('\ue0c6')}${s('\ue0c7')}${s('\ue0c8')}${s('\ue0c9')}${s('\ue0ca')}${s('\ue0cb')}${s('\ue0cc')}${s('\ue0cd')}${s('\ue0be')}${s('\ue0bf')}`);
term.writeln(`0xD_ ${s('\ue0d0')}${s('\ue0d1')}${s('\ue0d2')} ${s('\ue0d4')}`);
term.writeln('');
term.writeln('Sample of nerd fonts icons:');
term.writeln(' nf-linux-apple (\\uF302) \uf302');
term.writeln('nf-mdi-github_face (\\uFbd9) \ufbd9');
}
function addDecoration() {
term.options['overviewRulerWidth'] = 15;
const marker = term.addMarker(1);
const decoration = term.registerDecoration({ marker });
decoration.onRender(() => {
decoration.element.style.backgroundColor = 'red';
const decoration = term.registerDecoration({
marker,
backgroundColor: '#00FF00',
foregroundColor: '#00FE00',
overviewRulerOptions: { color: '#ef292980', position: 'left' }
});
decoration.onRender((e: HTMLElement) => {
e.style.right = '100%';
e.style.backgroundColor = '#ef292980';
});
}
function addOverviewRuler() {
term.options['overviewRulerWidth'] = 15;
term.registerDecoration({marker: term.addMarker(1), overviewRulerOptions: { color: '#ef2929' }});
term.registerDecoration({marker: term.addMarker(3), overviewRulerOptions: { color: '#8ae234' }});
term.registerDecoration({marker: term.addMarker(5), overviewRulerOptions: { color: '#729fcf' }});
term.registerDecoration({marker: term.addMarker(7), overviewRulerOptions: { color: '#ef2929', position: 'left' }});
term.registerDecoration({marker: term.addMarker(7), overviewRulerOptions: { color: '#8ae234', position: 'center' }});
term.registerDecoration({marker: term.addMarker(7), overviewRulerOptions: { color: '#729fcf', position: 'right' }});
term.registerDecoration({marker: term.addMarker(10), overviewRulerOptions: { color: '#8ae234', position: 'center' }});
term.registerDecoration({marker: term.addMarker(10), overviewRulerOptions: { color: '#ffffff80', position: 'full' }});
}
+18 -5
View File
@@ -28,7 +28,7 @@
</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>
<p>These options can be set in the <code>Terminal</code> constructor or by using the <code>Terminal.options</code> property.</p>
<div id="options-container"></div>
</div>
<div id="addons" class="tabContent">
@@ -40,9 +40,11 @@
<div style= "display:flex; flex-direction:column;">
<label>Find next <input id="find-next"/></label>
<label>Find previous <input id="find-previous"/></label>
<div>Results: <span id="find-results"></span></div>
<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>
<label><input type="checkbox" id="highlight-all-matches" checked/>Highlight All Matches</label>
</div>
<h4>SerializeAddon</h4>
<div>
@@ -65,10 +67,21 @@
<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>
<button id="add-decoration" title="Add a decoration to the terminal">Decoration</button>
<dl>
<dt>Lifecycle</dt>
<dd><button id="dispose" title="This is used to testing memory leaks">Dispose terminal</button></dd>
<dt>Performance</dt>
<dd><button id="load-test" title="Write several MB of data to simulate a lot of data coming from the process">Load test</button></dd>
<dt>Styles</dt>
<dd><button id="custom-glyph" title="Write custom box drawing and block element characters to the terminal">Test custom glyphs</button></dd>
<dd><button id="powerline-symbol-test" title="Write powerline symbol characters to the terminal (\ue0a0+)">Powerline symbol test</button></dd>
<dt>Decorations</dt>
<dd><button id="add-decoration" title="Add a decoration to the terminal">Decoration</button></dd>
<dd><button id="add-overview-ruler" title="Add an overview ruler to the terminal">Add Overview Ruler</button></dd>
</dl>
</div>
</div>
</div>
+3
View File
@@ -114,6 +114,9 @@ function startServer() {
}
const send = USE_BINARY ? bufferUtf8(ws, 5) : buffer(ws, 5);
// WARNING: This is a naive implementation that will not throttle the flow of data. This means
// it could flood the communication channel and make the terminal unresponsive. Learn more about
// the problem and how to implement flow control at https://xtermjs.org/docs/guides/flowcontrol/
term.on('data', function(data) {
try {
send(data);
+3 -3
View File
@@ -1,7 +1,7 @@
{
"name": "xterm",
"description": "Full xterm terminal, in your browser",
"version": "4.17.0",
"version": "4.19.0",
"main": "lib/xterm.js",
"style": "css/xterm.css",
"types": "typings/xterm.d.ts",
@@ -76,7 +76,7 @@
"mustache": "^4.2.0",
"node-pty": "^0.10.1",
"nyc": "^15.1.0",
"playwright": "^1.16.2",
"playwright": "^1.22.1",
"source-map-loader": "^3.0.0",
"source-map-support": "^0.5.20",
"ts-loader": "^9.1.2",
@@ -85,6 +85,6 @@
"webpack": "^5.61.0",
"webpack-cli": "^4.9.1",
"ws": "^8.2.3",
"xterm-benchmark": "^0.3.0"
"xterm-benchmark": "^0.3.1"
}
}
+2 -1
View File
@@ -3,7 +3,8 @@
* @license MIT
*/
import { IColor, IColorContrastCache } from 'browser/Types';
import { IColorContrastCache } from 'browser/Types';
import { IColor } from 'common/Types';
export class ColorContrastCache implements IColorContrastCache {
private _color: { [bg: number]: { [fg: number]: IColor | null | undefined } | undefined } = {};
+1 -1
View File
@@ -34,7 +34,7 @@ describe('ColorManager', () => {
describe('constructor', () => {
it('should fill all colors with values', () => {
for (const key of Object.keys(cm.colors)) {
if (key !== 'ansi' && key !== 'contrastCache') {
if (key !== 'ansi' && key !== 'contrastCache' && key !== 'selectionForeground') {
// A #rrggbb or rgba(...)
assert.ok((cm.colors as any)[key].css.length >= 7);
}
+14 -4
View File
@@ -3,11 +3,11 @@
* @license MIT
*/
import { IColorManager, IColor, IColorSet, IColorContrastCache } from 'browser/Types';
import { IColorManager, IColorSet, IColorContrastCache } from 'browser/Types';
import { ITheme } from 'common/services/Services';
import { channels, color, css } from 'browser/Color';
import { channels, color, css } from 'common/Color';
import { ColorContrastCache } from 'browser/ColorContrastCache';
import { ColorIndex } from 'common/Types';
import { ColorIndex, IColor } from 'common/Types';
interface IRestoreColorSet {
@@ -104,6 +104,7 @@ export class ColorManager implements IColorManager {
cursorAccent: DEFAULT_CURSOR_ACCENT,
selectionTransparent: DEFAULT_SELECTION,
selectionOpaque: color.blend(DEFAULT_BACKGROUND, DEFAULT_SELECTION),
selectionForeground: undefined,
ansi: DEFAULT_ANSI_COLORS.slice(),
contrastCache: this._contrastCache
};
@@ -128,6 +129,15 @@ export class ColorManager implements IColorManager {
this.colors.cursorAccent = this._parseColor(theme.cursorAccent, DEFAULT_CURSOR_ACCENT, true);
this.colors.selectionTransparent = this._parseColor(theme.selection, DEFAULT_SELECTION, true);
this.colors.selectionOpaque = color.blend(this.colors.background, this.colors.selectionTransparent);
const nullColor: IColor = {
css: '',
rgba: 0
};
this.colors.selectionForeground = theme.selectionForeground ? this._parseColor(theme.selectionForeground, nullColor) : undefined;
if (this.colors.selectionForeground === nullColor) {
this.colors.selectionForeground = undefined;
}
/**
* If selection color is opaque, blend it with background with 0.3 opacity
* Issue #2737
@@ -185,7 +195,7 @@ export class ColorManager implements IColorManager {
foreground: this.colors.foreground,
background: this.colors.background,
cursor: this.colors.cursor,
ansi: [...this.colors.ansi]
ansi: this.colors.ansi.slice()
};
}
+12 -7
View File
@@ -18,6 +18,7 @@ export class Linkifier2 extends Disposable implements ILinkifier2 {
private _linkProviders: ILinkProvider[] = [];
public get currentLink(): ILinkWithState | undefined { return this._currentLink; }
protected _currentLink: ILinkWithState | undefined;
private _mouseDownLink: ILinkWithState | undefined;
private _lastMouseEvent: MouseEvent | undefined;
private _linkCacheDisposables: IDisposable[] = [];
private _lastBufferCell: IBufferCellPosition | undefined;
@@ -61,7 +62,8 @@ export class Linkifier2 extends Disposable implements ILinkifier2 {
this._clearCurrentLink();
}));
this.register(addDisposableDomListener(this._element, 'mousemove', this._onMouseMove.bind(this)));
this.register(addDisposableDomListener(this._element, 'click', this._onClick.bind(this)));
this.register(addDisposableDomListener(this._element, 'mousedown', this._handleMouseDown.bind(this)));
this.register(addDisposableDomListener(this._element, 'mouseup', this._handleMouseUp.bind(this)));
}
private _onMouseMove(event: MouseEvent): void {
@@ -129,7 +131,7 @@ export class Linkifier2 extends Disposable implements ILinkifier2 {
let linkProvided = false;
// There is no link cached, so ask for one
this._linkProviders.forEach((linkProvider, i) => {
for (const [i, linkProvider] of this._linkProviders.entries()) {
if (useLineCache) {
const existingReply = this._activeProviderReplies?.get(i);
// If there isn't a reply, the provider hasn't responded yet.
@@ -156,7 +158,7 @@ export class Linkifier2 extends Disposable implements ILinkifier2 {
}
});
}
});
}
}
private _removeIntersectingLinks(y: number, replies: Map<Number, ILinkWithState[] | undefined>): void {
@@ -222,18 +224,21 @@ export class Linkifier2 extends Disposable implements ILinkifier2 {
return linkProvided;
}
private _onClick(event: MouseEvent): void {
private _handleMouseDown(): void {
this._mouseDownLink = this._currentLink;
}
private _handleMouseUp(event: MouseEvent): void {
if (!this._element || !this._mouseService || !this._currentLink) {
return;
}
const position = this._positionFromMouseEvent(event, this._element, this._mouseService);
if (!position) {
return;
}
if (this._linkAtPosition(this._currentLink.link, position)) {
if (this._mouseDownLink === this._currentLink && this._linkAtPosition(this._currentLink.link, position)) {
this._currentLink.link.activate(event, this._currentLink.link.text);
}
}
@@ -303,7 +308,7 @@ export class Linkifier2 extends Disposable implements ILinkifier2 {
// Add listener for rerendering
if (this._renderService) {
this._linkCacheDisposables.push(this._renderService.onRenderedBufferChange(e => {
this._linkCacheDisposables.push(this._renderService.onRenderedViewportChange(e => {
// When start is 0 a scroll most likely occurred, make sure links above the fold also get
// cleared.
const start = e.start === 0 ? 0 : e.start + 1 + this._bufferService.buffer.ydisp;
+22 -3
View File
@@ -3,16 +3,17 @@
* @license MIT
*/
import { IRenderDebouncer } from 'browser/Types';
import { IRenderDebouncerWithCallback } from 'browser/Types';
/**
* Debounces calls to render terminal rows using animation frames.
*/
export class RenderDebouncer implements IRenderDebouncer {
export class RenderDebouncer implements IRenderDebouncerWithCallback {
private _rowStart: number | undefined;
private _rowEnd: number | undefined;
private _rowCount: number | undefined;
private _animationFrame: number | undefined;
private _refreshCallbacks: FrameRequestCallback[] = [];
constructor(
private _renderCallback: (start: number, end: number) => void
@@ -26,6 +27,14 @@ export class RenderDebouncer implements IRenderDebouncer {
}
}
public addRefreshCallback(callback: FrameRequestCallback): number {
this._refreshCallbacks.push(callback);
if (!this._animationFrame) {
this._animationFrame = window.requestAnimationFrame(() => this._innerRefresh());
}
return this._animationFrame;
}
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
@@ -43,8 +52,11 @@ export class RenderDebouncer implements IRenderDebouncer {
}
private _innerRefresh(): void {
this._animationFrame = undefined;
// Make sure values are set
if (this._rowStart === undefined || this._rowEnd === undefined || this._rowCount === undefined) {
this._runRefreshCallbacks();
return;
}
@@ -55,9 +67,16 @@ export class RenderDebouncer implements IRenderDebouncer {
// Reset debouncer (this happens before render callback as the render could trigger it again)
this._rowStart = undefined;
this._rowEnd = undefined;
this._animationFrame = undefined;
// Run render callback
this._renderCallback(start, end);
this._runRefreshCallbacks();
}
private _runRefreshCallbacks(): void {
for (const callback of this._refreshCallbacks) {
callback(0);
}
this._refreshCallbacks = [];
}
}
+3 -3
View File
@@ -231,7 +231,7 @@ describe('Terminal', () => {
});
term.paste('foo');
});
it('should sanitize \n chars', done => {
it('should sanitize \\n chars', done => {
term.onData(e => {
assert.equal(e, '\rfoo\rbar\r');
done();
@@ -1332,8 +1332,8 @@ describe('Terminal', () => {
(!(i % 3))
? input[i]
: (i % 3 === 1)
? input.substr(i, 2)
: input.substr(i - 1, 2),
? input.slice(i, i + 2)
: input.slice(i - 1, i + 1),
terminal.buffer.lines.get(bufferIndex[0])!.loadCell(bufferIndex[1], new CellData()).getChars());
}
});
+57 -18
View File
@@ -26,7 +26,7 @@ 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';
import { C0 } from 'common/data/EscapeSequences';
import { C0, C1_ESCAPED } from 'common/data/EscapeSequences';
import { WindowsOptionsReportType } from '../common/InputHandler';
import { Renderer } from 'browser/renderer/Renderer';
import { Linkifier } from 'browser/Linkifier';
@@ -45,17 +45,20 @@ import { EventEmitter, IEvent, forwardEvent } from 'common/EventEmitter';
import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine';
import { ColorManager } from 'browser/ColorManager';
import { RenderService } from 'browser/services/RenderService';
import { ICharSizeService, IRenderService, IMouseService, ISelectionService, ISoundService, ICoreBrowserService, ICharacterJoinerService, IDecorationService } 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';
import { Linkifier2 } from 'browser/Linkifier2';
import { CoreBrowserService } from 'browser/services/CoreBrowserService';
import { CoreTerminal } from 'common/CoreTerminal';
import { color, rgba } from 'browser/Color';
import { color, rgba } from 'common/Color';
import { CharacterJoinerService } from 'browser/services/CharacterJoinerService';
import { toRgbString } from 'common/input/XParseColor';
import { DecorationService } from 'browser/services/DecorationService';
import { BufferDecorationRenderer } from 'browser/decorations/BufferDecorationRenderer';
import { OverviewRulerRenderer } from 'browser/decorations/OverviewRulerRenderer';
import { DecorationService } from 'common/services/DecorationService';
import { IDecorationService } from 'common/services/Services';
// Let it work inside Node.js for automated testing purposes.
const document: Document = (typeof window !== 'undefined') ? window.document : null as any;
@@ -71,6 +74,8 @@ export class Terminal extends CoreTerminal implements ITerminal {
private _helperContainer: HTMLElement | undefined;
private _compositionView: HTMLElement | undefined;
private _overviewRulerRenderer: OverviewRulerRenderer | undefined;
// private _visualBellTimer: number;
public browser: IBrowser = Browser as any;
@@ -78,6 +83,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
private _customKeyEventHandler: CustomKeyEventHandler | undefined;
// browser services
private _decorationService: DecorationService;
private _charSizeService: ICharSizeService | undefined;
private _mouseService: IMouseService | undefined;
private _renderService: IRenderService | undefined;
@@ -92,6 +98,12 @@ export class Terminal extends CoreTerminal implements ITerminal {
*/
private _keyDownHandled: boolean = false;
/**
* Records whether a keydown event has occured since the last keyup event, i.e. whether a key
* is currently "pressed".
*/
private _keyDownSeen: 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
@@ -109,7 +121,6 @@ export class Terminal extends CoreTerminal implements ITerminal {
public linkifier: ILinkifier;
public linkifier2: ILinkifier2;
public viewport: IViewport | undefined;
public decorationService: IDecorationService;
private _compositionHelper: ICompositionHelper | undefined;
private _mouseZoneManager: IMouseZoneManager | undefined;
private _accessibilityManager: AccessibilityManager | undefined;
@@ -159,7 +170,8 @@ export class Terminal extends CoreTerminal implements ITerminal {
this.linkifier = this._instantiationService.createInstance(Linkifier);
this.linkifier2 = this.register(this._instantiationService.createInstance(Linkifier2));
this.decorationService = this.register(this._instantiationService.createInstance(DecorationService));
this._decorationService = this._instantiationService.createInstance(DecorationService);
this._instantiationService.setService(IDecorationService, this._decorationService);
// Setup InputHandler listeners
this.register(this._inputHandler.onRequestBell(() => this.bell()));
@@ -212,7 +224,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
const channels = color.toColorRGB(acc === 'ansi'
? this._colorManager.colors.ansi[req.index]
: this._colorManager.colors[acc]);
this.coreService.triggerDataEvent(`${C0.ESC}]${ident};${toRgbString(channels)}${C0.BEL}`);
this.coreService.triggerDataEvent(`${C0.ESC}]${ident};${toRgbString(channels)}${C1_ESCAPED.ST}`);
break;
case ColorRequestType.SET:
if (acc === 'ansi') this._colorManager.colors.ansi[req.index] = rgba.toColor(...req.color);
@@ -471,6 +483,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
this._viewportElement = document.createElement('div');
this._viewportElement.classList.add('xterm-viewport');
fragment.appendChild(this._viewportElement);
this._viewportScrollArea = document.createElement('div');
this._viewportScrollArea.classList.add('xterm-scroll-area');
this._viewportElement.appendChild(this._viewportScrollArea);
@@ -513,7 +526,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
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.register(this._renderService.onRenderedViewportChange(e => this._onRender.fire(e)));
this.onResize(e => this._renderService!.resize(e.cols, e.rows));
this._compositionView = document.createElement('div');
@@ -576,8 +589,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
this.register(this.onScroll(() => this._mouseZoneManager!.clearAll()));
this.linkifier.attachToDom(this.element, this._mouseZoneManager);
this.linkifier2.attachToDom(this.screenElement, this._mouseService, this._renderService);
this.decorationService.attachToDom(this.screenElement, this._renderService, this._bufferService);
this.register(this._instantiationService.createInstance(BufferDecorationRenderer, this.screenElement));
// This event listener must be registered aftre MouseZoneManager is created
this.register(addDisposableDomListener(this.element, 'mousedown', (e: MouseEvent) => this._selectionService!.onMouseDown(e)));
@@ -595,6 +607,14 @@ export class Terminal extends CoreTerminal implements ITerminal {
this._accessibilityManager = new AccessibilityManager(this, this._renderService);
}
if (this.options.overviewRulerWidth) {
this._overviewRulerRenderer = this._instantiationService.createInstance(OverviewRulerRenderer, this._viewportElement, this.screenElement);
}
this.optionsService.onOptionChange(() => {
if (!this._overviewRulerRenderer && this.options.overviewRulerWidth && this._viewportElement && this.screenElement) {
this._overviewRulerRenderer = this._instantiationService.createInstance(OverviewRulerRenderer, this._viewportElement, this.screenElement);
}
});
// Measure the character size
this._charSizeService.measure();
@@ -683,10 +703,13 @@ export class Terminal extends CoreTerminal implements ITerminal {
but = ev.button < 3 ? ev.button : CoreMouseButton.NONE;
break;
case 'wheel':
// only UP/DOWN wheel events are respected
if ((ev as WheelEvent).deltaY !== 0) {
action = (ev as WheelEvent).deltaY < 0 ? CoreMouseAction.UP : CoreMouseAction.DOWN;
const amount = self.viewport!.getLinesScrolled(ev as WheelEvent);
if (amount === 0) {
return false;
}
action = (ev as WheelEvent).deltaY < 0 ? CoreMouseAction.UP : CoreMouseAction.DOWN;
but = CoreMouseButton.WHEEL;
break;
default:
@@ -1003,7 +1026,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
}
public registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined {
return this.decorationService!.registerDecoration(decorationOptions);
return this._decorationService.registerDecoration(decorationOptions);
}
/**
@@ -1070,19 +1093,23 @@ export class Terminal extends CoreTerminal implements ITerminal {
*/
protected _keyDown(event: KeyboardEvent): boolean | undefined {
this._keyDownHandled = false;
this._keyDownSeen = true;
if (this._customKeyEventHandler && this._customKeyEventHandler(event) === false) {
return false;
}
if (!this._compositionHelper!.keydown(event)) {
// Ignore composing with Alt key on Mac when macOptionIsMeta is enabled
const shouldIgnoreComposition = this.browser.isMac && this.options.macOptionIsMeta && event.altKey;
if (!shouldIgnoreComposition && !this._compositionHelper!.keydown(event)) {
if (this.buffer.ybase !== this.buffer.ydisp) {
this._bufferService.scrollToBottom();
}
return false;
}
if (event.key === 'Dead' || event.key === 'AltGraph') {
if (!shouldIgnoreComposition && (event.key === 'Dead' || event.key === 'AltGraph')) {
this._unprocessedDeadKey = true;
}
@@ -1113,6 +1140,14 @@ export class Terminal extends CoreTerminal implements ITerminal {
return true;
}
// HACK: Process A-Z in the keypress event to fix an issue with macOS IMEs where lower case
// letters cannot be input while caps lock is on.
if (event.key && !event.ctrlKey && !event.altKey && !event.metaKey && event.key.length === 1) {
if (event.key.charCodeAt(0) >= 65 && event.key.charCodeAt(0) <= 90) {
return true;
}
}
if (this._unprocessedDeadKey) {
this._unprocessedDeadKey = false;
return true;
@@ -1155,6 +1190,8 @@ export class Terminal extends CoreTerminal implements ITerminal {
}
protected _keyUp(ev: KeyboardEvent): void {
this._keyDownSeen = false;
if (this._customKeyEventHandler && this._customKeyEventHandler(ev) === false) {
return;
}
@@ -1228,7 +1265,8 @@ export class Terminal extends CoreTerminal implements ITerminal {
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' && !ev.composed && !this.optionsService.rawOptions.screenReaderMode) {
// Based on these event traces: https://github.com/xtermjs/xterm.js/issues/3679
if (ev.data && ev.inputType === 'insertText' && (!ev.composed || !this._keyDownSeen) && !this.optionsService.rawOptions.screenReaderMode) {
if (this._keyPressHandled) {
return false;
}
@@ -1301,7 +1339,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
// Don't clear if it's already clear
return;
}
this.buffer.clearMarkers();
this.buffer.clearAllMarkers();
this.buffer.lines.set(0, this.buffer.lines.get(this.buffer.ybase + this.buffer.y)!);
this.buffer.lines.length = 1;
this.buffer.ydisp = 0;
@@ -1334,6 +1372,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
this._setup();
super.reset();
this._selectionService?.reset();
this._decorationService.reset();
// reattach
this._customKeyEventHandler = customKeyEventHandler;
+63 -6
View File
@@ -7,7 +7,7 @@ import { IDisposable, IMarker, ISelectionPosition, ILinkProvider, IDecorationOpt
import { IEvent, EventEmitter } from 'common/EventEmitter';
import { ICharacterJoinerService, ICharSizeService, IMouseService, IRenderService, ISelectionService } from 'browser/services/Services';
import { IRenderDimensions, IRenderer, IRequestRedrawEvent } from 'browser/renderer/Types';
import { IColorSet, ILinkMatcherOptions, ITerminal, ILinkifier, ILinkifier2, IBrowser, IViewport, IColorManager, ICompositionHelper, CharacterJoinerHandler } from 'browser/Types';
import { IColorSet, ILinkMatcherOptions, ITerminal, ILinkifier, ILinkifier2, IBrowser, IViewport, IColorManager, ICompositionHelper, CharacterJoinerHandler, IRenderDebouncer } from 'browser/Types';
import { IBuffer, IBufferStringIterator, IBufferSet } from 'common/buffer/Types';
import { IBufferLine, ICellData, IAttributeData, ICircularList, XtermListener, ICharset, ITerminalOptions } from 'common/Types';
import { Buffer } from 'common/buffer/Buffer';
@@ -16,6 +16,7 @@ import { Terminal } from 'browser/Terminal';
import { IUnicodeService, IOptionsService, ICoreService, ICoreMouseService } from 'common/services/Services';
import { IFunctionIdentifier, IParams } from 'common/parser/Types';
import { AttributeData } from 'common/buffer/AttributeData';
import { ISelectionRedrawRequestEvent, ISelectionRequestScrollLinesEvent } from 'browser/selection/Types';
export class TestTerminal extends Terminal {
public get curAttrData(): IAttributeData { return (this as any)._inputHandler._curAttrData; }
@@ -30,6 +31,7 @@ export class MockTerminal implements ITerminal {
public onBlur!: IEvent<void>;
public onFocus!: IEvent<void>;
public onA11yChar!: IEvent<string>;
public onWriteParsed!: IEvent<void>;
public onA11yTab!: IEvent<number>;
public onCursorMove!: IEvent<void>;
public onLineFeed!: IEvent<void>;
@@ -257,7 +259,10 @@ export class MockBuffer implements IBuffer {
public getWhitespaceCell(attr?: IAttributeData): ICellData {
throw new Error('Method not implemented.');
}
public clearMarkers(): void {
public clearMarkers(y: number): void {
throw new Error('Method not implemented.');
}
public clearAllMarkers(): void {
throw new Error('Method not implemented.');
}
}
@@ -367,7 +372,8 @@ export class MockMouseService implements IMouseService {
export class MockRenderService implements IRenderService {
public serviceBrand: undefined;
public onDimensionsChange: IEvent<IRenderDimensions> = new EventEmitter<IRenderDimensions>().event;
public onRenderedBufferChange: IEvent<{ start: number, end: number }, void> = new EventEmitter<{ start: number, end: number }>().event;
public onRenderedViewportChange: IEvent<{ start: number, end: number }, void> = new EventEmitter<{ start: number, end: number }>().event;
public onRender: IEvent<{ start: number, end: number }, void> = new EventEmitter<{ start: number, end: number }>().event;
public onRefreshRequest: IEvent<{ start: number, end: number}, void> = new EventEmitter<{ start: number, end: number }>().event;
public dimensions: IRenderDimensions = {
scaledCharWidth: 0,
@@ -386,15 +392,15 @@ export class MockRenderService implements IRenderService {
public refreshRows(start: number, end: number): void {
throw new Error('Method not implemented.');
}
public addRefreshCallback(callback: FrameRequestCallback): number {
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.');
}
public changeOptions(): void {
throw new Error('Method not implemented.');
}
public setRenderer(renderer: IRenderer): void {
throw new Error('Method not implemented.');
}
@@ -445,3 +451,54 @@ export class MockCharacterJoinerService implements ICharacterJoinerService {
return [];
}
}
export class MockSelectionService implements ISelectionService {
public serviceBrand: undefined;
public selectionText: string = '';
public hasSelection: boolean = false;
public selectionStart: [number, number] | undefined;
public selectionEnd: [number, number] | undefined;
public onLinuxMouseSelection = new EventEmitter<string>().event;
public onRequestRedraw = new EventEmitter<ISelectionRedrawRequestEvent>().event;
public onRequestScrollLines = new EventEmitter<ISelectionRequestScrollLinesEvent>().event;
public onSelectionChange = new EventEmitter<void>().event;
public disable(): void {
throw new Error('Method not implemented.');
}
public enable(): void {
throw new Error('Method not implemented.');
}
public reset(): void {
throw new Error('Method not implemented.');
}
public setSelection(row: number, col: number, length: number): void {
throw new Error('Method not implemented.');
}
public selectAll(): void {
throw new Error('Method not implemented.');
}
public selectLines(start: number, end: number): void {
throw new Error('Method not implemented.');
}
public clearSelection(): void {
throw new Error('Method not implemented.');
}
public rightClickSelect(event: MouseEvent): void {
throw new Error('Method not implemented.');
}
public shouldColumnSelect(event: MouseEvent | KeyboardEvent): boolean {
throw new Error('Method not implemented.');
}
public shouldForceSelection(event: MouseEvent): boolean {
throw new Error('Method not implemented.');
}
public refresh(isLinuxMouseSelection?: boolean): void {
throw new Error('Method not implemented.');
}
public onMouseDown(event: MouseEvent): void {
throw new Error('Method not implemented.');
}
public isCellInSelection(x: number, y: number): boolean {
return false;
}
}
+7 -6
View File
@@ -5,7 +5,7 @@
import { IDecorationOptions, IDecoration, IDisposable, IMarker, ISelectionPosition } from 'xterm';
import { IEvent } from 'common/EventEmitter';
import { ICoreTerminal, CharData, ITerminalOptions } from 'common/Types';
import { ICoreTerminal, CharData, ITerminalOptions, IColor } from 'common/Types';
import { IMouseService, IRenderService } from './services/Services';
import { IBuffer } from 'common/buffer/Types';
import { IFunctionIdentifier, IParams } from 'common/parser/Types';
@@ -44,6 +44,7 @@ export interface IPublicTerminal extends IDisposable {
onSelectionChange: IEvent<void>;
onRender: IEvent<{ start: number, end: number }>;
onResize: IEvent<{ cols: number, rows: number }>;
onWriteParsed: IEvent<void>;
onTitleChange: IEvent<string>;
onBell: IEvent<void>;
blur(): void;
@@ -112,11 +113,6 @@ export interface IColorManager {
onOptionsChange(key: string): void;
}
export interface IColor {
css: string;
rgba: number; // 32-bit int with rgba in each byte
}
export interface IColorSet {
foreground: IColor;
background: IColor;
@@ -125,6 +121,7 @@ export interface IColorSet {
selectionTransparent: IColor;
/** The selection blended on top of background. */
selectionOpaque: IColor;
selectionForeground: IColor | undefined;
ansi: IColor[];
contrastCache: IColorContrastCache;
}
@@ -314,3 +311,7 @@ export interface ICharacterJoiner {
export interface IRenderDebouncer extends IDisposable {
refresh(rowStart: number | undefined, rowEnd: number | undefined, rowCount: number): void;
}
export interface IRenderDebouncerWithCallback extends IRenderDebouncer {
addRefreshCallback(callback: FrameRequestCallback): number;
}
-18
View File
@@ -26,7 +26,6 @@ export class Viewport extends Disposable implements IViewport {
private _lastRecordedBufferHeight: number = 0;
private _lastTouchY: number = 0;
private _lastScrollTop: number = 0;
private _lastHadScrollBar: boolean = false;
private _activeBuffer: IBuffer;
private _renderDimensions: IRenderDimensions;
@@ -54,7 +53,6 @@ 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
@@ -109,17 +107,6 @@ export class Viewport extends Disposable implements IViewport {
this._viewportElement.scrollTop = scrollTop;
}
// Update scroll bar width
if (this._optionsService.rawOptions.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;
}
@@ -151,11 +138,6 @@ export class Viewport extends Disposable implements IViewport {
this._refresh(immediate);
return;
}
// If the scroll bar visibility changed
if (this._lastHadScrollBar !== (this._optionsService.rawOptions.scrollback > 0)) {
this._refresh(immediate);
}
}
/**
@@ -0,0 +1,131 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { addDisposableDomListener } from 'browser/Lifecycle';
import { IRenderService } from 'browser/services/Services';
import { Disposable } from 'common/Lifecycle';
import { IBufferService, IDecorationService, IInternalDecoration } from 'common/services/Services';
export class BufferDecorationRenderer extends Disposable {
private readonly _container: HTMLElement;
private readonly _decorationElements: Map<IInternalDecoration, HTMLElement> = new Map();
private _animationFrame: number | undefined;
private _altBufferIsActive: boolean = false;
private _dimensionsChanged: boolean = false;
constructor(
private readonly _screenElement: HTMLElement,
@IBufferService private readonly _bufferService: IBufferService,
@IDecorationService private readonly _decorationService: IDecorationService,
@IRenderService private readonly _renderService: IRenderService
) {
super();
this._container = document.createElement('div');
this._container.classList.add('xterm-decoration-container');
this._screenElement.appendChild(this._container);
this.register(this._renderService.onRenderedViewportChange(() => this._queueRefresh()));
this.register(this._renderService.onDimensionsChange(() => {
this._dimensionsChanged = true;
this._queueRefresh();
}));
this.register(addDisposableDomListener(window, 'resize', () => this._queueRefresh()));
this.register(this._bufferService.buffers.onBufferActivate(() => {
this._altBufferIsActive = this._bufferService.buffer === this._bufferService.buffers.alt;
}));
this.register(this._decorationService.onDecorationRegistered(() => this._queueRefresh()));
this.register(this._decorationService.onDecorationRemoved(decoration => this._removeDecoration(decoration)));
}
public override dispose(): void {
this._container.remove();
this._decorationElements.clear();
super.dispose();
}
private _queueRefresh(): void {
if (this._animationFrame !== undefined) {
return;
}
this._animationFrame = this._renderService.addRefreshCallback(() => {
this.refreshDecorations();
this._animationFrame = undefined;
});
}
public refreshDecorations(): void {
for (const decoration of this._decorationService.decorations) {
this._renderDecoration(decoration);
}
this._dimensionsChanged = false;
}
private _renderDecoration(decoration: IInternalDecoration): void {
this._refreshStyle(decoration);
if (this._dimensionsChanged) {
this._refreshXPosition(decoration);
}
}
private _createElement(decoration: IInternalDecoration): HTMLElement {
const element = document.createElement('div');
element.classList.add('xterm-decoration');
element.style.width = `${Math.round((decoration.options.width || 1) * this._renderService.dimensions.actualCellWidth)}px`;
element.style.height = `${(decoration.options.height || 1) * this._renderService.dimensions.actualCellHeight}px`;
element.style.top = `${(decoration.marker.line - this._bufferService.buffers.active.ydisp) * this._renderService.dimensions.actualCellHeight}px`;
element.style.lineHeight = `${this._renderService.dimensions.actualCellHeight}px`;
const x = decoration.options.x ?? 0;
if (x && x > this._bufferService.cols) {
// exceeded the container width, so hide
element.style.display = 'none';
}
this._refreshXPosition(decoration, element);
return element;
}
private _refreshStyle(decoration: IInternalDecoration): void {
const line = decoration.marker.line - this._bufferService.buffers.active.ydisp;
if (line < 0 || line >= this._bufferService.rows) {
// outside of viewport
if (decoration.element) {
decoration.element.style.display = 'none';
decoration.onRenderEmitter.fire(decoration.element);
}
} else {
let element = this._decorationElements.get(decoration);
if (!element) {
decoration.onDispose(() => this._removeDecoration(decoration));
element = this._createElement(decoration);
decoration.element = element;
this._decorationElements.set(decoration, element);
this._container.appendChild(element);
}
element.style.top = `${line * this._renderService.dimensions.actualCellHeight}px`;
element.style.display = this._altBufferIsActive ? 'none' : 'block';
decoration.onRenderEmitter.fire(element);
}
}
private _refreshXPosition(decoration: IInternalDecoration, element: HTMLElement | undefined = decoration.element): void {
if (!element) {
return;
}
const x = decoration.options.x ?? 0;
if ((decoration.options.anchor || 'left') === 'right') {
element.style.right = x ? `${x * this._renderService.dimensions.actualCellWidth}px` : '';
} else {
element.style.left = x ? `${x * this._renderService.dimensions.actualCellWidth}px` : '';
}
}
private _removeDecoration(decoration: IInternalDecoration): void {
this._decorationElements.get(decoration)?.remove();
this._decorationElements.delete(decoration);
}
}
@@ -0,0 +1,88 @@
/**
* Copyright (c) 2017 The xterm.js authors. All rights reserved.
* @license MIT
*/
import { assert } from 'chai';
import { ColorZoneStore } from 'browser/decorations/ColorZoneStore';
const optionsRedFull = {
overviewRulerOptions: {
color: 'red',
position: 'full' as 'full'
}
};
describe('ColorZoneStore', () => {
let store: ColorZoneStore;
beforeEach(() => {
store = new ColorZoneStore();
store.setPadding({
full: 1,
left: 1,
center: 1,
right: 1
});
});
it('should merge adjacent zones', () => {
store.addDecoration({
marker: { line: 0 },
options: optionsRedFull
});
store.addDecoration({
marker: { line: 1 },
options: optionsRedFull
});
assert.deepStrictEqual(store.zones, [
{
color: 'red',
position: 'full',
startBufferLine: 0,
endBufferLine: 1
}
]);
});
it('should not merge non-adjacent zones', () => {
store.addDecoration({
marker: { line: 0 },
options: optionsRedFull
});
store.addDecoration({
marker: { line: 2 },
options: optionsRedFull
});
assert.deepStrictEqual(store.zones, [
{
color: 'red',
position: 'full',
startBufferLine: 0,
endBufferLine: 0
},
{
color: 'red',
position: 'full',
startBufferLine: 2,
endBufferLine: 2
}
]);
});
it('should reuse zone objects', () => {
const obj = {
marker: { line: 0 },
options: optionsRedFull
};
store.addDecoration(obj);
const zone = store.zones[0];
store.clear();
store.addDecoration({
marker: { line: 1 },
options: optionsRedFull
});
// The object reference should be the same
assert.equal(zone, store.zones[0]);
});
});
+117
View File
@@ -0,0 +1,117 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IInternalDecoration } from 'common/services/Services';
export interface IColorZoneStore {
readonly zones: IColorZone[];
clear(): void;
addDecoration(decoration: IInternalDecoration): void;
/**
* Sets the amount of padding in lines that will be added between zones, if new lines intersect
* the padding they will be merged into the same zone.
*/
setPadding(padding: { [position: string]: number }): void;
}
export interface IColorZone {
/** Color in a format supported by canvas' fillStyle. */
color: string;
position: 'full' | 'left' | 'center' | 'right' | undefined;
startBufferLine: number;
endBufferLine: number;
}
interface IMinimalDecorationForColorZone {
marker: Pick<IInternalDecoration['marker'], 'line'>;
options: Pick<IInternalDecoration['options'], 'overviewRulerOptions'>;
}
export class ColorZoneStore implements IColorZoneStore {
private _zones: IColorZone[] = [];
// The zone pool is used to keep zone objects from being freed between clearing the color zone
// store and fetching the zones. This helps reduce GC pressure since the color zones are
// accumulated on potentially every scroll event.
private _zonePool: IColorZone[] = [];
private _zonePoolIndex = 0;
private _linePadding: { [position: string]: number } = {
full: 0,
left: 0,
center: 0,
right: 0
};
public get zones(): IColorZone[] {
// Trim the zone pool to free unused memory
this._zonePool.length = Math.min(this._zonePool.length, this._zones.length);
return this._zones;
}
public clear(): void {
this._zones.length = 0;
this._zonePoolIndex = 0;
}
public addDecoration(decoration: IMinimalDecorationForColorZone): void {
if (!decoration.options.overviewRulerOptions) {
return;
}
for (const z of this._zones) {
if (z.color === decoration.options.overviewRulerOptions.color &&
z.position === decoration.options.overviewRulerOptions.position) {
if (this._lineIntersectsZone(z, decoration.marker.line)) {
return;
}
if (this._lineAdjacentToZone(z, decoration.marker.line, decoration.options.overviewRulerOptions.position)) {
this._addLineToZone(z, decoration.marker.line);
return;
}
}
}
// Create using zone pool if possible
if (this._zonePoolIndex < this._zonePool.length) {
this._zonePool[this._zonePoolIndex].color = decoration.options.overviewRulerOptions.color;
this._zonePool[this._zonePoolIndex].position = decoration.options.overviewRulerOptions.position;
this._zonePool[this._zonePoolIndex].startBufferLine = decoration.marker.line;
this._zonePool[this._zonePoolIndex].endBufferLine = decoration.marker.line;
this._zones.push(this._zonePool[this._zonePoolIndex++]);
return;
}
// Create
this._zones.push({
color: decoration.options.overviewRulerOptions.color,
position: decoration.options.overviewRulerOptions.position,
startBufferLine: decoration.marker.line,
endBufferLine: decoration.marker.line
});
this._zonePool.push(this._zones[this._zones.length - 1]);
this._zonePoolIndex++;
}
public setPadding(padding: { [position: string]: number }): void {
this._linePadding = padding;
}
private _lineIntersectsZone(zone: IColorZone, line: number): boolean {
return (
line >= zone.startBufferLine &&
line <= zone.endBufferLine
);
}
private _lineAdjacentToZone(zone: IColorZone, line: number, position: IColorZone['position']): boolean {
return (
(line >= zone.startBufferLine - this._linePadding[position || 'full']) &&
(line <= zone.endBufferLine + this._linePadding[position || 'full'])
);
}
private _addLineToZone(zone: IColorZone, line: number): void {
zone.startBufferLine = Math.min(zone.startBufferLine, line);
zone.endBufferLine = Math.max(zone.endBufferLine, line);
}
}
@@ -0,0 +1,228 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ColorZoneStore, IColorZone, IColorZoneStore } from 'browser/decorations/ColorZoneStore';
import { addDisposableDomListener } from 'browser/Lifecycle';
import { IRenderService } from 'browser/services/Services';
import { Disposable } from 'common/Lifecycle';
import { IBufferService, IDecorationService, IInternalDecoration, IOptionsService } from 'common/services/Services';
// Helper objects to avoid excessive calculation and garbage collection during rendering. These are
// static values for each render and can be accessed using the decoration position as the key.
const drawHeight = {
full: 0,
left: 0,
center: 0,
right: 0
};
const drawWidth = {
full: 0,
left: 0,
center: 0,
right: 0
};
const drawX = {
full: 0,
left: 0,
center: 0,
right: 0
};
export class OverviewRulerRenderer extends Disposable {
private readonly _canvas: HTMLCanvasElement;
private readonly _ctx: CanvasRenderingContext2D;
private readonly _colorZoneStore: IColorZoneStore = new ColorZoneStore();
private get _width(): number {
return this._optionsService.options.overviewRulerWidth || 0;
}
private _animationFrame: number | undefined;
private _shouldUpdateDimensions: boolean | undefined = true;
private _shouldUpdateAnchor: boolean | undefined = true;
private _lastKnownBufferLength: number = 0;
private _containerHeight: number | undefined;
constructor(
private readonly _viewportElement: HTMLElement,
private readonly _screenElement: HTMLElement,
@IBufferService private readonly _bufferService: IBufferService,
@IDecorationService private readonly _decorationService: IDecorationService,
@IRenderService private readonly _renderService: IRenderService,
@IOptionsService private readonly _optionsService: IOptionsService
) {
super();
this._canvas = document.createElement('canvas');
this._canvas.classList.add('xterm-decoration-overview-ruler');
this._refreshCanvasDimensions();
this._viewportElement.parentElement?.insertBefore(this._canvas, this._viewportElement);
const ctx = this._canvas.getContext('2d');
if (!ctx) {
throw new Error('Ctx cannot be null');
} else {
this._ctx = ctx;
}
this._registerDecorationListeners();
this._registerBufferChangeListeners();
this._registerDimensionChangeListeners();
}
/**
* On decoration add or remove, redraw
*/
private _registerDecorationListeners(): void {
this.register(this._decorationService.onDecorationRegistered(() => this._queueRefresh(undefined, true)));
this.register(this._decorationService.onDecorationRemoved(() => this._queueRefresh(undefined, true)));
}
/**
* On buffer change, redraw
* and hide the canvas if the alt buffer is active
*/
private _registerBufferChangeListeners(): void {
this.register(this._renderService.onRenderedViewportChange(() => this._queueRefresh()));
this.register(this._bufferService.buffers.onBufferActivate(() => {
this._canvas!.style.display = this._bufferService.buffer === this._bufferService.buffers.alt ? 'none' : 'block';
}));
this.register(this._bufferService.onScroll(() => {
if (this._lastKnownBufferLength !== this._bufferService.buffers.normal.lines.length) {
this._refreshDrawHeightConstants();
this._refreshColorZonePadding();
}
}));
}
/**
* On dimension change, update canvas dimensions
* and then redraw
*/
private _registerDimensionChangeListeners(): void {
// container height changed
this.register(this._renderService.onRender((): void => {
if (!this._containerHeight || this._containerHeight !== this._screenElement.clientHeight) {
this._queueRefresh(true);
this._containerHeight = this._screenElement.clientHeight;
}
}));
// overview ruler width changed
this.register(this._optionsService.onOptionChange(o => {
if (o === 'overviewRulerWidth') {
this._queueRefresh(true);
}
}));
// device pixel ratio changed
this.register(addDisposableDomListener(window, 'resize', () => {
this._queueRefresh(true);
}));
// set the canvas dimensions
this._queueRefresh(true);
}
public override dispose(): void {
this._canvas?.remove();
super.dispose();
}
private _refreshDrawConstants(): void {
// width
const outerWidth = Math.floor(this._canvas.width / 3);
const innerWidth = Math.ceil(this._canvas.width / 3);
drawWidth.full = this._canvas.width;
drawWidth.left = outerWidth;
drawWidth.center = innerWidth;
drawWidth.right = outerWidth;
// height
this._refreshDrawHeightConstants();
// x
drawX.full = 0;
drawX.left = 0;
drawX.center = drawWidth.left;
drawX.right = drawWidth.left + drawWidth.center;
}
private _refreshDrawHeightConstants(): void {
drawHeight.full = Math.round(2 * window.devicePixelRatio);
// Calculate actual pixels per line
const pixelsPerLine = this._canvas.height / this._bufferService.buffer.lines.length;
// Clamp actual pixels within a range
const nonFullHeight = Math.round(Math.max(Math.min(pixelsPerLine, 12), 6) * window.devicePixelRatio);
drawHeight.left = nonFullHeight;
drawHeight.center = nonFullHeight;
drawHeight.right = nonFullHeight;
}
private _refreshColorZonePadding(): void {
this._colorZoneStore.setPadding({
full: Math.floor(this._bufferService.buffers.active.lines.length / (this._canvas.height - 1) * drawHeight.full),
left: Math.floor(this._bufferService.buffers.active.lines.length / (this._canvas.height - 1) * drawHeight.left),
center: Math.floor(this._bufferService.buffers.active.lines.length / (this._canvas.height - 1) * drawHeight.center),
right: Math.floor(this._bufferService.buffers.active.lines.length / (this._canvas.height - 1) * drawHeight.right)
});
this._lastKnownBufferLength = this._bufferService.buffers.normal.lines.length;
}
private _refreshCanvasDimensions(): void {
this._canvas.style.width = `${this._width}px`;
this._canvas.width = Math.round(this._width * window.devicePixelRatio);
this._canvas.style.height = `${this._screenElement.clientHeight}px`;
this._canvas.height = Math.round(this._screenElement.clientHeight * window.devicePixelRatio);
this._refreshDrawConstants();
this._refreshColorZonePadding();
}
private _refreshDecorations(): void {
if (this._shouldUpdateDimensions) {
this._refreshCanvasDimensions();
}
this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height);
this._colorZoneStore.clear();
for (const decoration of this._decorationService.decorations) {
this._colorZoneStore.addDecoration(decoration);
}
this._ctx.lineWidth = 1;
const zones = this._colorZoneStore.zones;
for (const zone of zones) {
if (zone.position !== 'full') {
this._renderColorZone(zone);
}
}
for (const zone of zones) {
if (zone.position === 'full') {
this._renderColorZone(zone);
}
}
this._shouldUpdateDimensions = false;
this._shouldUpdateAnchor = false;
}
private _renderColorZone(zone: IColorZone): void {
// TODO: Is _decorationElements needed?
this._ctx.fillStyle = zone.color;
this._ctx.fillRect(
/* x */ drawX[zone.position || 'full'],
/* y */ Math.round(
(this._canvas.height - 1) * // -1 to ensure at least 2px are allowed for decoration on last line
(zone.startBufferLine / this._bufferService.buffers.active.lines.length) - drawHeight[zone.position || 'full'] / 2
),
/* w */ drawWidth[zone.position || 'full'],
/* h */ Math.round(
(this._canvas.height - 1) * // -1 to ensure at least 2px are allowed for decoration on last line
((zone.endBufferLine - zone.startBufferLine) / this._bufferService.buffers.active.lines.length) + drawHeight[zone.position || 'full']
)
);
}
private _queueRefresh(updateCanvasDimensions?: boolean, updateAnchor?: boolean): void {
this._shouldUpdateDimensions = updateCanvasDimensions || this._shouldUpdateDimensions;
this._shouldUpdateAnchor = updateAnchor || this._shouldUpdateAnchor;
if (this._animationFrame !== undefined) {
return;
}
this._animationFrame = window.requestAnimationFrame(() => {
this._refreshDecorations();
this._animationFrame = undefined;
});
}
}
+14 -6
View File
@@ -11,30 +11,38 @@ const CHAR_WIDTH = 10;
const CHAR_HEIGHT = 20;
describe('Mouse getCoords', () => {
let windowOverride: Pick<Window, 'getComputedStyle'>;
let document: Document;
beforeEach(() => {
windowOverride = {
getComputedStyle(): any {
return {
getPropertyValue: () => '0px'
} as Pick<CSSStyleDeclaration, 'getPropertyValue'>;
}
};
document = new jsdom.JSDOM('').window.document;
});
it('should return the cell that was clicked', () => {
let coords: [number, number] | undefined;
coords = getCoords({ clientX: CHAR_WIDTH / 2, clientY: CHAR_HEIGHT / 2 }, document.createElement('div'), 10, 10, true, CHAR_WIDTH, CHAR_HEIGHT);
coords = getCoords(windowOverride, { clientX: CHAR_WIDTH / 2, clientY: CHAR_HEIGHT / 2 }, document.createElement('div'), 10, 10, true, CHAR_WIDTH, CHAR_HEIGHT);
assert.deepEqual(coords, [1, 1]);
coords = getCoords({ clientX: CHAR_WIDTH, clientY: CHAR_HEIGHT }, document.createElement('div'), 10, 10, true, CHAR_WIDTH, CHAR_HEIGHT);
coords = getCoords(windowOverride, { clientX: CHAR_WIDTH, clientY: CHAR_HEIGHT }, document.createElement('div'), 10, 10, true, CHAR_WIDTH, CHAR_HEIGHT);
assert.deepEqual(coords, [1, 1]);
coords = getCoords({ clientX: CHAR_WIDTH, clientY: CHAR_HEIGHT + 1 }, document.createElement('div'), 10, 10, true, CHAR_WIDTH, CHAR_HEIGHT);
coords = getCoords(windowOverride, { clientX: CHAR_WIDTH, clientY: CHAR_HEIGHT + 1 }, document.createElement('div'), 10, 10, true, CHAR_WIDTH, CHAR_HEIGHT);
assert.deepEqual(coords, [1, 2]);
coords = getCoords({ clientX: CHAR_WIDTH + 1, clientY: CHAR_HEIGHT }, document.createElement('div'), 10, 10, true, CHAR_WIDTH, CHAR_HEIGHT);
coords = getCoords(windowOverride, { clientX: CHAR_WIDTH + 1, clientY: CHAR_HEIGHT }, document.createElement('div'), 10, 10, true, CHAR_WIDTH, CHAR_HEIGHT);
assert.deepEqual(coords, [2, 1]);
});
it('should ensure the coordinates are returned within the terminal bounds', () => {
let coords: [number, number] | undefined;
coords = getCoords({ clientX: -1, clientY: -1 }, document.createElement('div'), 10, 10, true, CHAR_WIDTH, CHAR_HEIGHT);
coords = getCoords(windowOverride, { clientX: -1, clientY: -1 }, document.createElement('div'), 10, 10, true, CHAR_WIDTH, CHAR_HEIGHT);
assert.deepEqual(coords, [1, 1]);
// Event are double the cols/rows
coords = getCoords({ clientX: CHAR_WIDTH * 20, clientY: CHAR_HEIGHT * 20 }, document.createElement('div'), 10, 10, true, CHAR_WIDTH, CHAR_HEIGHT);
coords = getCoords(windowOverride, { clientX: CHAR_WIDTH * 20, clientY: CHAR_HEIGHT * 20 }, document.createElement('div'), 10, 10, true, CHAR_WIDTH, CHAR_HEIGHT);
assert.deepEqual(coords, [10, 10], 'coordinates should never come back as larger than the terminal');
});
});
+10 -4
View File
@@ -3,9 +3,15 @@
* @license MIT
*/
export function getCoordsRelativeToElement(event: {clientX: number, clientY: number}, element: HTMLElement): [number, number] {
export function getCoordsRelativeToElement(window: Pick<Window, 'getComputedStyle'>, event: {clientX: number, clientY: number}, element: HTMLElement): [number, number] {
const rect = element.getBoundingClientRect();
return [event.clientX - rect.left, event.clientY - rect.top];
const elementStyle = window.getComputedStyle(element);
const leftPadding = parseInt(elementStyle.getPropertyValue('padding-left'));
const topPadding = parseInt(elementStyle.getPropertyValue('padding-top'));
return [
event.clientX - rect.left - leftPadding,
event.clientY - rect.top - topPadding
];
}
/**
@@ -20,13 +26,13 @@ export function getCoordsRelativeToElement(event: {clientX: number, clientY: num
* apply an offset to the x value such that the left half of the cell will
* select that cell and the right half will select the next cell.
*/
export function getCoords(event: {clientX: number, clientY: number}, element: HTMLElement, colCount: number, rowCount: number, hasValidCharSize: boolean, actualCellWidth: number, actualCellHeight: number, isSelection?: boolean): [number, number] | undefined {
export function getCoords(window: Pick<Window, 'getComputedStyle'>, event: {clientX: number, clientY: number}, element: HTMLElement, colCount: number, rowCount: number, hasValidCharSize: boolean, actualCellWidth: number, actualCellHeight: number, isSelection?: boolean): [number, number] | undefined {
// Coordinates cannot be measured if there are no valid
if (!hasValidCharSize) {
return undefined;
}
const coords = getCoordsRelativeToElement(event, element);
const coords = getCoordsRelativeToElement(window, event, element);
if (!coords) {
return undefined;
}
+1
View File
@@ -74,6 +74,7 @@ export class Terminal implements ITerminalApi {
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 onWriteParsed(): IEvent<void> { return this._core.onWriteParsed; }
public get element(): HTMLElement | undefined { return this._core.element; }
public get parser(): IParser {
+89 -20
View File
@@ -4,18 +4,18 @@
*/
import { IRenderDimensions, IRenderLayer } from 'browser/renderer/Types';
import { ICellData } from 'common/Types';
import { ICellData, IColor } from 'common/Types';
import { DEFAULT_COLOR, WHITESPACE_CELL_CHAR, WHITESPACE_CELL_CODE, Attributes } from 'common/buffer/Constants';
import { IGlyphIdentifier } from 'browser/renderer/atlas/Types';
import { DIM_OPACITY, INVERTED_DEFAULT_COLOR, TEXT_BASELINE } from 'browser/renderer/atlas/Constants';
import { BaseCharAtlas } from 'browser/renderer/atlas/BaseCharAtlas';
import { acquireCharAtlas } from 'browser/renderer/atlas/CharAtlasCache';
import { AttributeData } from 'common/buffer/AttributeData';
import { IColorSet, IColor } from 'browser/Types';
import { IColorSet } from 'browser/Types';
import { CellData } from 'common/buffer/CellData';
import { IBufferService, IOptionsService } from 'common/services/Services';
import { throwIfFalsy } from 'browser/renderer/RendererUtils';
import { channels, color, rgba } from 'browser/Color';
import { IBufferService, IDecorationService, IOptionsService } from 'common/services/Services';
import { excludeFromContrastRatioDemands, throwIfFalsy } from 'browser/renderer/RendererUtils';
import { channels, color, rgba } from 'common/Color';
import { removeElementFromParent } from 'browser/Dom';
import { tryDrawCustomChar } from 'browser/renderer/CustomGlyphs';
@@ -29,6 +29,10 @@ export abstract class BaseRenderLayer implements IRenderLayer {
private _scaledCharLeft: number = 0;
private _scaledCharTop: number = 0;
private _selectionStart: [number, number] | undefined;
private _selectionEnd: [number, number] | undefined;
private _columnSelectMode: boolean = false;
protected _charAtlas: BaseCharAtlas | undefined;
/**
@@ -52,7 +56,8 @@ export abstract class BaseRenderLayer implements IRenderLayer {
protected _colors: IColorSet,
private _rendererId: number,
protected readonly _bufferService: IBufferService,
protected readonly _optionsService: IOptionsService
protected readonly _optionsService: IOptionsService,
protected readonly _decorationService: IDecorationService
) {
this._canvas = document.createElement('canvas');
this._canvas.classList.add(`xterm-${id}-layer`);
@@ -79,7 +84,12 @@ export abstract class BaseRenderLayer implements IRenderLayer {
public onFocus(): void {}
public onCursorMove(): void {}
public onGridChanged(startRow: number, endRow: number): void {}
public onSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean = false): void {}
public onSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean = false): void {
this._selectionStart = start;
this._selectionEnd = end;
this._columnSelectMode = columnSelectMode;
}
public setColors(colorSet: IColorSet): void {
this._refreshCharAtlas(colorSet);
@@ -294,7 +304,7 @@ export abstract class BaseRenderLayer implements IRenderLayer {
* @param bold Whether the text is bold.
*/
protected _drawChars(cell: ICellData, x: number, y: number): void {
const contrastColor = this._getContrastColor(cell);
const contrastColor = this._getContrastColor(cell, x, y);
// skip cache right away if we draw in RGB
// Note: to avoid bad runtime JoinedCellData will be skipped
@@ -325,7 +335,17 @@ export abstract class BaseRenderLayer implements IRenderLayer {
this._currentGlyphIdentifier.bold = !!cell.isBold();
this._currentGlyphIdentifier.dim = !!cell.isDim();
this._currentGlyphIdentifier.italic = !!cell.isItalic();
const atlasDidDraw = this._charAtlas?.draw(this._ctx, this._currentGlyphIdentifier, x * this._scaledCellWidth + this._scaledCharLeft, y * this._scaledCellHeight + this._scaledCharTop);
// Don't try cache the glyph if it uses any decoration foreground/background override.
let hasOverrides = false;
for (const d of this._decorationService.getDecorationsAtCell(x, y)) {
if (d.backgroundColorRGB || d.foregroundColorRGB) {
hasOverrides = true;
break;
}
}
const atlasDidDraw = hasOverrides ? false : this._charAtlas?.draw(this._ctx, this._currentGlyphIdentifier, x * this._scaledCellWidth + this._scaledCharLeft, y * this._scaledCellHeight + this._scaledCharTop);
if (!atlasDidDraw) {
this._drawUncachedChars(cell, x, y);
@@ -427,15 +447,42 @@ export abstract class BaseRenderLayer implements IRenderLayer {
return `${fontStyle} ${fontWeight} ${this._optionsService.rawOptions.fontSize * window.devicePixelRatio}px ${this._optionsService.rawOptions.fontFamily}`;
}
private _getContrastColor(cell: CellData): IColor | undefined {
if (this._optionsService.rawOptions.minimumContrastRatio === 1) {
private _getContrastColor(cell: CellData, x: number, y: number): IColor | undefined {
// Get any decoration foreground/background overrides, this must be fetched before the early
// exist but applied after inverse
let bgOverride: number | undefined;
let fgOverride: number | undefined;
let isTop = false;
for (const d of this._decorationService.getDecorationsAtCell(x, y)) {
if (d.options.layer !== 'top' && isTop) {
continue;
}
if (d.backgroundColorRGB) {
bgOverride = d.backgroundColorRGB.rgba;
}
if (d.foregroundColorRGB) {
fgOverride = d.foregroundColorRGB.rgba;
}
isTop = d.options.layer === 'top';
}
// Apply selection foreground if applicable
if (!isTop) {
if (this._colors.selectionForeground && this._isCellInSelection(x, y)) {
fgOverride = this._colors.selectionForeground.rgba;
}
}
if (!bgOverride && !fgOverride && (this._optionsService.rawOptions.minimumContrastRatio === 1 || excludeFromContrastRatioDemands(cell.getCode()))) {
return undefined;
}
// Try get from cache first
const adjustedColor = this._colors.contrastCache.getColor(cell.bg, cell.fg);
if (adjustedColor !== undefined) {
return adjustedColor || undefined;
if (!bgOverride && !fgOverride) {
// Try get from cache
const adjustedColor = this._colors.contrastCache.getColor(cell.bg, cell.fg);
if (adjustedColor !== undefined) {
return adjustedColor || undefined;
}
}
let fgColor = cell.getFgColor();
@@ -453,13 +500,17 @@ export abstract class BaseRenderLayer implements IRenderLayer {
bgColorMode = temp2;
}
const bgRgba = this._resolveBackgroundRgba(bgColorMode, bgColor, isInverse);
const bgRgba = this._resolveBackgroundRgba(bgOverride !== undefined ? Attributes.CM_RGB : bgColorMode, bgOverride ?? bgColor, isInverse);
const fgRgba = this._resolveForegroundRgba(fgColorMode, fgColor, isInverse, isBold);
const result = rgba.ensureContrastRatio(bgRgba, fgRgba, this._optionsService.rawOptions.minimumContrastRatio);
let result = rgba.ensureContrastRatio(bgOverride ?? bgRgba, fgOverride ?? fgRgba, this._optionsService.rawOptions.minimumContrastRatio);
if (!result) {
this._colors.contrastCache.setColor(cell.bg, cell.fg, null);
return undefined;
if (!fgOverride) {
this._colors.contrastCache.setColor(cell.bg, cell.fg, null);
return undefined;
}
// If it was an override and there was no contrast change, set as the result
result = fgOverride;
}
const color: IColor = {
@@ -470,7 +521,9 @@ export abstract class BaseRenderLayer implements IRenderLayer {
),
rgba: result
};
this._colors.contrastCache.setColor(cell.bg, cell.fg, color);
if (!bgOverride && !fgOverride) {
this._colors.contrastCache.setColor(cell.bg, cell.fg, color);
}
return color;
}
@@ -509,5 +562,21 @@ export abstract class BaseRenderLayer implements IRenderLayer {
return this._colors.foreground.rgba;
}
}
private _isCellInSelection(x: number, y: number): boolean {
const start = this._selectionStart;
const end = this._selectionEnd;
if (!start || !end) {
return false;
}
if (this._columnSelectMode) {
return x >= start[0] && y >= start[1] &&
x < end[0] && y < end[1];
}
return (y > start[1] && y < end[1]) ||
(start[1] === end[1] && y === start[1] && x >= start[0] && x < end[0]) ||
(start[1] < end[1] && y === end[1] && x < end[0]) ||
(start[1] < end[1] && y === start[1] && x >= start[0]);
}
}
+4 -3
View File
@@ -8,7 +8,7 @@ import { BaseRenderLayer } from 'browser/renderer/BaseRenderLayer';
import { ICellData } from 'common/Types';
import { CellData } from 'common/buffer/CellData';
import { IColorSet } from 'browser/Types';
import { IBufferService, IOptionsService, ICoreService } from 'common/services/Services';
import { IBufferService, IOptionsService, ICoreService, IDecorationService } from 'common/services/Services';
import { IEventEmitter } from 'common/EventEmitter';
import { ICoreBrowserService } from 'browser/services/Services';
@@ -40,9 +40,10 @@ export class CursorRenderLayer extends BaseRenderLayer {
@IBufferService bufferService: IBufferService,
@IOptionsService optionsService: IOptionsService,
@ICoreService private readonly _coreService: ICoreService,
@ICoreBrowserService private readonly _coreBrowserService: ICoreBrowserService
@ICoreBrowserService private readonly _coreBrowserService: ICoreBrowserService,
@IDecorationService decorationService: IDecorationService
) {
super(container, 'cursor', zIndex, true, colors, rendererId, bufferService, optionsService);
super(container, 'cursor', zIndex, true, colors, rendererId, bufferService, optionsService, decorationService);
this._state = {
x: 0,
y: 0,
+76 -7
View File
@@ -325,6 +325,37 @@ export const boxDrawingDefinitions: { [character: string]: { [fontWeight: number
'╰': { [Style.NORMAL]: 'C.5,0,.5,.5,1,.5' }
};
interface IVectorShape {
d: string;
type: VectorType;
/** Padding to apply to the vector's x axis in CSS pixels. */
horizontalPadding?: number;
}
const enum VectorType {
FILL,
STROKE
}
/**
* This contains the definitions of the primarily used box drawing characters as vector shapes. The
* reason these characters are defined specially is to avoid common problems if a user's font has
* not been patched with powerline characters and also to get pixel perfect rendering as rendering
* issues can occur around AA/SPAA.
*
* Original symbols defined in https://github.com/powerline/fontpatcher
*/
export const powerlineDefinitions: { [index: string]: IVectorShape } = {
// Right triangle solid
'\u{E0B0}': { d: 'M0,0 L1,.5 L0,1', type: VectorType.FILL },
// Right triangle line
'\u{E0B1}': { d: 'M0,0 L1,.5 L0,1', type: VectorType.STROKE, horizontalPadding: 0.5 },
// Left triangle solid
'\u{E0B2}': { d: 'M1,0 L0,.5 L1,1', type: VectorType.FILL },
// Left triangle line
'\u{E0B3}': { d: 'M1,0 L0,.5 L1,1', type: VectorType.STROKE, horizontalPadding: 0.5 }
};
/**
* Try drawing a custom block element or box drawing character, returning whether it was
* successfully drawn.
@@ -355,6 +386,12 @@ export function tryDrawCustomChar(
return true;
}
const powerlineDefinition = powerlineDefinitions[c];
if (powerlineDefinition) {
drawPowerlineChar(ctx, powerlineDefinition, xOffset, yOffset, scaledCellWidth, scaledCellHeight);
return true;
}
return false;
}
@@ -414,10 +451,10 @@ function drawPatternChar(
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;
r = parseInt(fillStyle.slice(1, 3), 16);
g = parseInt(fillStyle.slice(3, 5), 16);
b = parseInt(fillStyle.slice(5, 7), 16);
a = fillStyle.length > 7 && parseInt(fillStyle.slice(7, 9), 16) || 1;
} else if (fillStyle.startsWith('rgba')) {
([r, g, b, a] = fillStyle.substring(5, fillStyle.length - 1).split(',').map(e => parseFloat(e)));
} else {
@@ -518,6 +555,38 @@ function drawBoxDrawingChar(
}
}
function drawPowerlineChar(
ctx: CanvasRenderingContext2D,
charDefinition: IVectorShape,
xOffset: number,
yOffset: number,
scaledCellWidth: number,
scaledCellHeight: number
): void {
ctx.beginPath();
ctx.lineWidth = window.devicePixelRatio;
for (const instruction of charDefinition.d.split(' ')) {
const type = instruction[0];
const f = svgToCanvasInstructionMap[type];
if (!f) {
console.error(`Could not find drawing instructions for "${type}"`);
continue;
}
const args: string[] = instruction.substring(1).split(',');
if (!args[0] || !args[1]) {
continue;
}
f(ctx, translateArgs(args, scaledCellWidth, scaledCellHeight, xOffset, yOffset, charDefinition.horizontalPadding));
}
if (charDefinition.type === VectorType.STROKE) {
ctx.strokeStyle = ctx.fillStyle;
ctx.stroke();
} else {
ctx.fill();
}
ctx.closePath();
}
function clamp(value: number, max: number, min: number = 0): number {
return Math.max(Math.min(value, max), min);
}
@@ -528,7 +597,7 @@ const svgToCanvasInstructionMap: { [index: string]: any } = {
'M': (ctx: CanvasRenderingContext2D, args: number[]) => ctx.moveTo(args[0], args[1])
};
function translateArgs(args: string[], cellWidth: number, cellHeight: number, xOffset: number, yOffset: number): number[] {
function translateArgs(args: string[], cellWidth: number, cellHeight: number, xOffset: number, yOffset: number, horizontalPadding: number = 0): number[] {
const result = args.map(e => parseFloat(e) || parseInt(e));
if (result.length < 2) {
@@ -537,14 +606,14 @@ function translateArgs(args: string[], cellWidth: number, cellHeight: number, xO
for (let x = 0; x < result.length; x += 2) {
// Translate from 0-1 to 0-cellWidth
result[x] *= cellWidth;
result[x] *= cellWidth - (horizontalPadding * 2 * window.devicePixelRatio);
// Ensure coordinate doesn't escape cell bounds and round to the nearest 0.5 to ensure a crisp
// line at 100% devicePixelRatio
if (result[x] !== 0) {
result[x] = clamp(Math.round(result[x] + 0.5) - 0.5, cellWidth, 0);
}
// Apply the cell's offset (ie. x*cellWidth)
result[x] += xOffset;
result[x] += xOffset + (horizontalPadding * window.devicePixelRatio);
}
for (let y = 1; y < result.length; y += 2) {
+4 -3
View File
@@ -8,7 +8,7 @@ import { BaseRenderLayer } from './BaseRenderLayer';
import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants';
import { is256Color } from 'browser/renderer/atlas/CharAtlasUtils';
import { IColorSet, ILinkifierEvent, ILinkifier, ILinkifier2 } from 'browser/Types';
import { IBufferService, IOptionsService } from 'common/services/Services';
import { IBufferService, IDecorationService, IOptionsService } from 'common/services/Services';
export class LinkRenderLayer extends BaseRenderLayer {
private _state: ILinkifierEvent | undefined;
@@ -21,9 +21,10 @@ export class LinkRenderLayer extends BaseRenderLayer {
linkifier: ILinkifier,
linkifier2: ILinkifier2,
@IBufferService bufferService: IBufferService,
@IOptionsService optionsService: IOptionsService
@IOptionsService optionsService: IOptionsService,
@IDecorationService decorationService: IDecorationService
) {
super(container, 'link', zIndex, true, colors, rendererId, bufferService, optionsService);
super(container, 'link', zIndex, true, colors, rendererId, bufferService, optionsService, decorationService);
linkifier.onShowLinkUnderline(e => this._onShowLinkUnderline(e));
linkifier.onHideLinkUnderline(e => this._onHideLinkUnderline(e));
+4 -1
View File
@@ -14,7 +14,6 @@ import { ICharSizeService } from 'browser/services/Services';
import { IBufferService, IOptionsService, IInstantiationService } from 'common/services/Services';
import { removeTerminalFromCache } from 'browser/renderer/atlas/CharAtlasCache';
import { EventEmitter, IEvent } from 'common/EventEmitter';
import { IDecorationOptions, IDecoration } from 'xterm';
let nextRendererId = 1;
@@ -120,6 +119,10 @@ export class Renderer extends Disposable implements IRenderer {
public onSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean = false): void {
this._runOperation(l => l.onSelectionChanged(start, end, columnSelectMode));
// Selection foreground requires a full re-render
if (this._colors.selectionForeground) {
this._onRequestRedraw.fire({ start: 0, end: this._bufferService.rows - 1 });
}
}
public onCursorMove(): void {
+15
View File
@@ -9,3 +9,18 @@ export function throwIfFalsy<T>(value: T | undefined | null): T {
}
return value;
}
export function isPowerlineGlyph(codepoint: number): boolean {
// Only return true for Powerline symbols which require
// different padding and should be excluded from minimum contrast
// ratio standards
return 0xE0A4 <= codepoint && codepoint <= 0xE0D6;
}
function isBoxOrBlockGlyph(codepoint: number): boolean {
return (0x2500 <= codepoint && codepoint <= 0x259F);
}
export function excludeFromContrastRatioDemands(codepoint: number): boolean {
return isPowerlineGlyph(codepoint) || isBoxOrBlockGlyph(codepoint);
}
+6 -3
View File
@@ -6,7 +6,7 @@
import { IRenderDimensions } from 'browser/renderer/Types';
import { BaseRenderLayer } from 'browser/renderer/BaseRenderLayer';
import { IColorSet } from 'browser/Types';
import { IBufferService, IOptionsService } from 'common/services/Services';
import { IBufferService, IDecorationService, IOptionsService } from 'common/services/Services';
interface ISelectionState {
start?: [number, number];
@@ -24,9 +24,10 @@ export class SelectionRenderLayer extends BaseRenderLayer {
colors: IColorSet,
rendererId: number,
@IBufferService bufferService: IBufferService,
@IOptionsService optionsService: IOptionsService
@IOptionsService optionsService: IOptionsService,
@IDecorationService decorationService: IDecorationService
) {
super(container, 'selection', zIndex, true, colors, rendererId, bufferService, optionsService);
super(container, 'selection', zIndex, true, colors, rendererId, bufferService, optionsService, decorationService);
this._clearState();
}
@@ -53,6 +54,8 @@ export class SelectionRenderLayer extends BaseRenderLayer {
}
public onSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void {
super.onSelectionChanged(start, end, columnSelectMode);
// Selection has not changed
if (!this._didStateChange(start, end, columnSelectMode, this._bufferService.buffer.ydisp)) {
return;
+17 -3
View File
@@ -11,7 +11,7 @@ import { AttributeData } from 'common/buffer/AttributeData';
import { NULL_CELL_CODE, Content } from 'common/buffer/Constants';
import { IColorSet } from 'browser/Types';
import { CellData } from 'common/buffer/CellData';
import { IOptionsService, IBufferService } from 'common/services/Services';
import { IOptionsService, IBufferService, IDecorationService } from 'common/services/Services';
import { ICharacterJoinerService } from 'browser/services/Services';
import { JoinedCellData } from 'browser/services/CharacterJoinerService';
@@ -37,9 +37,10 @@ export class TextRenderLayer extends BaseRenderLayer {
rendererId: number,
@IBufferService bufferService: IBufferService,
@IOptionsService optionsService: IOptionsService,
@ICharacterJoinerService private readonly _characterJoinerService: ICharacterJoinerService
@ICharacterJoinerService private readonly _characterJoinerService: ICharacterJoinerService,
@IDecorationService decorationService: IDecorationService
) {
super(container, 'text', zIndex, alpha, colors, rendererId, bufferService, optionsService);
super(container, 'text', zIndex, alpha, colors, rendererId, bufferService, optionsService, decorationService);
this._state = new GridCache<CharData>();
}
@@ -176,6 +177,19 @@ export class TextRenderLayer extends BaseRenderLayer {
nextFillStyle = this._colors.ansi[cell.getBgColor()].css;
}
// Get any decoration foreground/background overrides, this must be fetched before the early
// exist but applied after inverse
let isTop = false;
for (const d of this._decorationService.getDecorationsAtCell(x, this._bufferService.buffer.ydisp + y)) {
if (d.options.layer !== 'top' && isTop) {
continue;
}
if (d.backgroundColorRGB) {
nextFillStyle = d.backgroundColorRGB.css;
}
isTop = d.options.layer === 'top';
}
if (prevFillStyle === null) {
// This is either the first iteration, or the default background was set. Either way, we
// don't need to draw anything.
+1 -1
View File
@@ -16,7 +16,7 @@ export function generateConfig(scaledCharWidth: number, scaledCharHeight: number
cursor: undefined,
cursorAccent: undefined,
selection: undefined,
ansi: [...colors.ansi]
ansi: colors.ansi.slice()
};
return {
devicePixelRatio: window.devicePixelRatio,
@@ -9,9 +9,9 @@ import { BaseCharAtlas } from 'browser/renderer/atlas/BaseCharAtlas';
import { DEFAULT_ANSI_COLORS } from 'browser/ColorManager';
import { LRUMap } from 'browser/renderer/atlas/LRUMap';
import { isFirefox, isSafari } from 'common/Platform';
import { IColor } from 'browser/Types';
import { IColor } from 'common/Types';
import { throwIfFalsy } from 'browser/renderer/RendererUtils';
import { color } from 'browser/Color';
import { color } from 'common/Color';
// 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.
+12 -9
View File
@@ -9,9 +9,9 @@ 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, IInstantiationService } from 'common/services/Services';
import { IOptionsService, IBufferService, IInstantiationService, IDecorationService } from 'common/services/Services';
import { EventEmitter, IEvent } from 'common/EventEmitter';
import { color } from 'browser/Color';
import { color } from 'common/Color';
import { removeElementFromParent } from 'browser/Dom';
const TERMINAL_CLASS_PREFIX = 'xterm-dom-renderer-owner-';
@@ -87,11 +87,11 @@ export class DomRenderer extends Disposable implements IRenderer {
this._screenElement.appendChild(this._rowContainer);
this._screenElement.appendChild(this._selectionContainer);
this._linkifier.onShowLinkUnderline(e => this._onLinkHover(e));
this._linkifier.onHideLinkUnderline(e => this._onLinkLeave(e));
this.register(this._linkifier.onShowLinkUnderline(e => this._onLinkHover(e)));
this.register(this._linkifier.onHideLinkUnderline(e => this._onLinkLeave(e)));
this._linkifier2.onShowLinkUnderline(e => this._onLinkHover(e));
this._linkifier2.onHideLinkUnderline(e => this._onLinkLeave(e));
this.register(this._linkifier2.onShowLinkUnderline(e => this._onLinkHover(e)));
this.register(this._linkifier2.onHideLinkUnderline(e => this._onLinkLeave(e)));
}
public dispose(): void {
@@ -226,7 +226,7 @@ export class DomRenderer extends Disposable implements IRenderer {
`}` +
`${this._terminalSelector} .${SELECTION_CLASS} div {` +
` position: absolute;` +
` background-color: ${this._colors.selectionTransparent.css};` +
` background-color: ${this._colors.selectionOpaque.css};` +
`}`;
// Colors
this._colors.ansi.forEach((c, i) => {
@@ -281,6 +281,9 @@ export class DomRenderer extends Disposable implements IRenderer {
this._selectionContainer.removeChild(this._selectionContainer.children[0]);
}
this._rowFactory.onSelectionChanged(start, end, columnSelectMode);
this.renderRows(0, this._bufferService.rows - 1);
// Selection does not exist
if (!start || !end) {
return;
@@ -301,8 +304,9 @@ export class DomRenderer extends Disposable implements IRenderer {
const documentFragment = document.createDocumentFragment();
if (columnSelectMode) {
const isXFlipped = start[0] > end[0];
documentFragment.appendChild(
this._createSelectionElement(viewportCappedStartRow, start[0], end[0], viewportCappedEndRow - viewportCappedStartRow + 1)
this._createSelectionElement(viewportCappedStartRow, isXFlipped ? end[0] : start[0], isXFlipped ? start[0] : end[0], viewportCappedEndRow - viewportCappedStartRow + 1)
);
} else {
// Draw first row
@@ -361,7 +365,6 @@ export class DomRenderer extends Disposable implements IRenderer {
for (let y = start; y <= end; y++) {
const rowElement = this._rowElements[y];
rowElement.innerText = '';
const row = y + this._bufferService.buffer.ydisp;
const lineData = this._bufferService.buffer.lines.get(row);
const cursorStyle = this._optionsService.rawOptions.cursorStyle;
@@ -10,8 +10,8 @@ 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 { MockCoreService, MockOptionsService } from 'common/TestUtils.test';
import { css } from 'browser/Color';
import { MockCoreService, MockDecorationService, MockOptionsService } from 'common/TestUtils.test';
import { css } from 'common/Color';
import { MockCharacterJoinerService } from 'browser/TestUtils.test';
describe('DomRendererRowFactory', () => {
@@ -49,7 +49,8 @@ describe('DomRendererRowFactory', () => {
} as any,
new MockCharacterJoinerService(),
new MockOptionsService({ drawBoldTextInBrightColors: true }),
new MockCoreService()
new MockCoreService(),
new MockDecorationService()
);
lineData = createEmptyLineData(2);
});
@@ -183,7 +184,7 @@ describe('DomRendererRowFactory', () => {
lineData.setCell(0, cell);
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>'
'<span class="xterm-bg-2 xterm-fg-1">a</span>'
);
});
@@ -194,7 +195,7 @@ describe('DomRendererRowFactory', () => {
lineData.setCell(0, cell);
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>'
'<span class="xterm-bg-257 xterm-fg-1">a</span>'
);
});
@@ -204,7 +205,7 @@ describe('DomRendererRowFactory', () => {
lineData.setCell(0, cell);
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>'
'<span class="xterm-bg-1 xterm-fg-257">a</span>'
);
});
@@ -229,7 +230,7 @@ describe('DomRendererRowFactory', () => {
lineData.setCell(0, cell);
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>'
'<span style="background-color:#040506;color:#010203;">a</span>'
);
});
@@ -240,7 +241,27 @@ describe('DomRendererRowFactory', () => {
lineData.setCell(0, cell);
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>'
'<span style="background-color:#010203;color:#040506;">a</span>'
);
});
});
describe('selectionForeground', () => {
it('should force selected cells with content to be rendered above the background', () => {
lineData.setCell(0, CellData.fromCharData([DEFAULT_ATTR, 'a', 1, 'a'.charCodeAt(0)]));
lineData.setCell(1, CellData.fromCharData([DEFAULT_ATTR, 'b', 1, 'b'.charCodeAt(0)]));
rowFactory.onSelectionChanged([1, 0], [2, 0], false);
const fragment = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, 20);
assert.equal(getFragmentHtml(fragment),
'<span>a</span><span class="xterm-decoration-top">b</span>'
);
});
it('should force whitespace cells to be rendered above the background', () => {
lineData.setCell(1, CellData.fromCharData([DEFAULT_ATTR, 'a', 1, 'a'.charCodeAt(0)]));
rowFactory.onSelectionChanged([0, 0], [2, 0], false);
const fragment = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, 20);
assert.equal(getFragmentHtml(fragment),
'<span class="xterm-decoration-top"> </span><span class="xterm-decoration-top">a</span>'
);
});
});
+116 -31
View File
@@ -3,15 +3,16 @@
* @license MIT
*/
import { IBufferLine } from 'common/Types';
import { IBufferLine, ICellData, IColor } from 'common/Types';
import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants';
import { NULL_CELL_CODE, WHITESPACE_CELL_CHAR, Attributes } from 'common/buffer/Constants';
import { CellData } from 'common/buffer/CellData';
import { ICoreService, IOptionsService } from 'common/services/Services';
import { color, rgba } from 'browser/Color';
import { IColorSet, IColor } from 'browser/Types';
import { ICharacterJoinerService } from 'browser/services/Services';
import { IBufferService, ICoreService, IDecorationService, IOptionsService } from 'common/services/Services';
import { color, rgba } from 'common/Color';
import { IColorSet } from 'browser/Types';
import { ICharacterJoinerService, ISelectionService } from 'browser/services/Services';
import { JoinedCellData } from 'browser/services/CharacterJoinerService';
import { excludeFromContrastRatioDemands } from 'browser/renderer/RendererUtils';
export const BOLD_CLASS = 'xterm-bold';
export const DIM_CLASS = 'xterm-dim';
@@ -27,12 +28,17 @@ export const CURSOR_STYLE_UNDERLINE_CLASS = 'xterm-cursor-underline';
export class DomRendererRowFactory {
private _workCell: CellData = new CellData();
private _selectionStart: [number, number] | undefined;
private _selectionEnd: [number, number] | undefined;
private _columnSelectMode: boolean = false;
constructor(
private readonly _document: Document,
private _colors: IColorSet,
@ICharacterJoinerService private readonly _characterJoinerService: ICharacterJoinerService,
@IOptionsService private readonly _optionsService: IOptionsService,
@ICoreService private readonly _coreService: ICoreService
@ICoreService private readonly _coreService: ICoreService,
@IDecorationService private readonly _decorationService: IDecorationService
) {
}
@@ -40,6 +46,12 @@ export class DomRendererRowFactory {
this._colors = colors;
}
public onSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void {
this._selectionStart = start;
this._selectionEnd = end;
this._columnSelectMode = columnSelectMode;
}
public createRow(lineData: IBufferLine, row: number, isCursorRow: boolean, cursorStyle: string | undefined, cursorX: number, cursorBlink: boolean, cellWidth: number, cols: number): DocumentFragment {
const fragment = this._document.createDocumentFragment();
@@ -171,6 +183,72 @@ export class DomRendererRowFactory {
bgColorMode = temp2;
}
// Apply any decoration foreground/background overrides, this must happen after inverse has
// been applied
let bgOverride: IColor | undefined;
let fgOverride: IColor | undefined;
let isTop = false;
for (const d of this._decorationService.getDecorationsAtCell(x, row)) {
if (d.options.layer !== 'top' && isTop) {
continue;
}
if (d.backgroundColorRGB) {
bgColorMode = Attributes.CM_RGB;
bg = d.backgroundColorRGB.rgba >> 8 & 0xFFFFFF;
bgOverride = d.backgroundColorRGB;
}
if (d.foregroundColorRGB) {
fgColorMode = Attributes.CM_RGB;
fg = d.foregroundColorRGB.rgba >> 8 & 0xFFFFFF;
fgOverride = d.foregroundColorRGB;
}
isTop = d.options.layer === 'top';
}
// Apply selection foreground if applicable
const isInSelection = this._isCellInSelection(x, row);
if (!isTop) {
if (this._colors.selectionForeground && isInSelection) {
fgColorMode = Attributes.CM_RGB;
fg = this._colors.selectionForeground.rgba >> 8 & 0xFFFFFF;
fgOverride = this._colors.selectionForeground;
}
}
// If in the selection, force the element to be above the selection to improve contrast and
// support opaque selections
if (isInSelection) {
bgOverride = this._colors.selectionOpaque;
isTop = true;
}
// If it's a top decoration, render above the selection
if (isTop) {
charElement.classList.add(`xterm-decoration-top`);
}
// Background
let resolvedBg: IColor;
switch (bgColorMode) {
case Attributes.CM_P16:
case Attributes.CM_P256:
resolvedBg = this._colors.ansi[bg];
charElement.classList.add(`xterm-bg-${bg}`);
break;
case Attributes.CM_RGB:
resolvedBg = rgba.toColor(bg >> 16, bg >> 8 & 0xFF, bg & 0xFF);
this._addStyle(charElement, `background-color:#${padStart((bg >>> 0).toString(16), '0', 6)}`);
break;
case Attributes.CM_DEFAULT:
default:
if (isInverse) {
resolvedBg = this._colors.foreground;
charElement.classList.add(`xterm-bg-${INVERTED_DEFAULT_COLOR}`);
} else {
resolvedBg = this._colors.background;
}
}
// Foreground
switch (fgColorMode) {
case Attributes.CM_P16:
@@ -178,7 +256,7 @@ export class DomRendererRowFactory {
if (cell.isBold() && fg < 8 && this._optionsService.rawOptions.drawBoldTextInBrightColors) {
fg += 8;
}
if (!this._applyMinimumContrast(charElement, this._colors.background, this._colors.ansi[fg])) {
if (!this._applyMinimumContrast(charElement, resolvedBg, this._colors.ansi[fg], cell, bgOverride, undefined)) {
charElement.classList.add(`xterm-fg-${fg}`);
}
break;
@@ -188,35 +266,19 @@ export class DomRendererRowFactory {
(fg >> 8) & 0xFF,
(fg ) & 0xFF
);
if (!this._applyMinimumContrast(charElement, this._colors.background, color)) {
if (!this._applyMinimumContrast(charElement, resolvedBg, color, cell, bgOverride, fgOverride)) {
this._addStyle(charElement, `color:#${padStart(fg.toString(16), '0', 6)}`);
}
break;
case Attributes.CM_DEFAULT:
default:
if (!this._applyMinimumContrast(charElement, this._colors.background, this._colors.foreground)) {
if (!this._applyMinimumContrast(charElement, resolvedBg, this._colors.foreground, cell, bgOverride, undefined)) {
if (isInverse) {
charElement.classList.add(`xterm-fg-${INVERTED_DEFAULT_COLOR}`);
}
}
}
// Background
switch (bgColorMode) {
case Attributes.CM_P16:
case Attributes.CM_P256:
charElement.classList.add(`xterm-bg-${bg}`);
break;
case Attributes.CM_RGB:
this._addStyle(charElement, `background-color:#${padStart(bg.toString(16), '0', 6)}`);
break;
case Attributes.CM_DEFAULT:
default:
if (isInverse) {
charElement.classList.add(`xterm-bg-${INVERTED_DEFAULT_COLOR}`);
}
}
fragment.appendChild(charElement);
x = lastCharX;
@@ -224,18 +286,21 @@ export class DomRendererRowFactory {
return fragment;
}
private _applyMinimumContrast(element: HTMLElement, bg: IColor, fg: IColor): boolean {
if (this._optionsService.rawOptions.minimumContrastRatio === 1) {
private _applyMinimumContrast(element: HTMLElement, bg: IColor, fg: IColor, cell: ICellData, bgOverride: IColor | undefined, fgOverride: IColor | undefined): boolean {
if (this._optionsService.rawOptions.minimumContrastRatio === 1 || excludeFromContrastRatioDemands(cell.getCode())) {
return false;
}
// Try get from cache first
let adjustedColor = this._colors.contrastCache.getColor(this._workCell.bg, this._workCell.fg);
// Try get from cache first, only use the cache when there are no decoration overrides
let adjustedColor: IColor | undefined | null = undefined;
if (!bgOverride && !fgOverride) {
adjustedColor = this._colors.contrastCache.getColor(bg.rgba, fg.rgba);
}
// Calculate and store in cache
if (adjustedColor === undefined) {
adjustedColor = color.ensureContrastRatio(bg, fg, this._optionsService.rawOptions.minimumContrastRatio);
this._colors.contrastCache.setColor(this._workCell.bg, this._workCell.fg, adjustedColor ?? null);
adjustedColor = color.ensureContrastRatio(bgOverride || bg, fgOverride || fg, this._optionsService.rawOptions.minimumContrastRatio);
this._colors.contrastCache.setColor((bgOverride || bg).rgba, (fgOverride || fg).rgba, adjustedColor ?? null);
}
if (adjustedColor) {
@@ -249,6 +314,26 @@ export class DomRendererRowFactory {
private _addStyle(element: HTMLElement, style: string): void {
element.setAttribute('style', `${element.getAttribute('style') || ''}${style};`);
}
private _isCellInSelection(x: number, y: number): boolean {
const start = this._selectionStart;
const end = this._selectionEnd;
if (!start || !end) {
return false;
}
if (this._columnSelectMode) {
if (start[0] <= end[0]) {
return x >= start[0] && y >= start[1] &&
x < end[0] && y <= end[1];
}
return x < start[0] && y >= start[1] &&
x >= end[0] && y <= end[1];
}
return (y > start[1] && y < end[1]) ||
(start[1] === end[1] && y === start[1] && x >= start[0] && x < end[0]) ||
(start[1] < end[1] && y === end[1] && x < end[0]) ||
(start[1] < end[1] && y === start[1] && x >= start[0]);
}
}
function padStart(text: string, padChar: string, length: number): string {
@@ -116,6 +116,12 @@ describe('SelectionModel', () => {
model.selectionStartLength = 4;
assert.deepEqual(model.finalSelectionEnd, [2, 3]);
});
it('should return the end on a different row when start + length overflows onto a following row with selectionEnd inbetween', () => {
model.selectionStart = [78, 2];
model.selectionEnd = [79, 2];
model.selectionStartLength = 4;
assert.deepEqual(model.finalSelectionEnd, [2, 3]);
});
it('should return selection end if selection end is after selection start + length', () => {
model.selectionStart = [2, 2];
model.selectionStartLength = 2;
+6 -1
View File
@@ -92,7 +92,12 @@ export class SelectionModel {
if (this.selectionStartLength) {
// Select the larger of the two when start and end are on the same line
if (this.selectionEnd[1] === this.selectionStart[1]) {
return [Math.max(this.selectionStart[0] + this.selectionStartLength, this.selectionEnd[0]), this.selectionEnd[1]];
// Keep the whole wrapped word/line selected if the content wraps multiple lines
const startPlusLength = this.selectionStart[0] + this.selectionStartLength;
if (startPlusLength > this._bufferService.cols) {
return [startPlusLength % this._bufferService.cols, this.selectionStart[1] + Math.floor(startPlusLength / this._bufferService.cols)];
}
return [Math.max(startPlusLength, this.selectionEnd[0]), this.selectionEnd[1]];
}
}
return this.selectionEnd;
-164
View File
@@ -1,164 +0,0 @@
/**
* Copyright (c) 2022 The xterm.js authors. All rights reserved.
* @license MIT
*/
import { IDecorationService, IRenderService } from 'browser/services/Services';
import { EventEmitter, IEvent } from 'common/EventEmitter';
import { Disposable } from 'common/Lifecycle';
import { IBufferService, IInstantiationService } from 'common/services/Services';
import { IDecorationOptions, IDecoration, IMarker } from 'xterm';
export class DecorationService extends Disposable implements IDecorationService {
private readonly _decorations: Decoration[] = [];
private _container: HTMLElement | undefined;
private _screenElement: HTMLElement | undefined;
private _renderService: IRenderService | undefined;
private _animationFrame: number | undefined;
constructor(@IInstantiationService private readonly _instantiationService: IInstantiationService) { super(); }
public attachToDom(screenElement: HTMLElement, renderService: IRenderService): void {
this._renderService = renderService;
this._screenElement = screenElement;
this._container = document.createElement('div');
this._container.classList.add('xterm-decoration-container');
screenElement.appendChild(this._container);
this.register(this._renderService.onRenderedBufferChange(() => this.refresh()));
this.register(this._renderService.onDimensionsChange(() => this.refresh(true)));
}
public registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined {
if (decorationOptions.marker.isDisposed || !this._container) {
return undefined;
}
const decoration = this._instantiationService.createInstance(Decoration, decorationOptions, this._container);
this._decorations.push(decoration);
decoration.onDispose(() => this._decorations.splice(this._decorations.indexOf(decoration), 1));
this._queueRefresh();
return decoration;
}
private _queueRefresh(): void {
if (this._animationFrame !== undefined) {
return;
}
this._animationFrame = window.requestAnimationFrame(() => {
this.refresh();
this._animationFrame = undefined;
});
}
public refresh(shouldRecreate?: boolean): void {
if (!this._renderService) {
return;
}
for (const decoration of this._decorations) {
decoration.render(this._renderService, shouldRecreate);
}
}
public dispose(): void {
for (const decoration of this._decorations) {
decoration.dispose();
}
if (this._screenElement && this._container && this._screenElement.contains(this._container)) {
this._screenElement.removeChild(this._container);
}
}
}
export class Decoration extends Disposable implements IDecoration {
private readonly _marker: IMarker;
private _element: HTMLElement | undefined;
public isDisposed: boolean = false;
public get element(): HTMLElement | undefined { return this._element; }
public get marker(): IMarker { return this._marker; }
private _onDispose = new EventEmitter<void>();
public get onDispose(): IEvent<void> { return this._onDispose.event; }
private _onRender = new EventEmitter<HTMLElement>();
public get onRender(): IEvent<HTMLElement> { return this._onRender.event; }
public x: number;
public anchor: 'left' | 'right';
public width: number;
public height: number;
constructor(
options: IDecorationOptions,
private readonly _container: HTMLElement,
@IBufferService private readonly _bufferService: IBufferService
) {
super();
this.x = options.x ?? 0;
this._marker = options.marker;
this._marker.onDispose(() => this.dispose());
this.anchor = options.anchor || 'left';
this.width = options.width || 1;
this.height = options.height || 1;
}
public render(renderService: IRenderService, shouldRecreate?: boolean): void {
if (!this._element || shouldRecreate) {
this._createElement(renderService, shouldRecreate);
}
if (this._container && this._element && !this._container.contains(this._element)) {
this._container.append(this._element);
}
this._refreshStyle(renderService);
if (this._element) {
this._onRender.fire(this._element);
}
}
private _createElement(renderService: IRenderService, shouldRecreate?: boolean): void {
if (shouldRecreate && this._element && this._container.contains(this._element)) {
this._container.removeChild(this._element);
}
this._element = document.createElement('div');
this._element.classList.add('xterm-decoration');
this._element.style.width = `${this.width * renderService.dimensions.actualCellWidth}px`;
this._element.style.height = `${this.height * renderService.dimensions.actualCellHeight}px`;
this._element.style.top = `${(this.marker.line - this._bufferService.buffers.active.ydisp) * renderService.dimensions.actualCellHeight}px`;
this._element.style.lineHeight = `${renderService.dimensions.actualCellHeight}px`;
if (this.x && this.x > this._bufferService.cols) {
// exceeded the container width, so hide
this._element.style.display = 'none';
}
if (this.anchor === 'right') {
this._element.style.right = this.x ? `${this.x * renderService.dimensions.actualCellWidth}px` : '';
} else {
this._element.style.left = this.x ? `${this.x * renderService.dimensions.actualCellWidth}px` : '';
}
}
private _refreshStyle(renderService: IRenderService): void {
if (!this._element) {
return;
}
const line = this.marker.line - this._bufferService.buffers.active.ydisp;
if (line < 0 || line > this._bufferService.rows) {
// outside of viewport
this._element.style.display = 'none';
} else {
this._element.style.top = `${line * renderService.dimensions.actualCellHeight}px`;
this._element.style.display = this._bufferService.buffer === this._bufferService.buffers.alt ? 'none' : 'block';
}
}
public override dispose(): void {
if (this.isDisposed) {
return;
}
if (this._element && this._container.contains(this._element)) {
this._container.removeChild(this._element);
}
this.isDisposed = true;
this._onDispose.fire();
}
}
+1
View File
@@ -17,6 +17,7 @@ export class MouseService implements IMouseService {
public getCoords(event: {clientX: number, clientY: number}, element: HTMLElement, colCount: number, rowCount: number, isSelection?: boolean): [number, number] | undefined {
return getCoords(
window,
event,
element,
colCount,
+21 -7
View File
@@ -9,8 +9,8 @@ import { EventEmitter, IEvent } from 'common/EventEmitter';
import { Disposable } from 'common/Lifecycle';
import { ScreenDprMonitor } from 'browser/ScreenDprMonitor';
import { addDisposableDomListener } from 'browser/Lifecycle';
import { IColorSet, IRenderDebouncer } from 'browser/Types';
import { IOptionsService, IBufferService } from 'common/services/Services';
import { IColorSet, IRenderDebouncer, IRenderDebouncerWithCallback } from 'browser/Types';
import { IOptionsService, IBufferService, IDecorationService } from 'common/services/Services';
import { ICharSizeService, IRenderService } from 'browser/services/Services';
interface ISelectionState {
@@ -22,7 +22,7 @@ interface ISelectionState {
export class RenderService extends Disposable implements IRenderService {
public serviceBrand: undefined;
private _renderDebouncer: IRenderDebouncer;
private _renderDebouncer: IRenderDebouncerWithCallback;
private _screenDprMonitor: ScreenDprMonitor;
private _isPaused: boolean = false;
@@ -39,8 +39,10 @@ export class RenderService extends Disposable implements IRenderService {
private _onDimensionsChange = new EventEmitter<IRenderDimensions>();
public get onDimensionsChange(): IEvent<IRenderDimensions> { return this._onDimensionsChange.event; }
private _onRenderedViewportChange = new EventEmitter<{ start: number, end: number }>();
public get onRenderedViewportChange(): IEvent<{ start: number, end: number }> { return this._onRenderedViewportChange.event; }
private _onRender = new EventEmitter<{ start: number, end: number }>();
public get onRenderedBufferChange(): IEvent<{ start: number, end: number }> { return this._onRender.event; }
public get onRender(): IEvent<{ start: number, end: number }> { return this._onRender.event; }
private _onRefreshRequest = new EventEmitter<{ start: number, end: number }>();
public get onRefreshRequest(): IEvent<{ start: number, end: number }> { return this._onRefreshRequest.event; }
@@ -52,6 +54,7 @@ export class RenderService extends Disposable implements IRenderService {
screenElement: HTMLElement,
@IOptionsService optionsService: IOptionsService,
@ICharSizeService private readonly _charSizeService: ICharSizeService,
@IDecorationService decorationService: IDecorationService,
@IBufferService bufferService: IBufferService
) {
super();
@@ -67,9 +70,15 @@ export class RenderService extends Disposable implements IRenderService {
this.register(bufferService.onResize(() => this._fullRefresh()));
this.register(bufferService.buffers.onBufferActivate(() => this._renderer?.clear()));
this.register(optionsService.onOptionChange(() => this._renderer.onOptionsChanged()));
this.register(optionsService.onOptionChange(() => this._handleOptionsChanged()));
this.register(this._charSizeService.onCharSizeChange(() => this.onCharSizeChanged()));
// Do a full refresh whenever any decoration is added or removed. This may not actually result
// in changes but since decorations should be used sparingly or added/removed all in the same
// frame this should have minimal performance impact.
this.register(decorationService.onDecorationRegistered(() => this._fullRefresh()));
this.register(decorationService.onDecorationRemoved(() => this._fullRefresh()));
// No need to register this as renderer is explicitly disposed in RenderService.dispose
this._renderer.onRequestRedraw(e => this.refreshRows(e.start, e.end, true));
@@ -122,8 +131,9 @@ export class RenderService extends Disposable implements IRenderService {
// Fire render event only if it was not a redraw
if (!this._isNextRenderRedrawOnly) {
this._onRender.fire({ start, end });
this._onRenderedViewportChange.fire({ start, end });
}
this._onRender.fire({ start, end });
this._isNextRenderRedrawOnly = true;
}
@@ -132,7 +142,7 @@ export class RenderService extends Disposable implements IRenderService {
this._fireOnCanvasResize();
}
public changeOptions(): void {
private _handleOptionsChanged(): void {
this._renderer.onOptionsChanged();
this.refreshRows(0, this._rowCount - 1);
this._fireOnCanvasResize();
@@ -161,6 +171,10 @@ export class RenderService extends Disposable implements IRenderService {
this._fullRefresh();
}
public addRefreshCallback(callback: FrameRequestCallback): number {
return this._renderDebouncer.addRefreshCallback(callback);
}
private _fullRefresh(): void {
if (this._isPaused) {
this._needsFullRefresh = true;
@@ -340,6 +340,9 @@ describe('SelectionService', () => {
buffer.lines.set(0, stringToRow('foo bar'));
selectionService.selectLineAt(0);
assert.equal(selectionService.selectionText, 'foo bar', 'The selected text is correct');
assert.deepEqual(selectionService.model.selectionStart, [0, 0]);
assert.deepEqual(selectionService.model.selectionEnd, undefined);
assert.deepEqual(selectionService.model.selectionStartLength, 20);
assert.deepEqual(selectionService.model.finalSelectionStart, [0, 0]);
assert.deepEqual(selectionService.model.finalSelectionEnd, [bufferService.cols, 0], 'The actual selection spans the entire column');
});
@@ -350,6 +353,9 @@ describe('SelectionService', () => {
buffer.lines.set(1, line2);
selectionService.selectLineAt(0);
assert.equal(selectionService.selectionText, 'foobar', 'The selected text is correct');
assert.deepEqual(selectionService.model.selectionStart, [0, 0]);
assert.deepEqual(selectionService.model.selectionEnd, undefined);
assert.deepEqual(selectionService.model.selectionStartLength, 40);
assert.deepEqual(selectionService.model.finalSelectionStart, [0, 0]);
assert.deepEqual(selectionService.model.finalSelectionEnd, [bufferService.cols, 1], 'The actual selection spans the entire column');
});
+23 -5
View File
@@ -11,7 +11,7 @@ import { SelectionModel } from 'browser/selection/SelectionModel';
import { CellData } from 'common/buffer/CellData';
import { EventEmitter, IEvent } from 'common/EventEmitter';
import { IMouseService, ISelectionService, IRenderService } from 'browser/services/Services';
import { ILinkifier2 } from 'browser/Types';
import { IBufferRange, ILinkifier2 } from 'browser/Types';
import { IBufferService, IOptionsService, ICoreService } from 'common/services/Services';
import { getCoordsRelativeToElement } from 'browser/input/Mouse';
import { moveToCellSequence } from 'browser/input/MoveToCell';
@@ -207,8 +207,12 @@ export class SelectionService extends Disposable implements ISelectionService {
return '';
}
// For column selection it's not enough to rely on final selection's swapping of reversed
// values, it also needs the x coordinates to swap independently of the y coordinate is needed
const startCol = start[0] < end[0] ? start[0] : end[0];
const endCol = start[0] < end[0] ? end[0] : start[0];
for (let i = start[1]; i <= end[1]; i++) {
const lineText = buffer.translateBufferLineToString(i, true, start[0], end[0]);
const lineText = buffer.translateBufferLineToString(i, true, startCol, endCol);
result.push(lineText);
}
} else {
@@ -308,6 +312,15 @@ export class SelectionService extends Disposable implements ISelectionService {
return this._areCoordsInSelection(coords, start, end);
}
public isCellInSelection(x: number, y: number): boolean {
const start = this._model.finalSelectionStart;
const end = this._model.finalSelectionEnd;
if (!start || !end) {
return false;
}
return this._areCoordsInSelection([x, y], start, end);
}
protected _areCoordsInSelection(coords: [number, number], start: [number, number], end: [number, number]): boolean {
return (coords[1] > start[1] && coords[1] < end[1]) ||
(start[1] === end[1] && coords[1] === start[1] && coords[0] >= start[0] && coords[0] < end[0]) ||
@@ -393,7 +406,7 @@ export class SelectionService extends Disposable implements ISelectionService {
* @param event The mouse event.
*/
private _getMouseEventScrollAmount(event: MouseEvent): number {
let offset = getCoordsRelativeToElement(event, this._screenElement)[1];
let offset = getCoordsRelativeToElement(window, event, this._screenElement)[1];
const terminalHeight = this._renderService.dimensions.canvasHeight;
if (offset >= 0 && offset <= terminalHeight) {
return 0;
@@ -771,6 +784,7 @@ export class SelectionService extends Disposable implements ISelectionService {
this._model.selectionStart = [col, row];
this._model.selectionStartLength = length;
this.refresh();
this._fireEventIfSelectionChanged();
}
public rightClickSelect(ev: MouseEvent): void {
@@ -1002,8 +1016,12 @@ export class SelectionService extends Disposable implements ISelectionService {
*/
protected _selectLineAt(line: number): void {
const wrappedRange = this._bufferService.buffer.getWrappedRangeForLine(line);
const range: IBufferRange = {
start: { x: 0, y: wrappedRange.first },
end: { x: this._bufferService.cols - 1, y: wrappedRange.last }
};
this._model.selectionStart = [0, wrappedRange.first];
this._model.selectionEnd = [this._bufferService.cols, wrappedRange.last];
this._model.selectionStartLength = 0;
this._model.selectionEnd = undefined;
this._model.selectionStartLength = getRangeLength(range, this._bufferService.cols);
}
}
+9 -11
View File
@@ -5,7 +5,7 @@
import { IEvent } from 'common/EventEmitter';
import { IRenderDimensions, IRenderer } from 'browser/renderer/Types';
import { IColorSet } from 'browser/Types';
import { IColorSet, IRenderDebouncer } from 'browser/Types';
import { ISelectionRedrawRequestEvent as ISelectionRequestRedrawEvent, ISelectionRequestScrollLinesEvent } from 'browser/selection/Types';
import { createDecorator } from 'common/services/ServiceRegistry';
import { IDisposable } from 'common/Types';
@@ -49,15 +49,20 @@ export interface IRenderService extends IDisposable {
* Fires when buffer changes are rendered. This does not fire when only cursor
* or selections are rendered.
*/
onRenderedBufferChange: IEvent<{ start: number, end: number }>;
onRenderedViewportChange: IEvent<{ start: number, end: number }>;
/**
* Fires on render
*/
onRender: IEvent<{ start: number, end: number }>;
onRefreshRequest: IEvent<{ start: number, end: number }>;
dimensions: IRenderDimensions;
addRefreshCallback(callback: FrameRequestCallback): number;
refreshRows(start: number, end: number): void;
clearTextureAtlas(): void;
resize(cols: number, rows: number): void;
changeOptions(): void;
setRenderer(renderer: IRenderer): void;
setColors(colors: IColorSet): void;
onDevicePixelRatioChange(): void;
@@ -97,6 +102,7 @@ export interface ISelectionService {
shouldForceSelection(event: MouseEvent): boolean;
refresh(isLinuxMouseSelection?: boolean): void;
onMouseDown(event: MouseEvent): void;
isCellInSelection(x: number, y: number): boolean;
}
export const ISoundService = createDecorator<ISoundService>('SoundService');
@@ -115,11 +121,3 @@ export interface ICharacterJoinerService {
deregister(joinerId: number): boolean;
getJoinedCharacters(row: number): [number, number][];
}
export const IDecorationService = createDecorator<IDecorationService>('DecorationService');
export interface IDecorationService extends IDisposable {
registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined;
refresh(): void;
attachToDom(screenElement: HTMLElement, renderService: IRenderService, bufferService: IBufferService): void;
}
@@ -4,7 +4,7 @@
*/
import { assert } from 'chai';
import { channels, color, css, rgb, rgba, toPaddedHex, contrastRatio } from 'browser/Color';
import { channels, color, css, rgb, rgba, toPaddedHex, contrastRatio } from 'common/Color';
describe('Color', () => {
@@ -153,6 +153,44 @@ describe('Color', () => {
describe('css', () => {
describe('toColor', () => {
it('should convert the #rgb format to an IColor', () => {
assert.deepEqual(css.toColor('#000'), { css: '#000000', rgba: 0x000000FF });
assert.deepEqual(css.toColor('#111'), { css: '#111111', rgba: 0x111111FF });
assert.deepEqual(css.toColor('#222'), { css: '#222222', rgba: 0x222222FF });
assert.deepEqual(css.toColor('#333'), { css: '#333333', rgba: 0x333333FF });
assert.deepEqual(css.toColor('#444'), { css: '#444444', rgba: 0x444444FF });
assert.deepEqual(css.toColor('#555'), { css: '#555555', rgba: 0x555555FF });
assert.deepEqual(css.toColor('#666'), { css: '#666666', rgba: 0x666666FF });
assert.deepEqual(css.toColor('#777'), { css: '#777777', rgba: 0x777777FF });
assert.deepEqual(css.toColor('#888'), { css: '#888888', rgba: 0x888888FF });
assert.deepEqual(css.toColor('#999'), { css: '#999999', rgba: 0x999999FF });
assert.deepEqual(css.toColor('#aaa'), { css: '#aaaaaa', rgba: 0xaaaaaaFF });
assert.deepEqual(css.toColor('#bbb'), { css: '#bbbbbb', rgba: 0xbbbbbbFF });
assert.deepEqual(css.toColor('#ccc'), { css: '#cccccc', rgba: 0xccccccFF });
assert.deepEqual(css.toColor('#ddd'), { css: '#dddddd', rgba: 0xddddddFF });
assert.deepEqual(css.toColor('#eee'), { css: '#eeeeee', rgba: 0xeeeeeeFF });
assert.deepEqual(css.toColor('#fff'), { css: '#ffffff', rgba: 0xffffffFF });
assert.deepEqual(css.toColor('#fff'), { css: '#ffffff', rgba: 0xffffffFF });
});
it('should convert the #rgb format to an IColor', () => {
assert.deepEqual(css.toColor('#0000'), { css: '#00000000', rgba: 0x00000000 });
assert.deepEqual(css.toColor('#1111'), { css: '#11111111', rgba: 0x11111111 });
assert.deepEqual(css.toColor('#2222'), { css: '#22222222', rgba: 0x22222222 });
assert.deepEqual(css.toColor('#3333'), { css: '#33333333', rgba: 0x33333333 });
assert.deepEqual(css.toColor('#4444'), { css: '#44444444', rgba: 0x44444444 });
assert.deepEqual(css.toColor('#5555'), { css: '#55555555', rgba: 0x55555555 });
assert.deepEqual(css.toColor('#6666'), { css: '#66666666', rgba: 0x66666666 });
assert.deepEqual(css.toColor('#7777'), { css: '#77777777', rgba: 0x77777777 });
assert.deepEqual(css.toColor('#8888'), { css: '#88888888', rgba: 0x88888888 });
assert.deepEqual(css.toColor('#9999'), { css: '#99999999', rgba: 0x99999999 });
assert.deepEqual(css.toColor('#aaaa'), { css: '#aaaaaaaa', rgba: 0xaaaaaaaa });
assert.deepEqual(css.toColor('#bbbb'), { css: '#bbbbbbbb', rgba: 0xbbbbbbbb });
assert.deepEqual(css.toColor('#cccc'), { css: '#cccccccc', rgba: 0xcccccccc });
assert.deepEqual(css.toColor('#dddd'), { css: '#dddddddd', rgba: 0xdddddddd });
assert.deepEqual(css.toColor('#eeee'), { css: '#eeeeeeee', rgba: 0xeeeeeeee });
assert.deepEqual(css.toColor('#ffff'), { css: '#ffffffff', rgba: 0xffffffff });
assert.deepEqual(css.toColor('#ffff'), { css: '#ffffffff', rgba: 0xffffffff });
});
it('should convert the #rrggbb format to an IColor', () => {
assert.deepEqual(css.toColor('#000000'), { css: '#000000', rgba: 0x000000FF });
assert.deepEqual(css.toColor('#101010'), { css: '#101010', rgba: 0x101010FF });
@@ -191,6 +229,20 @@ describe('Color', () => {
assert.deepEqual(css.toColor('#f0f0f0f0'), { css: '#f0f0f0f0', rgba: 0xf0f0f0f0 });
assert.deepEqual(css.toColor('#ffffffff'), { css: '#ffffffff', rgba: 0xffffffff });
});
it('should convert the rgb() format to an IColor', () => {
assert.deepEqual(css.toColor('rgb(0, 0, 0)'), { css: '#000000ff', rgba: 0x000000ff });
assert.deepEqual(css.toColor('rgb(80, 0, 0)'), { css: '#500000ff', rgba: 0x500000ff });
assert.deepEqual(css.toColor('rgb(0, 80, 0)'), { css: '#005000ff', rgba: 0x005000ff });
assert.deepEqual(css.toColor('rgb(0, 0, 80)'), { css: '#000050ff', rgba: 0x000050ff });
assert.deepEqual(css.toColor('rgb(255, 255, 255)'), { css: '#ffffffff', rgba: 0xffffffff });
});
it('should convert the rgba() format to an IColor', () => {
assert.deepEqual(css.toColor('rgba(0, 0, 0, 0)'), { css: '#00000000', rgba: 0x00000000 });
assert.deepEqual(css.toColor('rgba(80, 0, 0, 0.5)'), { css: '#50000080', rgba: 0x50000080 });
assert.deepEqual(css.toColor('rgba(0, 80, 0, 0.5)'), { css: '#00500080', rgba: 0x00500080 });
assert.deepEqual(css.toColor('rgba(0, 0, 80, 0.5)'), { css: '#00005080', rgba: 0x00005080 });
assert.deepEqual(css.toColor('rgba(255, 255, 255, 1)'), { css: '#ffffffff', rgba: 0xffffffff });
});
});
});
+71 -22
View File
@@ -3,8 +3,7 @@
* @license MIT
*/
import { IColor } from 'browser/Types';
import { IColorRGB } from 'common/Types';
import { IColor, IColorRGB } from 'common/Types';
/**
* Helper functions where the source type is "channels" (individual color channels as numbers).
@@ -95,17 +94,40 @@ export namespace color {
*/
export namespace css {
export function toColor(css: string): IColor {
switch (css.length) {
case 7: // #rrggbb
return {
css,
rgba: (parseInt(css.slice(1), 16) << 8 | 0xFF) >>> 0
};
case 9: // #rrggbbaa
return {
css,
rgba: parseInt(css.slice(1), 16) >>> 0
};
if (css.match(/#[0-9a-f]{3,8}/i)) {
switch (css.length) {
case 4: { // #rgb
const r = parseInt(css.slice(1, 2).repeat(2), 16);
const g = parseInt(css.slice(2, 3).repeat(2), 16);
const b = parseInt(css.slice(3, 4).repeat(2), 16);
return rgba.toColor(r, g, b);
}
case 5: { // #rgba
const r = parseInt(css.slice(1, 2).repeat(2), 16);
const g = parseInt(css.slice(2, 3).repeat(2), 16);
const b = parseInt(css.slice(3, 4).repeat(2), 16);
const a = parseInt(css.slice(4, 5).repeat(2), 16);
return rgba.toColor(r, g, b, a);
}
case 7: // #rrggbb
return {
css,
rgba: (parseInt(css.slice(1), 16) << 8 | 0xFF) >>> 0
};
case 9: // #rrggbbaa
return {
css,
rgba: parseInt(css.slice(1), 16) >>> 0
};
}
}
const rgbaMatch = css.match(/rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(,\s*(0|1|\d?\.(\d+))\s*)?\)/);
if (rgbaMatch) { // rgb() or rgba()
const r = parseInt(rgbaMatch[1]);
const g = parseInt(rgbaMatch[2]);
const b = parseInt(rgbaMatch[3]);
const a = Math.round((rgbaMatch[5] === undefined ? 1 : parseFloat(rgbaMatch[5])) * 0xFF);
return rgba.toColor(r, g, b, a);
}
throw new Error('css.toColor: Unsupported css format');
}
@@ -151,15 +173,42 @@ export namespace rgb {
* Helper functions where the source type is "rgba" (number: 0xrrggbbaa).
*/
export namespace rgba {
/**
* Given a foreground color and a background color, either increase or reduce the luminance of the
* foreground color until the specified contrast ratio is met. If pure white or black is hit
* without the contrast ratio being met, go the other direction using the background color as the
* foreground color and take either the first or second result depending on which has the higher
* contrast ratio.
*
* `undefined` will be returned if the contrast ratio is already met.
*
* @param bgRgba The background color in rgba format.
* @param fgRgba The foreground color in rgba format.
* @param ratio The contrast ratio to achieve.
*/
export function ensureContrastRatio(bgRgba: number, fgRgba: number, ratio: number): number | undefined {
const bgL = rgb.relativeLuminance(bgRgba >> 8);
const fgL = rgb.relativeLuminance(fgRgba >> 8);
const cr = contrastRatio(bgL, fgL);
if (cr < ratio) {
if (fgL < bgL) {
return reduceLuminance(bgRgba, fgRgba, ratio);
const resultA = reduceLuminance(bgRgba, fgRgba, ratio);
const resultARatio = contrastRatio(bgL, rgb.relativeLuminance(resultA >> 8));
if (resultARatio < ratio) {
const resultB = increaseLuminance(bgRgba, bgRgba, ratio);
const resultBRatio = contrastRatio(bgL, rgb.relativeLuminance(resultB >> 8));
return resultARatio > resultBRatio ? resultA : resultB;
}
return resultA;
}
return increaseLuminance(bgRgba, fgRgba, ratio);
const resultA = increaseLuminance(bgRgba, fgRgba, ratio);
const resultARatio = contrastRatio(bgL, rgb.relativeLuminance(resultA >> 8));
if (resultARatio < ratio) {
const resultB = reduceLuminance(bgRgba, bgRgba, ratio);
const resultBRatio = contrastRatio(bgL, rgb.relativeLuminance(resultB >> 8));
return resultARatio > resultBRatio ? resultA : resultB;
}
return resultA;
}
return undefined;
}
@@ -173,13 +222,13 @@ export namespace rgba {
let fgR = (fgRgba >> 24) & 0xFF;
let fgG = (fgRgba >> 16) & 0xFF;
let fgB = (fgRgba >> 8) & 0xFF;
let cr = contrastRatio(rgb.relativeLuminance2(fgR, fgB, fgG), rgb.relativeLuminance2(bgR, bgG, bgB));
let cr = contrastRatio(rgb.relativeLuminance2(fgR, fgG, fgB), rgb.relativeLuminance2(bgR, bgG, bgB));
while (cr < ratio && (fgR > 0 || fgG > 0 || fgB > 0)) {
// Reduce by 10% until the ratio is hit
fgR -= Math.max(0, Math.ceil(fgR * 0.1));
fgG -= Math.max(0, Math.ceil(fgG * 0.1));
fgB -= Math.max(0, Math.ceil(fgB * 0.1));
cr = contrastRatio(rgb.relativeLuminance2(fgR, fgB, fgG), rgb.relativeLuminance2(bgR, bgG, bgB));
cr = contrastRatio(rgb.relativeLuminance2(fgR, fgG, fgB), rgb.relativeLuminance2(bgR, bgG, bgB));
}
return (fgR << 24 | fgG << 16 | fgB << 8 | 0xFF) >>> 0;
}
@@ -193,13 +242,13 @@ export namespace rgba {
let fgR = (fgRgba >> 24) & 0xFF;
let fgG = (fgRgba >> 16) & 0xFF;
let fgB = (fgRgba >> 8) & 0xFF;
let cr = contrastRatio(rgb.relativeLuminance2(fgR, fgB, fgG), rgb.relativeLuminance2(bgR, bgG, bgB));
let cr = contrastRatio(rgb.relativeLuminance2(fgR, fgG, fgB), rgb.relativeLuminance2(bgR, bgG, bgB));
while (cr < ratio && (fgR < 0xFF || fgG < 0xFF || fgB < 0xFF)) {
// Increase by 10% until the ratio is hit
fgR = Math.min(0xFF, fgR + Math.ceil((255 - fgR) * 0.1));
fgG = Math.min(0xFF, fgG + Math.ceil((255 - fgG) * 0.1));
fgB = Math.min(0xFF, fgB + Math.ceil((255 - fgB) * 0.1));
cr = contrastRatio(rgb.relativeLuminance2(fgR, fgB, fgG), rgb.relativeLuminance2(bgR, bgG, bgB));
cr = contrastRatio(rgb.relativeLuminance2(fgR, fgG, fgB), rgb.relativeLuminance2(bgR, bgG, bgB));
}
return (fgR << 24 | fgG << 16 | fgB << 8 | 0xFF) >>> 0;
}
@@ -209,10 +258,10 @@ export namespace rgba {
return [(value >> 24) & 0xFF, (value >> 16) & 0xFF, (value >> 8) & 0xFF, value & 0xFF];
}
export function toColor(r: number, g: number, b: number): IColor {
export function toColor(r: number, g: number, b: number, a?: number): IColor {
return {
css: channels.toCss(r, g, b),
rgba: channels.toRgba(r, g, b)
css: channels.toCss(r, g, b, a),
rgba: channels.toRgba(r, g, b, a)
};
}
}
+3
View File
@@ -68,6 +68,8 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal {
private _onResize = new EventEmitter<{ cols: number, rows: number }>();
public get onResize(): IEvent<{ cols: number, rows: number }> { return this._onResize.event; }
protected _onScroll = new EventEmitter<IScrollEvent, void>();
public get onWriteParsed(): IEvent<void> { return this._onWriteParsed.event; }
protected _onWriteParsed = new EventEmitter<void>();
/**
* Internally we track the source of the scroll but this is meaningless outside the library so
* it's filtered out.
@@ -138,6 +140,7 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal {
// Setup WriteBuffer
this._writeBuffer = new WriteBuffer((data, promiseResult) => this._inputHandler.parse(data, promiseResult));
this.register(forwardEvent(this._writeBuffer.onWriteParsed, this._onWriteParsed));
}
public dispose(): void {
+107
View File
@@ -0,0 +1,107 @@
/**
* Copyright (c) 2018 The xterm.js authors. All rights reserved.
* @license MIT
*/
import { assert } from 'chai';
import { SortedList } from 'common/SortedList';
const deepStrictEqual = assert.deepStrictEqual;
describe('SortedList', () => {
let list: SortedList<number>;
function assertList(expected: number[]): void {
deepStrictEqual(Array.from(list.values()), expected);
}
beforeEach(() => {
list = new SortedList<number>(e => e);
});
describe('insert', () => {
it('should maintain sorted values', () => {
list.insert(10);
assertList([10]);
list.insert(8);
assertList([8, 10]);
list.insert(15);
assertList([8, 10, 15]);
list.insert(2);
assertList([2, 8, 10, 15]);
list.insert(1);
assertList([1, 2, 8, 10, 15]);
list.insert(6);
assertList([1, 2, 6, 8, 10, 15]);
});
it('should allow duplicates of the same key', () => {
list.insert(5);
assertList([5]);
list.insert(5);
assertList([5, 5]);
list.insert(8);
assertList([5, 5, 8]);
list.insert(5);
assertList([5, 5, 5, 8]);
list.insert(8);
assertList([5, 5, 5, 8, 8]);
list.insert(6);
assertList([5, 5, 5, 6, 8, 8]);
});
});
it('delete', () => {
list.insert(1);
list.insert(2);
list.insert(4);
list.insert(3);
list.insert(5);
assertList([1, 2, 3, 4, 5]);
list.delete(1);
assertList([2, 3, 4, 5]);
list.delete(3);
assertList([2, 4, 5]);
list.delete(4);
assertList([2, 5]);
list.delete(5);
assertList([2]);
list.delete(2);
assertList([]);
});
it('getKeyIterator', () => {
list.insert(5);
list.insert(5);
list.insert(8);
list.insert(5);
list.insert(8);
list.insert(6);
assertList([5, 5, 5, 6, 8, 8]);
deepStrictEqual(Array.from(list.getKeyIterator(1)), []);
deepStrictEqual(Array.from(list.getKeyIterator(5)), [5, 5, 5]);
deepStrictEqual(Array.from(list.getKeyIterator(6)), [6]);
deepStrictEqual(Array.from(list.getKeyIterator(8)), [8, 8]);
deepStrictEqual(Array.from(list.getKeyIterator(9)), []);
});
it('clear', () => {
list.insert(1);
list.insert(2);
list.insert(4);
list.insert(3);
list.insert(5);
list.clear();
assertList([]);
});
it('custom key', () => {
const customList = new SortedList<{ key: number }>(e => e.key);
customList.insert({ key: 5 });
customList.insert({ key: 2 });
customList.insert({ key: 10 });
customList.insert({ key: 5 });
customList.insert({ key: 6 });
deepStrictEqual(Array.from(customList.values()), [
{ key: 2 },
{ key: 5 },
{ key: 5 },
{ key: 6 },
{ key: 10 }
]);
});
});
+88
View File
@@ -0,0 +1,88 @@
/**
* Copyright (c) 2022 The xterm.js authors. All rights reserved.
* @license MIT
*/
/**
* A generic list that is maintained in sorted order and allows values with duplicate keys. This
* list is based on binary search and as such locating a key will take O(log n) amortized, this
* includes the by key iterator.
*/
export class SortedList<T> {
private readonly _array: T[] = [];
constructor(
private readonly _getKey: (value: T) => number
) {
}
public clear(): void {
this._array.length = 0;
}
public insert(value: T): void {
if (this._array.length === 0) {
this._array.push(value);
return;
}
const i = this._search(this._getKey(value), 0, this._array.length - 1);
this._array.splice(i, 0, value);
}
public delete(value: T): boolean {
if (this._array.length === 0) {
return false;
}
const key = this._getKey(value);
let i = this._search(key, 0, this._array.length - 1);
if (this._getKey(this._array[i]) !== key) {
return false;
}
do {
if (this._array[i] === value) {
this._array.splice(i, 1);
return true;
}
} while (++i < this._array.length && this._getKey(this._array[i]) === key);
return false;
}
public *getKeyIterator(key: number): IterableIterator<T> {
if (this._array.length === 0) {
return;
}
let i = this._search(key, 0, this._array.length - 1);
if (i < 0 || i >= this._array.length) {
return;
}
if (this._getKey(this._array[i]) !== key) {
return;
}
do {
yield this._array[i];
} while (++i < this._array.length && this._getKey(this._array[i]) === key);
}
public values(): IterableIterator<T> {
return this._array.values();
}
private _search(key: number, min: number, max: number): number {
if (max < min) {
return min;
}
let mid = Math.floor((min + max) / 2);
if (this._getKey(this._array[mid]) > key) {
return this._search(key, min, mid - 1);
}
if (this._getKey(this._array[mid]) < key) {
return this._search(key, mid + 1, max);
}
// Value found! Since keys can be duplicates, move the result index back to the lowest index
// that matches the key.
while (mid > 0 && this._getKey(this._array[mid - 1]) === key) {
mid--;
}
return mid;
}
}
+14 -1
View File
@@ -3,7 +3,7 @@
* @license MIT
*/
import { IBufferService, ICoreService, ILogService, IOptionsService, ITerminalOptions, IDirtyRowService, ICoreMouseService, ICharsetService, IUnicodeService, IUnicodeVersionProvider, LogLevelEnum } from 'common/services/Services';
import { IBufferService, ICoreService, ILogService, IOptionsService, ITerminalOptions, IDirtyRowService, ICoreMouseService, ICharsetService, IUnicodeService, IUnicodeVersionProvider, LogLevelEnum, IDecorationService, IInternalDecoration } from 'common/services/Services';
import { IEvent, EventEmitter } from 'common/EventEmitter';
import { clone } from 'common/Clone';
import { DEFAULT_OPTIONS } from 'common/services/OptionsService';
@@ -11,6 +11,7 @@ import { IBufferSet, IBuffer } from 'common/buffer/Types';
import { BufferSet } from 'common/buffer/BufferSet';
import { IDecPrivateModes, ICoreMouseEvent, CoreMouseEventType, ICharset, IModes, IAttributeData } from 'common/Types';
import { UnicodeV6 } from 'common/input/UnicodeV6';
import { IDecorationOptions, IDecoration } from 'xterm';
export class MockBufferService implements IBufferService {
public serviceBrand: any;
@@ -158,3 +159,15 @@ export class MockUnicodeService implements IUnicodeService {
throw new Error('Method not implemented.');
}
}
export class MockDecorationService implements IDecorationService {
public serviceBrand: any;
public get decorations(): IterableIterator<IInternalDecoration> { return [].values(); }
public onDecorationRegistered = new EventEmitter<IInternalDecoration>().event;
public onDecorationRemoved = new EventEmitter<IInternalDecoration>().event;
public registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined { return undefined; }
public reset(): void { }
public *getDecorationsAtLine(line: number): IterableIterator<IInternalDecoration> { }
public *getDecorationsAtCell(x: number, line: number): IterableIterator<IInternalDecoration> { }
public dispose(): void { }
}
+6
View File
@@ -50,6 +50,7 @@ export interface IKeyboardEvent {
keyCode: number;
key: string;
type: string;
code: string;
}
export interface IScrollEvent {
@@ -102,6 +103,11 @@ export interface ICharset {
}
export type CharData = [number, string, number, number];
export interface IColor {
css: string;
rgba: number; // 32-bit int with rgba in each byte
}
export type IColorRGB = [number, number, number];
export interface IExtendedAttrs {
+22 -13
View File
@@ -585,20 +585,29 @@ export class Buffer implements IBuffer {
return x >= this._cols ? this._cols - 1 : x < 0 ? 0 : x;
}
public clearMarkers(y?: number): void {
/**
* Clears markers on single line.
* @param y The line to clear.
*/
public clearMarkers(y: number): void {
this._isClearing = true;
if (y !== undefined) {
for (let i = 0; i < this.markers.length; i++) {
if (this.markers[i].line === y) {
this.markers[i].dispose();
this.markers.splice(i--, 1);
}
for (let i = 0; i < this.markers.length; i++) {
if (this.markers[i].line === y) {
this.markers[i].dispose();
this.markers.splice(i--, 1);
}
} else {
for (const marker of this.markers) {
marker.dispose();
}
this.markers = [];
}
this._isClearing = false;
}
/**
* Clears markers on all lines
*/
public clearAllMarkers(): void {
this._isClearing = true;
for (let i = 0; i < this.markers.length; i++) {
this.markers[i].dispose();
this.markers.splice(i--, 1);
}
this._isClearing = false;
}
@@ -659,7 +668,7 @@ export class Buffer implements IBuffer {
export class BufferStringIterator implements IBufferStringIterator {
private _current: number;
constructor (
constructor(
private _buffer: IBuffer,
private _trimRight: boolean,
private _startIndex: number = 0,
+3 -2
View File
@@ -10,7 +10,7 @@ import { IEvent } from 'common/EventEmitter';
export type BufferIndex = [number, number];
export interface IBufferStringIteratorResult {
range: {first: number, last: number};
range: { first: number, last: number };
content: string;
}
@@ -45,7 +45,8 @@ export interface IBuffer {
getNullCell(attr?: IAttributeData): ICellData;
getWhitespaceCell(attr?: IAttributeData): ICellData;
addMarker(y: number): IMarker;
clearMarkers(y?: number): void;
clearMarkers(y: number): void;
clearAllMarkers(): void;
}
export interface IBufferSet extends IDisposable {
+3
View File
@@ -148,3 +148,6 @@ export namespace C1 {
/** Application Program Command */
export const APC = '\x9f';
}
export namespace C1_ESCAPED {
export const ST = `${C0.ESC}\\`;
}
+6
View File
@@ -13,6 +13,7 @@ function testEvaluateKeyboardEvent(partialEvent: {
shiftKey?: boolean;
metaKey?: boolean;
keyCode?: number;
code?: string;
key?: string;
type?: string;
}, partialOptions: {
@@ -26,6 +27,7 @@ function testEvaluateKeyboardEvent(partialEvent: {
shiftKey: partialEvent.shiftKey || false,
metaKey: partialEvent.metaKey || false,
keyCode: partialEvent.keyCode !== undefined ? partialEvent.keyCode : 0,
code: partialEvent.code || '',
key: partialEvent.key || '',
type: partialEvent.type || ''
};
@@ -313,5 +315,9 @@ describe('Keyboard', () => {
assert.equal(testEvaluateKeyboardEvent({ shiftKey: true, keyCode: 49, key: '!' }).key, '!');
});
it('should return proper sequence for ctrl+@', () => {
assert.equal(testEvaluateKeyboardEvent({ ctrlKey: true, shiftKey: true, keyCode: 50, key: '@' }).key, '\x00');
});
});
});
+24 -1
View File
@@ -230,6 +230,8 @@ export function evaluateKeyboardEvent(
// page up
if (ev.shiftKey) {
result.type = KeyboardResultType.PAGE_UP;
} else if (ev.ctrlKey) {
result.key = C0.ESC + '[5;' + (modifiers + 1) + '~';
} else {
result.key = C0.ESC + '[5~';
}
@@ -238,6 +240,8 @@ export function evaluateKeyboardEvent(
// page down
if (ev.shiftKey) {
result.type = KeyboardResultType.PAGE_DOWN;
} else if (ev.ctrlKey) {
result.key = C0.ESC + '[6;' + (modifiers + 1) + '~';
} else {
result.key = C0.ESC + '[6~';
}
@@ -354,7 +358,23 @@ export function evaluateKeyboardEvent(
result.key = C0.ESC + key;
} else if (ev.keyCode >= 65 && ev.keyCode <= 90) {
const keyCode = ev.ctrlKey ? ev.keyCode - 64 : ev.keyCode + 32;
result.key = C0.ESC + String.fromCharCode(keyCode);
let keyString = String.fromCharCode(keyCode);
if (ev.shiftKey) {
keyString = keyString.toUpperCase();
}
result.key = C0.ESC + keyString;
} else if (ev.key === 'Dead' && ev.code.startsWith('Key')) {
// Reference: https://github.com/xtermjs/xterm.js/issues/3725
// Alt will produce a "dead key" (initate composition) with some
// of the letters in US layout (e.g. N/E/U).
// It's safe to match against Key* since no other `code` values begin with "Key".
// https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/code/code_values#code_values_on_mac
let keyString = ev.code.slice(3, 4);
if (!ev.shiftKey) {
keyString = keyString.toLowerCase();
}
result.key = C0.ESC + keyString;
result.cancel = true;
}
} else if (isMac && !ev.altKey && !ev.ctrlKey && !ev.shiftKey && ev.metaKey) {
if (ev.keyCode === 65) { // cmd + a
@@ -367,6 +387,9 @@ export function evaluateKeyboardEvent(
if (ev.key === '_') { // ^_
result.key = C0.US;
}
if (ev.key === '@') { // ^ + shift + 2 = ^ + @
result.key = C0.NUL;
}
}
break;
}
+5
View File
@@ -4,6 +4,8 @@
* @license MIT
*/
import { EventEmitter, IEvent } from 'common/EventEmitter';
declare const setTimeout: (handler: () => void, timeout?: number) => void;
/**
@@ -44,6 +46,8 @@ export class WriteBuffer {
private _bufferOffset = 0;
private _isSyncWriting = false;
private _syncCalls = 0;
public get onWriteParsed(): IEvent<void> { return this._onWriteParsed.event; }
private _onWriteParsed = new EventEmitter<void>();
constructor(private _action: (data: string | Uint8Array, promiseResult?: boolean) => void | Promise<boolean>) { }
@@ -220,5 +224,6 @@ export class WriteBuffer {
this._pendingData = 0;
this._bufferOffset = 0;
}
this._onWriteParsed.fire();
}
}
+1 -1
View File
@@ -5,7 +5,7 @@
// 'rgb:' rule - matching: r/g/b | rr/gg/bb | rrr/ggg/bbb | rrrr/gggg/bbbb (hex digits)
const RGB_REX = /^([\da-f]{1})\/([\da-f]{1})\/([\da-f]{1})$|^([\da-f]{2})\/([\da-f]{2})\/([\da-f]{2})$|^([\da-f]{3})\/([\da-f]{3})\/([\da-f]{3})$|^([\da-f]{4})\/([\da-f]{4})\/([\da-f]{4})$/;
const RGB_REX = /^([\da-f])\/([\da-f])\/([\da-f])$|^([\da-f]{2})\/([\da-f]{2})\/([\da-f]{2})$|^([\da-f]{3})\/([\da-f]{3})\/([\da-f]{3})$|^([\da-f]{4})\/([\da-f]{4})\/([\da-f]{4})$/;
// '#...' rule - matching any hex digits
const HASH_REX = /^[\da-f]+$/;
+139
View File
@@ -0,0 +1,139 @@
/**
* Copyright (c) 2022 The xterm.js authors. All rights reserved.
* @license MIT
*/
import { css } from 'common/Color';
import { EventEmitter } from 'common/EventEmitter';
import { Disposable } from 'common/Lifecycle';
import { IDecorationService, IInternalDecoration } from 'common/services/Services';
import { SortedList } from 'common/SortedList';
import { IColor } from 'common/Types';
import { IDecorationOptions, IDecoration, IMarker, IEvent } from 'xterm';
export class DecorationService extends Disposable implements IDecorationService {
public serviceBrand: any;
/**
* A list of all decorations, sorted by the marker's line value. This relies on the fact that
* while marker line values do change, they should all change by the same amount so this should
* never become out of order.
*/
private readonly _decorations: SortedList<IInternalDecoration> = new SortedList(e => e.marker.line);
private _onDecorationRegistered = this.register(new EventEmitter<IInternalDecoration>());
public get onDecorationRegistered(): IEvent<IInternalDecoration> { return this._onDecorationRegistered.event; }
private _onDecorationRemoved = this.register(new EventEmitter<IInternalDecoration>());
public get onDecorationRemoved(): IEvent<IInternalDecoration> { return this._onDecorationRemoved.event; }
public get decorations(): IterableIterator<IInternalDecoration> { return this._decorations.values(); }
constructor() {
super();
}
public registerDecoration(options: IDecorationOptions): IDecoration | undefined {
if (options.marker.isDisposed) {
return undefined;
}
const decoration = new Decoration(options);
if (decoration) {
const markerDispose = decoration.marker.onDispose(() => decoration.dispose());
decoration.onDispose(() => {
if (decoration) {
if (this._decorations.delete(decoration)) {
this._onDecorationRemoved.fire(decoration);
}
markerDispose.dispose();
}
});
this._decorations.insert(decoration);
this._onDecorationRegistered.fire(decoration);
}
return decoration;
}
public reset(): void {
for (const d of this._decorations.values()) {
d.dispose();
}
this._decorations.clear();
}
public *getDecorationsAtLine(line: number): IterableIterator<IInternalDecoration> {
return this._decorations.getKeyIterator(line);
}
public *getDecorationsAtCell(x: number, line: number, layer?: 'bottom' | 'top'): IterableIterator<IInternalDecoration> {
let xmin = 0;
let xmax = 0;
for (const d of this._decorations.getKeyIterator(line)) {
xmin = d.options.x ?? 0;
xmax = xmin + (d.options.width ?? 1);
if (x >= xmin && x < xmax && (!layer || (d.options.layer ?? 'bottom') === layer)) {
yield d;
}
}
}
public dispose(): void {
for (const d of this._decorations.values()) {
this._onDecorationRemoved.fire(d);
}
this.reset();
}
}
class Decoration extends Disposable implements IInternalDecoration {
public readonly marker: IMarker;
public element: HTMLElement | undefined;
public isDisposed: boolean = false;
public readonly onRenderEmitter = this.register(new EventEmitter<HTMLElement>());
public readonly onRender = this.onRenderEmitter.event;
private _onDispose = this.register(new EventEmitter<void>());
public readonly onDispose = this._onDispose.event;
private _cachedBg: IColor | undefined | null = null;
public get backgroundColorRGB(): IColor | undefined {
if (this._cachedBg === null) {
if (this.options.backgroundColor) {
this._cachedBg = css.toColor(this.options.backgroundColor);
} else {
this._cachedBg = undefined;
}
}
return this._cachedBg;
}
private _cachedFg: IColor | undefined | null = null;
public get foregroundColorRGB(): IColor | undefined {
if (this._cachedFg === null) {
if (this.options.foregroundColor) {
this._cachedFg = css.toColor(this.options.foregroundColor);
} else {
this._cachedFg = undefined;
}
}
return this._cachedFg;
}
constructor(
public readonly options: IDecorationOptions
) {
super();
this.marker = options.marker;
if (this.options.overviewRulerOptions && !this.options.overviewRulerOptions.position) {
this.options.overviewRulerOptions.position = 'full';
}
}
public override dispose(): void {
if (this._isDisposed) {
return;
}
this._isDisposed = true;
this._onDispose.fire();
super.dispose();
}
}
+2 -1
View File
@@ -52,7 +52,8 @@ export const DEFAULT_OPTIONS: Readonly<ITerminalOptions> = {
altClickMovesCursor: true,
convertEol: false,
termName: 'xterm',
cancelEvents: false
cancelEvents: false,
overviewRulerWidth: undefined
};
const FONT_WEIGHT_OPTIONS: Extract<FontWeight, string>[] = ['normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900'];
+25 -2
View File
@@ -3,10 +3,11 @@
* @license MIT
*/
import { IEvent } from 'common/EventEmitter';
import { IEvent, IEventEmitter } from 'common/EventEmitter';
import { IBuffer, IBufferSet } from 'common/buffer/Types';
import { IDecPrivateModes, ICoreMouseEvent, CoreMouseEncoding, ICoreMouseProtocol, CoreMouseEventType, ICharset, IWindowOptions, IModes, IAttributeData, ScrollSource } from 'common/Types';
import { IDecPrivateModes, ICoreMouseEvent, CoreMouseEncoding, ICoreMouseProtocol, CoreMouseEventType, ICharset, IWindowOptions, IModes, IAttributeData, ScrollSource, IDisposable, IColorRGB, IColor } from 'common/Types';
import { createDecorator } from 'common/services/ServiceRegistry';
import { IDecorationOptions, IDecoration } from 'xterm';
export const IBufferService = createDecorator<IBufferService>('BufferService');
export interface IBufferService {
@@ -245,6 +246,7 @@ export interface ITerminalOptions {
windowsMode: boolean;
windowOptions: IWindowOptions;
wordSeparator: string;
overviewRulerWidth?: number;
[key: string]: any;
cancelEvents: boolean;
@@ -257,6 +259,7 @@ export interface ITheme {
cursor?: string;
cursorAccent?: string;
selection?: string;
selectionForeground?: string;
black?: string;
red?: string;
green?: string;
@@ -298,3 +301,23 @@ export interface IUnicodeVersionProvider {
readonly version: string;
wcwidth(ucs: number): 0 | 1 | 2;
}
export const IDecorationService = createDecorator<IDecorationService>('DecorationService');
export interface IDecorationService extends IDisposable {
serviceBrand: undefined;
readonly decorations: IterableIterator<IInternalDecoration>;
readonly onDecorationRegistered: IEvent<IInternalDecoration>;
readonly onDecorationRemoved: IEvent<IInternalDecoration>;
registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined;
reset(): void;
/** Iterates over the decorations at a line (in no particular order). */
getDecorationsAtLine(line: number): IterableIterator<IInternalDecoration>;
/** Iterates over the decorations at a cell (in no particular order). */
getDecorationsAtCell(x: number, line: number, layer?: 'bottom' | 'top'): IterableIterator<IInternalDecoration>;
}
export interface IInternalDecoration extends IDecoration {
readonly options: IDecorationOptions;
readonly backgroundColorRGB: IColor | undefined;
readonly foregroundColorRGB: IColor | undefined;
readonly onRenderEmitter: IEventEmitter<HTMLElement>;
}
+2 -1
View File
@@ -4,6 +4,7 @@
"composite": true,
"strict": true,
"declarationMap": true,
"experimentalDecorators": true
"experimentalDecorators": true,
"downlevelIteration": true
}
}
+27 -27
View File
@@ -362,7 +362,7 @@ describe('InputHandler Integration Tests', function(): void {
await pollFor(page, async () => await page.evaluate(`(() => _stack)()`), []);
});
it('14 - GetWinSizePixels', async function(): Promise<any> {
await page.evaluate(`window.term.setOption('windowOptions', { getWinSizePixels: true }); `);
await page.evaluate(`window.term.options.windowOptions = { getWinSizePixels: true }; `);
await page.evaluate(`(() => {
window._stack = [];
const _h = window.term.onData(data => window._stack.push(data));
@@ -373,7 +373,7 @@ describe('InputHandler Integration Tests', function(): void {
await pollFor(page, async () => await page.evaluate(`(() => _stack)()`), [`\x1b[4;${d.height};${d.width}t`]);
});
it('16 - GetCellSizePixels', async function(): Promise<any> {
await page.evaluate(`window.term.setOption('windowOptions', { getCellSizePixels: true }); `);
await page.evaluate(`window.term.options.windowOptions = { getCellSizePixels: true }; `);
await page.evaluate(`(() => {
window._stack = [];
const _h = window.term.onData(data => window._stack.push(data));
@@ -399,13 +399,13 @@ describe('InputHandler Integration Tests', function(): void {
});
it('query single color', async () => {
await writeSync(page, '\x1b]4;0;?\x07');
assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]4;0;rgb:2e2e/3434/3636\x07']);
assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]4;0;rgb:2e2e/3434/3636\x1b\\']);
await writeSync(page, '\x1b]4;77;?\x07');
assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]4;0;rgb:2e2e/3434/3636\x07', '\x1b]4;77;rgb:5f5f/d7d7/5f5f\x07']);
assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]4;0;rgb:2e2e/3434/3636\x1b\\', '\x1b]4;77;rgb:5f5f/d7d7/5f5f\x1b\\']);
});
it('query multiple colors', async () => {
await writeSync(page, '\x1b]4;0;?;77;?\x07');
assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]4;0;rgb:2e2e/3434/3636\x07', '\x1b]4;77;rgb:5f5f/d7d7/5f5f\x07']);
assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]4;0;rgb:2e2e/3434/3636\x1b\\', '\x1b]4;77;rgb:5f5f/d7d7/5f5f\x1b\\']);
});
it('set & query single color', async () => {
await writeSync(page, '\x1b]4;0;?\x07');
@@ -413,10 +413,10 @@ describe('InputHandler Integration Tests', function(): void {
assert.deepEqual(await page.evaluate('window._recordedData'), restore);
// set new color & query
await writeSync(page, '\x1b]4;0;rgb:01/02/03\x07\x1b]4;0;?\x07');
assert.deepEqual(await page.evaluate('window._recordedData'), [restore[0], '\x1b]4;0;rgb:0101/0202/0303\x07']);
assert.deepEqual(await page.evaluate('window._recordedData'), [restore[0], '\x1b]4;0;rgb:0101/0202/0303\x1b\\']);
// restore should set old color
await writeSync(page, restore[0] + '\x1b]4;0;?\x07');
assert.deepEqual(await page.evaluate('window._recordedData'), [restore[0], '\x1b]4;0;rgb:0101/0202/0303\x07', restore[0]]);
assert.deepEqual(await page.evaluate('window._recordedData'), [restore[0], '\x1b]4;0;rgb:0101/0202/0303\x1b\\', restore[0]]);
});
it('query & set colors mixed', async () => {
await writeSync(page, '\x1b]4;0;?;77;?\x07');
@@ -424,11 +424,11 @@ describe('InputHandler Integration Tests', function(): void {
await page.evaluate('window._recordedData.length = 0;');
// mixed call - change 0, query 43, change 77
await writeSync(page, '\x1b]4;0;rgb:01/02/03;43;?;77;#aabbcc\x07');
assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]4;43;rgb:0000/d7d7/afaf\x07']);
assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]4;43;rgb:0000/d7d7/afaf\x1b\\']);
await page.evaluate('window._recordedData.length = 0;');
// query new values for 0 + 77
await writeSync(page, '\x1b]4;0;?;77;?\x07');
assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]4;0;rgb:0101/0202/0303\x07', '\x1b]4;77;rgb:aaaa/bbbb/cccc\x07']);
assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]4;0;rgb:0101/0202/0303\x1b\\', '\x1b]4;77;rgb:aaaa/bbbb/cccc\x1b\\']);
await page.evaluate('window._recordedData.length = 0;');
// restore old values for 0 + 77
await writeSync(page, restore[0] + restore[1] + '\x1b]4;0;?;77;?\x07');
@@ -451,10 +451,10 @@ describe('InputHandler Integration Tests', function(): void {
await writeSync(page, `\x1b]4;${i};?\x07`);
const restore: string[] = await page.evaluate('window._recordedData');
await writeSync(page, `\x1b]4;${i};rgb:01/02/03\x07\x1b]4;${i};?\x07`);
assert.deepEqual(await page.evaluate('window._recordedData'), [restore[0], `\x1b]4;${i};rgb:0101/0202/0303\x07`]);
assert.deepEqual(await page.evaluate('window._recordedData'), [restore[0], `\x1b]4;${i};rgb:0101/0202/0303\x1b\\`]);
// restore slot color
await writeSync(page, `\x1b]104;${i}\x07\x1b]4;${i};?\x07`);
assert.deepEqual(await page.evaluate('window._recordedData'), [restore[0], `\x1b]4;${i};rgb:0101/0202/0303\x07`, restore[0]]);
assert.deepEqual(await page.evaluate('window._recordedData'), [restore[0], `\x1b]4;${i};rgb:0101/0202/0303\x1b\\`, restore[0]]);
await page.evaluate('window._recordedData.length = 0;');
}
});
@@ -491,62 +491,62 @@ describe('InputHandler Integration Tests', function(): void {
});
it('query FG color', async () => {
await writeSync(page, '\x1b]10;?\x07');
assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]10;rgb:ffff/ffff/ffff\x07']);
assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]10;rgb:ffff/ffff/ffff\x1b\\']);
});
it('query BG color', async () => {
await writeSync(page, '\x1b]11;?\x07');
assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]11;rgb:0000/0000/0000\x07']);
assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]11;rgb:0000/0000/0000\x1b\\']);
});
it('query FG & BG color in one call', async () => {
await writeSync(page, '\x1b]10;?;?\x07');
assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]10;rgb:ffff/ffff/ffff\x07', '\x1b]11;rgb:0000/0000/0000\x07']);
assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]10;rgb:ffff/ffff/ffff\x1b\\', '\x1b]11;rgb:0000/0000/0000\x1b\\']);
});
it('set & query FG', async () => {
await writeSync(page, '\x1b]10;rgb:1/2/3\x07\x1b]10;?\x07');
assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]10;rgb:1111/2222/3333\x07']);
assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]10;rgb:1111/2222/3333\x1b\\']);
await writeSync(page, '\x1b]10;#ffffff\x07\x1b]10;?\x07');
assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]10;rgb:1111/2222/3333\x07', '\x1b]10;rgb:ffff/ffff/ffff\x07']);
assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]10;rgb:1111/2222/3333\x1b\\', '\x1b]10;rgb:ffff/ffff/ffff\x1b\\']);
});
it('set & query BG', async () => {
await writeSync(page, '\x1b]11;rgb:1/2/3\x07\x1b]11;?\x07');
assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]11;rgb:1111/2222/3333\x07']);
assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]11;rgb:1111/2222/3333\x1b\\']);
await writeSync(page, '\x1b]11;#000000\x07\x1b]11;?\x07');
assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]11;rgb:1111/2222/3333\x07', '\x1b]11;rgb:0000/0000/0000\x07']);
assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]11;rgb:1111/2222/3333\x1b\\', '\x1b]11;rgb:0000/0000/0000\x1b\\']);
});
it('set & query cursor color', async () => {
await writeSync(page, '\x1b]12;rgb:1/2/3\x07\x1b]12;?\x07');
assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]12;rgb:1111/2222/3333\x07']);
assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]12;rgb:1111/2222/3333\x1b\\']);
await writeSync(page, '\x1b]12;#ffffff\x07\x1b]12;?\x07');
assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]12;rgb:1111/2222/3333\x07', '\x1b]12;rgb:ffff/ffff/ffff\x07']);
assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]12;rgb:1111/2222/3333\x1b\\', '\x1b]12;rgb:ffff/ffff/ffff\x1b\\']);
});
it('set & query FG & BG color in one call', async () => {
await writeSync(page, '\x1b]10;#123456;rgb:aa/bb/cc\x07\x1b]10;?;?\x07');
assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]10;rgb:1212/3434/5656\x07', '\x1b]11;rgb:aaaa/bbbb/cccc\x07']);
assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]10;rgb:1212/3434/5656\x1b\\', '\x1b]11;rgb:aaaa/bbbb/cccc\x1b\\']);
await writeSync(page, '\x1b]10;#ffffff;#000000\x07');
});
it('OSC 110: restore FG color', async () => {
await writeSync(page, '\x1b]10;rgb:1/2/3\x07\x1b]10;?\x07');
assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]10;rgb:1111/2222/3333\x07']);
assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]10;rgb:1111/2222/3333\x1b\\']);
await page.evaluate('window._recordedData.length = 0;');
// restore
await writeSync(page, '\x1b]110\x07\x1b]10;?\x07');
assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]10;rgb:ffff/ffff/ffff\x07']);
assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]10;rgb:ffff/ffff/ffff\x1b\\']);
});
it('OSC 111: restore BG color', async () => {
await writeSync(page, '\x1b]11;rgb:1/2/3\x07\x1b]11;?\x07');
assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]11;rgb:1111/2222/3333\x07']);
assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]11;rgb:1111/2222/3333\x1b\\']);
await page.evaluate('window._recordedData.length = 0;');
// restore
await writeSync(page, '\x1b]111\x07\x1b]11;?\x07');
assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]11;rgb:0000/0000/0000\x07']);
assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]11;rgb:0000/0000/0000\x1b\\']);
});
it('OSC 112: restore cursor color', async () => {
await writeSync(page, '\x1b]12;rgb:1/2/3\x07\x1b]12;?\x07');
assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]12;rgb:1111/2222/3333\x07']);
assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]12;rgb:1111/2222/3333\x1b\\']);
await page.evaluate('window._recordedData.length = 0;');
// restore
await writeSync(page, '\x1b]112\x07\x1b]12;?\x07');
assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]12;rgb:ffff/ffff/ffff\x07']);
assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]12;rgb:ffff/ffff/ffff\x1b\\']);
});
});
});
+1 -1
View File
@@ -229,7 +229,7 @@ describe('Mouse Tracking Tests', async () => {
window.calls = [];
window.term.onData(e => calls.push( Array.from(e).map(el => el.charCodeAt(0)) ));
window.term.onBinary(e => calls.push( Array.from(e).map(el => el.charCodeAt(0)) ));
window.term.setOption('fontSize', ${fontSize});
window.term.options.fontSize = ${fontSize};
window.term.resize(${cols}, ${rows});
`);
});
+73 -51
View File
@@ -574,7 +574,7 @@ describe('API Integration Tests', function(): void {
await page.evaluate(`window.term.scrollLines(10)`);
await page.evaluate(`window.term.addMarker(3)`);
await page.evaluate(`window.term.addMarker(4)`);
await page.evaluate(`
await page.evaluate(`
for (let i = 0; i < window.term.markers.length; ++i) {
const marker = window.term.markers[i];
marker.onDispose(() => window.disposeStack.push(marker));
@@ -732,54 +732,69 @@ describe('API Integration Tests', function(): void {
});
describe('registerDecoration', () => {
it('should register decorations and render them', async () => {
await openTerminal(page);
await writeSync(page, '\\n\\n\\n\\n');
await writeSync(page, '\\n\\n\\n\\n');
await writeSync(page, '\\n\\n\\n\\n');
await page.evaluate(`window.marker1 = window.term.addMarker(1)`);
await page.evaluate(`window.marker2 = window.term.addMarker(2)`);
await page.evaluate(`window.term.registerDecoration({ marker: window.marker1 })`);
await page.evaluate(`window.term.registerDecoration({ marker: window.marker2 })`);
await page.evaluate(`window.term.resize(10, 5)`);
assert.equal(await page.evaluate(`document.querySelectorAll('.xterm-screen .xterm-decoration').length`), 2);
describe('bufferDecorations', () => {
it('should register decorations and render them when terminal open is called', async () => {
await page.evaluate(`window.term = new Terminal({})`);
await page.evaluate(`window.term.open(document.querySelector('#terminal-container'))`);
await page.waitForSelector('.xterm-text-layer');
await page.evaluate(`window.marker1 = window.term.addMarker(1)`);
await page.evaluate(`window.marker2 = window.term.addMarker(2)`);
await page.evaluate(`window.term.registerDecoration({ marker: window.marker1 })`);
await page.evaluate(`window.term.registerDecoration({ marker: window.marker2 })`);
await openTerminal(page);
await pollFor(page, `document.querySelectorAll('.xterm-screen .xterm-decoration').length`, 2);
});
it('should return undefined when the marker has already been disposed of', async () => {
await openTerminal(page);
await page.evaluate(`window.marker = window.term.addMarker(1)`);
await page.evaluate(`window.marker.dispose()`);
await pollFor(page, `window.decoration = window.term.registerDecoration({ marker: window.marker });`, undefined);
});
it('should throw when a negative x offset is provided', async () => {
await openTerminal(page);
await page.evaluate(`window.marker = window.term.addMarker(1)`);
await page.evaluate(`
try {
window.decoration = window.term.registerDecoration({ marker: window.marker, x: -2 });
} catch (e) {
window.throwMessage = e.message;
}
`);
await pollFor(page, 'window.throwMessage', 'This API only accepts positive integers');
});
});
it('on resize should dispose of the old decoration and create a new one', async () => {
await openTerminal(page);
await writeSync(page, '\\n\\n\\n\\n');
await writeSync(page, '\\n\\n\\n\\n');
await page.evaluate(`window.marker = window.term.addMarker(1)`);
await page.evaluate(`window.decoration = window.term.registerDecoration({ marker: window.marker })`);
await page.evaluate(`window.term.resize(10, 5)`);
assert.equal(await page.evaluate(`document.querySelectorAll('.xterm-screen .xterm-decoration').length`), 1);
});
it('should return undefined when the marker has already been disposed of', async () => {
await openTerminal(page);
await writeSync(page, '\\n\\n\\n\\n');
await writeSync(page, '\\n\\n\\n\\n');
await page.evaluate(`window.marker = window.term.addMarker(1)`);
await page.evaluate(`window.marker.dispose()`);
assert.equal(await page.evaluate(`window.decoration = window.term.registerDecoration({ marker: window.marker });`), undefined);
});
it('should throw when a negative x offset is provided', async () => {
await openTerminal(page);
await writeSync(page, '\\n\\n\\n\\n');
await writeSync(page, '\\n\\n\\n\\n');
await page.evaluate(`window.marker = window.term.addMarker(1)`);
await page.evaluate(`
try {
window.decoration = window.term.registerDecoration({ marker: window.marker, x: -2 });
} catch (e) {
window.throwMessage = e.message;
}
`);
await pollFor(page, 'window.throwMessage', 'This API only accepts positive integers');
describe('overviewRulerDecorations', () => {
it('should not add an overview ruler when width is not set', async () => {
await page.evaluate(`window.term = new Terminal({})`);
await page.evaluate(`window.term.open(document.querySelector('#terminal-container'))`);
await page.waitForSelector('.xterm-text-layer');
await page.evaluate(`window.marker1 = window.term.addMarker(1)`);
await page.evaluate(`window.marker2 = window.term.addMarker(2)`);
await page.evaluate(`window.term.registerDecoration({ marker: window.marker1, overviewRulerOptions: { color: 'red', position: 'full' } })`);
await page.evaluate(`window.term.registerDecoration({ marker: window.marker2, overviewRulerOptions: { color: 'blue', position: 'full' } })`);
await openTerminal(page);
await pollFor(page, `document.querySelectorAll('.xterm-decoration-overview-ruler').length`, 0);
});
it('should add an overview ruler when width is set', async () => {
await page.evaluate(`window.term = new Terminal({ overviewRulerWidth: 15 })`);
await page.evaluate(`window.term.open(document.querySelector('#terminal-container'))`);
await page.waitForSelector('.xterm-text-layer');
await page.evaluate(`window.marker1 = window.term.addMarker(1)`);
await page.evaluate(`window.marker2 = window.term.addMarker(2)`);
await page.evaluate(`window.term.registerDecoration({ marker: window.marker1, overviewRulerOptions: { color: 'red', position: 'full' } })`);
await page.evaluate(`window.term.registerDecoration({ marker: window.marker2, overviewRulerOptions: { color: 'blue', position: 'full' } })`);
await openTerminal(page);
await pollFor(page, `document.querySelectorAll('.xterm-decoration-overview-ruler').length`, 1);
});
});
});
describe('registerLinkProvider', () => {
it('should fire provideLinks when hovering cells', async () => {
await openTerminal(page, { rendererType: 'dom' });
// Focus the terminal as the cursor will show and trigger a rerender, which can clear the
// active link
await page.evaluate('window.term.focus()');
await page.evaluate(`
window.calls = [];
window.disposable = window.term.registerLinkProvider({
@@ -799,6 +814,9 @@ describe('API Integration Tests', function(): void {
it('should fire hover and leave events on the link', async () => {
await openTerminal(page, { rendererType: 'dom' });
// Focus the terminal as the cursor will show and trigger a rerender, which can clear the
// active link
await page.evaluate('window.term.focus()');
await writeSync(page, 'foo bar baz');
// Wait for renderer to catch up as links are cleared on render
await pollFor(page, `document.querySelector('.xterm-rows').textContent`, 'foo bar baz ');
@@ -834,6 +852,9 @@ describe('API Integration Tests', function(): void {
it('should work fine when hover and leave callbacks are not provided', async () => {
await openTerminal(page, { rendererType: 'dom' });
// Focus the terminal as the cursor will show and trigger a rerender, which can clear the
// active link
await page.evaluate('window.term.focus()');
await writeSync(page, 'foo bar baz');
// Wait for renderer to catch up as links are cleared on render
await pollFor(page, `document.querySelector('.xterm-rows').textContent`, 'foo bar baz ');
@@ -874,18 +895,12 @@ describe('API Integration Tests', function(): void {
it('should fire activate events when clicking the link', async () => {
await openTerminal(page, { rendererType: 'dom' });
// Focus the terminal as the cursor will show and trigger a rerender, which can clear the
// active link
await page.evaluate('window.term.focus()');
await writeSync(page, 'a b c');
// Wait for renderer to catch up as links are cleared on render
await pollFor(page, `document.querySelector('.xterm-rows').textContent`, 'a b c ');
// Focus terminal to avoid a render event clearing the active link
const dims = await getDimensions();
await moveMouseCell(page, dims, 5, 5);
await page.mouse.down();
await page.mouse.up();
await timeout(200); // Not sure how to avoid this timeout, checking for xterm-focus doesn't help
await page.evaluate(`
window.calls = [];
window.disposable = window.term.registerLinkProvider({
@@ -901,6 +916,7 @@ describe('API Integration Tests', function(): void {
}
});
`);
const dims = await getDimensions();
await moveMouseCell(page, dims, 3, 1);
await pollFor(page, `window.calls`, ['provide 1', 'hover 1']);
await page.mouse.down();
@@ -921,6 +937,9 @@ describe('API Integration Tests', function(): void {
it('should work when multiple links are provided on the same line', async () => {
await openTerminal(page, { rendererType: 'dom' });
// Focus the terminal as the cursor will show and trigger a rerender, which can clear the
// active link
await page.evaluate('window.term.focus()');
await writeSync(page, 'foo bar baz');
// Wait for renderer to catch up as links are cleared on render
await pollFor(page, `document.querySelector('.xterm-rows').textContent`, 'foo bar baz ');
@@ -967,6 +986,9 @@ describe('API Integration Tests', function(): void {
it('should dispose links when hovering away', async () => {
await openTerminal(page, { rendererType: 'dom' });
// Focus the terminal as the cursor will show and trigger a rerender, which can clear the
// active link
await page.evaluate('window.term.focus()');
await writeSync(page, 'foo bar baz');
// Wait for renderer to catch up as links are cleared on render
await pollFor(page, `document.querySelector('.xterm-rows').textContent`, 'foo bar baz ');
+80 -19
View File
@@ -266,6 +266,12 @@ declare module 'xterm' {
* All features are disabled by default for security reasons.
*/
windowOptions?: IWindowOptions;
/**
* The width, in pixels, of the canvas for the overview ruler. The overview
* ruler will be hidden when not set.
*/
overviewRulerWidth?: number;
}
/**
@@ -282,6 +288,8 @@ declare module 'xterm' {
cursorAccent?: string;
/** The selection background color (can be transparent) */
selection?: string;
/** The selection foreground color */
selectionForeground?: string;
/** ANSI black (eg. `\x1b[30m`) */
black?: string;
/** ANSI red (eg. `\x1b[31m`) */
@@ -394,7 +402,7 @@ declare module 'xterm' {
}
/**
* Represents a disposable with an
* Represents a disposable that tracks is disposed state.
* @param onDispose event listener and
* @param isDisposed property.
*/
@@ -427,49 +435,91 @@ declare module 'xterm' {
readonly onRender: IEvent<HTMLElement>;
/**
* The HTMLElement that gets created after the
* first _onRender call, or undefined if accessed before
* The element that the decoration is rendered to. This will be undefined
* until it is rendered for the first time by {@link IDecoration.onRender}.
* that.
*/
readonly element: HTMLElement | undefined;
element: HTMLElement | undefined;
/**
* The options for the overview ruler that can be updated.
* This will only take effect when {@link IDecorationOptions.overviewRulerOptions}
* were provided initially.
*/
options: Pick<IDecorationOptions, 'overviewRulerOptions'>;
}
/**
* Options provided when registering a decoration
* containing a @param marker, @param anchor,
* @param x offset from the anchor, @param width in cells
* and @param height in cells.
* Overview ruler decoration options
*/
interface IDecorationOverviewRulerOptions {
color: string;
position?: 'left' | 'center' | 'right' | 'full';
}
/*
* Options that define the presentation of the decoration.
*/
export interface IDecorationOptions {
/**
* The line in the terminal where
* the decoration will be displayed
*/
marker: IMarker;
readonly marker: IMarker;
/*
* Where the decoration will be anchored -
* defaults to the left edge
*/
anchor?: 'right' | 'left';
readonly anchor?: 'right' | 'left';
/**
* The x position offset relative to the anchor
*/
x?: number;
*/
readonly x?: number;
/**
* The width of the decoration in cells, which defaults to
* cell width
* The width of the decoration in cells, defaults to 1.
*/
width?: number;
readonly width?: number;
/**
* The height of the decoration in cells, which defaults to
* cell height
* The height of the decoration in cells, defaults to 1.
*/
height?: number;
readonly height?: number;
/**
* The background color of the cell(s). When 2 decorations both set the foreground color the
* last registered decoration will be used. Only the `#RRGGBB` format is supported.
*/
readonly backgroundColor?: string;
/**
* The foreground color of the cell(s). When 2 decorations both set the foreground color the
* last registered decoration will be used. Only the `#RRGGBB` format is supported.
*/
readonly foregroundColor?: string;
/**
* What layer to render the decoration at when {@link backgroundColor} or
* {@link foregroundColor} are used. `'bottom'` will render under the selection, `'top`' will
* render above the selection\*.
*
* *\* The selection will render on top regardless of layer on the canvas renderer due to how
* it renders selection separately.*
*/
readonly layer?: 'bottom' | 'top';
/**
* When defined, renders the decoration in the overview ruler to the right
* of the terminal. {@link ITerminalOptions.overviewRulerWidth} must be set
* in order to see the overview ruler.
* @param color The color of the decoration.
* @param position The position of the decoration.
*/
overviewRulerOptions?: IDecorationOverviewRulerOptions
}
/**
@@ -791,6 +841,17 @@ declare module 'xterm' {
*/
onRender: IEvent<{ start: number, end: number }>;
/**
* Adds an event listener for when data has been parsed by the terminal,
* after {@link write} is called. This event is useful to listen for any
* changes in the buffer.
*
* This fires at most once per frame, after data parsing completes. Note
* that this can fire when there are still writes pending if there is a lot
* of data.
*/
onWriteParsed: IEvent<void>;
/**
* Adds an event listener for when the terminal is resized. The event value
* contains the new size.
@@ -939,7 +1000,7 @@ declare module 'xterm' {
/**
* (EXPERIMENTAL) Adds a decoration to the terminal using
* @param decorationOptions, which takes a marker and an optional anchor,
* @param decorationOptions, which takes a marker and an optional anchor,
* width, height, and x offset from the anchor. Returns the decoration or
* undefined if the alt buffer is active or the marker has already been disposed of.
* @throws when options include a negative x offset.
+98 -448
View File
@@ -2,7 +2,7 @@
# yarn lockfile v1
"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.8.3":
"@babel/code-frame@^7.8.3":
version "7.8.3"
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.8.3.tgz#33e25903d7481181534e12ec0a25f16b6fcf419e"
integrity sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==
@@ -144,6 +144,13 @@
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.9.4.tgz#68a35e6b0319bbc014465be43828300113f2f2e8"
integrity sha512-bC49otXX6N0/VYhgOMh4gnP26E9xnDZK3TmbNpxYzzz9BQLBosQwfyOe9/cXUU3txYhTzLCbcqd5c8y/OmCjHA==
"@babel/runtime@^7.15.4":
version "7.17.9"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.17.9.tgz#d19fbf802d01a8cb6cf053a64e472d42c434ba72"
integrity sha512-lSiBBvodq29uShpWGNbgFdKYNiFDo5/HIYsaCEY9ff4sb10x9jizo2+pRrSyF4jKZCXqgzuqBOQKbUm90gQwJg==
dependencies:
regenerator-runtime "^0.13.4"
"@babel/template@^7.7.4", "@babel/template@^7.8.3", "@babel/template@^7.8.6":
version "7.8.6"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.8.6.tgz#86b22af15f828dfb086474f964dcc3e39c43ce2b"
@@ -355,9 +362,9 @@
integrity sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==
"@types/mocha@^8.2.1":
version "8.2.2"
resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-8.2.2.tgz#91daa226eb8c2ff261e6a8cbf8c7304641e095e0"
integrity sha512-Lwh0lzzqT5Pqh6z61P3c3P5nm6fzQK/MMHl9UKeneAeInVflBSz1O2EkX6gM6xfJd7FBXBY5purtLx7fUiZ7Hw==
version "8.2.3"
resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-8.2.3.tgz#bbeb55fbc73f28ea6de601fbfa4613f58d785323"
integrity sha512-ekGvFhFgrc2zYQoX4JeZPmVzZxw6Dtllga7iGHzfbYIYkAMUx/sAFP2GdFpLff+vdHXu5fl7WX9AT+TtqYcsyw==
"@types/mocha@^9.0.0":
version "9.0.0"
@@ -375,9 +382,9 @@
integrity sha512-hkzMMD3xu6BrJpGVLeQ3htQQNAcOrJjX7WFmtK8zWQpz2UJf13LCFF2ALA7c9OVdvc2vQJeDdjfR35M0sBCxvw==
"@types/node@^12.12.37":
version "12.20.12"
resolved "https://registry.yarnpkg.com/@types/node/-/node-12.20.12.tgz#fd9c1c2cfab536a2383ed1ef70f94adea743a226"
integrity sha512-KQZ1al2hKOONAs2MFv+yTQP1LkDWMrRJ9YCVRalXltOfXsBmH5IownLxQaiq0lnAHwAViLnh2aTYqrPcRGEbgg==
version "12.20.50"
resolved "https://registry.yarnpkg.com/@types/node/-/node-12.20.50.tgz#14ba5198f1754ffd0472a2f84ab433b45ee0b65e"
integrity sha512-+9axpWx2b2JCVovr7Ilgt96uc6C1zBKOQMpGtRbWT9IoR/8ue32GGMfGA4woP8QyP2gBs6GQWEVM3tCybGCxDA==
"@types/node@^14.14.44":
version "14.14.44"
@@ -390,9 +397,9 @@
integrity sha512-+hQX+WyJAOne7Fh3zF5CxPemILIbuhNcqHHodzK9caYOLnC8pD5efmPleRnw0z++LfKUC/sVNMwk0Gap+B0baA==
"@types/puppeteer@^5.4.3":
version "5.4.3"
resolved "https://registry.yarnpkg.com/@types/puppeteer/-/puppeteer-5.4.3.tgz#cdca84aa7751d77448d8a477dbfa0af1f11485f2"
integrity sha512-3nE8YgR9DIsgttLW+eJf6mnXxq8Ge+27m5SU3knWmrlfl6+KOG0Bf9f7Ua7K+C4BnaTMAh3/UpySqdAYvrsvjg==
version "5.4.6"
resolved "https://registry.yarnpkg.com/@types/puppeteer/-/puppeteer-5.4.6.tgz#afc438e41dcbc27ca1ba0235ea464a372db2b21c"
integrity sha512-98Kghehs7+/GD9b56qryhqdqVCXUTbetTv3PlvDnmFRTHQH0j9DIp1f7rkAW3BAj4U3yoeSEQnKgdW8bDq0Y0Q==
dependencies:
"@types/node" "*"
@@ -422,13 +429,6 @@
dependencies:
"@types/node" "*"
"@types/yauzl@^2.9.1":
version "2.9.2"
resolved "https://registry.yarnpkg.com/@types/yauzl/-/yauzl-2.9.2.tgz#c48e5d56aff1444409e39fa164b0b4d4552a7b7a"
integrity sha512-8uALY5LTvSuHgloDVUvWP3pIauILm+8/0pDMokuDYIoNsOkSwd5AiHBTSEJjKTDcZr5z8UpgOWZkxBF4iJftoA==
dependencies:
"@types/node" "*"
"@typescript-eslint/eslint-plugin@^5.3.0":
version "5.3.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.3.0.tgz#a55ae72d28ffeb6badd817fe4566c9cced1f5e29"
@@ -824,7 +824,7 @@ acorn@^8.4.1, acorn@^8.5.0:
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.5.0.tgz#4512ccb99b3698c752591e9bb4472e38ad43cee2"
integrity sha512-yXbYeFy+jUuYd3/CDcg2NkIYE991XYX/bje7LmjJigUciaeO1JR4XxXgCIV1/Zc/dRuFEyw1L0pbA+qynJkW5Q==
agent-base@6, agent-base@^6.0.2:
agent-base@6:
version "6.0.2"
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77"
integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==
@@ -869,20 +869,10 @@ ansi-colors@4.1.1, ansi-colors@^4.1.1:
resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348"
integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==
ansi-regex@^2.0.0:
version "2.1.1"
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df"
integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8=
ansi-regex@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998"
integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=
ansi-regex@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75"
integrity sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==
ansi-regex@^5.0.0, ansi-regex@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304"
integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==
ansi-styles@^3.2.1:
version "3.2.1"
@@ -899,14 +889,6 @@ ansi-styles@^4.0.0, ansi-styles@^4.1.0:
"@types/color-name" "^1.1.1"
color-convert "^2.0.1"
anymatch@~3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.1.tgz#c55ecf02185e2469259399310c173ce31233b142"
integrity sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==
dependencies:
normalize-path "^3.0.0"
picomatch "^2.0.4"
anymatch@~3.1.2:
version "3.1.2"
resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716"
@@ -1038,21 +1020,11 @@ browserslist@^4.14.5:
escalade "^3.1.1"
node-releases "^1.1.71"
buffer-crc32@~0.2.3:
version "0.2.13"
resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242"
integrity sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=
buffer-from@^1.0.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef"
integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==
builtin-modules@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f"
integrity sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=
bytes@3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6"
@@ -1108,7 +1080,7 @@ chai@^4.3.4:
pathval "^1.1.1"
type-detect "^4.0.5"
chalk@^2.0.0, chalk@^2.3.0:
chalk@^2.0.0:
version "2.4.2"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
@@ -1138,21 +1110,6 @@ check-error@^1.0.2:
resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.2.tgz#574d312edd88bb5dd8912e9286dd6c0aed4aac82"
integrity sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=
chokidar@3.5.1:
version "3.5.1"
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.1.tgz#ee9ce7bbebd2b79f49f304799d5468e31e14e68a"
integrity sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==
dependencies:
anymatch "~3.1.1"
braces "~3.0.2"
glob-parent "~5.1.0"
is-binary-path "~2.1.0"
is-glob "~4.0.1"
normalize-path "~3.0.0"
readdirp "~3.5.0"
optionalDependencies:
fsevents "~2.3.1"
chokidar@3.5.3:
version "3.5.3"
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd"
@@ -1179,9 +1136,9 @@ clean-stack@^2.0.0:
integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==
cli-table@^0.3.6:
version "0.3.6"
resolved "https://registry.yarnpkg.com/cli-table/-/cli-table-0.3.6.tgz#e9d6aa859c7fe636981fd3787378c2a20bce92fc"
integrity sha512-ZkNZbnZjKERTY5NwC2SeMeLeifSPq/pubeRoTpdr3WchLlnZg6hEgvHkK5zL7KNFdd9PmHN8lxrENUwI3cE8vQ==
version "0.3.11"
resolved "https://registry.yarnpkg.com/cli-table/-/cli-table-0.3.11.tgz#ac69cdecbe81dccdba4889b9a18b7da312a9d3ee"
integrity sha512-IqLQi4lO0nIB4tcdTpN4LCB9FI3uqrJZK7RC515EnhZ6qBaglkIgICb1wjeAqpdoOabm1+SuQtkXIPdYC93jhQ==
dependencies:
colors "1.0.3"
@@ -1257,11 +1214,11 @@ colors@1.0.3:
integrity sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=
columnify@^1.5.4:
version "1.5.4"
resolved "https://registry.yarnpkg.com/columnify/-/columnify-1.5.4.tgz#4737ddf1c7b69a8a7c340570782e947eec8e78bb"
integrity sha1-Rzfd8ce2mop8NAVweC6UfuyOeLs=
version "1.6.0"
resolved "https://registry.yarnpkg.com/columnify/-/columnify-1.6.0.tgz#6989531713c9008bb29735e61e37acf5bd553cf3"
integrity sha512-lomjuFZKfM6MSAnV9aCZC9sc0qGbmZdfygNv+nCpqVkSKdCxCklLtd16O0EILGkImHw9ZpHkAnHaB+8Zxq5W6Q==
dependencies:
strip-ansi "^3.0.0"
strip-ansi "^6.0.1"
wcwidth "^1.0.0"
combined-stream@^1.0.8:
@@ -1271,7 +1228,7 @@ combined-stream@^1.0.8:
dependencies:
delayed-stream "~1.0.0"
commander@^2.12.1, commander@^2.20.0:
commander@^2.20.0:
version "2.20.3"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
@@ -1286,20 +1243,15 @@ commander@^7.0.0:
resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7"
integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==
commander@^8.2.0:
version "8.3.0"
resolved "https://registry.yarnpkg.com/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66"
integrity sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==
commondir@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b"
integrity sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=
complex.js@^2.0.11:
version "2.0.12"
resolved "https://registry.yarnpkg.com/complex.js/-/complex.js-2.0.12.tgz#fa4df97d8928e5f7b6a86b35bdeecc3a3eda8a22"
integrity sha512-oQX99fwL6LrTVg82gDY1dIWXy6qZRnRL35N+YhIX0N7tSwsa0KFy6IEMHTNuCW4mP7FS7MEqZ/2I/afzYwPldw==
complex.js@^2.0.15:
version "2.1.1"
resolved "https://registry.yarnpkg.com/complex.js/-/complex.js-2.1.1.tgz#0675dac8e464ec431fb2ab7d30f41d889fb25c31"
integrity sha512-8njCHOTtFFLtegk6zQo0kkVX1rngygb/KQI6z1qZxlFI3scluC+LVTCFbrkWjBv4vvLlbQ9t88IPMC6k95VTTg==
concat-map@0.0.1:
version "0.0.1"
@@ -1391,13 +1343,6 @@ debug@4, debug@^4.1.0, debug@^4.1.1:
dependencies:
ms "2.1.2"
debug@4.3.1:
version "4.3.1"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee"
integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==
dependencies:
ms "2.1.2"
debug@4.3.3:
version "4.3.3"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.3.tgz#04266e0b70a98d4462e6e288e38259213332b664"
@@ -1405,7 +1350,7 @@ debug@4.3.3:
dependencies:
ms "2.1.2"
debug@^4.3.1, debug@^4.3.2:
debug@^4.3.2:
version "4.3.2"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.2.tgz#f0a49c18ac8779e31d4a0c6029dfb76873c7428b"
integrity sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==
@@ -1422,12 +1367,7 @@ decamelize@^4.0.0:
resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-4.0.0.tgz#aa472d7bf660eb15f3494efd531cab7f2a709837"
integrity sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==
decimal.js@^10.0.0, decimal.js@^10.2.1:
version "10.2.1"
resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.2.1.tgz#238ae7b0f0c793d3e3cea410108b35a2c01426a3"
integrity sha512-KaL7+6Fw6i5A2XSnsbhm/6B+NuEA7TZ4vqxnd5tXz9sbKtrN9Srj8ab4vKVdK8YAqZO9P1kg45Y6YLoduPf+kw==
decimal.js@^10.3.1:
decimal.js@^10.0.0, decimal.js@^10.3.1:
version "10.3.1"
resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.3.1.tgz#d8c3a444a9c6774ba60ca6ad7261c3a94fd5e783"
integrity sha512-V0pfhfr8suzyPGOx3nmq4aHqabehUZn6Ch9kyFpV79TGDTWFmHqUqXdabR7QHqxzrYolF4+tVmJhUG4OURg5dQ==
@@ -1506,11 +1446,6 @@ diff@5.0.0:
resolved "https://registry.yarnpkg.com/diff/-/diff-5.0.0.tgz#7ed6ad76d859d030787ec35855f5b1daf31d852b"
integrity sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==
diff@^4.0.1:
version "4.0.2"
resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d"
integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==
dir-glob@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f"
@@ -1552,13 +1487,6 @@ encodeurl@~1.0.2:
resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=
end-of-stream@^1.1.0:
version "1.4.4"
resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0"
integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==
dependencies:
once "^1.4.0"
enhanced-resolve@^5.0.0, enhanced-resolve@^5.8.3:
version "5.8.3"
resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.8.3.tgz#6d552d465cce0423f5b3d718511ea53826a7b2f0"
@@ -1672,11 +1600,6 @@ escape-string-regexp@^1.0.5:
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=
escape-string-regexp@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344"
integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==
escodegen@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-2.0.0.tgz#5e32b12833e8aa8fa35e1bf0befa89380484c7dd"
@@ -1882,17 +1805,6 @@ express@^4.17.1:
utils-merge "1.0.1"
vary "~1.1.2"
extract-zip@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-2.0.1.tgz#663dca56fe46df890d5f131ef4a06d22bb8ba13a"
integrity sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==
dependencies:
debug "^4.1.1"
get-stream "^5.1.0"
yauzl "^2.10.0"
optionalDependencies:
"@types/yauzl" "^2.9.1"
fast-deep-equal@^3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz#545145077c501491e33b15ec408c294376e94ae4"
@@ -1936,13 +1848,6 @@ fastq@^1.6.0:
dependencies:
reusify "^1.0.4"
fd-slicer@~1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e"
integrity sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=
dependencies:
pend "~1.2.0"
file-entry-cache@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027"
@@ -2040,10 +1945,10 @@ forwarded@~0.1.2:
resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84"
integrity sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=
fraction.js@^4.0.13:
version "4.0.13"
resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.0.13.tgz#3c1c315fa16b35c85fffa95725a36fa729c69dfe"
integrity sha512-E1fz2Xs9ltlUp+qbiyx9wmt2n9dRzPsS11Jtdb8D2o+cC7wr9xkkKsVKJuBX0ST+LVS+LhLO+SbLJNtfWcJvXA==
fraction.js@^4.1.1:
version "4.2.0"
resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.2.0.tgz#448e5109a313a3527f5a3ab2119ec4cf0e0e2950"
integrity sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==
fresh@0.5.2:
version "0.5.2"
@@ -2060,7 +1965,7 @@ fs.realpath@^1.0.0:
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8=
fsevents@~2.3.1, fsevents@~2.3.2:
fsevents@~2.3.2:
version "2.3.2"
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a"
integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==
@@ -2104,19 +2009,12 @@ get-package-type@^0.1.0:
resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a"
integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==
get-stream@^5.1.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3"
integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==
dependencies:
pump "^3.0.0"
get-stream@^6.0.0:
version "6.0.1"
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7"
integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==
glob-parent@^5.1.2, glob-parent@~5.1.0, glob-parent@~5.1.2:
glob-parent@^5.1.2, glob-parent@~5.1.2:
version "5.1.2"
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4"
integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==
@@ -2135,18 +2033,6 @@ glob-to-regexp@^0.4.1:
resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e"
integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==
glob@7.1.6, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6:
version "7.1.6"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6"
integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==
dependencies:
fs.realpath "^1.0.0"
inflight "^1.0.4"
inherits "2"
minimatch "^3.0.4"
once "^1.3.0"
path-is-absolute "^1.0.0"
glob@7.2.0, glob@^7.2.0:
version "7.2.0"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023"
@@ -2159,10 +2045,10 @@ glob@7.2.0, glob@^7.2.0:
once "^1.3.0"
path-is-absolute "^1.0.0"
glob@^7.1.1:
version "7.1.7"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.7.tgz#3b193e9233f01d42d0b3f78294bbeeb418f94a90"
integrity sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==
glob@^7.1.3, glob@^7.1.4, glob@^7.1.6:
version "7.1.6"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6"
integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==
dependencies:
fs.realpath "^1.0.0"
inflight "^1.0.4"
@@ -2379,11 +2265,6 @@ interpret@^2.2.0:
resolved "https://registry.yarnpkg.com/interpret/-/interpret-2.2.0.tgz#1a78a0b5965c40a5416d007ad6f50ad27c417df9"
integrity sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==
ip@^1.1.5:
version "1.1.5"
resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a"
integrity sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=
ipaddr.js@1.9.1:
version "1.9.1"
resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3"
@@ -2437,11 +2318,6 @@ is-extglob@^2.1.1:
resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=
is-fullwidth-code-point@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f"
integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=
is-fullwidth-code-point@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d"
@@ -2666,23 +2542,11 @@ jest-worker@^27.0.6:
merge-stream "^2.0.0"
supports-color "^8.0.0"
jpeg-js@^0.4.2:
version "0.4.3"
resolved "https://registry.yarnpkg.com/jpeg-js/-/jpeg-js-0.4.3.tgz#6158e09f1983ad773813704be80680550eff977b"
integrity sha512-ru1HWKek8octvUHFHvE5ZzQ1yAsJmIvRdGWvSoKV52XKyuyYA437QWDttXT8eZXDSbuMpHlLzPDZUPd6idIz+Q==
js-tokens@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
js-yaml@4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.0.0.tgz#f426bc0ff4b4051926cd588c71113183409a121f"
integrity sha512-pqon0s+4ScYUvX30wxQi3PogGFAlUyH0awepWvwkj4jD4v+ova3RiYw8bmA6x2rDrEaj8i/oWKoRxpVNW+Re8Q==
dependencies:
argparse "^2.0.1"
js-yaml@4.1.0, js-yaml@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602"
@@ -2813,13 +2677,6 @@ lodash@^4.17.13:
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
log-symbols@4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.0.0.tgz#69b3cc46d20f448eccdb75ea1fa733d9e821c920"
integrity sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA==
dependencies:
chalk "^4.0.0"
log-symbols@4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503"
@@ -2843,14 +2700,15 @@ make-dir@^3.0.0, make-dir@^3.0.2:
semver "^6.0.0"
mathjs@^9.3.0:
version "9.3.2"
resolved "https://registry.yarnpkg.com/mathjs/-/mathjs-9.3.2.tgz#6523dd5c963d200ff1cea0ff7963b10521b82185"
integrity sha512-0YKSKAeN9OkbIQrxfxnBT4kk/KlH71piWOsvVvAasyRIj/Xd/zlpc5VP/aFxwr+llOq2F3f6booPEu2fWv3yjQ==
version "9.5.2"
resolved "https://registry.yarnpkg.com/mathjs/-/mathjs-9.5.2.tgz#e0f3279320dc6f49e45d99c4fcdd8b52231f0462"
integrity sha512-c0erTq0GP503/Ch2OtDOAn50GIOsuxTMjmE00NI/vKJFSWrDaQHRjx6ai+16xYv70yBSnnpUgHZGNf9FR9IwmA==
dependencies:
complex.js "^2.0.11"
decimal.js "^10.2.1"
"@babel/runtime" "^7.15.4"
complex.js "^2.0.15"
decimal.js "^10.3.1"
escape-latex "^1.2.0"
fraction.js "^4.0.13"
fraction.js "^4.1.1"
javascript-natural-sort "^0.7.1"
seedrandom "^3.0.5"
tiny-emitter "^2.1.0"
@@ -2930,11 +2788,6 @@ mime@1.6.0:
resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1"
integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==
mime@^2.4.6:
version "2.5.2"
resolved "https://registry.yarnpkg.com/mime/-/mime-2.5.2.tgz#6e3dc6cc2b9510643830e5f19d5cb753da5eeabe"
integrity sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg==
mimic-fn@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b"
@@ -2948,47 +2801,9 @@ minimatch@3.0.4, minimatch@^3.0.4:
brace-expansion "^1.1.7"
minimist@^1.2.5:
version "1.2.5"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
mkdirp@^0.5.3:
version "0.5.5"
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def"
integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==
dependencies:
minimist "^1.2.5"
mocha@^8.3.2:
version "8.4.0"
resolved "https://registry.yarnpkg.com/mocha/-/mocha-8.4.0.tgz#677be88bf15980a3cae03a73e10a0fc3997f0cff"
integrity sha512-hJaO0mwDXmZS4ghXsvPVriOhsxQ7ofcpQdm8dE+jISUOKopitvnXFQmpRR7jd2K6VBG6E26gU3IAbXXGIbu4sQ==
dependencies:
"@ungap/promise-all-settled" "1.1.2"
ansi-colors "4.1.1"
browser-stdout "1.3.1"
chokidar "3.5.1"
debug "4.3.1"
diff "5.0.0"
escape-string-regexp "4.0.0"
find-up "5.0.0"
glob "7.1.6"
growl "1.10.5"
he "1.2.0"
js-yaml "4.0.0"
log-symbols "4.0.0"
minimatch "3.0.4"
ms "2.1.3"
nanoid "3.1.20"
serialize-javascript "5.0.1"
strip-json-comments "3.1.1"
supports-color "8.1.1"
which "2.0.2"
wide-align "1.1.3"
workerpool "6.1.0"
yargs "16.2.0"
yargs-parser "20.2.4"
yargs-unparser "2.0.0"
version "1.2.6"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44"
integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==
mocha@^9.2.0:
version "9.2.0"
@@ -3050,11 +2865,6 @@ nan@^2.14.0:
resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.2.tgz#f5376400695168f4cc694ac9393d0c9585eeea19"
integrity sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ==
nanoid@3.1.20:
version "3.1.20"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.20.tgz#badc263c6b1dcf14b71efaa85f6ab4c1d6cfc788"
integrity sha512-a1cQNyczgKbLX9jwbS/+d7W8fX/RfgYR7lVWwWOGIPNgK2m0MWvrGF6/m4kk6U3QcFMnZf3RIhL0v2Jgh/0Uxw==
nanoid@3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.2.0.tgz#62667522da6673971cca916a6d3eff3f415ff80c"
@@ -3179,7 +2989,7 @@ on-finished@~2.3.0:
dependencies:
ee-first "1.1.1"
once@^1.3.0, once@^1.3.1, once@^1.4.0:
once@^1.3.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E=
@@ -3326,11 +3136,6 @@ pathval@^1.1.1:
resolved "https://registry.yarnpkg.com/pathval/-/pathval-1.1.1.tgz#8534e77a77ce7ac5a2512ea21e0fdb8fcf6c3d8d"
integrity sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==
pend@~1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50"
integrity sha1-elfrVQpng/kRUzH89GY9XI4AelA=
picomatch@^2.0.4, picomatch@^2.2.1:
version "2.2.2"
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad"
@@ -3348,39 +3153,17 @@ pkg-dir@^4.1.0, pkg-dir@^4.2.0:
dependencies:
find-up "^4.0.0"
playwright-core@=1.16.2:
version "1.16.2"
resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.16.2.tgz#13c4c352a41e431ba167dbadb80e8c628e1e1b79"
integrity sha512-8WkoP5OfZAYrRxtW/PCVACn9bNgqrTxVVPlc+MoxvJ48knNsZ+skrPjfno/XF3SgTUY9DyYX0g5fVOB7lkPtGg==
dependencies:
commander "^8.2.0"
debug "^4.1.1"
extract-zip "^2.0.1"
https-proxy-agent "^5.0.0"
jpeg-js "^0.4.2"
mime "^2.4.6"
pngjs "^5.0.0"
progress "^2.0.3"
proper-lockfile "^4.1.1"
proxy-from-env "^1.1.0"
rimraf "^3.0.2"
socks-proxy-agent "^6.1.0"
stack-utils "^2.0.3"
ws "^7.4.6"
yauzl "^2.10.0"
yazl "^2.5.1"
playwright-core@1.22.1:
version "1.22.1"
resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.22.1.tgz#59ddf903546171fdfd9c3dc189630c883619667c"
integrity sha512-H+ZUVYnceWNXrRf3oxTEKAr81QzFsCKu5Fp//fEjQvqgKkfA1iX3E9DBrPJpPNOrgVzcE+IqeI0fDmYJe6Ynnw==
playwright@^1.16.2:
version "1.16.2"
resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.16.2.tgz#92e1c48a74cab778970facca4094ce79fb57cae4"
integrity sha512-lqndwy5rDIp3tOLex5lnLSrltomqxgVkRkRi547rXoTl2qy7PY2rtHbUouQl7KY2XoVSeSeECYVq/bLJ+bbJNg==
playwright@^1.22.1:
version "1.22.1"
resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.22.1.tgz#a41bf55065953a6b31c151433e0f00c9e23c54dd"
integrity sha512-pn4dvphQdL3zTgI+xfqkTL3HK1xbtQz+zjJxzyhwKLDxMNuAH7/NCm75auWbYLyssdnvApznujwrQuJvqXTKUw==
dependencies:
playwright-core "=1.16.2"
pngjs@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-5.0.0.tgz#e79dd2b215767fd9c04561c01236df960bce7fbb"
integrity sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==
playwright-core "1.22.1"
prelude-ls@^1.2.1:
version "1.2.1"
@@ -3399,20 +3182,11 @@ process-on-spawn@^1.0.0:
dependencies:
fromentries "^1.2.0"
progress@^2.0.0, progress@^2.0.3:
progress@^2.0.0:
version "2.0.3"
resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8"
integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==
proper-lockfile@^4.1.1:
version "4.1.2"
resolved "https://registry.yarnpkg.com/proper-lockfile/-/proper-lockfile-4.1.2.tgz#c8b9de2af6b2f1601067f98e01ac66baa223141f"
integrity sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==
dependencies:
graceful-fs "^4.2.4"
retry "^0.12.0"
signal-exit "^3.0.2"
proxy-addr@~2.0.5:
version "2.0.6"
resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.6.tgz#fdc2336505447d3f2f2c638ed272caf614bbb2bf"
@@ -3421,24 +3195,11 @@ proxy-addr@~2.0.5:
forwarded "~0.1.2"
ipaddr.js "1.9.1"
proxy-from-env@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2"
integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==
psl@^1.1.33:
version "1.8.0"
resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24"
integrity sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==
pump@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64"
integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==
dependencies:
end-of-stream "^1.1.0"
once "^1.3.1"
punycode@^2.1.0, punycode@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
@@ -3476,13 +3237,6 @@ raw-body@2.4.0:
iconv-lite "0.4.24"
unpipe "1.0.0"
readdirp@~3.5.0:
version "3.5.0"
resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.5.0.tgz#9ba74c019b15d365278d2e91bb8c48d7b4d42c9e"
integrity sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==
dependencies:
picomatch "^2.2.1"
readdirp@~3.6.0:
version "3.6.0"
resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7"
@@ -3497,6 +3251,11 @@ rechoir@^0.7.0:
dependencies:
resolve "^1.9.0"
regenerator-runtime@^0.13.4:
version "0.13.9"
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz#8925742a98ffd90814988d7566ad30ca3b263b52"
integrity sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==
regexp.prototype.flags@^1.3.0:
version "1.3.1"
resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.3.1.tgz#7ef352ae8d159e758c0eadca6f8fcb4eef07be26"
@@ -3559,11 +3318,6 @@ resolve@^1.9.0:
is-core-module "^2.2.0"
path-parse "^1.0.6"
retry@^0.12.0:
version "0.12.0"
resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b"
integrity sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=
reusify@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76"
@@ -3628,7 +3382,7 @@ seedrandom@^3.0.5:
resolved "https://registry.yarnpkg.com/seedrandom/-/seedrandom-3.0.5.tgz#54edc85c95222525b0c7a6f6b3543d8e0b3aa0a7"
integrity sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==
semver@^5.3.0, semver@^5.4.1:
semver@^5.4.1:
version "5.7.1"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==
@@ -3664,13 +3418,6 @@ send@0.17.1:
range-parser "~1.2.1"
statuses "~1.5.0"
serialize-javascript@5.0.1, serialize-javascript@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-5.0.1.tgz#7886ec848049a462467a97d3d918ebb2aaf934f4"
integrity sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA==
dependencies:
randombytes "^2.1.0"
serialize-javascript@6.0.0, serialize-javascript@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.0.tgz#efae5d88f45d7924141da8b5c3a7a7e663fefeb8"
@@ -3678,6 +3425,13 @@ serialize-javascript@6.0.0, serialize-javascript@^6.0.0:
dependencies:
randombytes "^2.1.0"
serialize-javascript@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-5.0.1.tgz#7886ec848049a462467a97d3d918ebb2aaf934f4"
integrity sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA==
dependencies:
randombytes "^2.1.0"
serve-static@1.14.1:
version "1.14.1"
resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.14.1.tgz#666e636dc4f010f7ef29970a88a674320898b2f9"
@@ -3741,28 +3495,6 @@ slash@^3.0.0:
resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634"
integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==
smart-buffer@^4.1.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.2.0.tgz#6e1d71fa4f18c05f7d0ff216dd16a481d0e8d9ae"
integrity sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==
socks-proxy-agent@^6.1.0:
version "6.1.0"
resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-6.1.0.tgz#869cf2d7bd10fea96c7ad3111e81726855e285c3"
integrity sha512-57e7lwCN4Tzt3mXz25VxOErJKXlPfXmkMLnk310v/jwW20jWRVcgsOit+xNkN3eIEdB47GwnfAEBLacZ/wVIKg==
dependencies:
agent-base "^6.0.2"
debug "^4.3.1"
socks "^2.6.1"
socks@^2.6.1:
version "2.6.1"
resolved "https://registry.yarnpkg.com/socks/-/socks-2.6.1.tgz#989e6534a07cf337deb1b1c94aaa44296520d30e"
integrity sha512-kLQ9N5ucj8uIcxrDwjm0Jsqk06xdpBjGNQtpXy4Q8/QY2k+fY7nZH8CARy+hkbG+SGAovmzzuauCpBlb8FrnBA==
dependencies:
ip "^1.1.5"
smart-buffer "^4.1.0"
source-list-map@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34"
@@ -3830,26 +3562,11 @@ sprintf-js@~1.0.2:
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=
stack-utils@^2.0.3:
version "2.0.5"
resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.5.tgz#d25265fca995154659dbbfba3b49254778d2fdd5"
integrity sha512-xrQcmYhOsn/1kX+Vraq+7j4oE2j/6BFscZ0etmYg81xuM8Gq0022Pxb8+IqgOFUIaxHs0KaSb7T1+OegiNrNFA==
dependencies:
escape-string-regexp "^2.0.0"
"statuses@>= 1.5.0 < 2", statuses@~1.5.0:
version "1.5.0"
resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c"
integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=
"string-width@^1.0.2 || 2":
version "2.1.1"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e"
integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==
dependencies:
is-fullwidth-code-point "^2.0.0"
strip-ansi "^4.0.0"
string-width@^4.1.0, string-width@^4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.0.tgz#952182c46cc7b2c313d1596e623992bd163b72b5"
@@ -3875,20 +3592,6 @@ string.prototype.trimstart@^1.0.4:
call-bind "^1.0.2"
define-properties "^1.1.3"
strip-ansi@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf"
integrity sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=
dependencies:
ansi-regex "^2.0.0"
strip-ansi@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f"
integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8=
dependencies:
ansi-regex "^3.0.0"
strip-ansi@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.0.tgz#0b1571dd7669ccd4f3e06e14ef1eed26225ae532"
@@ -3896,6 +3599,13 @@ strip-ansi@^6.0.0:
dependencies:
ansi-regex "^5.0.0"
strip-ansi@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
dependencies:
ansi-regex "^5.0.1"
strip-bom@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878"
@@ -4046,42 +3756,11 @@ ts-loader@^9.1.2:
micromatch "^4.0.0"
semver "^7.3.4"
tslib@^1.13.0:
version "1.14.1"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
tslib@^1.8.1:
version "1.11.1"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.11.1.tgz#eb15d128827fbee2841549e171f45ed338ac7e35"
integrity sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA==
tslint@^6.1.3:
version "6.1.3"
resolved "https://registry.yarnpkg.com/tslint/-/tslint-6.1.3.tgz#5c23b2eccc32487d5523bd3a470e9aa31789d904"
integrity sha512-IbR4nkT96EQOvKE2PW/djGz8iGNeJ4rF2mBfiYaR/nvUWYKJhLwimoJKgjIFEIDibBtOevj7BqCRL4oHeWWUCg==
dependencies:
"@babel/code-frame" "^7.0.0"
builtin-modules "^1.1.1"
chalk "^2.3.0"
commander "^2.12.1"
diff "^4.0.1"
glob "^7.1.1"
js-yaml "^3.13.1"
minimatch "^3.0.4"
mkdirp "^0.5.3"
resolve "^1.3.2"
semver "^5.3.0"
tslib "^1.13.0"
tsutils "^2.29.0"
tsutils@^2.29.0:
version "2.29.0"
resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-2.29.0.tgz#32b488501467acbedd4b85498673a0812aca0b99"
integrity sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==
dependencies:
tslib "^1.8.1"
tsutils@^3.21.0:
version "3.21.0"
resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623"
@@ -4127,9 +3806,9 @@ type-is@~1.6.17, type-is@~1.6.18:
mime-types "~2.1.24"
typed-function@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/typed-function/-/typed-function-2.0.0.tgz#15ab3825845138a8b1113bd89e60cd6a435739e8"
integrity sha512-Hhy1Iwo/e4AtLZNK10ewVVcP2UEs408DS35ubP825w/YgSBK1KVLwALvvIG4yX75QJrxjCpcWkzkVRB0BwwYlA==
version "2.1.0"
resolved "https://registry.yarnpkg.com/typed-function/-/typed-function-2.1.0.tgz#ded6f8a442ba8749ff3fe75bc41419c8d46ccc3f"
integrity sha512-bctQIOqx2iVbWGDGPWwIm18QScpu2XRmkC19D8rQGFsjKSgteq/o1hTZvIG/wuDq8fanpBDrLkLq+aEN/6y5XQ==
typedarray-to-buffer@^3.1.5:
version "3.1.5"
@@ -4139,9 +3818,9 @@ typedarray-to-buffer@^3.1.5:
is-typedarray "^1.0.0"
typescript@^4.2.3:
version "4.2.4"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.2.4.tgz#8610b59747de028fda898a8aef0e103f156d0961"
integrity sha512-V+evlYHZnQkaz8TRBuxTA92yZBPotr5H+WhQ7bD3hZUndx5tGOa1fuCgeSjxAzM1RiN5IzvadIXTVefuuwZCRg==
version "4.6.4"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.6.4.tgz#caa78bbc3a59e6a5c510d35703f6a09877ce45e9"
integrity sha512-9ia/jWHIEbo49HfjrLGfKbZSuWo9iTMwXO+Ca3pRsSpbsMbc7/IU8NKdCZVRRBafVPGnoJeFL76ZOAA84I9fEg==
typescript@^4.4.4:
version "4.4.4"
@@ -4406,13 +4085,6 @@ which@2.0.2, which@^2.0.1:
dependencies:
isexe "^2.0.0"
wide-align@1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457"
integrity sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==
dependencies:
string-width "^1.0.2 || 2"
wildcard@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/wildcard/-/wildcard-2.0.0.tgz#a77d20e5200c6faaac979e4b3aadc7b3dd7f8fec"
@@ -4423,11 +4095,6 @@ word-wrap@^1.2.3, word-wrap@~1.2.3:
resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c"
integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==
workerpool@6.1.0:
version "6.1.0"
resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.1.0.tgz#a8e038b4c94569596852de7a8ea4228eefdeb37b"
integrity sha512-toV7q9rWNYha963Pl/qyeZ6wG+3nnsyvolaNUS8+R5Wtw6qJPTxIlOP1ZSvcGhEJw+l3HMMmtiNo9Gl61G4GVg==
workerpool@6.2.0:
version "6.2.0"
resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.2.0.tgz#827d93c9ba23ee2019c3ffaff5c27fccea289e8b"
@@ -4486,10 +4153,10 @@ xmlchars@^2.2.0:
resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb"
integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==
xterm-benchmark@^0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/xterm-benchmark/-/xterm-benchmark-0.3.0.tgz#8702ae41672ff1e656423336f4e54699a3ab74e8"
integrity sha512-JTC1NjaqAWRHA3vPwbAocOJvz42ysYelrq7z2PS9egly8Hi5W7zfJIZlpkzEsZ8dXo0fcEm/6azmoYFy+TMjig==
xterm-benchmark@^0.3.1:
version "0.3.1"
resolved "https://registry.yarnpkg.com/xterm-benchmark/-/xterm-benchmark-0.3.1.tgz#dcaaf808e40605c7c27a83b5a5b81f9c45045e24"
integrity sha512-JjsCrSxkYKWf5CmBt2BeXm83KQdStyoGWREWQ0jSFF5N8CYVbdKQoWgs56mmy6qWD5GDKxO+V89Cvnbc8YUFjw==
dependencies:
"@types/app-root-path" "^1.2.4"
"@types/cli-table" "^0.3.0"
@@ -4502,8 +4169,6 @@ xterm-benchmark@^0.3.0:
columnify "^1.5.4"
commander "^6.2.1"
mathjs "^9.3.0"
mocha "^8.3.2"
tslint "^6.1.3"
typescript "^4.2.3"
y18n@^4.0.0:
@@ -4579,21 +4244,6 @@ yargs@^15.0.2:
y18n "^4.0.0"
yargs-parser "^18.1.1"
yauzl@^2.10.0:
version "2.10.0"
resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9"
integrity sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=
dependencies:
buffer-crc32 "~0.2.3"
fd-slicer "~1.1.0"
yazl@^2.5.1:
version "2.5.1"
resolved "https://registry.yarnpkg.com/yazl/-/yazl-2.5.1.tgz#a3d65d3dd659a5b0937850e8609f22fffa2b5c35"
integrity sha512-phENi2PLiHnHb6QBVot+dJnaAZ0xosj7p3fWl+znIjBDlnMI2PsZCJZ306BPTFOaHf5qdDEI8x5qFrSOBN5vrw==
dependencies:
buffer-crc32 "~0.2.3"
yocto-queue@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"