Compare commits

...
817 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
Daniel Imms a8fbc8d7a3 Merge pull request #3664 from hello-smile6/patch-1
Add libv86 terminal forwarding to README "Programs using xterm.js"
2022-02-28 05:39:36 -08:00
Daniel Imms c5a48043ae Merge pull request #3663 from childrentime/dev_wlw
Make registerMarker's cursor offset optional
2022-02-28 05:33:51 -08:00
儿时 9b02e950f8 Make registerMarker's cursor offset optional 2022-02-27 13:25:07 +08:00
hello-smile6 14366e7c40 Add libv86 terminal forwarding
https://github.com/hello-smile6/libv86-terminal-forwarding
2022-02-26 16:54:05 -08:00
Daniel Imms e1c149521a Merge pull request #3662 from Tyriar/tyriar/faster_tasks
Make vscode tasks faster on reload
2022-02-25 11:34:52 -08:00
Daniel Imms f5ccf75781 Merge branch 'master' into tyriar/faster_tasks 2022-02-25 11:25:42 -08:00
Daniel Imms 771d5f580a Simplify task names 2022-02-25 11:25:38 -08:00
Daniel Imms 336b45a645 Make vscode tasks faster on reload
See microsoft/vscode#142413
2022-02-25 11:24:40 -08:00
Megan Rogge 0f15a78ba2 Merge pull request #3661 from meganrogge/master
set decoration line height
2022-02-24 13:13:32 -06:00
meganrogge 69d6d53631 Merge branch 'master' of https://github.com/meganrogge/xterm.js 2022-02-24 12:24:37 -06:00
meganrogge 2c76f9bee7 remove height as a factor for lineheight 2022-02-24 12:24:28 -06:00
Megan Rogge 0314d95a32 Merge branch 'xtermjs:master' into master 2022-02-24 12:19:31 -06:00
meganrogge d06a2e1ad7 set line height of decoration 2022-02-24 12:19:05 -06:00
Megan Rogge 134e1ebb5f Merge pull request #3659 from meganrogge/master
use proper y value for clearing markers
2022-02-24 12:18:54 -06:00
meganrogge 3c5b73df8e :Merge branch 'master' of https://github.com/meganrogge/xterm.js 2022-02-24 12:07:59 -06:00
meganrogge ef0312473e re-add i-- so that markers on the same line get disposed of 2022-02-24 12:07:54 -06:00
Megan Rogge 7003c24126 Merge branch 'xtermjs:master' into master 2022-02-24 12:02:25 -06:00
meganrogge 34ec80a322 use ybase + y when clearing markers 2022-02-24 12:02:11 -06:00
Megan Rogge ded6750ff5 Merge pull request #3658 from meganrogge/master
fix marker splice by decrementing index
2022-02-23 16:14:59 -06:00
Megan Rogge d2e34277e8 Merge branch 'xtermjs:master' into master 2022-02-23 16:02:28 -06:00
meganrogge e8579bd655 fix marker splice by decrementing index 2022-02-23 16:02:08 -06:00
Daniel Imms a4f5cbf0cc Merge pull request #3656 from Tyriar/decoration_queue
Queue a refresh when registering a decoration
2022-02-23 13:50:29 -08:00
Daniel Imms b6937d05cd Merge branch 'master' into decoration_queue 2022-02-23 13:41:46 -08:00
Daniel Imms 56b824d379 Queue a refresh when registering a decoration
Part of microsoft/vscode#143798
2022-02-23 13:40:56 -08:00
Megan Rogge 72f7e3fac3 Merge pull request #3655 from meganrogge/master
get Decoration dispose to actually work and revert document role accessibility change
2022-02-22 17:51:13 -06:00
meganrogge d90bbdaa8a Merge branch 'master' of https://github.com/meganrogge/xterm.js 2022-02-22 17:40:43 -06:00
meganrogge 8df23582d2 remove super.dispose call 2022-02-22 17:40:37 -06:00
Megan Rogge e5459d7e24 Merge branch 'master' into master 2022-02-22 14:42:40 -06:00
meganrogge b3d9104136 Revert "Add document role to accessibility tree root"
This reverts commit 3a71b3f11e.
2022-02-22 14:33:30 -06:00
Megan Rogge 912284c24c Merge pull request #3654 from Tyriar/clear_marker
Fix clearMarkers when y=0 and avoid extra indexOf iterations
2022-02-22 14:24:40 -06:00
meganrogge dd86776c6c Merge branch 'master' of https://github.com/meganrogge/xterm.js 2022-02-22 14:18:43 -06:00
meganrogge 44bc9a1f58 don't dispose of marker on dispose of decoration 2022-02-22 14:18:39 -06:00
meganrogge 7b9d775a97 Merge branch 'master' of https://github.com/meganrogge/xterm.js 2022-02-22 14:14:09 -06:00
meganrogge 1da492646c re-add dispose of marker 2022-02-22 14:14:05 -06:00
Megan Rogge e7743c4baf Merge branch 'xtermjs:master' into master 2022-02-22 14:12:42 -06:00
meganrogge cd30f54276 get dispose to work 2022-02-22 14:12:29 -06:00
Megan Rogge a923953373 Merge branch 'master' into clear_marker 2022-02-22 14:11:13 -06:00
Megan Rogge c16b45cc1c Merge pull request #3619 from silamon/htmlserialize
HTML serialization
2022-02-22 14:11:00 -06:00
Megan Rogge 365a07968b Merge branch 'master' into htmlserialize 2022-02-22 10:09:49 -06:00
Daniel Imms da5c24a33f Fix clearMarkers when y=0 and avoid extra indexOf 2022-02-22 06:38:01 -08:00
Simon Lamon 92a61e6506 tests for serializeAsHTML 2022-02-19 13:17:26 +00:00
Megan Rogge e08962b8b8 Merge pull request #3649 from meganrogge/master
on buffer clear, update markers array once each has been disposed of
2022-02-18 17:58:50 -06:00
meganrogge f839a0da8b use splice instead of filter 2022-02-18 17:50:12 -06:00
meganrogge 1efdc2e4ce Merge branch 'master' of https://github.com/meganrogge/xterm.js 2022-02-18 16:11:47 -06:00
meganrogge a4fee6d4b5 get rid of the bane of my existence 2022-02-18 16:11:00 -06:00
Megan Rogge ed24e952f8 Merge branch 'xtermjs:master' into master 2022-02-18 15:38:26 -06:00
meganrogge 5b44deaa30 get rid of markers once they've been disposed via clear 2022-02-18 15:38:07 -06:00
Megan Rogge db1fd267a6 Merge pull request #3644 from meganrogge/master 2022-02-15 21:13:36 -06:00
meganrogge 816f62cebc remove unnecessary prop 2022-02-15 15:36:03 -06:00
meganrogge b8e003768d Merge branch 'master' of https://github.com/meganrogge/xterm.js 2022-02-15 11:57:27 -06:00
meganrogge 7134eaf20c remove trailing space 2022-02-15 11:57:20 -06:00
Megan Rogge b5477f71a8 Merge branch 'xtermjs:master' into master 2022-02-15 11:51:57 -06:00
meganrogge f42e78d832 fix https://github.com/microsoft/vscode/issues/143076 2022-02-15 11:47:41 -06:00
meganrogge 9847dcaacc fix #3641 2022-02-15 11:38:04 -06:00
Daniel Imms d943aa3e5b Merge pull request #3638 from xtermjs/dependabot/npm_and_yarn/addons/xterm-addon-ligatures/follow-redirects-1.14.8
Bump follow-redirects from 1.14.7 to 1.14.8 in /addons/xterm-addon-ligatures
2022-02-15 07:03:35 -08:00
dependabot[bot] 8098fd8d01 Bump follow-redirects in /addons/xterm-addon-ligatures
Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.14.7 to 1.14.8.
- [Release notes](https://github.com/follow-redirects/follow-redirects/releases)
- [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.14.7...v1.14.8)

---
updated-dependencies:
- dependency-name: follow-redirects
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-13 12:22:04 +00:00
Simon Lamon 4ca26f7e7b text improvements 2022-02-12 09:42:08 +00:00
Megan Rogge 4da2db8e0b Merge pull request #3637 from meganrogge/master
polish decorations
2022-02-11 17:19:34 -06:00
meganrogge 8dfed8f979 be safe and check if container contains element before removing 2022-02-11 17:08:08 -06:00
meganrogge e720643315 get rid of unused 2022-02-11 17:04:11 -06:00
meganrogge 40e6dba43c cleaner 2022-02-11 17:00:24 -06:00
meganrogge 7d357b941f check if container has child before removing it 2022-02-11 16:48:40 -06:00
meganrogge 3f525fb960 fix #3636 2022-02-11 15:40:44 -06:00
meganrogge de3a09e05f fix #3633 2022-02-11 15:09:40 -06:00
meganrogge d8573e38a8 cleanup dispose 2022-02-11 15:01:07 -06:00
meganrogge e44659e8bd remove unused ID - can just use marker's 2022-02-11 14:56:56 -06:00
meganrogge f160436df8 dispose of decoration on dispose of marker 2022-02-11 14:54:01 -06:00
meganrogge 06357b1184 get rid of some unnecessary code 2022-02-11 14:08:46 -06:00
Megan Rogge d64cbc41d5 Merge branch 'master' into htmlserialize 2022-02-10 10:29:43 -06:00
Megan Rogge 71bfbe434b Merge pull request #3630 from meganrogge/merogge/buffer-marker-decoration
add Decorations
2022-02-09 19:30:49 -06:00
meganrogge 9ed95206c1 try to fix tests 2022-02-09 18:48:41 -06:00
meganrogge 14b71b611e fix test 2022-02-09 18:38:47 -06:00
meganrogge 881bc4f0a8 different approach 2022-02-09 17:37:07 -06:00
meganrogge b3b9f3c7cd add poll for 2022-02-09 17:17:36 -06:00
meganrogge 74246497a4 get term to scroll so it refreshes 2022-02-09 15:29:28 -06:00
meganrogge bf5fed083b fix test 2022-02-09 14:55:24 -06:00
meganrogge c7df6e3c2e inject bufferService 2022-02-09 14:50:02 -06:00
meganrogge 6a79017484 on dispose, remove container 2022-02-09 14:33:33 -06:00
meganrogge e5d4e96aad fix test 2022-02-09 14:30:53 -06:00
meganrogge 93bfc18237 fix test 2022-02-09 12:10:50 -06:00
meganrogge 87675649e1 hide if invalid x 2022-02-09 11:55:17 -06:00
meganrogge e2a402d80e add tests 2022-02-09 11:51:25 -06:00
meganrogge 3e204584d9 refresh on dimensions changed 2022-02-09 10:12:25 -06:00
meganrogge 95c9fe11fb save options in constructor 2022-02-09 10:07:20 -06:00
meganrogge 313cb2fe09 add a container 2022-02-09 09:55:30 -06:00
meganrogge 8e75cecba3 change how rendering happens 2022-02-09 09:44:24 -06:00
meganrogge a2721d43fd Merge branch 'merogge/buffer-marker-decoration' of https://github.com/meganrogge/xterm.js into merogge/buffer-marker-decoration 2022-02-09 08:57:14 -06:00
meganrogge db9bdd46b4 fix some things 2022-02-09 08:56:45 -06:00
Megan RoggeandDaniel Imms 591f32d3eb Update src/browser/Terminal.ts
Co-authored-by: Daniel Imms <2193314+Tyriar@users.noreply.github.com>
2022-02-09 08:51:05 -06:00
meganrogge fb4b19a393 polish 2022-02-09 08:47:10 -06:00
meganrogge ae958c2b12 fix the problem 2022-02-08 21:41:26 -06:00
meganrogge 216849b135 Merge branch 'merogge/buffer-marker-decoration' of https://github.com/meganrogge/xterm.js into merogge/buffer-marker-decoration 2022-02-08 21:31:00 -06:00
meganrogge cfe306fa32 break things 2022-02-08 21:30:11 -06:00
Megan RoggeandDaniel Imms a9b496f06f Update src/browser/services/DecorationService.ts
Co-authored-by: Daniel Imms <2193314+Tyriar@users.noreply.github.com>
2022-02-08 21:18:31 -06:00
meganrogge f2991a5636 cleanup 2022-02-08 21:10:34 -06:00
Megan RoggeandDaniel Imms 53606820a7 Update src/browser/services/DecorationService.ts
Co-authored-by: Daniel Imms <2193314+Tyriar@users.noreply.github.com>
2022-02-08 21:02:15 -06:00
Megan RoggeandDaniel Imms ad9818fb1a Update typings/xterm.d.ts
Co-authored-by: Daniel Imms <2193314+Tyriar@users.noreply.github.com>
2022-02-08 20:29:20 -06:00
meganrogge 88bd33f2d7 bring jumpyness back 2022-02-08 17:27:15 -06:00
meganrogge 5fd7216bf2 add api tests 2022-02-08 15:57:43 -06:00
meganrogge a9722371e1 add decoration test to demo 2022-02-08 15:14:36 -06:00
meganrogge 58c2ba5310 remove empty line 2022-02-08 14:41:33 -06:00
meganrogge d8234affa4 move listener into terminal.ts 2022-02-08 14:35:33 -06:00
meganrogge 571bf9e533 fix bouncing on re render 2022-02-08 14:32:29 -06:00
meganrogge 723e915895 more decorationsService -> singular 2022-02-08 14:25:24 -06:00
meganrogge 3e5ecbf52b more formatting 2022-02-08 14:23:59 -06:00
meganrogge 01b66b756f revert formatting changes 2022-02-08 14:22:37 -06:00
meganrogge 275ae6b2d3 DecorationsService -> DecorationService 2022-02-08 14:20:18 -06:00
meganrogge 094763ea8b Revert changes to webglRenderer 2022-02-08 14:03:49 -06:00
meganrogge 3299315822 remove elt from screen on dispose 2022-02-08 12:26:19 -06:00
Megan Rogge 10d2e66137 Merge branch 'master' into merogge/buffer-marker-decoration 2022-02-08 12:07:13 -06:00
meganrogge 19b9d0eadd cleanup 2022-02-08 12:05:38 -06:00
meganrogge cae9f44f5c remove mention of buffer 2022-02-08 11:45:22 -06:00
meganrogge 8b540c5f3b set initial top value 2022-02-07 16:37:34 -06:00
meganrogge 8bf3cd1102 add to test 2022-02-07 16:06:16 -06:00
meganrogge b226eb2d64 throw for negative x 2022-02-07 14:49:11 -06:00
Megan Rogge e96b709d49 Merge branch 'master' into htmlserialize 2022-02-07 10:44:49 -06:00
meganrogge ac6877aba7 clean up 2022-02-07 10:14:56 -06:00
Megan Rogge a13f11adb4 Merge pull request #3628 from meganrogge/merogge/marker-clear 2022-02-04 21:04:06 -06:00
meganrogge 494aef1c94 remove .only 2022-02-04 19:59:10 -06:00
meganrogge 10adc87a3e add api test 2022-02-04 19:32:34 -06:00
meganrogge 4d7d666c71 add test 2022-02-04 18:00:13 -06:00
meganrogge ac7486856c improve conditional 2022-02-04 17:41:54 -06:00
meganrogge 8999d3948a fix test and improve name 2022-02-04 17:39:37 -06:00
meganrogge f3bd1e24b7 undo formatting change 2022-02-04 16:13:20 -06:00
meganrogge e9913522ac fix buffers not clearing 2022-02-04 16:11:04 -06:00
meganrogge 8f7db4b270 fix buffers not clearing 2022-02-04 16:06:53 -06:00
Megan Rogge 3745496d73 Merge branch 'xtermjs:master' into master 2022-02-04 15:58:01 -06:00
Megan Rogge a64d352d48 Merge branch 'master' into htmlserialize 2022-02-03 23:15:01 -06:00
meganrogge 42d403807c use css class for statics 2022-02-03 13:44:08 -06:00
meganrogge b2f541d4a6 clean up 2022-02-03 13:36:11 -06:00
meganrogge 912c33ca67 get it to wrk 2022-02-03 13:33:30 -06:00
Simon Lamon 4c1722da2c fixup text/plain removal 2022-02-03 18:30:28 +00:00
Simon Lamon 4d70606feb Apply feedback from code review 2022-02-03 18:00:16 +00:00
silamonandDaniel Imms 16257386b1 Apply suggestions from code review
Co-authored-by: Daniel Imms <2193314+Tyriar@users.noreply.github.com>
2022-02-03 18:55:07 +01:00
Daniel Imms a63890e627 Merge pull request #3624 from Tyriar/3617_clear_on_buffer_change
Ensure the glyph vertices are cleared when switching buffers
2022-02-03 08:53:51 -08:00
Daniel Imms 904b491521 Merge branch 'master' into 3617_clear_on_buffer_change 2022-02-03 08:43:53 -08:00
Daniel Imms fea1079b1e Force clear of the glyph renderer when switching
Fixes #3617
2022-02-03 08:33:22 -08:00
Daniel Imms b39d245050 Log parsing data string when using buffers 2022-02-03 08:32:24 -08:00
Daniel Imms b557ebe0a2 Merge pull request #3623 from Tyriar/mocha_update
Update mocha
2022-02-03 07:35:54 -08:00
Daniel Imms bb44dbfc51 Merge branch 'master' into mocha_update 2022-02-03 07:21:49 -08:00
Daniel Imms 490bf81b86 Update mocha 2022-02-03 07:21:00 -08:00
Daniel Imms c3261beaea Merge pull request #3622 from Tyriar/4_17_release
v4.17.0
2022-02-03 07:10:07 -08:00
Daniel Imms 345b8d910b v4.17.0 2022-02-03 07:04:07 -08:00
Daniel Imms 922ee7c48d Merge pull request #3612 from syssecfsu/master
Update README.md to link to WiTTY
2022-02-03 06:48:55 -08:00
meganrogge ad59db9a8a try 2022-02-02 22:33:05 -06:00
meganrogge 49085bbd5b use decorations service instead of decorations render layer 2022-02-02 20:51:28 -06:00
Simon Lamon 93ac9efe92 Use alternate buffer if alternate buffer is active 2022-02-02 19:21:02 +00:00
Simon Lamon bc9e6b49e8 Fix serialize tests 2022-02-02 18:57:41 +00:00
Simon Lamon adedfed2bd Fix serialize tests 2022-02-02 17:44:26 +00:00
Simon Lamon 9473777e43 Fix serialize tests 2022-02-02 17:18:35 +00:00
Megan Rogge a359647683 Merge branch 'master' into htmlserialize 2022-02-02 11:13:59 -06:00
Simon Lamon 09a77998ba improvements for html serialize 2022-02-02 06:27:01 +00:00
meganrogge 5ccb41b9b9 re add height and width 2022-02-01 10:32:09 -06:00
Daniel Imms 131e11b52b Merge pull request #3620 from xtermjs/Tyriar-patch-1
Add keywords to package.json
2022-02-01 08:26:20 -08:00
meganrogge 7d7726817a improve docs 2022-01-31 14:24:24 -06:00
meganrogge fc6245122d more cleanup 2022-01-31 13:08:09 -06:00
meganrogge c5a0711f84 clean up 2022-01-31 12:53:07 -06:00
Daniel Imms 5a3e0612a7 Merge pull request #3621 from felixse/set-text-baseline-for-legacy-edge
fix text baseline for Legacy Edge
2022-01-31 07:41:10 -08:00
Felix Seidl b246318090 fix text baseline for legacy edge 2022-01-30 21:21:07 +01:00
Daniel Imms d807bdac5e Add keywords to addons too 2022-01-30 05:47:58 -08:00
Daniel Imms 1695baa5b0 Add keywords to package.json
I noticed we have no search tags on npmjs.com
2022-01-30 05:43:20 -08:00
meganrogge 9b4801fb43 more polish 2022-01-30 00:36:31 -06:00
meganrogge d7854bc154 remove unused imports 2022-01-30 00:29:02 -06:00
meganrogge f9ac540031 polish 2022-01-30 00:28:01 -06:00
meganrogge 00ce85166e better button 2022-01-29 22:34:16 -06:00
Simon Lamon 6dc813bdff html serialization 2022-01-29 19:49:57 +01:00
meganrogge ebdd07b8ae get button to work 2022-01-29 00:07:51 -06:00
meganrogge c5810e6d54 Take 1 of api 2022-01-27 13:20:02 -06:00
syssecfsu c71252cd23 Update README.md to link to WiTTY 2022-01-22 18:45:30 -05:00
Daniel Imms 6c18b7f2cc Merge pull request #3609 from erulabs/master
Update README.md - add realworld usage @ KubeSail
2022-01-20 07:03:44 -08:00
Seandon Mooy 5e77ad5cb4 Update README.md - add realworld usage @ KubeSail
❤️ 🦞
2022-01-19 23:21:36 -08:00
Daniel Imms e1578813a7 Merge pull request #3606 from xtermjs/dependabot/npm_and_yarn/addons/xterm-addon-ligatures/follow-redirects-1.14.7
Bump follow-redirects from 1.14.3 to 1.14.7 in /addons/xterm-addon-ligatures
2022-01-14 07:18:25 -08:00
dependabot[bot] 21811cb37d Bump follow-redirects in /addons/xterm-addon-ligatures
Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.14.3 to 1.14.7.
- [Release notes](https://github.com/follow-redirects/follow-redirects/releases)
- [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.14.3...v1.14.7)

---
updated-dependencies:
- dependency-name: follow-redirects
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-01-14 08:59:31 +00:00
Daniel Imms 265227edcb Merge pull request #3604 from Tyriar/exception_3602
Prevent exception being thrown in reflow smaller
2022-01-11 08:25:28 -08:00
Daniel Imms 1d3c1964f9 Merge branch 'master' into exception_3602 2022-01-11 08:12:24 -08:00
Daniel Imms 58b7a4a2d7 Merge pull request #3603 from Tyriar/test_task
Set default vscode test task
2022-01-11 08:12:17 -08:00
Daniel Imms a6b0734fe8 Prevent exception being thrown in reflow smaller
Fixes #3602
2022-01-11 08:11:36 -08:00
Daniel Imms 55cd34c7a5 Set default vscode test task
This allows running the test task via a command/keybinding
2022-01-11 07:50:17 -08:00
Daniel Imms 0e8aeebedb Merge pull request #3599 from silamon/options5
Add options to headless typings
2022-01-11 07:36:30 -08:00
Daniel Imms 1c4d41db86 Merge branch 'master' into options5 2022-01-11 07:27:48 -08:00
Daniel Imms 63c281e405 Merge pull request #3559 from silamon/options3
Resolve perf regression from OptionsService.options
2022-01-11 07:26:43 -08:00
Simon Lamon f03d0858cc fix linting error 2022-01-11 11:58:53 +00:00
Simon Lamon 8bdeb14bc6 add options to headless typings 2022-01-08 10:14:33 +00:00
Simon Lamon ad7fe8f881 fix tests again 2022-01-08 08:51:29 +00:00
Simon Lamon 1dcc1d1baf fix test and replace options with rawOptions 2022-01-08 08:20:50 +00:00
Daniel Imms 0029bc3b42 Merge pull request #3598 from LabhanshAgrawal/ligatures
Bump font-ligatures to 1.4.1
2022-01-07 09:14:21 -08:00
Labhansh Agrawal 0b73a8d469 Bump font-ligatures to 1.4.1 2022-01-07 11:51:24 +05:30
Daniel Imms 3a2b97e0f7 Merge pull request #3597 from Aaaaash/chore/add-opensumi
Add opensumi to the list of users
2022-01-06 07:42:03 -08:00
大表哥 9bcc98beb9 Add opensumi to the list of users 2022-01-06 10:10:10 +08:00
Daniel Imms 1fb11707a5 Merge pull request #3596 from wesbos/patch-1
Extend TLD matching to 14 chars
2022-01-05 13:54:05 -08:00
Wes Bos 84548d5d7b Update WebLinksAddon.ts 2022-01-05 16:29:39 -05:00
Wes Bos 6f666f56c1 Extend TLD matching to 14 chars
I ran into this issue with hyper wasn't highlighting my `.localhost` domains, which is a legitimate TLD and used by caddy server to allow SSL certs locally.

Then I went down the rabbit hole of TLDs and found currently the longest TLDs are 14 chars which include .BANANAREPUBLIC, .COOKINGCHANNEL, .KERRYLOGISTICS, .WEATHERCHANNEL

I'm not regex expert, but I *think* this should work? 😀
2022-01-05 16:28:05 -05:00
Daniel Imms 2866977941 Merge pull request #3595 from xtermjs/Tyriar-patch-1
Fix dependents link
2022-01-05 12:58:50 -08:00
Daniel Imms c087592e2c Fix dependents link 2022-01-05 12:58:29 -08:00
Simon Lamon 65182176b9 remove proxy 2021-12-25 14:02:09 +00:00
Daniel Imms c8123d911e Merge pull request #3591 from Tyriar/3590
Disable macOS integration tests
2021-12-22 11:00:40 -08:00
Daniel Imms cacda99707 Disable macOS integration tests
Fixes #3590
2021-12-22 10:45:49 -08:00
Daniel Imms 10e4d075a3 Merge pull request #3589 from Tyriar/v4_16
v4.16.0
2021-12-22 10:32:27 -08:00
Daniel Imms 35c2d1ac4a v4.16.0 2021-12-22 10:26:30 -08:00
Daniel Imms 86ad5198ac Merge pull request #3588 from Tyriar/3505
Log characters as a code array when parsing data as well
2021-12-22 10:06:55 -08:00
Daniel Imms 9a3ee49160 Log characters as a code array when parsing data as well
Makes it consistent with sending data debug logs

Fixes #3505
2021-12-22 09:58:15 -08:00
Daniel Imms 3da594abb0 Merge pull request #3587 from Tyriar/3493
Make link provider API stable
2021-12-22 09:50:44 -08:00
Daniel Imms 72308b4f7f Make link provider API stable
Fixes #3493
2021-12-22 09:50:11 -08:00
Daniel Imms c22b3a92d7 Merge pull request #3236 from gera2ld/master
Search Addon: Fix length calculation of wide unicode chars
2021-12-22 09:31:56 -08:00
Daniel Imms f685360707 Add docs to line cache entry items 2021-12-22 09:29:32 -08:00
Daniel Imms 0fcd87085b Name line cache entry type 2021-12-22 09:26:19 -08:00
Daniel Imms 8d2e4328ca Merge branch 'master' into master 2021-12-22 09:12:00 -08:00
Daniel Imms ae6f5754f2 Merge pull request #3524 from jerch/osc_colors
OSC 4/10/11/12 color handling
2021-12-22 09:10:37 -08:00
Daniel Imms 086ca58ece Comment tweaks 2021-12-22 09:10:12 -08:00
Daniel Imms e635b3d507 Merge remote-tracking branch 'upstream/master' into pr/jerch/3524 2021-12-22 08:56:36 -08:00
Daniel Imms df548e1106 Merge pull request #3586 from Tyriar/3585
Fix demo
2021-12-22 08:55:58 -08:00
Daniel Imms 13b8c78947 Fix demo
Fixes #3585
2021-12-22 08:55:39 -08:00
Daniel Imms 3cc9994a7d Merge remote-tracking branch 'upstream/master' into pr/jerch/3524 2021-12-22 08:28:43 -08:00
Simon Lamon bac4eba35e Remove the test referencing publicOptions 2021-12-21 19:41:56 +00:00
Simon Lamon 625df61489 clean up 2021-12-21 19:29:18 +00:00
Simon Lamon ccca58bb95 Merge branch 'master' into options3 2021-12-21 19:22:41 +00:00
Daniel Imms d95d4b7dd4 Merge pull request #3583 from Tyriar/3552
Don't include trailing EOL when selecting multiple lines
2021-12-21 10:59:57 -08:00
Daniel Imms d261c3b494 Merge branch 'master' into 3552 2021-12-21 10:50:28 -08:00
Daniel Imms 8c94c97a28 Don't include trailing EOL when selecting multiple lines
Fixes #3552
2021-12-21 10:49:58 -08:00
Daniel Imms 46c316b795 Merge pull request #3546 from silamon/options2
Allow setting multiple options through term.options
2021-12-21 09:41:04 -08:00
Daniel Imms 7535d2e527 Move options test to Terminal.api.ts 2021-12-21 09:13:53 -08:00
Daniel Imms 6cc1708abe Merge branch 'master' into options2 2021-12-21 08:40:46 -08:00
Daniel Imms e623672e81 Merge pull request #3536 from silamon/isWrappedEraseInBufferline
Clear isWrapped in eraseBufferLine
2021-12-21 08:39:03 -08:00
Daniel Imms 006e0b7513 Merge branch 'master' into isWrappedEraseInBufferline 2021-12-21 08:30:46 -08:00
Daniel Imms c5b5f3610e Merge pull request #3582 from Tyriar/deprecated_event
Mark IKeyboardEvent.keyCode as deprecated
2021-12-20 10:03:40 -08:00
Daniel Imms 6571298061 Mark IKeyboardEvent.keyCode as deprecated 2021-12-20 10:02:49 -08:00
Daniel Imms 9350a8b237 Merge pull request #3580 from Tyriar/3579
Have linkifier2 use screen element for link detection
2021-12-20 08:57:16 -08:00
Daniel Imms f4c263f380 Merge branch 'master' into 3579 2021-12-20 08:46:50 -08:00
Daniel Imms 9a920a10f3 Have linkifier2 use screen element for link detection
Fixes #3579
2021-12-20 08:46:13 -08:00
Daniel Imms 2381096b06 Merge pull request #3577 from SquitchYT/master
Update README.md
2021-12-20 07:14:02 -08:00
Squitch 0ddb43ac8a Update README.md 2021-12-18 16:40:58 +01:00
Daniel Imms 45705e0614 Merge branch 'master' into options2 2021-12-17 11:13:31 -08:00
Daniel Imms 9557b9132b Merge branch 'master' into options3 2021-12-17 11:13:23 -08:00
Daniel Imms 6d6e8845c1 Merge pull request #3553 from jerch/fix_3548
fix canvas renderer color changes.
2021-12-17 11:10:43 -08:00
Daniel Imms 5facfe55b8 Merge pull request #3535 from Eugeny/fix-duplicate-ime-input
input: prevent duplicate IME input on Linux - fixes #3533
2021-12-17 08:49:49 -08:00
Daniel Imms 35cd505e9e Merge pull request #3554 from acid-chicken/microsoft-vscode-137047
wide characters overflow the cache canvas on WebGL render
2021-12-16 06:25:13 -08:00
Daniel Imms 78d3e22052 Merge pull request #3576 from Heysunk/master
Added support for custom regex to WebLinkProvider
2021-12-15 12:18:50 -08:00
Daniel Imms 7157b03a7e Change interface to use ?
So it's not a breaking change
2021-12-15 12:04:07 -08:00
Daniel Imms 7663e4a4e3 Simplify with || 2021-12-15 12:03:29 -08:00
Michael Chlebek ddc3d07bd4 Added support for custom regex to WebLinkProvider 2021-12-15 18:21:31 +01:00
Megan Rogge f6d3fae25e Merge branch 'master' into microsoft-vscode-137047 2021-12-14 14:44:55 -06:00
Daniel Imms 952d0efde2 Merge pull request #3574 from Tyriar/clean_dead_key
Clear unprocessed dead key state on input or press
2021-12-10 10:15:24 -08:00
Daniel Imms b7c3397471 Clear unprocessed dead key state on input or press
Fixes #3573
2021-12-09 13:14:43 -08:00
Daniel Imms cde4277e84 Merge pull request #3572 from Tyriar/a11y_tab_index
Add tabIndex to xterm-accessibility
2021-12-09 12:41:28 -08:00
Daniel Imms 37b54f8e8a Merge branch 'master' into a11y_tab_index 2021-12-09 12:31:30 -08:00
Daniel Imms 1304fd8a72 Add tabIndex to xterm-accessibility
See microsoft/vscode#135920
2021-12-09 20:30:38 +00:00
Daniel Imms 7b072893be Merge pull request #3565 from LabhanshAgrawal/characterJoiner
trycatch characterJoiner handlers
2021-11-24 10:55:54 -08:00
Labhansh Agrawal d10d5be222 make requested changes 2021-11-25 00:11:44 +05:30
Labhansh Agrawal 070afc64c8 trycatch characterJoiner handlers 2021-11-24 18:27:33 +05:30
Daniel Imms 20141518d9 Merge pull request #3560 from silamon/headless-compile-error
Remove headless include in benchmark projects
2021-11-21 03:50:33 -08:00
Simon Lamon d85a57118e remove headless include in benchmark projects 2021-11-20 17:48:45 +00:00
Simon Lamon d7d85870d2 remove headless include in benchmark projects 2021-11-20 17:33:38 +00:00
Simon Lamon d6d540e748 use a proxy 2021-11-20 14:58:07 +00:00
Acid Chicken (硫酸鶏) dbd3766a9d chore: reflect reviews 2021-11-13 01:36:46 +09:00
Acid Chicken (硫酸鶏) 935a1ba903 fix(xterm-addon-webgl): wide characters overflow the cache canvas 2021-11-13 00:32:24 +09:00
Jörg Breitbart 0c20589a7f remove temp atlas fix 2021-11-10 23:00:36 +01:00
Jörg Breitbart 84325627aa cleanup 2021-11-10 22:36:50 +01:00
Jörg Breitbart 9964210ac4 implement OSC 104|110|111|112 2021-11-10 22:15:35 +01:00
Jörg Breitbart e62e82ba10 Merge branch 'master' into osc_colors 2021-11-10 19:17:54 +01:00
Jörg Breitbart 5a468c0877 fix #3548 2021-11-10 18:02:00 +01:00
Simon Lamon 71257ec00e Fix tests 2021-11-10 10:45:42 +00:00
Simon Lamon 4245ce33b5 Remove publicOptions completely 2021-11-10 10:17:14 +00:00
Simon Lamon 317641c3bb Add some tests 2021-11-10 09:42:45 +00:00
Simon Lamon 2c65d96863 Fix tests 2021-11-10 08:39:28 +00:00
silamonandDaniel Imms b1c179461d Update typings/xterm.d.ts
Co-authored-by: Daniel Imms <2193314+Tyriar@users.noreply.github.com>
2021-11-10 09:15:43 +01:00
Daniel Imms 0bc02d6838 Merge pull request #3551 from Tyriar/scroll
Allow shift+wheel to bubble up the DOM
2021-11-09 06:54:22 -08:00
Daniel Imms d54c40a3ed Allow shift+wheel to bubble up the DOM
Fixes #3550
2021-11-09 06:46:00 -08:00
Jörg Breitbart 896f5a7e4c extend logic to OSC 12 cursor color 2021-11-09 13:46:54 +01:00
Jörg Breitbart 4be063be0f Merge branch 'master' into osc_colors 2021-11-08 20:57:54 +01:00
Jörg Breitbart 855ed636e7 remove only from tests 2021-11-08 20:57:22 +01:00
Jörg Breitbart b8097f53a6 OSC 4 integration tests 2021-11-08 20:55:47 +01:00
Jörg Breitbart 54822af73a use xcolor parser for OSC 4 2021-11-08 19:40:35 +01:00
Jörg Breitbart b55ee772b4 fix inputhandler type definition 2021-11-08 19:01:30 +01:00
Jörg Breitbart 56f0e69e1f api tests for OSC 10 & 11 2021-11-08 18:36:11 +01:00
Jörg Breitbart 064f683781 make linter happy 2021-11-08 17:23:34 +01:00
Jörg Breitbart 54375713bc simplify color events 2021-11-08 17:17:39 +01:00
Daniel Imms cb3dd19039 Merge pull request #3538 from coderaiser/chore/lint-using-putout
Halloween Lint using Putout (part 5)
2021-11-08 07:47:25 -08:00
Jörg Breitbart 1c5f24a707 Merge branch 'master' into osc_colors 2021-11-08 14:06:35 +01:00
Jörg Breitbart b7560be2b5 remove named colors 2021-11-08 14:03:36 +01:00
Jörg Breitbart 9d95c8c9c3 Merge branch 'master' into osc_colors 2021-11-07 15:51:30 +01:00
Simon Lamon 3582c00a61 Allow setting multiple options through term.options 2021-11-06 14:36:40 +00:00
coderaiser 8f1239674c Merge branch 'master' into chore/lint-using-putout 2021-11-05 21:38:30 +02:00
coderaiser 01b35ae4eb Update src/common/parser/Params.ts 2021-11-05 21:37:50 +02:00
coderaiser 82f8e06ff6 chore: lint using putout 2021-11-05 21:34:37 +02:00
Megan Rogge 1a8ac80cdf Merge pull request #3545 from meganrogge/merogge/release
v4.15.0
2021-11-05 13:17:11 -05:00
Megan Rogge b7c7fd9273 Merge branch 'master' into merogge/release 2021-11-05 13:08:28 -05:00
Megan Rogge 16641a5bb1 Revert "Revert "Avoid property use and float->number conversion""
This reverts commit b627f003e3.
2021-11-05 11:08:23 -07:00
Megan Rogge b7e5f0903b bump versions 2021-11-05 11:07:03 -07:00
Daniel Imms e40cf8043a Merge pull request #3544 from xtermjs/tyriar/length_warning
Add warning to IBufferLine.length
2021-11-05 09:09:37 -07:00
Daniel Imms 32cb95c7fb Add warning to IBufferLine.length 2021-11-05 08:53:48 -07:00
Eugeny f0303072f3 Merge branch 'master' into fix-duplicate-ime-input 2021-11-02 14:42:32 +01:00
jerch b2068531dd Merge pull request #3537 from jerch/update_deps
upgrade dev deps
2021-11-01 18:55:50 +01:00
Jörg Breitbart b29d7d2c8d update to windows-2019 on azure 2021-11-01 18:39:48 +01:00
Jörg Breitbart b684929d1e upgrade dev deps 2021-11-01 18:20:17 +01:00
Simon Lamon 47e53cbdaa Fix unit test calling eraseInDisplay instead of eraseInLine 2021-11-01 15:42:01 +00:00
Simon Lamon 086ac39095 Add whitespaces 2021-11-01 15:23:10 +00:00
Simon Lamon bec4f6d5c7 clear isWrapped in eraseBufferLine 2021-11-01 15:09:25 +00:00
Eugeny cfa0b30905 Merge branch 'master' into fix-duplicate-ime-input 2021-10-31 12:30:46 +01:00
Eugene Pankov 5a4e88dc51 input: prevent duplicate IME input on Linux - fixes #3533 2021-10-31 12:27:41 +01:00
Megan Rogge b627f003e3 Revert "Avoid property use and float->number conversion"
This reverts commit ffef3dba00.
2021-10-27 08:38:04 -07:00
Daniel Imms 07ba3dc197 Merge pull request #3532 from jeffg2k/patch-1
Check to make sure cols & rows are INT
2021-10-26 08:49:07 -07:00
ElJeffe 5c9adeef93 Check to make sure cols & rows are INT
I've noticed that if the terminal is hidden and the screen experiences a resize trigger from a blur / focus, it can cause the dimensions to be undefined.  This creates an exception in Xterm.js that the values are not integers.  Figured it would be ok to just check before doing a resize.
2021-10-26 11:44:52 -04:00
Daniel Imms 7abfccc23e Merge branch 'master' into master 2021-10-25 03:16:38 -07:00
Daniel Imms 394d7a2ee6 Merge pull request #3370 from dstein64/master
Set text baseline conditionally by browser.
2021-10-25 03:15:38 -07:00
Daniel Imms 987bb8dd35 Merge pull request #3476 from silamon/cleanbuild
Update npmignore files
2021-10-25 02:50:23 -07:00
Daniel Imms ce314844d2 Merge branch 'master' into cleanbuild 2021-10-25 02:47:52 -07:00
Jörg Breitbart 0871ace481 create minified color names module 2021-10-25 00:30:26 +02:00
Simon Lamon 645263b0ac update npmignore, don't include test files 2021-10-23 08:22:00 +00:00
Jörg Breitbart 5478dd2733 Merge branch 'master' into osc_colors 2021-10-23 00:57:17 +02:00
Jörg Breitbart 9eb72ee0cc report 16 bit color spec for query 2021-10-23 00:22:55 +02:00
Jörg Breitbart 258178fe85 cleanup & docs for OSC 10/11 2021-10-23 00:13:58 +02:00
Daniel Imms 6978fafd28 Merge pull request #3448 from silamon/options1
Expose typed options from OptionsService through terminal.options
2021-10-22 14:15:39 -07:00
Simon Lamon c7ee362ae7 revert changes to interface ITerminalOptions 2021-10-22 20:54:42 +00:00
Simon Lamon 57e6cfd500 setOption and getOption should look at publicOptions 2021-10-22 20:53:06 +00:00
Simon Lamon 33bbc4f4b8 cache options and type _options 2021-10-22 20:50:00 +00:00
Jörg Breitbart 01a732274c OSC 10/11 working, OSC 4 partially fixed 2021-10-22 22:48:57 +02:00
Daniel Imms a974d33c79 Merge branch 'master' into master 2021-10-22 13:14:11 -07:00
Daniel Imms 8549915afa Merge pull request #3525 from metonym/fix-viewport-scrollbar
Include xterm padding when computing viewport width
2021-10-22 13:09:57 -07:00
Daniel Steinberg 59f940c19c Set text baseline conditionally by browser. 2021-10-22 19:20:21 +00:00
metonym ed1aecb251 chore(viewport): run yarn lint --fix 2021-10-22 11:28:30 -07:00
metonym 7deb01267b Use computed padding of terminal element to compute viewport width 2021-10-22 11:02:59 -07:00
Daniel Imms 4b9ab81848 Add deprecated to getOption/setOption 2021-10-22 10:34:25 -07:00
Daniel Imms 6c8d801894 Adopt typed options in demo client 2021-10-22 10:14:03 -07:00
Daniel Imms 1a9de5f227 Merge branch 'master' into options1 2021-10-22 10:07:36 -07:00
metonym 9b1eec8ade fix(viewport): account for fallback scrollbar width 2021-10-22 09:49:01 -07:00
Jörg Breitbart 8e71cec208 remove only from tests 2021-10-22 18:32:30 +02:00
Jörg Breitbart 0f4d71b8f8 make linter happy 2021-10-22 18:26:46 +02:00
Jörg Breitbart 6a6043daa3 properly parse xcolor names 2021-10-22 18:17:52 +02:00
Daniel Imms 1a697bd844 Merge pull request #3479 from silamon/devcontainermocha
Add Mocha Test Explorer to devcontainer
2021-10-22 07:41:01 -07:00
jerch b23e100266 Merge pull request #3523 from jerch/fix_3347
properly await all parseP in inputhandler tests
2021-10-22 13:43:20 +02:00
Jörg Breitbart c9e8db38b6 properly await all parseP calls 2021-10-22 13:32:31 +02:00
Daniel Imms 0a7ac8b978 Merge pull request #3484 from JohanKnutzen/master
Add FleetDeck to real-world uses
2021-10-21 12:54:18 -07:00
Daniel Imms 780b48db53 Move newest to bottom of list 2021-10-21 12:54:01 -07:00
Daniel Imms 82d09ec909 Merge pull request #3522 from Tyriar/bump_release
Use Ubuntu 18.04 in release job
2021-10-21 11:44:13 -07:00
Daniel Imms b876639429 Use Ubuntu 18.04 in release job
Fixes #3521
2021-10-21 11:43:20 -07:00
jerch afe19e96fe simplify wheel handlers
- merge wheel handlers
- remove passive handler
2021-10-21 20:25:26 +02:00
jerch c599fbb7a1 Merge branch 'master' into fix_3518 2021-10-21 20:06:40 +02:00
Daniel Imms c63e9ef876 Merge pull request #3520 from meganrogge/cursor-blink
webgl - initialize CursorBlinkStateManager when CursorRenderLayer is created
2021-10-21 10:55:19 -07:00
Megan Rogge d6e828a2f3 fix merge conflict 2021-10-21 10:44:21 -07:00
Megan Rogge 2445f35a32 Merge branch 'master' into cursor-blink 2021-10-21 12:37:38 -05:00
Megan RoggeandDaniel Imms d2bcbc73d3 fixes #3517
Co-authored-by: Daniel Imms <Tyriar@users.noreply.github.com>
2021-10-21 10:33:22 -07:00
Jörg Breitbart f46ed82d7e simplify wheel handlers 2021-10-21 12:35:03 +02:00
Daniel Imms 4222dfb69d Merge pull request #3511 from Tyriar/windows_opener
Swallow error when opener can't be sent
2021-10-19 10:50:20 -07:00
Daniel Imms 1b3eb412b8 Merge pull request #3512 from Tyriar/serialize_doc
Call out deserialize into same size terminal
2021-10-19 10:50:10 -07:00
Daniel Imms e9f6636df1 Merge branch 'master' into serialize_doc 2021-10-19 10:49:47 -07:00
Daniel ImmsandMegan Rogge 472e410205 Call out deserialize into same size terminal
Fixes #3093

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

Co-authored-by: Megan Rogge <merogge@microsoft.com>
2021-10-19 10:28:09 -07:00
Johan Knutzen e8e21e9d0b Merge branch 'master' into master 2021-10-18 11:02:13 -07:00
Daniel Imms 80d4742ded Merge pull request #3506 from Tyriar/2333_sendfocus
Report focus state on DECSET 1004
2021-10-12 11:58:43 -07:00
Daniel Imms 50939e0b1d Merge branch 'master' into 2333_sendfocus 2021-10-12 09:38:45 -07:00
Daniel Imms 5d5f34f65f Report focus state on DECSET 1004
Fixes #2333
2021-10-12 09:05:44 -07:00
jerch d3b762eb97 Merge pull request #3503 from jerch/fix_cursor_dom
fix DECTCEM in DOM renderer
2021-10-11 15:07:23 +02:00
Jörg Breitbart 0c60478953 fix DECTCEM in DOM renderer 2021-10-10 19:36:20 +02:00
Daniel Imms 3b23921a41 Merge pull request #3500 from meganrogge/merogge/safari-webgl
throw if activate webgl is called on safari
2021-10-08 08:00:53 -07:00
Megan Rogge e3422eda58 Merge branch 'master' into merogge/safari-webgl 2021-10-08 09:46:03 -05:00
Megan Rogge 37a4100220 throw if activate webgl called on safari 2021-10-08 07:45:48 -07:00
Daniel Imms 8592c5cca3 Merge pull request #3495 from MyunghoBae/master
Added goormIDE on Real-world uses
2021-10-07 06:50:35 -07:00
mac.bae 014530f582 Added goormIDE on Real-world uses 2021-10-07 07:44:30 +00:00
Johan Knutzen a994fbc865 Merge branch 'master' into master 2021-09-22 17:43:52 -07:00
Johan Knutzen 0d14336ec5 Add FleetDeck to real-world uses 2021-09-22 17:39:21 -07:00
Daniel Imms c20c07c6d8 Merge pull request #3483 from Tyriar/document_role
Add document role to accessibility tree root
2021-09-22 14:31:42 -07:00
Daniel Imms 3a71b3f11e Add document role to accessibility tree root
See microsoft/vscode#98918
2021-09-22 14:21:50 -07:00
Daniel Imms f2384694a1 Merge pull request #3481 from Tyriar/clear_texture_atlas
Add API to clear canvas renderer texture atlas
2021-09-22 06:51:56 -07:00
Daniel Imms ca46b8c805 Merge branch 'master' into clear_texture_atlas 2021-09-22 06:39:53 -07:00
Daniel Imms 189ff56224 Add API to clear canvas renderer texture atlas
Fixes #3455
2021-09-22 06:38:47 -07:00
Daniel Imms ef6516e561 Merge pull request #3480 from silamon/strikethrough-serialize
Support strikethrough in serialize addon
2021-09-22 05:22:40 -07:00
Simon Lamon 1d1fffdabc Include src files in distribution 2021-09-22 10:08:48 +00:00
Simon Lamon 3fe32c0724 Adjust test to include strikethrough test 2021-09-22 09:38:37 +00:00
Simon Lamon 2e4e29ad73 Support strikethrough in serialize addon 2021-09-22 08:47:36 +00:00
Simon Lamon 9c4a08a55a devcontainer mocha 2021-09-22 08:41:57 +00:00
Simon Lamon 5188633458 Update npmignore files 2021-09-15 17:39:39 +00:00
Daniel Imms a8a24eba51 Merge pull request #3473 from Tyriar/serialize_granularity
Add granularity to what the serialize addon serializes
2021-09-15 06:03:47 -07:00
Daniel Imms 6493edcddf Add new serialize option tests 2021-09-15 05:40:46 -07:00
Daniel Imms c186fbeafc Add exclude mode/alt buffer options to serialize addon
Fixes #3472
2021-09-15 05:04:11 -07:00
Simon Lamon 99239e9160 Allow modifying constructor only options 2021-09-09 18:02:40 +00:00
Simon Lamon 72a557cc1a Make options available in terminal 2021-09-01 07:59:45 +00:00
Daniel Imms 6bca6bdc1a Merge branch 'master' into master 2021-08-31 07:31:02 -07:00
Gerald db42dcfae2 fix: translate string offset to buffer index in wrapped line 2021-03-02 11:31:26 +08:00
Gerald ae037bdac0 fix: translate buffer col to string offset 2021-02-22 16:23:08 +08:00
Gerald 6f6378aaf2 fix length calculation of wide unicode chars 2021-02-22 16:06:57 +08:00
145 changed files with 6786 additions and 2870 deletions
+2 -1
View File
@@ -4,6 +4,7 @@
"appPort": 3000,
"extensions": [
"dbaeumer.vscode-eslint",
"editorconfig.editorconfig"
"editorconfig.editorconfig",
"hbenl.vscode-mocha-test-adapter"
]
}
+8 -22
View File
@@ -2,10 +2,6 @@
*
!*/
# Whitelist - entries to be included must be negated with "!"
!*.js
!*.json
# Whitelist - css/
!css/**/*.css
@@ -32,25 +28,15 @@
!src/**/*.css
# Blacklist - src/ test files
src/**/*.test.ts
src/**/*.test.d.ts
src/**/*.test.js
src/**/*.test.js.map
# Whitelist - typings/
!typings/*.d.ts
# Blacklist - (normal behavior) these will override any whitelist
*.d.ts.map
*.test.ts
*.test.d.ts
*.test.js
*.test.js.map
lib/test/
webpack.config.js
docs/
/.idea/
.vscode/
bin/
build/
fixtures/
demo/
.devcontainer/
out/
addons/
headless/
typings/xterm-headless.d.ts
+8 -3
View File
@@ -11,10 +11,14 @@
{
"type": "npm",
"script": "test",
"group": "test",
"group":{
"kind": "test",
"isDefault": true
},
"problemMatcher": []
},
{
"label": "watch",
"type": "npm",
"script": "watch",
"group": "build",
@@ -25,9 +29,10 @@
}
},
{
"label": "start",
"type": "npm",
"script": "start",
"dependsOn": "npm: watch",
"dependsOn": "watch",
"group": "build",
"isBackground": true,
"problemMatcher": [],
@@ -37,7 +42,7 @@
},
{
"label": "Start demo",
"dependsOn": "npm: start",
"dependsOn": "start",
"group": {
"kind": "build",
"isDefault": true
+11 -2
View File
@@ -179,11 +179,20 @@ Xterm.js is used in several world-class applications to provide great terminal e
- [**WizardWebssh**](https://gitlab.com/mikeramsey/wizardwebssh): A terminal with Pyqt5 Widget for embedding, which can be used as an ssh client to connect to your ssh servers. It is written in Python, based on tornado, paramiko, and xterm.js.
- [**Wizard Assistant**](https://wizardassistant.com/): Wizard Assistant comes with advanced automation tools, preloaded common and special time-saving commands, and a built-in SSH terminal. Now you can remotely administer, troubleshoot, and analyze any system with ease.
- [**ucli**](https://github.com/tsadarsh/ucli): Command Line for everyone :family_man_woman_girl_boy: at [www.ucli.tech](https://www.ucli.tech).
- [**Tess**](https://github.com/SquitchYT/Tess/): Simple Terminal Fully Customizable for Everyone.
- [**Tess**](https://github.com/SquitchYT/Tess/): Simple Terminal Fully Customizable for Everyone. Discover more at [tessapp.dev](https://tessapp.dev)
- [**HashiCorp Nomad**](https://www.nomadproject.io/): A container orchestrator with the ability to connect to remote tasks via a web interface using websockets and xterm.js.
- [**TermPair**](https://github.com/cs01/termpair): View and control terminals from your browser with end-to-end encryption
- [**gdbgui**](https://github.com/cs01/gdbgui): Browser-based frontend to gdb (gnu debugger)
- [And much more...](https://github.com/xtermjs/xterm.js/network/dependents)
- [**goormIDE**](https://ide.goorm.io/): Run almost every programming languages with real-time collaboration, live pair programming, and built-in messenger.
- [**FleetDeck**](https://fleetdeck.io): Remote desktop & virtual terminal
- [**OpenSumi**](https://github.com/opensumi/core): A framework helps you quickly build Cloud or Desktop IDE products.
- [**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.
+29 -5
View File
@@ -1,5 +1,29 @@
**/*.api.js
**/*.api.ts
tsconfig.json
.yarnrc
webpack.config.js
# Blacklist - exclude everything except npm defaults such as LICENSE, etc
*
!*/
# Whitelist - lib/
!lib/**/*.d.ts
!lib/**/*.js
!lib/**/*.js.map
!lib/**/*.css
# Whitelist - src/
!src/**/*.ts
!src/**/*.d.ts
!src/**/*.js
!src/**/*.js.map
!src/**/*.css
# Blacklist - src/ test files
src/**/*.test.ts
src/**/*.test.d.ts
src/**/*.test.js
src/**/*.test.js.map
# Whitelist - typings/
!typings/*.d.ts
+5
View File
@@ -9,6 +9,11 @@
"types": "typings/xterm-addon-attach.d.ts",
"repository": "https://github.com/xtermjs/xterm.js",
"license": "MIT",
"keywords": [
"terminal",
"xterm",
"xterm.js"
],
"scripts": {
"build": "../../node_modules/.bin/tsc -p .",
"prepackage": "npm run build",
+29 -5
View File
@@ -1,5 +1,29 @@
**/*.api.js
**/*.api.ts
tsconfig.json
.yarnrc
webpack.config.js
# Blacklist - exclude everything except npm defaults such as LICENSE, etc
*
!*/
# Whitelist - lib/
!lib/**/*.d.ts
!lib/**/*.js
!lib/**/*.js.map
!lib/**/*.css
# Whitelist - src/
!src/**/*.ts
!src/**/*.d.ts
!src/**/*.js
!src/**/*.js.map
!src/**/*.css
# Blacklist - src/ test files
src/**/*.test.ts
src/**/*.test.d.ts
src/**/*.test.js
src/**/*.test.js.map
# Whitelist - typings/
!typings/*.d.ts
+5
View File
@@ -9,6 +9,11 @@
"types": "typings/xterm-addon-fit.d.ts",
"repository": "https://github.com/xtermjs/xterm.js",
"license": "MIT",
"keywords": [
"terminal",
"xterm",
"xterm.js"
],
"scripts": {
"build": "../../node_modules/.bin/tsc -p .",
"prepackage": "npm run build",
+5 -2
View File
@@ -33,7 +33,7 @@ export class FitAddon implements ITerminalAddon {
public fit(): void {
const dims = this.proposeDimensions();
if (!dims || !this._terminal) {
if (!dims || !this._terminal || isNaN(dims.cols) || isNaN(dims.rows)) {
return;
}
@@ -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))
+10 -29
View File
@@ -2,21 +2,13 @@
*
!*/
# Whitelist - entries to be included must be negated with "!"
!*.js
!*.json
# Whitelist - lib/
!lib/**/*.d.ts
!lib/**/*.js
!lib/**/*.js.map
# Whitelist - out/
!out/**/*.d.ts
!out/**/*.js
!out/**/*.js.map
!lib/**/*.css
# Whitelist - src/
!src/**/*.ts
@@ -25,24 +17,13 @@
!src/**/*.js
!src/**/*.js.map
!src/**/*.css
# Blacklist - src/ test files
src/**/*.test.ts
src/**/*.test.d.ts
src/**/*.test.js
src/**/*.test.js.map
# Whitelist - typings/
!typings/**/*.d.ts
# Blacklist - (normal behavior) these will override any whitelist
*.test.ts
*.test.d.ts
*.test.js
*.test.js.map
docs/
/.idea/
.vscode/
coverage/
.nyc_output/
fonts/
**/*.api.js
**/*.api.ts
tsconfig.json
.yarnrc
webpack.config.js
!typings/*.d.ts
+4 -4
View File
@@ -1,6 +1,6 @@
{
"name": "xterm-addon-ligatures",
"version": "0.5.1",
"version": "0.5.3",
"description": "Add support for programming ligatures to xterm.js",
"author": {
"name": "The xterm.js authors",
@@ -25,14 +25,14 @@
"keywords": [
"font",
"ligature",
"terminal",
"xterm",
"xterm.js",
"terminal"
"xterm.js"
],
"license": "MIT",
"dependencies": {
"font-finder": "^1.1.0",
"font-ligatures": "^1.4.0"
"font-ligatures": "^1.4.1"
},
"devDependencies": {
"@types/sinon": "^5.0.1",
+2 -2
View File
@@ -43,7 +43,7 @@ export default async function load(fontFamily: string, cacheSize: number): Promi
if (status && status.state !== 'granted') {
throw new Error('Permission to access local fonts not granted.');
}
} catch (err) {
} catch (err: any) {
// A `TypeError` indicates the 'local-fonts'
// permission is not yet implemented, so
// only `throw` if this is _not_ the problem.
@@ -61,7 +61,7 @@ export default async function load(fontFamily: string, cacheSize: number): Promi
fonts[metadata.family].push(metadata);
}
fontsPromise = Promise.resolve(fonts);
} catch (err) {
} catch (err: any) {
console.error(err.name, err.message);
}
}
+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;
+10 -10
View File
@@ -67,9 +67,9 @@ fd-slicer@~1.1.0:
pend "~1.2.0"
follow-redirects@^1.14.0:
version "1.14.3"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.3.tgz#6ada78118d8d24caee595595accdc0ac6abd022e"
integrity sha512-3MkHxknWMUtb23apkgz/83fDoe+y+qr0TdgacGIA7bew+QLBo3vdgEN2xEsuXNivpFy4CyDhBBZnNZOtalmenw==
version "1.14.8"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.8.tgz#016996fb9a11a100566398b1c6839337d7bfa8fc"
integrity sha512-1x0S9UVJHsQprFcEC/qnNzBLcIxsjAV905f/UkQxbclCsoTWlacCNOpQa/anodLl2uaEKFhfWOvM2Qg77+15zA==
font-finder@^1.0.3:
version "1.0.4"
@@ -86,10 +86,10 @@ font-finder@^1.1.0:
get-system-fonts "^2.0.0"
promise-stream-reader "^1.0.1"
font-ligatures@^1.4.0:
version "1.4.0"
resolved "https://registry.npmjs.org/font-ligatures/-/font-ligatures-1.4.0.tgz#6a7b370d96be1358dddfad67830e82fbfd59e6dc"
integrity sha512-n7DFnnEpJ0NrVoLqZIL4tMGVs+CnFwQc92m80LWyrbgAFO4x234+t2/H9o4eOYA1eh6ta9dZAEEsJAwsBdNezA==
font-ligatures@^1.4.1:
version "1.4.1"
resolved "https://registry.npmjs.org/font-ligatures/-/font-ligatures-1.4.1.tgz#e8c9df7cf6bee80c0cca4a9a578618c7b1f5d8de"
integrity sha512-7W6zlfyhvCqShZ5ReUWqmSd9vBaUudW0Hxis+tqUjtHhsPU+L3Grf8mcZAtCiXHTzorhwdRTId2WeH/88gdFkw==
dependencies:
font-finder "^1.0.3"
lru-cache "^6.0.0"
@@ -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"
+29 -5
View File
@@ -1,5 +1,29 @@
**/*.api.js
**/*.api.ts
tsconfig.json
.yarnrc
webpack.config.js
# Blacklist - exclude everything except npm defaults such as LICENSE, etc
*
!*/
# Whitelist - lib/
!lib/**/*.d.ts
!lib/**/*.js
!lib/**/*.js.map
!lib/**/*.css
# Whitelist - src/
!src/**/*.ts
!src/**/*.d.ts
!src/**/*.js
!src/**/*.js.map
!src/**/*.css
# Blacklist - src/ test files
src/**/*.test.ts
src/**/*.test.d.ts
src/**/*.test.js
src/**/*.test.js.map
# Whitelist - typings/
!typings/*.d.ts
+6 -1
View File
@@ -1,6 +1,6 @@
{
"name": "xterm-addon-search",
"version": "0.8.1",
"version": "0.9.0",
"author": {
"name": "The xterm.js authors",
"url": "https://xtermjs.org/"
@@ -9,6 +9,11 @@
"types": "typings/xterm-addon-search.d.ts",
"repository": "https://github.com/xtermjs/xterm.js",
"license": "MIT",
"keywords": [
"terminal",
"xterm",
"xterm.js"
],
"scripts": {
"build": "../../node_modules/.bin/tsc -p .",
"prepackage": "npm run build",
+422 -72
View File
@@ -3,13 +3,25 @@
* @license MIT
*/
import { Terminal, 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 {
@@ -21,29 +33,93 @@ export interface ISearchResult {
term: string;
col: number;
row: number;
size: number;
}
type LineCacheEntry = [
/**
* The string representation of a line (as opposed to the buffer cell representation).
*/
lineAsString: string,
/**
* The offsets where each line starts when the entry describes a wrapped line.
*/
lineOffsets: number[]
];
const NON_WORD_CHARACTERS = ' ~!@#$%^&*()+`-=[]{}|\\;:"\',./<>?';
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.
* _linesCache is also invalidated when the terminal cursor moves.
*/
private _linesCache: string[] | undefined;
private _linesCache: LineCacheEntry[] | undefined;
private _linesCacheTimeoutId = 0;
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
@@ -56,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;
@@ -83,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) {
@@ -117,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.
@@ -132,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()) {
@@ -195,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);
}
/**
@@ -257,7 +484,7 @@ export class SearchAddon implements ITerminalAddon {
*/
protected _findInLine(term: string, searchPosition: ISearchPosition, searchOptions: ISearchOptions = {}, isReverseSearch: boolean = false): ISearchResult | undefined {
const terminal = this._terminal!;
let row = searchPosition.startRow;
const row = searchPosition.startRow;
const col = searchPosition.startCol;
// Ignore wrapped lines, only consider on unwrapped line (first row of command string).
@@ -274,14 +501,16 @@ export class SearchAddon implements ITerminalAddon {
searchPosition.startCol += terminal.cols;
return this._findInLine(term, searchPosition, searchOptions);
}
let stringLine = this._linesCache ? this._linesCache[row] : void 0;
if (stringLine === void 0) {
stringLine = this._translateBufferLineToStringWithWrap(row, true);
let cache = this._linesCache?.[row];
if (!cache) {
cache = this._translateBufferLineToStringWithWrap(row, true);
if (this._linesCache) {
this._linesCache[row] = stringLine;
this._linesCache[row] = cache;
}
}
const [stringLine, offsets] = cache;
const offset = this._bufferColsToStringOffset(row, col);
const searchTerm = searchOptions.caseSensitive ? term : term.toLowerCase();
const searchStringLine = searchOptions.caseSensitive ? stringLine : stringLine.toLowerCase();
@@ -290,68 +519,109 @@ export class SearchAddon implements ITerminalAddon {
const searchRegex = RegExp(searchTerm, 'g');
let foundTerm: RegExpExecArray | null;
if (isReverseSearch) {
// This loop will get the resultIndex of the _last_ regex match in the range 0..col
while (foundTerm = searchRegex.exec(searchStringLine.slice(0, col))) {
// This loop will get the resultIndex of the _last_ regex match in the range 0..offset
while (foundTerm = searchRegex.exec(searchStringLine.slice(0, offset))) {
resultIndex = searchRegex.lastIndex - foundTerm[0].length;
term = foundTerm[0];
searchRegex.lastIndex -= (term.length - 1);
}
} else {
foundTerm = searchRegex.exec(searchStringLine.slice(col));
foundTerm = searchRegex.exec(searchStringLine.slice(offset));
if (foundTerm && foundTerm[0].length > 0) {
resultIndex = col + (searchRegex.lastIndex - foundTerm[0].length);
resultIndex = offset + (searchRegex.lastIndex - foundTerm[0].length);
term = foundTerm[0];
}
}
} else {
if (isReverseSearch) {
if (col - searchTerm.length >= 0) {
resultIndex = searchStringLine.lastIndexOf(searchTerm, col - searchTerm.length);
if (offset - searchTerm.length >= 0) {
resultIndex = searchStringLine.lastIndexOf(searchTerm, offset - searchTerm.length);
}
} else {
resultIndex = searchStringLine.indexOf(searchTerm, col);
resultIndex = searchStringLine.indexOf(searchTerm, offset);
}
}
if (resultIndex >= 0) {
// Adjust the row number and search index if needed since a "line" of text can span multiple rows
if (resultIndex >= terminal.cols) {
row += Math.floor(resultIndex / terminal.cols);
resultIndex = resultIndex % terminal.cols;
}
if (searchOptions.wholeWord && !this._isWholeWord(resultIndex, searchStringLine, term)) {
return;
}
const line = terminal.buffer.active.getLine(row);
if (line) {
for (let i = 0; i < resultIndex; i++) {
const cell = line.getCell(i);
if (!cell) {
break;
}
// Adjust the searchIndex to normalize emoji into single chars
const char = cell.getChars();
if (char.length > 1) {
resultIndex -= char.length - 1;
}
// Adjust the searchIndex for empty characters following wide unicode
// chars (eg. CJK)
const charWidth = cell.getWidth();
if (charWidth === 0) {
resultIndex++;
}
}
// Adjust the row number and search index if needed since a "line" of text can span multiple rows
let startRowOffset = 0;
while (startRowOffset < offsets.length - 1 && resultIndex >= offsets[startRowOffset + 1]) {
startRowOffset++;
}
let endRowOffset = startRowOffset;
while (endRowOffset < offsets.length - 1 && resultIndex + term.length >= offsets[endRowOffset + 1]) {
endRowOffset++;
}
const startColOffset = resultIndex - offsets[startRowOffset];
const endColOffset = resultIndex + term.length - offsets[endRowOffset];
const startColIndex = this._stringLengthToBufferSize(row + startRowOffset, startColOffset);
const endColIndex = this._stringLengthToBufferSize(row + endRowOffset, endColOffset);
const size = endColIndex - startColIndex + terminal.cols * (endRowOffset - startRowOffset);
return {
term,
col: resultIndex,
row
col: startColIndex,
row: row + startRowOffset,
size
};
}
}
private _stringLengthToBufferSize(row: number, offset: number): number {
const line = this._terminal!.buffer.active.getLine(row);
if (!line) {
return 0;
}
for (let i = 0; i < offset; i++) {
const cell = line.getCell(i);
if (!cell) {
break;
}
// Adjust the searchIndex to normalize emoji into single chars
const char = cell.getChars();
if (char.length > 1) {
offset -= char.length - 1;
}
// Adjust the searchIndex for empty characters following wide unicode
// chars (eg. CJK)
const nextCell = line.getCell(i + 1);
if (nextCell && nextCell.getWidth() === 0) {
offset++;
}
}
return offset;
}
private _bufferColsToStringOffset(startRow: number, cols: number): number {
const terminal = this._terminal!;
let lineIndex = startRow;
let offset = 0;
let line = terminal.buffer.active.getLine(lineIndex);
while (cols > 0 && line) {
for (let i = 0; i < cols && i < terminal.cols; i++) {
const cell = line.getCell(i);
if (!cell) {
break;
}
if (cell.getWidth()) {
// Treat null characters as whitespace to align with the translateToString API
offset += cell.getCode() === 0 ? 1 : cell.getChars().length;
}
}
lineIndex++;
line = terminal.buffer.active.getLine(lineIndex);
if (line && !line.isWrapped) {
break;
}
cols -= terminal.cols;
}
return offset;
}
/**
* Translates a buffer line to a string, including subsequent lines if they are wraps.
* Wide characters will count as two columns in the resulting string. This
@@ -360,43 +630,123 @@ export class SearchAddon implements ITerminalAddon {
* @param line The line being translated.
* @param trimRight Whether to trim whitespace to the right.
*/
private _translateBufferLineToStringWithWrap(lineIndex: number, trimRight: boolean): string {
private _translateBufferLineToStringWithWrap(lineIndex: number, trimRight: boolean): LineCacheEntry {
const terminal = this._terminal!;
let lineString = '';
let lineWrapsToNext: boolean;
do {
const strings = [];
const lineOffsets = [0];
let line = terminal.buffer.active.getLine(lineIndex);
while (line) {
const nextLine = terminal.buffer.active.getLine(lineIndex + 1);
lineWrapsToNext = nextLine ? nextLine.isWrapped : false;
const line = terminal.buffer.active.getLine(lineIndex);
if (!line) {
const lineWrapsToNext = nextLine ? nextLine.isWrapped : false;
let string = line.translateToString(!lineWrapsToNext && trimRight);
if (lineWrapsToNext && nextLine) {
const lastCell = line.getCell(line.length - 1);
const lastCellIsNull = lastCell && lastCell.getCode() === 0 && lastCell.getWidth() === 1;
// a wide character wrapped to the next line
if (lastCellIsNull && nextLine.getCell(0)?.getWidth() === 2) {
string = string.slice(0, -1);
}
}
strings.push(string);
if (lineWrapsToNext) {
lineOffsets.push(lineOffsets[lineOffsets.length - 1] + string.length);
} else {
break;
}
lineString += line.translateToString(!lineWrapsToNext && trimRight).substring(0, terminal.cols);
lineIndex++;
} while (lineWrapsToNext);
return lineString;
line = nextLine;
}
return [strings.join(''), lineOffsets];
}
/**
* 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.term.length);
// 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);
terminal.select(result.col, result.row, result.size);
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 () => {
@@ -103,6 +106,193 @@ describe('Search Tests', function(): void {
assert.deepEqual(await page.evaluate(`window.term.getSelection()`), 'abc');
});
it('Search for result bounding with wide unicode chars', async () => {
await writeSync(page, '中文xx𝄞𝄞');
assert.deepEqual(await page.evaluate(`window.search.findNext('中')`), true);
assert.deepEqual(await page.evaluate(`window.term.getSelection()`), '中');
assert.deepEqual(await page.evaluate(`window.search.findNext('xx')`), true);
assert.deepEqual(await page.evaluate(`window.term.getSelection()`), 'xx');
assert.deepEqual(await page.evaluate(`window.search.findNext('𝄞')`), true);
assert.deepEqual(await page.evaluate(`window.term.getSelection()`), '𝄞');
assert.deepEqual(await page.evaluate(`window.search.findNext('𝄞')`), true);
assert.deepEqual(await page.evaluate(`window.term.getSelectionPosition()`), {
startRow: 0,
endRow: 0,
startColumn: 7,
endColumn: 8
});
});
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;
@@ -117,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);
@@ -189,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'),
+29 -5
View File
@@ -1,5 +1,29 @@
**/*.api.js
**/*.api.ts
tsconfig.json
.yarnrc
webpack.config.js
# Blacklist - exclude everything except npm defaults such as LICENSE, etc
*
!*/
# Whitelist - lib/
!lib/**/*.d.ts
!lib/**/*.js
!lib/**/*.js.map
!lib/**/*.css
# Whitelist - src/
!src/**/*.ts
!src/**/*.d.ts
!src/**/*.js
!src/**/*.js.map
!src/**/*.css
# Blacklist - src/ test files
src/**/*.test.ts
src/**/*.test.d.ts
src/**/*.test.js
src/**/*.test.js.map
# Whitelist - typings/
!typings/*.d.ts
@@ -14,7 +14,7 @@
"SerializeAddon": ["../src/SerializeAddon"]
}
},
"include": ["../**/*", "../../../typings/xterm.d.ts", "../../../out/**/*"],
"include": ["../**/*", "../../../typings/xterm.d.ts"],
"exclude": ["../../../**/*test.ts", "../../**/*api.ts"],
"references": [
{ "path": "../../../src/common" },
+6 -1
View File
@@ -1,6 +1,6 @@
{
"name": "xterm-addon-serialize",
"version": "0.6.0",
"version": "0.7.0",
"author": {
"name": "The xterm.js authors",
"url": "https://xtermjs.org/"
@@ -9,6 +9,11 @@
"types": "typings/xterm-addon-serialize.d.ts",
"repository": "https://github.com/xtermjs/xterm.js",
"license": "MIT",
"keywords": [
"terminal",
"xterm",
"xterm.js"
],
"scripts": {
"build": "../../node_modules/.bin/tsc -p .",
"prepackage": "npm run build",
@@ -0,0 +1,214 @@
/**
* Copyright (c) 2018 The xterm.js authors. All rights reserved.
* @license MIT
*/
import jsdom = require('jsdom');
import { assert } from 'chai';
import { SerializeAddon } from './SerializeAddon';
import { Terminal } from 'browser/public/Terminal';
import { ColorManager } from 'browser/ColorManager';
import { SelectionModel } from 'browser/selection/SelectionModel';
import { IBufferService } from 'common/services/Services';
function sgr(...seq: string[]): string {
return `\x1b[${seq.join(';')}m`;
}
function writeP(terminal: Terminal, data: string | Uint8Array): Promise<void> {
return new Promise(r => terminal.write(data, r));
}
class TestSelectionService {
private _model: SelectionModel;
private _hasSelection: boolean = false;
constructor(
bufferService: IBufferService
) {
this._model = new SelectionModel(bufferService);
}
public get model(): SelectionModel { return this._model; }
public get hasSelection(): boolean { return this._hasSelection; }
public get selectionStart(): [number, number] | undefined { return this._model.finalSelectionStart; }
public get selectionEnd(): [number, number] | undefined { return this._model.finalSelectionEnd; }
public setSelection(col: number, row: number, length: number): void {
this._model.selectionStart = [col, row];
this._model.selectionStartLength = length;
this._hasSelection = true;
}
}
describe('xterm-addon-serialize', () => {
let cm: ColorManager;
let dom: jsdom.JSDOM;
let document: Document;
let window: jsdom.DOMWindow;
let serializeAddon: SerializeAddon;
let terminal: Terminal;
let selectionService: any;
before(() => {
serializeAddon = new SerializeAddon();
});
beforeEach(() => {
dom = new jsdom.JSDOM('');
window = dom.window;
document = window.document;
(window as any).HTMLCanvasElement.prototype.getContext = () => ({
createLinearGradient(): any {
return null;
},
fillRect(): void { },
getImageData(): any {
return { data: [0, 0, 0, 0xFF] };
}
});
terminal = new Terminal({ cols: 10, rows: 2 });
terminal.loadAddon(serializeAddon);
selectionService = new TestSelectionService((terminal as any)._core._bufferService);
cm = new ColorManager(document, false);
(terminal as any)._core._colorManager = cm;
(terminal as any)._core._selectionService = selectionService;
});
describe('text', () => {
it('restoring cursor styles', async () => {
await writeP(terminal, sgr('32') + '> ' + sgr('0'));
assert.equal(serializeAddon.serialize(), '\u001b[32m> \u001b[0m');
});
});
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);
});
it('empty terminal with no selection', () => {
const output = serializeAddon.serializeAsHTML({
onlySelection: true
});
assert.equal(output, '');
});
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);
});
});
});
@@ -5,8 +5,9 @@
* (EXPERIMENTAL) This Addon is still under development
*/
import { Terminal, ITerminalAddon, IBuffer, IBufferCell } from 'xterm';
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));
@@ -19,18 +20,25 @@ abstract class BaseSerializeHandler {
) {
}
public serialize(startRow: number, endRow: number): string {
public serialize(range: IBufferRange): string {
// we need two of them to flip between old and new cell
const cell1 = this._buffer.getNullCell();
const cell2 = this._buffer.getNullCell();
let oldCell = cell1;
const startRow = range.start.x;
const endRow = range.end.x;
const startColumn = range.start.y;
const endColumn = range.end.y;
this._beforeSerialize(endRow - startRow, startRow, endRow);
for (let row = startRow; row < endRow; row++) {
for (let row = startRow; row <= endRow; row++) {
const line = this._buffer.getLine(row);
if (line) {
for (let col = 0; col < line.length; col++) {
const startLineColumn = row !== range.start.x ? 0 : startColumn;
const endLineColumn = row !== range.end.x ? line.length : endColumn;
for (let col = startLineColumn; col < endLineColumn; col++) {
const c = line.getCell(col, oldCell === cell1 ? cell2 : cell1);
if (!c) {
console.warn(`Can't get cell at row=${row}, col=${col}`);
@@ -40,7 +48,7 @@ abstract class BaseSerializeHandler {
oldCell = c;
}
}
this._rowEnd(row, row === endRow - 1);
this._rowEnd(row, row === endRow);
}
this._afterSerialize();
@@ -55,24 +63,25 @@ 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()
&& cell1.isBlink() === cell2.isBlink()
&& cell1.isInvisible() === cell2.isInvisible()
&& cell1.isItalic() === cell2.isItalic()
&& cell1.isDim() === cell2.isDim();
&& cell1.isDim() === cell2.isDim()
&& cell1.isStrikethrough() === cell2.isStrikethrough();
}
class StringSerializeHandler extends BaseSerializeHandler {
@@ -160,7 +169,7 @@ class StringSerializeHandler extends BaseSerializeHandler {
if (
// you must output character to cause overflow, control sequence can't do this
nextRowFirstChar.getChars() &&
isNextRowFirstCharDoubleWidth ? this._nullCellCount <= 1 : this._nullCellCount <= 0
isNextRowFirstCharDoubleWidth ? this._nullCellCount <= 1 : this._nullCellCount <= 0
) {
if (
// the last character can't be null,
@@ -221,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);
@@ -259,6 +268,7 @@ class StringSerializeHandler extends BaseSerializeHandler {
if (cell.isInvisible() !== oldCell.isInvisible()) { sgrSeq.push(cell.isInvisible() ? 8 : 28); }
if (cell.isItalic() !== oldCell.isItalic()) { sgrSeq.push(cell.isItalic() ? 3 : 23); }
if (cell.isDim() !== oldCell.isDim()) { sgrSeq.push(cell.isDim() ? 2 : 22); }
if (cell.isStrikethrough() !== oldCell.isStrikethrough()) { sgrSeq.push(cell.isStrikethrough() ? 9 : 29); }
}
}
}
@@ -384,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;
}
}
@@ -401,7 +420,35 @@ export class SerializeAddon implements ITerminalAddon {
const maxRows = buffer.length;
const handler = new StringSerializeHandler(buffer, terminal);
const correctRows = (scrollback === undefined) ? maxRows : constrain(scrollback + terminal.rows, 0, maxRows);
return handler.serialize(maxRows - correctRows, maxRows);
return handler.serialize({
start: { x: maxRows - correctRows, y: 0 },
end: { x: maxRows - 1, y: terminal.cols }
});
}
private _serializeBufferAsHTML(terminal: Terminal, options: Partial<IHTMLSerializeOptions>): string {
const buffer = terminal.buffer.active;
const handler = new HTMLSerializeHandler(buffer, terminal, options);
const onlySelection = options.onlySelection ?? false;
if (!onlySelection) {
const maxRows = buffer.length;
const scrollback = options.scrollback;
const correctRows = (scrollback === undefined) ? maxRows : constrain(scrollback + terminal.rows, 0, maxRows);
return handler.serialize({
start: { x: maxRows - correctRows, y: 0 },
end: { x: maxRows - 1, y: terminal.cols }
});
}
const selection = this._terminal?.getSelectionPosition();
if (selection !== undefined) {
return handler.serialize({
start: { x: selection.startRow, y: selection.startColumn },
end: { x: selection.endRow, y: selection.endColumn }
});
}
return '';
}
private _serializeModes(terminal: Terminal): string {
@@ -433,26 +480,192 @@ export class SerializeAddon implements ITerminalAddon {
return content;
}
public serialize(scrollback?: number): string {
public serialize(options?: ISerializeOptions): string {
// TODO: Add combinedData support
if (!this._terminal) {
throw new Error('Cannot use addon until it has been loaded');
}
// Normal buffer
let content = this._serializeBuffer(this._terminal, this._terminal.buffer.normal, scrollback);
let content = this._serializeBuffer(this._terminal, this._terminal.buffer.normal, options?.scrollback);
// Alternate buffer
if (this._terminal.buffer.active.type === 'alternate') {
const alternativeScreenContent = this._serializeBuffer(this._terminal, this._terminal.buffer.alternate, undefined);
content += `\u001b[?1049h\u001b[H${alternativeScreenContent}`;
if (!options?.excludeAltBuffer) {
if (this._terminal.buffer.active.type === 'alternate') {
const alternativeScreenContent = this._serializeBuffer(this._terminal, this._terminal.buffer.alternate, undefined);
content += `\u001b[?1049h\u001b[H${alternativeScreenContent}`;
}
}
// Modes
content += this._serializeModes(this._terminal);
if (!options?.excludeModes) {
content += this._serializeModes(this._terminal);
}
return content;
}
public serializeAsHTML(options?: Partial<IHTMLSerializeOptions>): string {
if (!this._terminal) {
throw new Error('Cannot use addon until it has been loaded');
}
return this._serializeBufferAsHTML(this._terminal, options || {});
}
public dispose(): void { }
}
interface ISerializeOptions {
scrollback?: number;
excludeModes?: boolean;
excludeAltBuffer?: boolean;
}
interface IHTMLSerializeOptions {
scrollback: number;
onlySelection: boolean;
includeGlobalBackground: boolean;
}
export class HTMLSerializeHandler extends BaseSerializeHandler {
private _currentRow: string = '';
private _htmlContent = '';
private _colors: IColorSet;
constructor(
buffer: IBuffer,
private readonly _terminal: Terminal,
private readonly _options: Partial<IHTMLSerializeOptions>
) {
super(buffer);
// https://github.com/xtermjs/xterm.js/issues/3601
this._colors = (_terminal as any)._core._colorManager.colors;
}
private _padStart(target: string, targetLength: number, padString: string): string {
targetLength = targetLength >> 0;
padString = padString ?? ' ';
if (target.length > targetLength) {
return target;
}
targetLength -= target.length;
if (targetLength > padString.length) {
padString += padString.repeat(targetLength / padString.length);
}
return padString.slice(0, targetLength) + target;
}
protected _beforeSerialize(rows: number, start: number, end: number): void {
this._htmlContent += '<html><body><!--StartFragment--><pre>';
let foreground = '#000000';
let background = '#ffffff';
if (this._options.includeGlobalBackground ?? false) {
foreground = this._terminal.options.theme?.foreground ?? '#ffffff';
background = this._terminal.options.theme?.background ?? '#000000';
}
const globalStyleDefinitions = [];
globalStyleDefinitions.push('color: ' + foreground + ';');
globalStyleDefinitions.push('background-color: ' + background + ';');
globalStyleDefinitions.push('font-family: ' + this._terminal.options.fontFamily + ';');
globalStyleDefinitions.push('font-size: ' + this._terminal.options.fontSize + 'px;');
this._htmlContent += '<div style=\'' + globalStyleDefinitions.join(' ') + '\'>';
}
protected _afterSerialize(): void {
this._htmlContent += '</div>';
this._htmlContent += '</pre><!--EndFragment--></body></html>';
}
protected _rowEnd(row: number, isLastRow: boolean): void {
this._htmlContent += '<div><span>' + this._currentRow + '</span></div>';
this._currentRow = '';
}
private _getHexColor(cell: IBufferCell, isFg: boolean): string | undefined {
const color = isFg ? cell.getFgColor() : cell.getBgColor();
if (isFg ? cell.isFgRGB() : cell.isBgRGB()) {
const rgb = [
(color >> 16) & 255,
(color >> 8) & 255,
(color ) & 255
];
return rgb.map(x => this._padStart(x.toString(16), 2, '0')).join('');
}
if (isFg ? cell.isFgPalette() : cell.isBgPalette()) {
return this._colors.ansi[color].css;
}
return undefined;
}
private _diffStyle(cell: IBufferCell, oldCell: IBufferCell): string[] | undefined {
const content: string[] = [];
const fgChanged = !equalFg(cell, oldCell);
const bgChanged = !equalBg(cell, oldCell);
const flagsChanged = !equalFlags(cell, oldCell);
if (fgChanged || bgChanged || flagsChanged) {
const fgHexColor = this._getHexColor(cell, true);
if (fgHexColor) {
content.push('color: ' + fgHexColor + ';');
}
const bgHexColor = this._getHexColor(cell, false);
if (bgHexColor) {
content.push('background-color: ' + bgHexColor + ';');
}
if (cell.isInverse()) { content.push('color: #000000; background-color: #BFBFBF;'); }
if (cell.isBold()) { content.push('font-weight: bold;'); }
if (cell.isUnderline()) { content.push('text-decoration: underline;'); }
if (cell.isBlink()) { content.push('text-decoration: blink;'); }
if (cell.isInvisible()) { content.push('visibility: hidden;'); }
if (cell.isItalic()) { content.push('font-style: italic;'); }
if (cell.isDim()) { content.push('opacity: 0.5;'); }
if (cell.isStrikethrough()) { content.push('text-decoration: line-through;'); }
return content;
}
return undefined;
}
protected _nextCell(cell: IBufferCell, oldCell: IBufferCell, row: number, col: number): void {
// a width 0 cell don't need to be count because it is just a placeholder after a CJK character;
const isPlaceHolderCell = cell.getWidth() === 0;
if (isPlaceHolderCell) {
return;
}
// this cell don't have content
const isEmptyCell = cell.getChars() === '';
const styleDefinitions = this._diffStyle(cell, oldCell);
// handles style change
if (styleDefinitions) {
this._currentRow += styleDefinitions.length === 0 ?
'</span><span>' :
'</span><span style=\'' + styleDefinitions.join(' ') + '\'>';
}
// handles actual content
if (isEmptyCell) {
this._currentRow += ' ';
} else {
this._currentRow += cell.getChars();
}
}
protected _serializeString(): string {
return this._htmlContent;
}
}
@@ -14,6 +14,9 @@
"paths": {
"common/*": [
"../../../src/common/*"
],
"browser/*": [
"../../../src/browser/*"
]
},
"strict": true,
@@ -28,6 +31,9 @@
"references": [
{
"path": "../../../src/common"
},
{
"path": "../../../src/browser"
}
]
}
@@ -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(() => {
@@ -146,7 +146,7 @@ describe('SerializeAddon', () => {
const cols = 10;
const lines = newArray<string>((index: number) => digitsString(cols, index), rows);
await writeSync(page, lines.join('\\r\\n'));
assert.equal(await page.evaluate(`serializeAddon.serialize(${halfScrollback});`), lines.slice(halfScrollback, rows).join('\r\n'));
assert.equal(await page.evaluate(`serializeAddon.serialize({ scrollback: ${halfScrollback} });`), lines.slice(halfScrollback, rows).join('\r\n'));
});
it('serialize 0 rows of scrollback', async function(): Promise<any> {
@@ -154,7 +154,19 @@ describe('SerializeAddon', () => {
const cols = 10;
const lines = newArray<string>((index: number) => digitsString(cols, index), rows);
await writeSync(page, lines.join('\\r\\n'));
assert.equal(await page.evaluate(`serializeAddon.serialize(0);`), lines.slice(rows - 10, rows).join('\r\n'));
assert.equal(await page.evaluate(`serializeAddon.serialize({ scrollback: 0 });`), lines.slice(rows - 10, rows).join('\r\n'));
});
it('serialize exclude modes', async () => {
await writeSync(page, 'before\\x1b[?1hafter');
assert.equal(await page.evaluate(`serializeAddon.serialize();`), 'beforeafter\x1b[?1h');
assert.equal(await page.evaluate(`serializeAddon.serialize({ excludeModes: true });`), 'beforeafter');
});
it('serialize exclude alt buffer', async () => {
await writeSync(page, 'normal\\x1b[?1049h\\x1b[Halt');
assert.equal(await page.evaluate(`serializeAddon.serialize();`), 'normal\x1b[?1049h\x1b[Halt');
assert.equal(await page.evaluate(`serializeAddon.serialize({ excludeAltBuffer: true });`), 'normal');
});
it('serialize all rows of content with color16', async function(): Promise<any> {
@@ -184,11 +196,13 @@ describe('SerializeAddon', () => {
sgr(UNDERLINED) + line,
sgr(BLINK) + line,
sgr(INVISIBLE) + line,
sgr(STRIKETHROUGH) + line,
sgr(NO_INVERSE) + line,
sgr(NO_BOLD) + line,
sgr(NO_UNDERLINED) + line,
sgr(NO_BLINK) + line,
sgr(NO_INVISIBLE) + line
sgr(NO_INVISIBLE) + line,
sgr(NO_STRIKETHROUGH) + line
];
const rows = lines.length;
await writeSync(page, lines.join('\\r\\n'));
@@ -579,20 +593,20 @@ const BG_RGB_GREEN = '48;2;0;255;0';
const BG_RGB_YELLOW = '48;2;255;255;0';
const BG_RESET = '49';
const INVERSE = '7';
const BOLD = '1';
const DIM = '2';
const ITALIC = '3';
const UNDERLINED = '4';
const BLINK = '5';
const INVERSE = '7';
const INVISIBLE = '8';
const STRIKETHROUGH = '9';
const NO_INVERSE = '27';
const NO_BOLD = '22';
const NO_DIM = '22';
const NO_ITALIC = '23';
const NO_UNDERLINED = '24';
const NO_BLINK = '25';
const NO_INVERSE = '27';
const NO_INVISIBLE = '28';
const ITALIC = '3';
const DIM = '2';
const NO_ITALIC = '23';
const NO_DIM = '22';
const NO_STRIKETHROUGH = '29';
@@ -7,14 +7,14 @@ import { Terminal, ITerminalAddon } from 'xterm';
declare module 'xterm-addon-serialize' {
/**
* An xterm.js addon that enables web links.
* An xterm.js addon that enables serialization of terminal contents.
*/
export class SerializeAddon implements ITerminalAddon {
constructor();
/**
* Activates the addon
* Activates the addon.
* @param terminal The terminal the addon is being loaded in.
*/
public activate(terminal: Terminal): void;
@@ -24,15 +24,65 @@ declare module 'xterm-addon-serialize' {
* the state. The cursor will also be positioned to the correct cell. When restoring a terminal
* it is best to do before `Terminal.open` is called to avoid wasting CPU cycles rendering
* incomplete frames.
* @param scrollback The number of rows in scrollback buffer to serialize, starting from the
* bottom of the scrollback buffer. This defaults to the all available rows in the scrollback
* buffer.
*
* It's recommended that you write the serialized data into a terminal of the same size in which
* it originated from and then resize it after if needed.
*
* @param options Custom options to allow control over what gets serialized.
*/
public serialize(scrollback?: number): string;
public serialize(options?: ISerializeOptions): string;
/**
* Serializes terminal content as HTML, which can be written to the clipboard using the
* `text/html` mimetype. For applications that support it, the pasted text should then retain
* its colors/styles.
*
* @param options Custom options to allow control over what gets serialized.
*/
public serializeAsHTML(options?: Partial<IHTMLSerializeOptions>): string;
/**
* Disposes the addon.
*/
public dispose(): void;
}
export interface ISerializeOptions {
/**
* The number of rows in the scrollback buffer to serialize, starting from the bottom of the
* scrollback buffer. When not specified, all available rows in the scrollback buffer will be
* serialized.
*/
scrollback?: number;
/**
* Whether to exclude the terminal modes from the serialization. False by default.
*/
excludeModes?: boolean;
/**
* Whether to exclude the alt buffer from the serialization. False by default.
*/
excludeAltBuffer?: boolean;
}
export interface IHTMLSerializeOptions {
/**
* The number of rows in the scrollback buffer to serialize, starting from the bottom of the
* scrollback buffer. When not specified, all available rows in the scrollback buffer will be
* serialized. This setting is ignored if {@link IHTMLSerializeOptions.onlySelection} is true.
*/
scrollback: number;
/**
* Whether to only serialize the selection. If false, the whole active buffer is serialized in HTML.
* False by default.
*/
onlySelection: boolean;
/**
* Whether to include the global background of the terminal. False by default.
*/
includeGlobalBackground: boolean;
}
}
+29 -5
View File
@@ -1,5 +1,29 @@
**/*.api.js
**/*.api.ts
tsconfig.json
.yarnrc
webpack.config.js
# Blacklist - exclude everything except npm defaults such as LICENSE, etc
*
!*/
# Whitelist - lib/
!lib/**/*.d.ts
!lib/**/*.js
!lib/**/*.js.map
!lib/**/*.css
# Whitelist - src/
!src/**/*.ts
!src/**/*.d.ts
!src/**/*.js
!src/**/*.js.map
!src/**/*.css
# Blacklist - src/ test files
src/**/*.test.ts
src/**/*.test.d.ts
src/**/*.test.js
src/**/*.test.js.map
# Whitelist - typings/
!typings/*.d.ts
@@ -9,6 +9,11 @@
"types": "typings/xterm-addon-unicode11.d.ts",
"repository": "https://github.com/xtermjs/xterm.js",
"license": "MIT",
"keywords": [
"terminal",
"xterm",
"xterm.js"
],
"scripts": {
"build": "../../node_modules/.bin/tsc -p .",
"prepackage": "npm run build",
+29 -5
View File
@@ -1,5 +1,29 @@
**/*.api.js
**/*.api.ts
tsconfig.json
.yarnrc
webpack.config.js
# Blacklist - exclude everything except npm defaults such as LICENSE, etc
*
!*/
# Whitelist - lib/
!lib/**/*.d.ts
!lib/**/*.js
!lib/**/*.js.map
!lib/**/*.css
# Whitelist - src/
!src/**/*.ts
!src/**/*.d.ts
!src/**/*.js
!src/**/*.js.map
!src/**/*.css
# Blacklist - src/ test files
src/**/*.test.ts
src/**/*.test.d.ts
src/**/*.test.js
src/**/*.test.js.map
# Whitelist - typings/
!typings/*.d.ts
+6 -1
View File
@@ -1,6 +1,6 @@
{
"name": "xterm-addon-web-links",
"version": "0.4.0",
"version": "0.6.0",
"author": {
"name": "The xterm.js authors",
"url": "https://xtermjs.org/"
@@ -9,6 +9,11 @@
"types": "typings/xterm-addon-web-links.d.ts",
"repository": "https://github.com/xtermjs/xterm.js",
"license": "MIT",
"keywords": [
"terminal",
"xterm",
"xterm.js"
],
"scripts": {
"build": "../../node_modules/.bin/tsc -p .",
"prepackage": "npm run build",
@@ -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,14 +3,14 @@
* @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\\.-]+';
const negatedDomainCharacterSet = '[^\\da-z\\.-]+';
const domainBodyClause = '(' + domainCharacterSet + ')';
const tldClause = '([a-z\\.]{2,6})';
const tldClause = '([a-z\\.]{2,18})';
const ipClause = '((\\d{1,3}\\.){3}\\d{1,3})';
const localHostClause = '(localhost)';
const portClause = '(:\\d{1,5})';
@@ -29,18 +29,17 @@ const strictUrlRegex = new RegExp(start + protocolClause + bodyClause + end);
function handleLink(event: MouseEvent, uri: string): void {
const newWindow = window.open();
if (newWindow) {
newWindow.opener = null;
try {
newWindow.opener = null;
} catch {
// no-op, Electron can throw
}
newWindow.location.href = uri;
} else {
console.warn('Opening link blocked as opener could not be cleared');
}
}
interface ILinkProviderOptions {
hover?(event: MouseEvent, text: string, location: IViewportRange): void;
leave?(event: MouseEvent, text: string): void;
}
export class WebLinksAddon implements ITerminalAddon {
private _linkMatcherId: number | undefined;
private _terminal: Terminal | undefined;
@@ -58,7 +57,8 @@ export class WebLinksAddon implements ITerminalAddon {
if (this._useLinkProvider && 'registerLinkProvider' in this._terminal) {
const options = this._options as ILinkProviderOptions;
this._linkProvider = this._terminal.registerLinkProvider(new WebLinkProvider(this._terminal, strictUrlRegex, this._handler, options));
const regex = options.urlRegex || strictUrlRegex;
this._linkProvider = this._terminal.registerLinkProvider(new WebLinkProvider(this._terminal, regex, this._handler, options));
} else {
// TODO: This should be removed eventually
const options = this._options as ILinkMatcherOptions;
@@ -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;
}
}
+29 -7
View File
@@ -1,7 +1,29 @@
**/*.api.js
**/*.api.ts
tsconfig.json
.yarnrc
webpack.config.js
out-test/
test/
# Blacklist - exclude everything except npm defaults such as LICENSE, etc
*
!*/
# Whitelist - lib/
!lib/**/*.d.ts
!lib/**/*.js
!lib/**/*.js.map
!lib/**/*.css
# Whitelist - src/
!src/**/*.ts
!src/**/*.d.ts
!src/**/*.js
!src/**/*.js.map
!src/**/*.css
# Blacklist - src/ test files
src/**/*.test.ts
src/**/*.test.d.ts
src/**/*.test.js
src/**/*.test.js.map
# Whitelist - typings/
!typings/*.d.ts
+7 -1
View File
@@ -1,6 +1,6 @@
{
"name": "xterm-addon-webgl",
"version": "0.11.2",
"version": "0.12.0",
"author": {
"name": "The xterm.js authors",
"url": "https://xtermjs.org/"
@@ -9,6 +9,12 @@
"types": "typings/xterm-addon-webgl.d.ts",
"repository": "https://github.com/xtermjs/xterm.js",
"license": "MIT",
"keywords": [
"terminal",
"webgl",
"xterm",
"xterm.js"
],
"scripts": {
"build": "../../node_modules/.bin/tsc -p .",
"prepackage": "npm run build",
+26 -109
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,121 +211,41 @@ export class GlyphRenderer {
// a_cellpos only changes on resize
}
public updateSelection(model: IRenderModel): void {
public clear(force?: boolean): void {
const terminal = this._terminal;
const newCount = terminal.cols * terminal.rows * INDICES_PER_CELL;
this._vertices.selectionAttributes = slice(this._vertices.attributes, 0);
// Don't clear if not forced and the array length is correct
if (!force && this._vertices.count === newCount) {
return;
}
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);
// Clear vertices
this._vertices.count = newCount;
this._vertices.attributes = new Float32Array(newCount);
for (let i = 0; i < this._vertices.attributesBuffers.length; i++) {
this._vertices.attributesBuffers[i] = new Float32Array(newCount);
}
let i = 0;
for (let y = 0; y < terminal.rows; y++) {
for (let x = 0; x < terminal.cols; x++) {
this._vertices.attributes[i + 8] = x / terminal.cols;
this._vertices.attributes[i + 9] = y / terminal.rows;
i += INDICES_PER_CELL;
}
}
}
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 onResize(): void {
const terminal = this._terminal;
const gl = this._gl;
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
// Update vertices
const newCount = terminal.cols * terminal.rows * INDICES_PER_CELL;
if (this._vertices.count !== newCount) {
this._vertices.count = newCount;
this._vertices.attributes = new Float32Array(newCount);
for (let i = 0; i < this._vertices.attributesBuffers.length; i++) {
this._vertices.attributesBuffers[i] = new Float32Array(newCount);
}
let i = 0;
for (let y = 0; y < terminal.rows; y++) {
for (let x = 0; x < terminal.cols; x++) {
this._vertices.attributes[i + 8] = x / terminal.cols;
this._vertices.attributes[i + 9] = y / terminal.rows;
i += INDICES_PER_CELL;
}
}
}
this.clear();
}
public setColors(): void {
}
public render(renderModel: IRenderModel, isSelectionVisible: boolean): void {
public render(renderModel: IRenderModel): void {
if (!this._atlas) {
return;
}
@@ -352,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;
+7 -1
View File
@@ -8,6 +8,8 @@ import { WebglRenderer } from './WebglRenderer';
import { ICharacterJoinerService, IRenderService } from 'browser/services/Services';
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;
@@ -23,11 +25,15 @@ export class WebglAddon implements ITerminalAddon {
if (!terminal.element) {
throw new Error('Cannot activate WebglAddon before Terminal.open');
}
if (isSafari) {
throw new Error('Webgl is not currently supported on Safari');
}
this._terminal = terminal;
const renderService: IRenderService = (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);
}
+124 -30
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();
@@ -60,7 +63,7 @@ export class WebglRenderer extends Disposable implements IRenderer {
this._renderLayers = [
new LinkRenderLayer(this._core.screenElement!, 2, this._colors, this._core),
new CursorRenderLayer(this._core.screenElement!, 3, this._colors, this._core, this._onRequestRedraw)
new CursorRenderLayer(_terminal, this._core.screenElement!, 3, this._colors, this._core, this._onRequestRedraw)
];
this.dimensions = {
scaledCharWidth: 0,
@@ -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,10 +239,12 @@ 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 {
this._model.clear();
this._glyphRenderer.clear(true);
for (const l of this._renderLayers) {
l.reset(this._terminal);
}
@@ -287,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 {
@@ -329,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;
}
@@ -347,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
@@ -361,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 {
@@ -380,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;
}
@@ -393,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;
}
@@ -405,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);
}
/**
@@ -438,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
@@ -480,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
};
}
@@ -4,18 +4,19 @@
*/
import { ICharAtlasConfig } from './Types';
import { DIM_OPACITY } from 'browser/renderer/atlas/Constants';
import { DIM_OPACITY, TEXT_BASELINE } from 'browser/renderer/atlas/Constants';
import { IRasterizedGlyph, IBoundingBox, IRasterizedGlyphSet } from '../Types';
import { DEFAULT_COLOR, Attributes } from 'common/buffer/Constants';
import { throwIfFalsy } from '../WebglUtils';
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';
// 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.
// For debugging purposes, it can be useful to set this to a really tiny value,
// to verify that LRU eviction works.
const TEXTURE_WIDTH = 1024;
const TEXTURE_HEIGHT = 1024;
@@ -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;
@@ -368,28 +374,18 @@ export class WebglCharAtlas implements IDisposable {
const fontStyle = italic ? 'italic' : '';
this._tmpCtx.font =
`${fontStyle} ${fontWeight} ${this._config.fontSize * this._config.devicePixelRatio}px ${this._config.fontFamily}`;
this._tmpCtx.textBaseline = 'ideographic';
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,11 +455,11 @@ 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
if (this._currentRowX + this._config.scaledCharWidth > TEXTURE_WIDTH) {
if (this._currentRowX + rasterizedGlyph.size.x > TEXTURE_WIDTH) {
this._currentRowX = 0;
this._currentRowY += this._currentRowHeight;
this._currentRowHeight = 0;
@@ -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++) {
@@ -7,6 +7,7 @@ import { IRenderLayer } from './Types';
import { acquireCharAtlas } from '../atlas/CharAtlasCache';
import { Terminal } from 'xterm';
import { IColorSet } from 'browser/Types';
import { TEXT_BASELINE } from 'browser/renderer/atlas/Constants';
import { IRenderDimensions } from 'browser/renderer/Types';
import { CellData } from 'common/buffer/CellData';
import { WebglCharAtlas } from 'atlas/WebglCharAtlas';
@@ -224,7 +225,7 @@ export abstract class BaseRenderLayer implements IRenderLayer {
*/
protected _fillCharTrueColor(terminal: Terminal, cell: CellData, x: number, y: number): void {
this._ctx.font = this._getFont(terminal, false, false);
this._ctx.textBaseline = 'ideographic';
this._ctx.textBaseline = TEXT_BASELINE;
this._clipRow(terminal, y);
this._ctx.fillText(
cell.getChars(),
@@ -253,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}`;
}
}
@@ -31,6 +31,7 @@ export class CursorRenderLayer extends BaseRenderLayer {
private _cell: ICellData = new CellData();
constructor(
terminal: Terminal,
container: HTMLElement,
zIndex: number,
colors: IColorSet,
@@ -50,7 +51,7 @@ export class CursorRenderLayer extends BaseRenderLayer {
'block': this._renderBlockCursor.bind(this),
'underline': this._renderUnderlineCursor.bind(this)
};
// TODO: Consider initial options? Maybe onOptionsChanged should be called at the end of open?
this.onOptionsChanged(terminal);
}
public resize(terminal: Terminal, dim: IRenderDimensions): void {
@@ -67,29 +68,22 @@ export class CursorRenderLayer extends BaseRenderLayer {
public reset(terminal: Terminal): void {
this._clearCursor();
if (this._cursorBlinkStateManager) {
this._cursorBlinkStateManager.dispose();
this.onOptionsChanged(terminal);
}
this._cursorBlinkStateManager?.restartBlinkAnimation(terminal);
this.onOptionsChanged(terminal);
}
public onBlur(terminal: Terminal): void {
if (this._cursorBlinkStateManager) {
this._cursorBlinkStateManager.pause();
}
this._cursorBlinkStateManager?.pause();
this._onRequestRefreshRowsEvent.fire({ start: terminal.buffer.active.cursorY, end: terminal.buffer.active.cursorY });
}
public onFocus(terminal: Terminal): void {
if (this._cursorBlinkStateManager) {
this._cursorBlinkStateManager.resume(terminal);
} else {
this._onRequestRefreshRowsEvent.fire({ start: terminal.buffer.active.cursorY, end: terminal.buffer.active.cursorY });
}
this._cursorBlinkStateManager?.resume(terminal);
this._onRequestRefreshRowsEvent.fire({ start: terminal.buffer.active.cursorY, end: terminal.buffer.active.cursorY });
}
public onOptionsChanged(terminal: Terminal): void {
if (terminal.getOption('cursorBlink')) {
if (terminal.options.cursorBlink) {
if (!this._cursorBlinkStateManager) {
this._cursorBlinkStateManager = new CursorBlinkStateManager(terminal, () => {
this._render(terminal, true);
@@ -105,9 +99,7 @@ export class CursorRenderLayer extends BaseRenderLayer {
}
public onCursorMove(terminal: Terminal): void {
if (this._cursorBlinkStateManager) {
this._cursorBlinkStateManager.restartBlinkAnimation(terminal);
}
this._cursorBlinkStateManager?.restartBlinkAnimation(terminal);
}
public onGridChanged(terminal: Terminal, startRow: number, endRow: number): void {
@@ -148,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 {
@@ -158,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;
}
@@ -174,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;
}
@@ -182,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();
}
@@ -213,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();
}
@@ -302,6 +294,7 @@ class CursorBlinkStateManager {
// Clear any existing interval
if (this._blinkInterval) {
window.clearInterval(this._blinkInterval);
this._blinkInterval = undefined;
}
// Setup the initial timeout which will hide the cursor, this is done before
@@ -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[]> {
+21 -20
View File
@@ -59,7 +59,7 @@ jobs:
- job: Windows
pool:
vmImage: 'vs2017-win2016'
vmImage: 'windows-2019'
steps:
- task: NodeTool@0
inputs:
@@ -105,26 +105,27 @@ jobs:
- script: xvfb-run --auto-servernum -- bash -c "yarn test-api-firefox --headless --forbid-only"
displayName: 'Integration tests (Firefox)'
- job: macOS_IntegrationTests
pool:
vmImage: 'macOS-10.15'
steps:
- task: NodeTool@0
inputs:
versionSpec: '14.x'
displayName: 'Install Node.js'
- script: yarn --frozen-lockfile
displayName: 'Install dependencies and build'
- script: yarn test-api-chromium --headless --forbid-only
displayName: 'Integration tests (Chromium)'
- script: yarn test-api-firefox --headless --forbid-only
displayName: 'Integration tests (Firefox)'
- script: yarn test-api-webkit --headless --forbid-only
displayName: 'Integration tests (Webkit)'
# Integration tests are too flaky on macOS https://github.com/xtermjs/xterm.js/issues/3590
# - job: macOS_IntegrationTests
# pool:
# vmImage: 'macOS-10.15'
# steps:
# - task: NodeTool@0
# inputs:
# versionSpec: '14.x'
# displayName: 'Install Node.js'
# - script: yarn --frozen-lockfile
# displayName: 'Install dependencies and build'
# - script: yarn test-api-chromium --headless --forbid-only
# displayName: 'Integration tests (Chromium)'
# - script: yarn test-api-firefox --headless --forbid-only
# displayName: 'Integration tests (Firefox)'
# - script: yarn test-api-webkit --headless --forbid-only
# displayName: 'Integration tests (Webkit)'
- job: Windows_IntegrationTests
pool:
vmImage: 'vs2017-win2016'
vmImage: 'windows-2019'
steps:
- task: NodeTool@0
inputs:
@@ -143,11 +144,11 @@ jobs:
- macOS
- Windows
- Linux_IntegrationTests
- macOS_IntegrationTests
# - macOS_IntegrationTests
- Windows_IntegrationTests
condition: and(succeeded(), or(eq(variables['Build.SourceBranch'], 'refs/heads/master'), eq(variables['FORCE_RELEASE'], 'true')))
pool:
vmImage: 'ubuntu-16.04'
vmImage: 'ubuntu-18.04'
steps:
- task: NodeTool@0
inputs:
+3 -3
View File
@@ -11,13 +11,13 @@ const Mustache = require('mustache');
* regexp to fetch all comments
* Fetches all multiline comments and single lines containing '// @vt:'.
*/
const REX_COMMENTS = /^\s*?[/][*][*]([\s\S]*?)[*][/]|^\s*?\/\/ ([@]vt[:].*?)$/mug;
const REX_COMMENTS = /^\s*?\/\*\*([\S\s]*?)\*\/|^\s*?\/\/ (@vt:.*?)$/mug;
/**
* regexp to parse the @vt line
* expected data - "@vt: <status> <kind> <mnemonic> "<name>" "<sequence>" "<short description>"
*/
const REX_VT_LINE = /^[@]vt\:\s*(\w+|#\w+|#\w+\[.*?\])\s*(\w+)\s*(\w+)\s*"(.*?)"\s*"(.*?)"\s*"(.*?)".*$/;
const REX_VT_LINE = /^@vt:\s*(\w+|#\w+|#\w+\[.*?\])\s*(\w+)\s*(\w+)\s*"(.*?)"\s*"(.*?)"\s*"(.*?)".*$/;
// known vt command types
const TYPES = [
@@ -362,7 +362,7 @@ function* parseMultiLineGen(filename, s) {
if (!s.includes('@vt:')) {
return;
}
const lines = s.split('\n').map(el => el.trim().replace(/[*]/, '').replace(/\s/, ''));
const lines = s.split('\n').map(el => el.trim().replace(/\*/, '').replace(/\s/, ''));
let grabLine = false;
let longDescription = [];
let feature = undefined;
+4 -4
View File
@@ -92,7 +92,7 @@ function checkAndPublishPackage(packageDir) {
}
function getNextBetaVersion(packageJson) {
if (!/^[0-9]+\.[0-9]+\.[0-9]+$/.exec(packageJson.version)) {
if (!/^\d+\.\d+\.\d+$/.exec(packageJson.version)) {
console.error('The package.json version must be of the form x.y.z');
process.exit(1);
}
@@ -104,11 +104,11 @@ function getNextBetaVersion(packageJson) {
return `${nextStableVersion}-${tag}.1`;
}
const latestPublishedVersion = publishedVersions.sort((a, b) => {
const aVersion = parseInt(a.substr(a.search(/[0-9]+$/)));
const bVersion = parseInt(b.substr(b.search(/[0-9]+$/)));
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(/[0-9]+$/)), 10);
const latestTagVersion = parseInt(latestPublishedVersion.slice(latestPublishedVersion.search(/\d+$/)), 10);
return `${nextStableVersion}-${tag}.${latestTagVersion + 1}`;
}
+2 -2
View File
@@ -21,7 +21,7 @@ let flagArgs = [];
if (process.argv.length > 2) {
const args = process.argv.slice(2);
flagArgs = args.filter(e => e.startsWith('--')).map(arg => arg.split('=')).reduce((arr, val) => arr.concat([...val], []));
flagArgs = args.filter(e => e.startsWith('--')).map(arg => arg.split('=')).reduce((arr, val) => arr.concat(val.slice(), []));
console.info(flagArgs);
// ability to inject particular test files via
// yarn test [testFileA testFileB ...]
@@ -44,7 +44,7 @@ const server = cp.spawn('node', ['demo/start'], {
server.stdout.on('data', (data) => {
// await for the server to fully start
if (data.indexOf("successfully") !== -1) {
if (data.includes("successfully")) {
const run = cp.spawnSync(
npmBinScript('mocha'),
[...testFiles, ...flagArgs], {
+21 -5
View File
@@ -36,6 +36,7 @@
*/
.xterm {
cursor: text;
position: relative;
user-select: none;
-ms-user-select: none;
@@ -124,16 +125,13 @@
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;
}
.xterm.xterm-cursor-pointer {
.xterm.xterm-cursor-pointer,
.xterm .xterm-cursor-pointer {
cursor: pointer;
}
@@ -172,3 +170,21 @@
.xterm-strikethrough {
text-decoration: line-through;
}
.xterm-screen .xterm-decoration-container .xterm-decoration {
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;
}
+144 -16
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
};
}
@@ -147,8 +157,12 @@ if (document.location.pathname === '/test') {
createTerminal();
document.getElementById('dispose').addEventListener('click', disposeRecreateButtonHandler);
document.getElementById('serialize').addEventListener('click', serializeButtonHandler);
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 {
@@ -200,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(() => {
@@ -293,11 +312,11 @@ function initOptions(term: TerminalType): void {
rendererType: ['dom', 'canvas'],
wordSeparator: null
};
const options = Object.keys((<any>term)._core.options);
const options = Object.getOwnPropertyNames(term.options);
const booleanOptions = [];
const numberOptions = [];
options.filter(o => blacklistedOptions.indexOf(o) === -1).forEach(o => {
switch (typeof term.getOption(o)) {
switch (typeof term.options[o]) {
case 'boolean':
booleanOptions.push(o);
break;
@@ -314,18 +333,18 @@ function initOptions(term: TerminalType): void {
let html = '';
html += '<div class="option-group">';
booleanOptions.forEach(o => {
html += `<div class="option"><label><input id="opt-${o}" type="checkbox" ${term.getOption(o) ? 'checked' : ''}/> ${o}</label></div>`;
html += `<div class="option"><label><input id="opt-${o}" type="checkbox" ${term.options[o] ? 'checked' : ''}/> ${o}</label></div>`;
});
html += '</div><div class="option-group">';
numberOptions.forEach(o => {
html += `<div class="option"><label>${o} <input id="opt-${o}" type="number" value="${term.getOption(o)}" step="${o === 'lineHeight' || o === 'scrollSensitivity' ? '0.1' : '1'}"/></label></div>`;
html += `<div class="option"><label>${o} <input id="opt-${o}" type="number" value="${term.options[o]}" step="${o === 'lineHeight' || o === 'scrollSensitivity' ? '0.1' : '1'}"/></label></div>`;
});
html += '</div><div class="option-group">';
Object.keys(stringOptions).forEach(o => {
if (stringOptions[o]) {
html += `<div class="option"><label>${o} <select id="opt-${o}">${stringOptions[o].map(v => `<option ${term.getOption(o) === v ? 'selected' : ''}>${v}</option>`).join('')}</select></label></div>`;
html += `<div class="option"><label>${o} <select id="opt-${o}">${stringOptions[o].map(v => `<option ${term.options[o] === v ? 'selected' : ''}>${v}</option>`).join('')}</select></label></div>`;
} else {
html += `<div class="option"><label>${o} <input id="opt-${o}" type="text" value="${term.getOption(o)}"/></label></div>`;
html += `<div class="option"><label>${o} <input id="opt-${o}" type="text" value="${term.options[o]}"/></label></div>`;
}
});
html += '</div>';
@@ -338,7 +357,7 @@ function initOptions(term: TerminalType): void {
const input = <HTMLInputElement>document.getElementById(`opt-${o}`);
addDomListener(input, 'change', () => {
console.log('change', o, input.checked);
term.setOption(o, input.checked);
term.options[o] = input.checked;
});
});
numberOptions.forEach(o => {
@@ -347,14 +366,17 @@ function initOptions(term: TerminalType): void {
console.log('change', o, input.value);
if (o === 'cols' || o === 'rows') {
updateTerminalSize();
} else if (o === 'lineHeight' || o === 'scrollSensitivity') {
term.setOption(o, parseFloat(input.value));
} else if (o === 'lineHeight') {
term.options.lineHeight = parseFloat(input.value);
updateTerminalSize();
} else if (o === 'scrollSensitivity') {
term.options.scrollSensitivity = parseFloat(input.value);
updateTerminalSize();
} else if(o === 'scrollback') {
term.setOption(o, parseInt(input.value));
term.options.scrollback = parseInt(input.value);
setTimeout(() => updateTerminalSize(), 5);
} else {
term.setOption(o, parseInt(input.value));
term.options[o] = parseInt(input.value);
}
});
});
@@ -362,7 +384,7 @@ function initOptions(term: TerminalType): void {
const input = <HTMLInputElement>document.getElementById(`opt-${o}`);
addDomListener(input, 'change', () => {
console.log('change', o, input.value);
term.setOption(o, input.value);
term.options[o] = input.value;
});
});
}
@@ -377,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();
@@ -390,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') {
@@ -418,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) });
@@ -444,6 +481,21 @@ function serializeButtonHandler(): void {
}
}
function htmlSerializeButtonHandler(): void {
const output = addons.serialize.instance.serializeAsHTML();
document.getElementById('htmlserialize-output').innerText = output;
// Deprecated, but the most supported for now.
function listener(e: any) {
e.clipboardData.setData("text/html", output);
e.preventDefault();
}
document.addEventListener("copy", listener);
document.execCommand("copy");
document.removeEventListener("copy", listener);
document.getElementById("htmlserialize-output-result").innerText = "Copied to clipboard";
}
function writeCustomGlyphHandler() {
term.write('\n\r');
@@ -522,3 +574,79 @@ function loadTest() {
term._core._onData.fire('\x03');
});
}
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,
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' }});
}
+22 -4
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,15 +40,21 @@
<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>
<button id="serialize">Serialize the content of terminal</button>
<label><input type="checkbox" id="write-to-terminal">Write back to terminal</label>
<div><pre id="serialize-output"></pre></div>
<button id="htmlserialize">Serialize the content of terminal in HTML</button>
<span id="htmlserialize-output-result"></span>
<div><pre id="htmlserialize-output"></pre></div>
</div>
</div>
<div id="style" class="tabContent">
@@ -61,9 +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>
<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);
+28 -5
View File
@@ -1,6 +1,29 @@
# Include
!typings/*.d.ts
!lib-headless/
# Blacklist - exclude everything except npm defaults such as LICENSE, etc
*
!*/
# Exclude
test/
# Whitelist - lib-headless/
!lib-headless/**/*.d.ts
!lib-headless/**/*.js
!lib-headless/**/*.js.map
!lib-headless/**/*.css
# Whitelist - src/
!src/**/*.ts
!src/**/*.d.ts
!src/**/*.js
!src/**/*.js.map
!src/**/*.css
# Blacklist - src/ test files
src/**/*.test.ts
src/**/*.test.d.ts
src/**/*.test.js
src/**/*.test.js.map
# Whitelist - typings/
!typings/*.d.ts
+38 -23
View File
@@ -1,12 +1,27 @@
{
"name": "xterm",
"description": "Full xterm terminal, in your browser",
"version": "4.14.1",
"version": "4.19.0",
"main": "lib/xterm.js",
"style": "css/xterm.css",
"types": "typings/xterm.d.ts",
"repository": "https://github.com/xtermjs/xterm.js",
"license": "MIT",
"keywords": [
"cli",
"command-line",
"console",
"pty",
"shell",
"ssh",
"styles",
"terminal-emulator",
"terminal",
"tty",
"vt100",
"webgl",
"xterm"
],
"scripts": {
"prepackage": "npm run build",
"package": "webpack",
@@ -37,39 +52,39 @@
"vtfeatures": "node bin/extract_vtfeatures.js src/**/*.ts src/*.ts"
},
"devDependencies": {
"@types/chai": "^4.2.18",
"@types/debug": "^4.1.5",
"@types/chai": "^4.2.22",
"@types/debug": "^4.1.7",
"@types/deep-equal": "^1.0.1",
"@types/glob": "^7.1.3",
"@types/jsdom": "^16.2.10",
"@types/mocha": "^8.2.2",
"@types/glob": "^7.2.0",
"@types/jsdom": "^16.2.13",
"@types/mocha": "^9.0.0",
"@types/node": "^14.14.44",
"@types/utf8": "^2.1.6",
"@types/utf8": "^3.0.0",
"@types/webpack": "^5.28.0",
"@types/ws": "^7.4.4",
"@typescript-eslint/eslint-plugin": "^4.23.0",
"@typescript-eslint/parser": "^4.23.0",
"@types/ws": "^8.2.0",
"@typescript-eslint/eslint-plugin": "^5.3.0",
"@typescript-eslint/parser": "^5.3.0",
"chai": "^4.3.4",
"cross-env": "^7.0.3",
"deep-equal": "^2.0.5",
"eslint": "^7.26.0",
"eslint": "^8.1.0",
"express": "^4.17.1",
"express-ws": "^4.0.0",
"glob": "^7.1.7",
"jsdom": "^16.5.3",
"mocha": "^8.4.0",
"express-ws": "^5.0.2",
"glob": "^7.2.0",
"jsdom": "^18.0.1",
"mocha": "^9.2.0",
"mustache": "^4.2.0",
"node-pty": "^0.10.1",
"nyc": "^15.1.0",
"playwright": "^1.11.0",
"source-map-loader": "^2.0.1",
"source-map-support": "^0.5.19",
"playwright": "^1.22.1",
"source-map-loader": "^3.0.0",
"source-map-support": "^0.5.20",
"ts-loader": "^9.1.2",
"typescript": "^4.2.4",
"typescript": "^4.4.4",
"utf8": "^3.0.0",
"webpack": "^5.37.0",
"webpack-cli": "^4.7.0",
"ws": "^7.4.5",
"xterm-benchmark": "^0.2.1"
"webpack": "^5.61.0",
"webpack-cli": "^4.9.1",
"ws": "^8.2.3",
"xterm-benchmark": "^0.3.1"
}
}
+1
View File
@@ -54,6 +54,7 @@ export class AccessibilityManager extends Disposable {
super();
this._accessibilityTreeRoot = document.createElement('div');
this._accessibilityTreeRoot.classList.add('xterm-accessibility');
this._accessibilityTreeRoot.tabIndex = 0;
this._rowContainer = document.createElement('div');
this._rowContainer.setAttribute('role', 'list');
+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);
}
+57 -2
View File
@@ -3,10 +3,20 @@
* @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, IColor } from 'common/Types';
interface IRestoreColorSet {
foreground: IColor;
background: IColor;
cursor: IColor;
ansi: IColor[];
}
const DEFAULT_FOREGROUND = css.toColor('#ffffff');
const DEFAULT_BACKGROUND = css.toColor('#000000');
@@ -73,6 +83,7 @@ export class ColorManager implements IColorManager {
private _ctx: CanvasRenderingContext2D;
private _litmusColor: CanvasGradient;
private _contrastCache: IColorContrastCache;
private _restoreColors!: IRestoreColorSet;
constructor(document: Document, public allowTransparency: boolean) {
const canvas = document.createElement('canvas');
@@ -93,9 +104,11 @@ 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
};
this._updateRestoreColors();
}
public onOptionsChange(key: string): void {
@@ -116,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
@@ -142,6 +164,39 @@ export class ColorManager implements IColorManager {
this.colors.ansi[15] = this._parseColor(theme.brightWhite, DEFAULT_ANSI_COLORS[15]);
// Clear our the cache
this._contrastCache.clear();
this._updateRestoreColors();
}
public restoreColor(slot?: ColorIndex): void {
// unset slot restores all ansi colors
if (slot === undefined) {
for (let i = 0; i < this._restoreColors.ansi.length; ++i) {
this.colors.ansi[i] = this._restoreColors.ansi[i];
}
return;
}
switch (slot) {
case ColorIndex.FOREGROUND:
this.colors.foreground = this._restoreColors.foreground;
break;
case ColorIndex.BACKGROUND:
this.colors.background = this._restoreColors.background;
break;
case ColorIndex.CURSOR:
this.colors.cursor = this._restoreColors.cursor;
break;
default:
this.colors.ansi[slot] = this._restoreColors.ansi[slot];
}
}
private _updateRestoreColors(): void {
this._restoreColors = {
foreground: this.colors.foreground,
background: this.colors.background,
cursor: this.colors.cursor,
ansi: this.colors.ansi.slice()
};
}
private _parseColor(
+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;
+1 -1
View File
@@ -150,7 +150,7 @@ export class MouseZoneManager extends Disposable implements IMouseZoneManager {
}
// Restart the tooltip timeout
this._tooltipTimeout = window.setTimeout(() => this._onTooltip(e), this._optionsService.options.linkTooltipHoverDuration);
this._tooltipTimeout = window.setTimeout(() => this._onTooltip(e), this._optionsService.rawOptions.linkTooltipHoverDuration);
}
private _onTooltip(e: MouseEvent): void {
+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 = [];
}
}
+20 -4
View File
@@ -11,7 +11,7 @@ import { IBufferService, IUnicodeService } from 'common/services/Services';
import { Linkifier } from 'browser/Linkifier';
import { MockLogService, MockUnicodeService } from 'common/TestUtils.test';
import { IRegisteredLinkMatcher, IMouseZoneManager, IMouseZone } from 'browser/Types';
import { IMarker } from 'common/Types';
import { IMarker, ITerminalOptions } from 'common/Types';
const INIT_COLS = 80;
const INIT_ROWS = 24;
@@ -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());
}
});
@@ -1501,6 +1501,22 @@ describe('Terminal', () => {
assert.deepEqual(markers.map(el => el.line), [-1, -1, 0, 1, 2]);
});
});
describe('options', () => {
beforeEach(async () => {
term = new TestTerminal({});
});
it('get options', () => {
assert.equal(term.options.cols, 80);
assert.equal(term.options.rows, 24);
});
it('set options', async () => {
term.options.cols = 40;
assert.equal(term.options.cols, 40);
term.options.rows = 20;
assert.equal(term.options.rows, 20);
});
});
});
class TestLinkifier extends Linkifier {
+161 -59
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';
@@ -37,9 +37,9 @@ import * as Strings from 'browser/LocalizableStrings';
import { SoundService } from 'browser/services/SoundService';
import { MouseZoneManager } from 'browser/MouseZoneManager';
import { AccessibilityManager } from './AccessibilityManager';
import { ITheme, IMarker, IDisposable, ISelectionPosition, ILinkProvider } from 'xterm';
import { ITheme, IMarker, IDisposable, ISelectionPosition, ILinkProvider, IDecorationOptions, IDecoration } from 'xterm';
import { DomRenderer } from 'browser/renderer/dom/DomRenderer';
import { IKeyboardEvent, KeyboardResultType, CoreMouseEventType, CoreMouseButton, CoreMouseAction, ITerminalOptions, ScrollSource, IAnsiColorChangeEvent } from 'common/Types';
import { KeyboardResultType, CoreMouseEventType, CoreMouseButton, CoreMouseAction, ITerminalOptions, ScrollSource, IColorEvent, ColorIndex, ColorRequestType } from 'common/Types';
import { evaluateKeyboardEvent } from 'common/input/Keyboard';
import { EventEmitter, IEvent, forwardEvent } from 'common/EventEmitter';
import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine';
@@ -52,9 +52,13 @@ import { MouseService } from 'browser/services/MouseService';
import { Linkifier2 } from 'browser/Linkifier2';
import { CoreBrowserService } from 'browser/services/CoreBrowserService';
import { CoreTerminal } from 'common/CoreTerminal';
import { ITerminalOptions as IInitializedTerminalOptions } from 'common/services/Services';
import { rgba } from 'browser/Color';
import { color, rgba } from 'common/Color';
import { CharacterJoinerService } from 'browser/services/CharacterJoinerService';
import { toRgbString } from 'common/input/XParseColor';
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;
@@ -70,16 +74,16 @@ 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;
// TODO: We should remove options once components adopt optionsService
public get options(): IInitializedTerminalOptions { return this.optionsService.options; }
private _customKeyEventHandler: CustomKeyEventHandler | undefined;
// browser services
private _decorationService: DecorationService;
private _charSizeService: ICharSizeService | undefined;
private _mouseService: IMouseService | undefined;
private _renderService: IRenderService | undefined;
@@ -94,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
@@ -152,7 +162,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
* @alias module:xterm/src/xterm
*/
constructor(
options: ITerminalOptions = {}
options: Partial<ITerminalOptions> = {}
) {
super(options);
@@ -160,13 +170,16 @@ export class Terminal extends CoreTerminal implements ITerminal {
this.linkifier = this._instantiationService.createInstance(Linkifier);
this.linkifier2 = this.register(this._instantiationService.createInstance(Linkifier2));
this._decorationService = this._instantiationService.createInstance(DecorationService);
this._instantiationService.setService(IDecorationService, this._decorationService);
// Setup InputHandler listeners
this.register(this._inputHandler.onRequestBell(() => this.bell()));
this.register(this._inputHandler.onRequestRefreshRows((start, end) => this.refresh(start, end)));
this.register(this._inputHandler.onRequestSendFocus(() => this._reportFocus()));
this.register(this._inputHandler.onRequestReset(() => this.reset()));
this.register(this._inputHandler.onRequestWindowsOptionsReport(type => this._reportWindowsOptions(type)));
this.register(this._inputHandler.onAnsiColorChange((event) => this._changeAnsiColor(event)));
this.register(this._inputHandler.onColor((event) => this._handleColorEvent(event)));
this.register(forwardEvent(this._inputHandler.onCursorMove, this._onCursorMove));
this.register(forwardEvent(this._inputHandler.onTitleChange, this._onTitleChange));
this.register(forwardEvent(this._inputHandler.onA11yChar, this._onA11yCharEmitter));
@@ -176,17 +189,55 @@ export class Terminal extends CoreTerminal implements ITerminal {
this.register(this._bufferService.onResize(e => this._afterResize(e.cols, e.rows)));
}
private _changeAnsiColor(event: IAnsiColorChangeEvent): void {
if (!this._colorManager) { return; }
for (const ansiColor of event.colors) {
const color = rgba.toColor(ansiColor.red, ansiColor.green, ansiColor.blue);
this._colorManager!.colors.ansi[ansiColor.colorIndex] = color;
/**
* Handle color event from inputhandler for OSC 4|104 | 10|110 | 11|111 | 12|112.
* An event from OSC 4|104 may contain multiple set or report requests, and multiple
* or none restore requests (resetting all),
* while an event from OSC 10|110 | 11|111 | 12|112 always contains a single request.
*/
private _handleColorEvent(event: IColorEvent): void {
if (!this._colorManager) return;
for (const req of event) {
let acc: 'foreground' | 'background' | 'cursor' | 'ansi' | undefined = undefined;
let ident = '';
switch (req.index) {
case ColorIndex.FOREGROUND: // OSC 10 | 110
acc = 'foreground';
ident = '10';
break;
case ColorIndex.BACKGROUND: // OSC 11 | 111
acc = 'background';
ident = '11';
break;
case ColorIndex.CURSOR: // OSC 12 | 112
acc = 'cursor';
ident = '12';
break;
default: // OSC 4 | 104
// we can skip the [0..255] range check here (already done in inputhandler)
acc = 'ansi';
ident = '4;' + req.index;
}
if (acc) {
switch (req.type) {
case ColorRequestType.REPORT:
const channels = color.toColorRGB(acc === 'ansi'
? this._colorManager.colors.ansi[req.index]
: this._colorManager.colors[acc]);
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);
else this._colorManager.colors[acc] = rgba.toColor(...req.color);
break;
case ColorRequestType.RESTORE:
this._colorManager.restoreColor(req.index);
break;
}
}
}
this._renderService?.setColors(this._colorManager!.colors);
this.viewport?.onThemeChange(this._colorManager!.colors);
this._renderService?.setColors(this._colorManager.colors);
this.viewport?.onThemeChange(this._colorManager.colors);
}
public dispose(): void {
@@ -262,7 +313,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
this.viewport?.syncScrollArea();
break;
case 'screenReaderMode':
if (this.optionsService.options.screenReaderMode) {
if (this.optionsService.rawOptions.screenReaderMode) {
if (!this._accessibilityManager && this._renderService) {
this._accessibilityManager = new AccessibilityManager(this, this._renderService);
}
@@ -273,7 +324,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
break;
case 'tabStopWidth': this.buffers.setupTabStops(); break;
case 'theme':
this._setTheme(this.optionsService.options.theme);
this._setTheme(this.optionsService.rawOptions.theme);
break;
}
}
@@ -432,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);
@@ -474,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');
@@ -493,7 +545,8 @@ export class Terminal extends CoreTerminal implements ITerminal {
this.viewport = this._instantiationService.createInstance(Viewport,
(amount: number) => this.scrollLines(amount, true, ScrollSource.VIEWPORT),
this._viewportElement,
this._viewportScrollArea
this._viewportScrollArea,
this.element
);
this.viewport.onThemeChange(this._colorManager.colors);
this.register(this._inputHandler.onRequestSyncScrollBar(() => this.viewport!.syncScrollArea()));
@@ -535,8 +588,8 @@ export class Terminal extends CoreTerminal implements ITerminal {
this.register(this._mouseZoneManager);
this.register(this.onScroll(() => this._mouseZoneManager!.clearAll()));
this.linkifier.attachToDom(this.element, this._mouseZoneManager);
this.linkifier2.attachToDom(this.element, this._mouseService, this._renderService);
this.linkifier2.attachToDom(this.screenElement, this._mouseService, this._renderService);
this.register(this._instantiationService.createInstance(BufferDecorationRenderer, this.screenElement));
// This event listener must be registered aftre MouseZoneManager is created
this.register(addDisposableDomListener(this.element, 'mousedown', (e: MouseEvent) => this._selectionService!.onMouseDown(e)));
@@ -554,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();
@@ -642,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:
@@ -698,8 +762,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
},
wheel: (ev: WheelEvent) => {
sendEvent(ev);
ev.preventDefault();
return this.cancel(ev);
return this.cancel(ev, true);
},
mousedrag: (ev: MouseEvent) => {
// deal only with move while a button is held
@@ -717,7 +780,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
this.register(this.coreMouseService.onProtocolChange(events => {
// apply global changes on events
if (events) {
if (this.optionsService.options.logLevel === 'debug') {
if (this.optionsService.rawOptions.logLevel === 'debug') {
this._logService.debug('Binding to mouse events:', this.coreMouseService.explainEvents(events));
}
this.element!.classList.add('enable-mouse-events');
@@ -794,34 +857,32 @@ export class Terminal extends CoreTerminal implements ITerminal {
}));
this.register(addDisposableDomListener(el, 'wheel', (ev: WheelEvent) => {
if (!requestedEvents.wheel) {
// do nothing, if app side handles wheel itself
if (requestedEvents.wheel) return;
if (!this.buffer.hasScrollback) {
// Convert wheel events into up/down events when the buffer does not have scrollback, this
// enables scrolling in apps hosted in the alt buffer such as vim or tmux.
if (!this.buffer.hasScrollback) {
const amount = this.viewport!.getLinesScrolled(ev);
const amount = this.viewport!.getLinesScrolled(ev);
// Do nothing if there's no vertical scroll
if (amount === 0) {
return;
}
// Construct and send sequences
const sequence = C0.ESC + (this.coreService.decPrivateModes.applicationCursorKeys ? 'O' : '[') + (ev.deltaY < 0 ? 'A' : 'B');
let data = '';
for (let i = 0; i < Math.abs(amount); i++) {
data += sequence;
}
this.coreService.triggerDataEvent(data, true);
// Do nothing if there's no vertical scroll
if (amount === 0) {
return;
}
return;
}
}, { passive: true }));
// allow wheel scrolling in
// the shell for example
this.register(addDisposableDomListener(el, 'wheel', (ev: WheelEvent) => {
if (requestedEvents.wheel) return;
if (!this.viewport!.onWheel(ev)) {
// Construct and send sequences
const sequence = C0.ESC + (this.coreService.decPrivateModes.applicationCursorKeys ? 'O' : '[') + (ev.deltaY < 0 ? 'A' : 'B');
let data = '';
for (let i = 0; i < Math.abs(amount); i++) {
data += sequence;
}
this.coreService.triggerDataEvent(data, true);
return this.cancel(ev, true);
}
// normal viewport scrolling
// conditionally stop event, if the viewport still had rows to scroll within
if (this.viewport!.onWheel(ev)) {
return this.cancel(ev);
}
}, { passive: false }));
@@ -964,6 +1025,10 @@ export class Terminal extends CoreTerminal implements ITerminal {
return this.buffer.addMarker(this.buffer.ybase + this.buffer.y + cursorYOffset);
}
public registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined {
return this._decorationService.registerDecoration(decorationOptions);
}
/**
* Gets whether the terminal has an active selection.
*/
@@ -1028,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;
}
@@ -1071,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;
@@ -1091,7 +1168,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
// other listeners. When screen reader mode is enabled, this could cause issues if the event
// is handled at a higher level, this is a compromise in order to echo keys to the screen
// reader.
if (!this.optionsService.options.screenReaderMode) {
if (!this.optionsService.rawOptions.screenReaderMode) {
return this.cancel(event, true);
}
@@ -1113,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;
}
@@ -1170,6 +1249,10 @@ export class Terminal extends CoreTerminal implements ITerminal {
this._keyPressHandled = true;
// The key was handled so clear the dead key state, otherwise certain keystrokes like arrow
// keys could be ignored
this._unprocessedDeadKey = false;
return true;
}
@@ -1182,11 +1265,16 @@ 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' && !this.optionsService.options.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;
}
// The key was handled so clear the dead key state, otherwise certain keystrokes like arrow
// keys could be ignored
this._unprocessedDeadKey = false;
const text = ev.data;
this.coreService.triggerDataEvent(text, true);
@@ -1251,6 +1339,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
// Don't clear if it's already clear
return;
}
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;
@@ -1283,6 +1372,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
this._setup();
super.reset();
this._selectionService?.reset();
this._decorationService.reset();
// reattach
this._customKeyEventHandler = customKeyEventHandler;
@@ -1292,6 +1382,18 @@ export class Terminal extends CoreTerminal implements ITerminal {
this.viewport?.syncScrollArea();
}
public clearTextureAtlas(): void {
this._renderService?.clearTextureAtlas();
}
private _reportFocus(): void {
if (this.element?.classList.contains('focus')) {
this.coreService.triggerDataEvent(C0.ESC + '[I');
} else {
this.coreService.triggerDataEvent(C0.ESC + '[O');
}
}
private _reportWindowsOptions(type: WindowsOptionsReportType): void {
if (!this._renderService) {
return;
+80 -5
View File
@@ -3,11 +3,11 @@
* @license MIT
*/
import { IDisposable, IMarker, ISelectionPosition, ILinkProvider } from 'xterm';
import { IDisposable, IMarker, ISelectionPosition, ILinkProvider, IDecorationOptions, IDecoration } from 'xterm';
import { IEvent, EventEmitter } from 'common/EventEmitter';
import { ICharacterJoinerService, ICharSizeService, IMouseService, IRenderService, ISelectionService } from 'browser/services/Services';
import { IRenderDimensions, IRenderer, IRequestRedrawEvent } from 'browser/renderer/Types';
import { IColorSet, 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>;
@@ -102,6 +104,9 @@ export class MockTerminal implements ITerminal {
public registerLinkProvider(linkProvider: ILinkProvider): IDisposable {
throw new Error('Method not implemented.');
}
public registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined {
throw new Error('Method not implemented.');
}
public hasSelection(): boolean {
throw new Error('Method not implemented.');
}
@@ -195,6 +200,9 @@ export class MockTerminal implements ITerminal {
public reset(): void {
throw new Error('Method not implemented.');
}
public clearTextureAtlas(): void {
throw new Error('Method not implemented.');
}
public refresh(start: number, end: number): void {
throw new Error('Method not implemented.');
}
@@ -251,6 +259,12 @@ export class MockBuffer implements IBuffer {
public getWhitespaceCell(attr?: IAttributeData): ICellData {
throw new Error('Method not implemented.');
}
public clearMarkers(y: number): void {
throw new Error('Method not implemented.');
}
public clearAllMarkers(): void {
throw new Error('Method not implemented.');
}
}
export class MockRenderer implements IRenderer {
@@ -277,6 +291,9 @@ export class MockRenderer implements IRenderer {
public setColors(colors: IColorSet): void {
throw new Error('Method not implemented.');
}
public registerDecoration(decorationOptions: IDecorationOptions): IDecoration {
throw new Error('Method not implemented.');
}
public onResize(cols: number, rows: number): void { }
public onCharSizeChanged(): void { }
public onBlur(): void { }
@@ -355,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,
@@ -374,10 +392,13 @@ export class MockRenderService implements IRenderService {
public refreshRows(start: number, end: number): void {
throw new Error('Method not implemented.');
}
public resize(cols: number, rows: number): void {
public addRefreshCallback(callback: FrameRequestCallback): number {
throw new Error('Method not implemented.');
}
public changeOptions(): void {
public clearTextureAtlas(): void {
throw new Error('Method not implemented.');
}
public resize(cols: number, rows: number): void {
throw new Error('Method not implemented.');
}
public setRenderer(renderer: IRenderer): void {
@@ -413,6 +434,9 @@ export class MockRenderService implements IRenderService {
public dispose(): void {
throw new Error('Method not implemented.');
}
public registerDecoration(decorationOptions: IDecorationOptions): IDecoration {
throw new Error('Method not implemented.');
}
}
export class MockCharacterJoinerService implements ICharacterJoinerService {
@@ -427,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;
}
}
+11 -9
View File
@@ -3,11 +3,11 @@
* @license MIT
*/
import { IDisposable, IMarker, ISelectionPosition } from 'xterm';
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, IBufferSet } from 'common/buffer/Types';
import { IBuffer } from 'common/buffer/Types';
import { IFunctionIdentifier, IParams } from 'common/parser/Types';
export interface ITerminal extends IPublicTerminal, ICoreTerminal {
@@ -16,7 +16,6 @@ export interface ITerminal extends IPublicTerminal, ICoreTerminal {
browser: IBrowser;
buffer: IBuffer;
viewport: IViewport | undefined;
// TODO: We should remove options once components adopt optionsService
options: ITerminalOptions;
linkifier: ILinkifier;
linkifier2: ILinkifier2;
@@ -45,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;
@@ -62,6 +62,7 @@ export interface IPublicTerminal extends IDisposable {
registerCharacterJoiner(handler: (text: string) => [number, number][]): number;
deregisterCharacterJoiner(joinerId: number): void;
addMarker(cursorYOffset: number): IMarker | undefined;
registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined;
hasSelection(): boolean;
getSelection(): string;
getSelectionPosition(): ISelectionPosition | undefined;
@@ -79,6 +80,7 @@ export interface IPublicTerminal extends IDisposable {
write(data: string | Uint8Array, callback?: () => void): void;
paste(data: string): void;
refresh(start: number, end: number): void;
clearTextureAtlas(): void;
reset(): void;
}
@@ -111,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;
@@ -124,6 +121,7 @@ export interface IColorSet {
selectionTransparent: IColor;
/** The selection blended on top of background. */
selectionOpaque: IColor;
selectionForeground: IColor | undefined;
ansi: IColor[];
contrastCache: IColorContrastCache;
}
@@ -313,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;
}
+6 -21
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;
@@ -42,6 +41,7 @@ export class Viewport extends Disposable implements IViewport {
private readonly _scrollLines: (amount: number) => void,
private readonly _viewportElement: HTMLElement,
private readonly _scrollArea: HTMLElement,
private readonly _element: HTMLElement,
@IBufferService private readonly _bufferService: IBufferService,
@IOptionsService private readonly _optionsService: IOptionsService,
@ICharSizeService private readonly _charSizeService: ICharSizeService,
@@ -53,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
@@ -108,15 +107,6 @@ export class Viewport extends Disposable implements IViewport {
this._viewportElement.scrollTop = scrollTop;
}
// Update scroll bar width
if (this._optionsService.options.scrollback === 0) {
this.scrollBarWidth = 0;
} else {
this.scrollBarWidth = (this._viewportElement.offsetWidth - this._scrollArea.offsetWidth) || FALLBACK_SCROLL_BAR_WIDTH;
}
this._lastHadScrollBar = this.scrollBarWidth > 0;
this._viewportElement.style.width = (this._renderService.dimensions.actualCellWidth * (this._bufferService.cols) + this.scrollBarWidth).toString() + 'px';
this._refreshAnimationFrame = null;
}
@@ -148,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.options.scrollback > 0)) {
this._refresh(immediate);
}
}
/**
@@ -217,7 +202,7 @@ export class Viewport extends Disposable implements IViewport {
private _getPixelsScrolled(ev: WheelEvent): number {
// Do nothing if it's not a vertical scroll event
if (ev.deltaY === 0) {
if (ev.deltaY === 0 || ev.shiftKey) {
return 0;
}
@@ -238,7 +223,7 @@ export class Viewport extends Disposable implements IViewport {
*/
public getLinesScrolled(ev: WheelEvent): number {
// Do nothing if it's not a vertical scroll event
if (ev.deltaY === 0) {
if (ev.deltaY === 0 || ev.shiftKey) {
return 0;
}
@@ -256,15 +241,15 @@ export class Viewport extends Disposable implements IViewport {
}
private _applyScrollModifier(amount: number, ev: WheelEvent): number {
const modifier = this._optionsService.options.fastScrollModifier;
const modifier = this._optionsService.rawOptions.fastScrollModifier;
// Multiply the scroll speed when the modifier is down
if ((modifier === 'alt' && ev.altKey) ||
(modifier === 'ctrl' && ev.ctrlKey) ||
(modifier === 'shift' && ev.shiftKey)) {
return amount * this._optionsService.options.fastScrollSensitivity * this._optionsService.options.scrollSensitivity;
return amount * this._optionsService.rawOptions.fastScrollSensitivity * this._optionsService.rawOptions.scrollSensitivity;
}
return amount * this._optionsService.options.scrollSensitivity;
return amount * this._optionsService.rawOptions.scrollSensitivity;
}
/**
@@ -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;
});
}
}
+2 -2
View File
@@ -217,8 +217,8 @@ export class CompositionHelper {
this._compositionView.style.top = cursorTop + 'px';
this._compositionView.style.height = cellHeight + 'px';
this._compositionView.style.lineHeight = cellHeight + 'px';
this._compositionView.style.fontFamily = this._optionsService.options.fontFamily;
this._compositionView.style.fontSize = this._optionsService.options.fontSize + 'px';
this._compositionView.style.fontFamily = this._optionsService.rawOptions.fontFamily;
this._compositionView.style.fontSize = this._optionsService.rawOptions.fontSize + 'px';
// Sync the textarea to the exact position of the composition view so the IME knows where the
// text is.
const compositionViewBounds = this._compositionView.getBoundingClientRect();
+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;
}
+3 -3
View File
@@ -121,7 +121,7 @@ function wrappedRowsCount(startY: number, targetY: number, bufferService: IBuffe
for (let i = 0; i < Math.abs(startRow - endRow); i++) {
const direction = verticalDirection(startY, targetY) === Direction.UP ? -1 : 1;
const line = bufferService.buffer.lines.get(startRow + (direction * i));
if (line && line.isWrapped) {
if (line?.isWrapped) {
wrappedRows++;
}
}
@@ -136,12 +136,12 @@ function wrappedRowsCount(startY: number, targetY: number, bufferService: IBuffe
function wrappedRowsForRow(bufferService: IBufferService, currentRow: number): number {
let rowCount = 0;
let line = bufferService.buffer.lines.get(currentRow);
let lineWraps = line && line.isWrapped;
let lineWraps = line?.isWrapped;
while (lineWraps && currentRow >= 0 && currentRow < bufferService.rows) {
rowCount++;
line = bufferService.buffer.lines.get(--currentRow);
lineWraps = line && line.isWrapped;
lineWraps = line?.isWrapped;
}
return rowCount;
+62 -3
View File
@@ -3,7 +3,7 @@
* @license MIT
*/
import { Terminal as ITerminalApi, ITerminalOptions, IMarker, IDisposable, ILinkMatcherOptions, ITheme, ILocalizableStrings, ITerminalAddon, ISelectionPosition, IBufferNamespace as IBufferNamespaceApi, IParser, ILinkProvider, IUnicodeHandling, FontWeight, IModes } from 'xterm';
import { Terminal as ITerminalApi, IMarker, IDisposable, ILinkMatcherOptions, ITheme, ILocalizableStrings, ITerminalAddon, ISelectionPosition, IBufferNamespace as IBufferNamespaceApi, IParser, ILinkProvider, IUnicodeHandling, FontWeight, IModes, IDecorationOptions, IDecoration } from 'xterm';
import { ITerminal } from 'browser/Types';
import { Terminal as TerminalCore } from 'browser/Terminal';
import * as Strings from 'browser/LocalizableStrings';
@@ -12,20 +12,53 @@ import { ParserApi } from 'common/public/ParserApi';
import { UnicodeApi } from 'common/public/UnicodeApi';
import { AddonManager } from 'common/public/AddonManager';
import { BufferNamespaceApi } from 'common/public/BufferNamespaceApi';
import { ITerminalOptions } from 'common/Types';
/**
* The set of options that only have an effect when set in the Terminal constructor.
*/
const CONSTRUCTOR_ONLY_OPTIONS = ['cols', 'rows'];
export class Terminal implements ITerminalApi {
private _core: ITerminal;
private _addonManager: AddonManager;
private _parser: IParser | undefined;
private _buffer: BufferNamespaceApi | undefined;
private _publicOptions: ITerminalOptions;
constructor(options?: ITerminalOptions) {
this._core = new TerminalCore(options);
this._addonManager = new AddonManager();
this._publicOptions = { ... this._core.options };
const getter = (propName: string): any => {
return this._core.options[propName];
};
const setter = (propName: string, value: any): void => {
this._checkReadonlyOptions(propName);
this._core.options[propName] = value;
};
for (const propName in this._core.options) {
const desc = {
get: getter.bind(this, propName),
set: setter.bind(this, propName)
};
Object.defineProperty(this._publicOptions, propName, desc);
}
}
private _checkReadonlyOptions(propName: string): void {
// Throw an error if any constructor only option is modified
// from terminal.options
// Modifications from anywhere else are allowed
if (CONSTRUCTOR_ONLY_OPTIONS.includes(propName)) {
throw new Error(`Option "${propName}" can only be set in the constructor`);
}
}
private _checkProposedApi(): void {
if (!this._core.optionsService.options.allowProposedApi) {
if (!this._core.optionsService.rawOptions.allowProposedApi) {
throw new Error('You must set the allowProposedApi option to true to use proposed API');
}
}
@@ -41,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,6 +123,14 @@ export class Terminal implements ITerminalApi {
wraparoundMode: m.wraparound
};
}
public get options(): ITerminalOptions {
return this._publicOptions;
}
public set options(options: ITerminalOptions) {
for (const propName in options) {
this._publicOptions[propName] = options[propName];
}
}
public blur(): void {
this._core.blur();
}
@@ -125,11 +167,16 @@ export class Terminal implements ITerminalApi {
this._checkProposedApi();
this._core.deregisterCharacterJoiner(joinerId);
}
public registerMarker(cursorYOffset: number): IMarker | undefined {
public registerMarker(cursorYOffset: number = 0): IMarker | undefined {
this._checkProposedApi();
this._verifyIntegers(cursorYOffset);
return this._core.addMarker(cursorYOffset);
}
public registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined {
this._checkProposedApi();
this._verifyPositiveIntegers(decorationOptions.x ?? 0, decorationOptions.width ?? 0, decorationOptions.height ?? 0);
return this._core.registerDecoration(decorationOptions);
}
public addMarker(cursorYOffset: number): IMarker | undefined {
return this.registerMarker(cursorYOffset);
}
@@ -213,6 +260,7 @@ export class Terminal implements ITerminalApi {
public setOption(key: 'cols' | 'rows', value: number): void;
public setOption(key: string, value: any): void;
public setOption(key: any, value: any): void {
this._checkReadonlyOptions(key);
this._core.optionsService.setOption(key, value);
}
public refresh(start: number, end: number): void {
@@ -222,6 +270,9 @@ export class Terminal implements ITerminalApi {
public reset(): void {
this._core.reset();
}
public clearTextureAtlas(): void {
this._core.clearTextureAtlas();
}
public loadAddon(addon: ITerminalAddon): void {
return this._addonManager.loadAddon(this, addon);
}
@@ -236,4 +287,12 @@ export class Terminal implements ITerminalApi {
}
}
}
private _verifyPositiveIntegers(...values: number[]): void {
for (const value of values) {
if (value && (value === Infinity || isNaN(value) || value % 1 !== 0 || value < 0)) {
throw new Error('This API only accepts positive integers');
}
}
}
}
+105 -37
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 } from 'browser/renderer/atlas/Constants';
import { DIM_OPACITY, INVERTED_DEFAULT_COLOR, TEXT_BASELINE } from 'browser/renderer/atlas/Constants';
import { BaseCharAtlas } from 'browser/renderer/atlas/BaseCharAtlas';
import { acquireCharAtlas } from 'browser/renderer/atlas/CharAtlasCache';
import { AttributeData } from 'common/buffer/AttributeData';
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);
@@ -112,7 +122,7 @@ export abstract class BaseRenderLayer implements IRenderLayer {
if (this._scaledCharWidth <= 0 && this._scaledCharHeight <= 0) {
return;
}
this._charAtlas = acquireCharAtlas(this._optionsService.options, this._rendererId, colorSet, this._scaledCharWidth, this._scaledCharHeight);
this._charAtlas = acquireCharAtlas(this._optionsService.rawOptions, this._rendererId, colorSet, this._scaledCharWidth, this._scaledCharHeight);
this._charAtlas.warmUp();
}
@@ -138,6 +148,10 @@ export abstract class BaseRenderLayer implements IRenderLayer {
public abstract reset(): void;
public clearTextureAtlas(): void {
this._charAtlas?.clear();
}
/**
* Fills 1+ cells completely. This uses the existing fillStyle on the context.
* @param x The column to start at.
@@ -258,12 +272,12 @@ export abstract class BaseRenderLayer implements IRenderLayer {
*/
protected _fillCharTrueColor(cell: CellData, x: number, y: number): void {
this._ctx.font = this._getFont(false, false);
this._ctx.textBaseline = 'ideographic';
this._ctx.textBaseline = TEXT_BASELINE;
this._clipRow(y);
// Draw custom characters if applicable
let drawSuccess = false;
if (this._optionsService.options.customGlyphs !== false) {
if (this._optionsService.rawOptions.customGlyphs !== false) {
drawSuccess = tryDrawCustomChar(this._ctx, cell.getChars(), x * this._scaledCellWidth, y * this._scaledCellHeight, this._scaledCellWidth, this._scaledCellHeight);
}
@@ -290,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
@@ -311,7 +325,7 @@ export abstract class BaseRenderLayer implements IRenderLayer {
fg = (cell.isFgDefault()) ? DEFAULT_COLOR : cell.getFgColor();
}
const drawInBrightColor = this._optionsService.options.drawBoldTextInBrightColors && cell.isBold() && fg < 8;
const drawInBrightColor = this._optionsService.rawOptions.drawBoldTextInBrightColors && cell.isBold() && fg < 8;
fg += drawInBrightColor ? 8 : 0;
this._currentGlyphIdentifier.chars = cell.getChars() || WHITESPACE_CELL_CHAR;
@@ -321,12 +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 && 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);
@@ -346,7 +365,7 @@ export abstract class BaseRenderLayer implements IRenderLayer {
private _drawUncachedChars(cell: ICellData, x: number, y: number, fgOverride?: IColor): void {
this._ctx.save();
this._ctx.font = this._getFont(!!cell.isBold(), !!cell.isItalic());
this._ctx.textBaseline = 'ideographic';
this._ctx.textBaseline = TEXT_BASELINE;
if (cell.isInverse()) {
if (fgOverride) {
@@ -357,7 +376,7 @@ export abstract class BaseRenderLayer implements IRenderLayer {
this._ctx.fillStyle = `rgb(${AttributeData.toColorRGB(cell.getBgColor()).join(',')})`;
} else {
let bg = cell.getBgColor();
if (this._optionsService.options.drawBoldTextInBrightColors && cell.isBold() && bg < 8) {
if (this._optionsService.rawOptions.drawBoldTextInBrightColors && cell.isBold() && bg < 8) {
bg += 8;
}
this._ctx.fillStyle = this._colors.ansi[bg].css;
@@ -371,7 +390,7 @@ export abstract class BaseRenderLayer implements IRenderLayer {
this._ctx.fillStyle = `rgb(${AttributeData.toColorRGB(cell.getFgColor()).join(',')})`;
} else {
let fg = cell.getFgColor();
if (this._optionsService.options.drawBoldTextInBrightColors && cell.isBold() && fg < 8) {
if (this._optionsService.rawOptions.drawBoldTextInBrightColors && cell.isBold() && fg < 8) {
fg += 8;
}
this._ctx.fillStyle = this._colors.ansi[fg].css;
@@ -387,7 +406,7 @@ export abstract class BaseRenderLayer implements IRenderLayer {
// Draw custom characters if applicable
let drawSuccess = false;
if (this._optionsService.options.customGlyphs !== false) {
if (this._optionsService.rawOptions.customGlyphs !== false) {
drawSuccess = tryDrawCustomChar(this._ctx, cell.getChars(), x * this._scaledCellWidth, y * this._scaledCellHeight, this._scaledCellWidth, this._scaledCellHeight);
}
@@ -422,21 +441,48 @@ export abstract class BaseRenderLayer implements IRenderLayer {
* @param isBold If we should use the bold fontWeight.
*/
protected _getFont(isBold: boolean, isItalic: boolean): string {
const fontWeight = isBold ? this._optionsService.options.fontWeightBold : this._optionsService.options.fontWeight;
const fontWeight = isBold ? this._optionsService.rawOptions.fontWeightBold : this._optionsService.rawOptions.fontWeight;
const fontStyle = isItalic ? 'italic' : '';
return `${fontStyle} ${fontWeight} ${this._optionsService.options.fontSize * window.devicePixelRatio}px ${this._optionsService.options.fontFamily}`;
return `${fontStyle} ${fontWeight} ${this._optionsService.rawOptions.fontSize * window.devicePixelRatio}px ${this._optionsService.rawOptions.fontFamily}`;
}
private _getContrastColor(cell: CellData): IColor | undefined {
if (this._optionsService.options.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();
@@ -454,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.options.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 = {
@@ -471,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;
}
@@ -496,7 +548,7 @@ export abstract class BaseRenderLayer implements IRenderLayer {
switch (fgColorMode) {
case Attributes.CM_P16:
case Attributes.CM_P256:
if (this._optionsService.options.drawBoldTextInBrightColors && bold && fgColor < 8) {
if (this._optionsService.rawOptions.drawBoldTextInBrightColors && bold && fgColor < 8) {
fgColor += 8;
}
return this._colors.ansi[fgColor].rgba;
@@ -510,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]);
}
}
+17 -26
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,
@@ -55,7 +56,6 @@ export class CursorRenderLayer extends BaseRenderLayer {
'block': this._renderBlockCursor.bind(this),
'underline': this._renderUnderlineCursor.bind(this)
};
// TODO: Consider initial options? Maybe onOptionsChanged should be called at the end of open?
}
public dispose(): void {
@@ -80,30 +80,22 @@ export class CursorRenderLayer extends BaseRenderLayer {
public reset(): void {
this._clearCursor();
if (this._cursorBlinkStateManager) {
this._cursorBlinkStateManager.dispose();
this._cursorBlinkStateManager = undefined;
this.onOptionsChanged();
}
this._cursorBlinkStateManager?.restartBlinkAnimation();
this.onOptionsChanged();
}
public onBlur(): void {
if (this._cursorBlinkStateManager) {
this._cursorBlinkStateManager.pause();
}
this._cursorBlinkStateManager?.pause();
this._onRequestRedraw.fire({ start: this._bufferService.buffer.y, end: this._bufferService.buffer.y });
}
public onFocus(): void {
if (this._cursorBlinkStateManager) {
this._cursorBlinkStateManager.resume();
} else {
this._onRequestRedraw.fire({ start: this._bufferService.buffer.y, end: this._bufferService.buffer.y });
}
this._cursorBlinkStateManager?.resume();
this._onRequestRedraw.fire({ start: this._bufferService.buffer.y, end: this._bufferService.buffer.y });
}
public onOptionsChanged(): void {
if (this._optionsService.options.cursorBlink) {
if (this._optionsService.rawOptions.cursorBlink) {
if (!this._cursorBlinkStateManager) {
this._cursorBlinkStateManager = new CursorBlinkStateManager(this._coreBrowserService.isFocused, () => {
this._render(true);
@@ -119,9 +111,7 @@ export class CursorRenderLayer extends BaseRenderLayer {
}
public onCursorMove(): void {
if (this._cursorBlinkStateManager) {
this._cursorBlinkStateManager.restartBlinkAnimation();
}
this._cursorBlinkStateManager?.restartBlinkAnimation();
}
public onGridChanged(startRow: number, endRow: number): void {
@@ -159,7 +149,7 @@ export class CursorRenderLayer extends BaseRenderLayer {
this._clearCursor();
this._ctx.save();
this._ctx.fillStyle = this._colors.cursor.css;
const cursorStyle = this._optionsService.options.cursorStyle;
const cursorStyle = this._optionsService.rawOptions.cursorStyle;
if (cursorStyle && cursorStyle !== 'block') {
this._cursorRenderers[cursorStyle](cursorX, viewportRelativeCursorY, this._cell);
} else {
@@ -185,7 +175,7 @@ export class CursorRenderLayer extends BaseRenderLayer {
if (this._state.x === cursorX &&
this._state.y === viewportRelativeCursorY &&
this._state.isFocused === this._coreBrowserService.isFocused &&
this._state.style === this._optionsService.options.cursorStyle &&
this._state.style === this._optionsService.rawOptions.cursorStyle &&
this._state.width === this._cell.getWidth()) {
return;
}
@@ -193,13 +183,13 @@ export class CursorRenderLayer extends BaseRenderLayer {
}
this._ctx.save();
this._cursorRenderers[this._optionsService.options.cursorStyle || 'block'](cursorX, viewportRelativeCursorY, this._cell);
this._cursorRenderers[this._optionsService.rawOptions.cursorStyle || 'block'](cursorX, viewportRelativeCursorY, this._cell);
this._ctx.restore();
this._state.x = cursorX;
this._state.y = viewportRelativeCursorY;
this._state.isFocused = false;
this._state.style = this._optionsService.options.cursorStyle;
this._state.style = this._optionsService.rawOptions.cursorStyle;
this._state.width = this._cell.getWidth();
}
@@ -224,7 +214,7 @@ export class CursorRenderLayer extends BaseRenderLayer {
private _renderBarCursor(x: number, y: number, cell: ICellData): void {
this._ctx.save();
this._ctx.fillStyle = this._colors.cursor.css;
this._fillLeftLineAtCell(x, y, this._optionsService.options.cursorWidth);
this._fillLeftLineAtCell(x, y, this._optionsService.rawOptions.cursorWidth);
this._ctx.restore();
}
@@ -313,6 +303,7 @@ class CursorBlinkStateManager {
// Clear any existing interval
if (this._blinkInterval) {
window.clearInterval(this._blinkInterval);
this._blinkInterval = undefined;
}
// Setup the initial timeout which will hide the cursor, this is done before
+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));
+17 -7
View File
@@ -10,8 +10,8 @@ import { IRenderLayer, IRenderer, IRenderDimensions, IRequestRedrawEvent } from
import { LinkRenderLayer } from 'browser/renderer/LinkRenderLayer';
import { Disposable } from 'common/Lifecycle';
import { IColorSet, ILinkifier, ILinkifier2 } from 'browser/Types';
import { ICharSizeService, ICoreBrowserService } from 'browser/services/Services';
import { IBufferService, IOptionsService, ICoreService, IInstantiationService } from 'common/services/Services';
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';
@@ -39,7 +39,7 @@ export class Renderer extends Disposable implements IRenderer {
@IOptionsService private readonly _optionsService: IOptionsService
) {
super();
const allowTransparency = this._optionsService.options.allowTransparency;
const allowTransparency = this._optionsService.rawOptions.allowTransparency;
this._renderLayers = [
instantiationService.createInstance(TextRenderLayer, this._screenElement, 0, this._colors, allowTransparency, this._id),
instantiationService.createInstance(SelectionRenderLayer, this._screenElement, 1, this._colors, this._id),
@@ -119,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 {
@@ -149,6 +153,12 @@ export class Renderer extends Disposable implements IRenderer {
}
}
public clearTextureAtlas(): void {
for (const layer of this._renderLayers) {
layer.clearTextureAtlas();
}
}
/**
* Recalculates the character and canvas dimensions.
*/
@@ -172,18 +182,18 @@ export class Renderer 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._optionsService.options.lineHeight);
this.dimensions.scaledCellHeight = Math.floor(this.dimensions.scaledCharHeight * this._optionsService.rawOptions.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._optionsService.options.lineHeight === 1 ? 0 : Math.round((this.dimensions.scaledCellHeight - this.dimensions.scaledCharHeight) / 2);
this.dimensions.scaledCharTop = this._optionsService.rawOptions.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._optionsService.options.letterSpacing);
this.dimensions.scaledCellWidth = this.dimensions.scaledCharWidth + Math.round(this._optionsService.rawOptions.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._optionsService.options.letterSpacing / 2);
this.dimensions.scaledCharLeft = Math.floor(this._optionsService.rawOptions.letterSpacing / 2);
// Recalculate the canvas dimensions; scaled* define the actual number of
// pixel in the canvas
+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;
+20 -6
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.
@@ -225,7 +239,7 @@ export class TextRenderLayer extends BaseRenderLayer {
this._ctx.fillStyle = `rgb(${AttributeData.toColorRGB(cell.getBgColor()).join(',')})`;
} else {
let bg = cell.getBgColor();
if (this._optionsService.options.drawBoldTextInBrightColors && cell.isBold() && bg < 8) {
if (this._optionsService.rawOptions.drawBoldTextInBrightColors && cell.isBold() && bg < 8) {
bg += 8;
}
this._ctx.fillStyle = this._colors.ansi[bg].css;
@@ -237,7 +251,7 @@ export class TextRenderLayer extends BaseRenderLayer {
this._ctx.fillStyle = `rgb(${AttributeData.toColorRGB(cell.getFgColor()).join(',')})`;
} else {
let fg = cell.getFgColor();
if (this._optionsService.options.drawBoldTextInBrightColors && cell.isBold() && fg < 8) {
if (this._optionsService.rawOptions.drawBoldTextInBrightColors && cell.isBold() && fg < 8) {
fg += 8;
}
this._ctx.fillStyle = this._colors.ansi[fg].css;
@@ -271,7 +285,7 @@ export class TextRenderLayer extends BaseRenderLayer {
}
public onOptionsChanged(): void {
this._setTransparency(this._optionsService.options.allowTransparency);
this._setTransparency(this._optionsService.rawOptions.allowTransparency);
}
/**
+6
View File
@@ -52,6 +52,7 @@ export interface IRenderer extends IDisposable {
onOptionsChanged(): void;
clear(): void;
renderRows(start: number, end: number): void;
clearTextureAtlas?(): void;
}
export interface IRenderLayer extends IDisposable {
@@ -100,4 +101,9 @@ export interface IRenderLayer extends IDisposable {
* Clear the state of the render layer.
*/
reset(): void;
/**
* Clears the texture atlas.
*/
clearTextureAtlas(): void;
}
@@ -28,6 +28,8 @@ export abstract class BaseCharAtlas implements IDisposable {
*/
private _doWarmUp(): void { }
public clear(): void { }
/**
* Called when we start drawing a new frame.
*
+1 -1
View File
@@ -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,
+6
View File
@@ -3,7 +3,13 @@
* @license MIT
*/
import { isFirefox, isLegacyEdge } from 'common/Platform';
export const INVERTED_DEFAULT_COLOR = 257;
export const DIM_OPACITY = 0.5;
// The text baseline is set conditionally by browser. Using 'ideographic' for Firefox or Legacy Edge would
// result in truncated text (Issue 3353). Using 'bottom' for Chrome would result in slightly
// unaligned Powerline fonts (PR 3356#issuecomment-850928179).
export const TEXT_BASELINE: CanvasTextBaseline = isFirefox || isLegacyEdge ? 'bottom' : 'ideographic';
export const CHAR_ATLAS_CELL_SPACING = 1;
+14 -4
View File
@@ -3,15 +3,15 @@
* @license MIT
*/
import { DIM_OPACITY, INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants';
import { DIM_OPACITY, INVERTED_DEFAULT_COLOR, TEXT_BASELINE } from 'browser/renderer/atlas/Constants';
import { IGlyphIdentifier, ICharAtlasConfig } from 'browser/renderer/atlas/Types';
import { BaseCharAtlas } from 'browser/renderer/atlas/BaseCharAtlas';
import { DEFAULT_ANSI_COLORS } from 'browser/ColorManager';
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.
@@ -119,6 +119,16 @@ export class DynamicCharAtlas extends BaseCharAtlas {
this._drawToCacheCount = 0;
}
public clear(): void {
if (this._cacheMap.size > 0) {
const capacity = this._width * this._height;
this._cacheMap = new LRUMap(capacity);
this._cacheMap.prealloc(capacity);
}
this._cacheCtx.clearRect(0, 0, TEXTURE_WIDTH, TEXTURE_HEIGHT);
this._tmpCtx.clearRect(0, 0, this._config.scaledCharWidth, this._config.scaledCharHeight);
}
public draw(
ctx: CanvasRenderingContext2D,
glyph: IGlyphIdentifier,
@@ -256,7 +266,7 @@ export class DynamicCharAtlas extends BaseCharAtlas {
const fontStyle = glyph.italic ? 'italic' : '';
this._tmpCtx.font =
`${fontStyle} ${fontWeight} ${this._config.fontSize * this._config.devicePixelRatio}px ${this._config.fontFamily}`;
this._tmpCtx.textBaseline = 'ideographic';
this._tmpCtx.textBaseline = TEXT_BASELINE;
this._tmpCtx.fillStyle = this._getForegroundColor(glyph).css;
+21 -18
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 {
@@ -107,8 +107,8 @@ export class DomRenderer extends Disposable implements IRenderer {
private _updateDimensions(): void {
this.dimensions.scaledCharWidth = this._charSizeService.width * window.devicePixelRatio;
this.dimensions.scaledCharHeight = Math.ceil(this._charSizeService.height * window.devicePixelRatio);
this.dimensions.scaledCellWidth = this.dimensions.scaledCharWidth + Math.round(this._optionsService.options.letterSpacing);
this.dimensions.scaledCellHeight = Math.floor(this.dimensions.scaledCharHeight * this._optionsService.options.lineHeight);
this.dimensions.scaledCellWidth = this.dimensions.scaledCharWidth + Math.round(this._optionsService.rawOptions.letterSpacing);
this.dimensions.scaledCellHeight = Math.floor(this.dimensions.scaledCharHeight * this._optionsService.rawOptions.lineHeight);
this.dimensions.scaledCharLeft = 0;
this.dimensions.scaledCharTop = 0;
this.dimensions.scaledCanvasWidth = this.dimensions.scaledCellWidth * this._bufferService.cols;
@@ -161,16 +161,16 @@ export class DomRenderer extends Disposable implements IRenderer {
let styles =
`${this._terminalSelector} .${ROW_CONTAINER_CLASS} {` +
` color: ${this._colors.foreground.css};` +
` font-family: ${this._optionsService.options.fontFamily};` +
` font-size: ${this._optionsService.options.fontSize}px;` +
` font-family: ${this._optionsService.rawOptions.fontFamily};` +
` font-size: ${this._optionsService.rawOptions.fontSize}px;` +
`}`;
// Text styles
styles +=
`${this._terminalSelector} span:not(.${BOLD_CLASS}) {` +
` font-weight: ${this._optionsService.options.fontWeight};` +
` font-weight: ${this._optionsService.rawOptions.fontWeight};` +
`}` +
`${this._terminalSelector} span.${BOLD_CLASS} {` +
` font-weight: ${this._optionsService.options.fontWeightBold};` +
` font-weight: ${this._optionsService.rawOptions.fontWeightBold};` +
`}` +
`${this._terminalSelector} span.${ITALIC_CLASS} {` +
` font-style: italic;` +
@@ -210,7 +210,7 @@ export class DomRenderer extends Disposable implements IRenderer {
` color: ${this._colors.cursorAccent.css};` +
`}` +
`${this._terminalSelector} .${ROW_CONTAINER_CLASS} .${CURSOR_CLASS}.${CURSOR_STYLE_BAR_CLASS} {` +
` box-shadow: ${this._optionsService.options.cursorWidth}px 0 0 ${this._colors.cursor.css} inset;` +
` box-shadow: ${this._optionsService.rawOptions.cursorWidth}px 0 0 ${this._colors.cursor.css} inset;` +
`}` +
`${this._terminalSelector} .${ROW_CONTAINER_CLASS} .${CURSOR_CLASS}.${CURSOR_STYLE_UNDERLINE_CLASS} {` +
` box-shadow: 0 -1px 0 ${this._colors.cursor.css} inset;` +
@@ -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
@@ -356,15 +360,14 @@ export class DomRenderer extends Disposable implements IRenderer {
public renderRows(start: number, end: number): void {
const cursorAbsoluteY = this._bufferService.buffer.ybase + this._bufferService.buffer.y;
const cursorX = Math.min(this._bufferService.buffer.x, this._bufferService.cols - 1);
const cursorBlink = this._optionsService.options.cursorBlink;
const cursorBlink = this._optionsService.rawOptions.cursorBlink;
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.options.cursorStyle;
const cursorStyle = this._optionsService.rawOptions.cursorStyle;
rowElement.appendChild(this._rowFactory.createRow(lineData!, row, row === cursorAbsoluteY, cursorStyle, cursorX, cursorBlink, this.dimensions.actualCellWidth, this._bufferService.cols));
}
}
@@ -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 { 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', () => {
@@ -21,30 +21,37 @@ describe('DomRendererRowFactory', () => {
beforeEach(() => {
dom = new jsdom.JSDOM('');
rowFactory = new DomRendererRowFactory(dom.window.document, {
background: css.toColor('#010101'),
foreground: css.toColor('#020202'),
ansi: [
// dark:
css.toColor('#2e3436'),
css.toColor('#cc0000'),
css.toColor('#4e9a06'),
css.toColor('#c4a000'),
css.toColor('#3465a4'),
css.toColor('#75507b'),
css.toColor('#06989a'),
css.toColor('#d3d7cf'),
// bright:
css.toColor('#555753'),
css.toColor('#ef2929'),
css.toColor('#8ae234'),
css.toColor('#fce94f'),
css.toColor('#729fcf'),
css.toColor('#ad7fa8'),
css.toColor('#34e2e2'),
css.toColor('#eeeeec')
]
} as any, new MockCharacterJoinerService(), new MockOptionsService({ drawBoldTextInBrightColors: true }));
rowFactory = new DomRendererRowFactory(
dom.window.document,
{
background: css.toColor('#010101'),
foreground: css.toColor('#020202'),
ansi: [
// dark:
css.toColor('#2e3436'),
css.toColor('#cc0000'),
css.toColor('#4e9a06'),
css.toColor('#c4a000'),
css.toColor('#3465a4'),
css.toColor('#75507b'),
css.toColor('#06989a'),
css.toColor('#d3d7cf'),
// bright:
css.toColor('#555753'),
css.toColor('#ef2929'),
css.toColor('#8ae234'),
css.toColor('#fce94f'),
css.toColor('#729fcf'),
css.toColor('#ad7fa8'),
css.toColor('#34e2e2'),
css.toColor('#eeeeec')
]
} as any,
new MockCharacterJoinerService(),
new MockOptionsService({ drawBoldTextInBrightColors: true }),
new MockCoreService(),
new MockDecorationService()
);
lineData = createEmptyLineData(2);
});
@@ -177,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>'
);
});
@@ -188,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>'
);
});
@@ -198,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>'
);
});
@@ -223,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>'
);
});
@@ -234,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>'
);
});
});
+119 -33
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 { 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,11 +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
@IOptionsService private readonly _optionsService: IOptionsService,
@ICoreService private readonly _coreService: ICoreService,
@IDecorationService private readonly _decorationService: IDecorationService
) {
}
@@ -39,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();
@@ -110,7 +123,7 @@ export class DomRendererRowFactory {
}
}
if (isCursorRow && x === cursorX) {
if (!this._coreService.isCursorHidden && isCursorRow && x === cursorX) {
charElement.classList.add(CURSOR_CLASS);
if (cursorBlink) {
@@ -170,14 +183,80 @@ 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:
case Attributes.CM_P256:
if (cell.isBold() && fg < 8 && this._optionsService.options.drawBoldTextInBrightColors) {
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;
@@ -187,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;
@@ -223,18 +286,21 @@ export class DomRendererRowFactory {
return fragment;
}
private _applyMinimumContrast(element: HTMLElement, bg: IColor, fg: IColor): boolean {
if (this._optionsService.options.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.options.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) {
@@ -248,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,11 +116,25 @@ 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;
model.selectionEnd = [5, 2];
assert.deepEqual(model.finalSelectionEnd, [5, 2]);
});
it('should not include a trailing EOL when the selection ends at the end of a line', () => {
model.selectionStart = [0, 0];
model.selectionStartLength = 80;
assert.deepEqual(model.finalSelectionEnd, [80, 0]);
model.selectionStart = [0, 0];
model.selectionStartLength = 160;
assert.deepEqual(model.finalSelectionEnd, [80, 1]);
});
});
});
+10 -1
View File
@@ -79,6 +79,10 @@ export class SelectionModel {
if (!this.selectionEnd || this.areSelectionValuesReversed()) {
const startPlusLength = this.selectionStart[0] + this.selectionStartLength;
if (startPlusLength > this._bufferService.cols) {
// Ensure the trailing EOL isn't included when the selection ends on the right edge
if (startPlusLength % this._bufferService.cols === 0) {
return [this._bufferService.cols, this.selectionStart[1] + Math.floor(startPlusLength / this._bufferService.cols) - 1];
}
return [startPlusLength % this._bufferService.cols, this.selectionStart[1] + Math.floor(startPlusLength / this._bufferService.cols)];
}
return [startPlusLength, this.selectionStart[1]];
@@ -88,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;
+2 -2
View File
@@ -69,8 +69,8 @@ class DomMeasureStrategy implements IMeasureStrategy {
}
public measure(): IReadonlyMeasureResult {
this._measureElement.style.fontFamily = this._optionsService.options.fontFamily;
this._measureElement.style.fontSize = `${this._optionsService.options.fontSize}px`;
this._measureElement.style.fontFamily = this._optionsService.rawOptions.fontFamily;
this._measureElement.style.fontSize = `${this._optionsService.rawOptions.fontSize}px`;
// Note that this triggers a synchronous layout
const geometry = this._measureElement.getBoundingClientRect();

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