Compare commits

..
386 Commits
Author SHA1 Message Date
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
Daniel Imms d32c6db967 Merge pull request #3470 from xtermjs/r4_14_1
v4.14.1
2021-09-10 09:50:06 -07:00
Daniel Imms db6b3c4bcc v4.14.1 2021-09-10 09:44:29 -07:00
Daniel Imms 50c5579c83 Merge pull request #3469 from Tyriar/3467
Disable emoji ime when screenReaderMode is on
2021-09-10 09:43:44 -07:00
Daniel Imms 5a65fd9c6a Disable emoji ime when screenReaderMode is on
Fixes #3467
2021-09-10 09:42:19 -07:00
Daniel Imms be7fcbe485 Merge pull request #3466 from xtermjs/dependabot/npm_and_yarn/addons/xterm-addon-ligatures/axios-0.21.2
Bump axios from 0.18.1 to 0.21.2 in /addons/xterm-addon-ligatures
2021-09-09 12:51:21 -07:00
Simon Lamon 99239e9160 Allow modifying constructor only options 2021-09-09 18:02:40 +00:00
dependabot[bot] 662154123d Bump axios from 0.18.1 to 0.21.2 in /addons/xterm-addon-ligatures
Bumps [axios](https://github.com/axios/axios) from 0.18.1 to 0.21.2.
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/master/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v0.18.1...v0.21.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2021-09-09 13:24:07 +00:00
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
108 changed files with 4027 additions and 1996 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
+8 -2
View File
@@ -179,11 +179,17 @@ 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.
- [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",
+1 -1
View File
@@ -33,7 +33,7 @@ export class FitAddon implements ITerminalAddon {
public fit(): void {
const dims = this.proposeDimensions();
if (!dims || !this._terminal) {
if (!dims || !this._terminal || isNaN(dims.cols) || isNaN(dims.rows)) {
return;
}
+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
+5 -5
View File
@@ -1,6 +1,6 @@
{
"name": "xterm-addon-ligatures",
"version": "0.5.1",
"version": "0.5.2",
"description": "Add support for programming ligatures to xterm.js",
"author": {
"name": "The xterm.js authors",
@@ -25,18 +25,18 @@
"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",
"axios": "^0.18.0",
"axios": "^0.21.2",
"mkdirp": "0.5.5",
"sinon": "6.3.5",
"yauzl": "^2.10.0"
+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);
}
}
+13 -28
View File
@@ -45,23 +45,17 @@ array-from@^2.1.1:
resolved "https://registry.yarnpkg.com/array-from/-/array-from-2.1.1.tgz#cfe9d8c26628b9dc5aecc62a9f5d8f1f352c1195"
integrity sha1-z+nYwmYoudxa7MYqn12PHzUsEZU=
axios@^0.18.0:
version "0.18.1"
resolved "https://registry.yarnpkg.com/axios/-/axios-0.18.1.tgz#ff3f0de2e7b5d180e757ad98000f1081b87bcea3"
axios@^0.21.2:
version "0.21.2"
resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.2.tgz#21297d5084b2aeeb422f5d38e7be4fbb82239017"
integrity sha512-87otirqUw3e8CzHTMO+/9kh/FSgXt/eVDvipijwDtEuwbkySWZ9SBm6VEubmJ/kLKEoLQV/POhxXFb66bfekfg==
dependencies:
follow-redirects "1.5.10"
is-buffer "^2.0.2"
follow-redirects "^1.14.0"
buffer-crc32@~0.2.3:
version "0.2.13"
resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242"
debug@=3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
dependencies:
ms "2.0.0"
diff@^3.5.0:
version "3.5.0"
resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12"
@@ -72,11 +66,10 @@ fd-slicer@~1.1.0:
dependencies:
pend "~1.2.0"
follow-redirects@1.5.10:
version "1.5.10"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.5.10.tgz#7b7a9f9aea2fdff36786a94ff643ed07f4ff5e2a"
dependencies:
debug "=3.1.0"
follow-redirects@^1.14.0:
version "1.14.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"
@@ -93,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"
@@ -110,10 +103,6 @@ has-flag@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
is-buffer@^2.0.2:
version "2.0.3"
resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.3.tgz#4ecf3fcf749cbd1e472689e109ac66261a25e725"
isarray@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf"
@@ -163,10 +152,6 @@ mkdirp@0.5.5:
dependencies:
minimist "^1.2.5"
ms@2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
nise@^1.4.5:
version "1.5.3"
resolved "https://registry.yarnpkg.com/nise/-/nise-1.5.3.tgz#9d2cfe37d44f57317766c6e9408a359c5d3ac1f7"
+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.8.2",
"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",
+118 -54
View File
@@ -3,7 +3,7 @@
* @license MIT
*/
import { Terminal, IDisposable, ITerminalAddon, ISelectionPosition } from 'xterm';
import { Terminal, IBufferLine, IDisposable, ITerminalAddon, ISelectionPosition } from 'xterm';
export interface ISearchOptions {
regex?: boolean;
@@ -21,8 +21,20 @@ 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
@@ -34,7 +46,7 @@ export class SearchAddon implements ITerminalAddon {
* 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;
@@ -257,7 +269,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 +286,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 +304,108 @@ 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()) {
offset += 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,23 +414,33 @@ 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];
}
/**
@@ -390,7 +454,7 @@ export class SearchAddon implements ITerminalAddon {
terminal.clearSelection();
return false;
}
terminal.select(result.col, result.row, result.term.length);
terminal.select(result.col, result.row, result.size);
// If it is not in the viewport then we scroll else it just gets selected
if (result.row >= (terminal.buffer.active.viewportY + terminal.rows) || result.row < terminal.buffer.active.viewportY) {
let scroll = result.row - terminal.buffer.active.viewportY;
@@ -103,6 +103,23 @@ 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('Regression tests', () => {
describe('#2444 wrapped line content not being found', () => {
let fixture: string;
+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.6.1",
"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,205 @@
/**
* 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 html', () => {
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;
});
it('empty terminal with selection turned off', () => {
const output = serializeAddon.serializeAsHTML();
assert.notEqual(output, '');
assert.equal((output.match(new RegExp('<div><span> {10}</span><\/div>', 'g')) || []).length, 2);
});
it('empty terminal with no selection', () => {
const output = serializeAddon.serializeAsHTML({
onlySelection: true
});
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(new RegExp('<div><span>terminal<\/span><\/div>', 'g')) || []).length, 1, output);
});
it('cells with bold styling', async () => {
await writeP(terminal, ' ' + sgr('1') + 'terminal' + sgr('22') + ' ');
const output = serializeAddon.serializeAsHTML();
assert.equal((output.match(new RegExp('<span style=\'font-weight: bold;\'>terminal<\/span>', 'g')) || []).length, 1, output);
});
it('cells with italic styling', async () => {
await writeP(terminal, ' ' + sgr('3') + 'terminal' + sgr('23') + ' ');
const output = serializeAddon.serializeAsHTML();
assert.equal((output.match(new RegExp('<span style=\'font-style: italic;\'>terminal<\/span>', 'g')) || []).length, 1, output);
});
it('cells with inverse styling', async () => {
await writeP(terminal, ' ' + sgr('7') + 'terminal' + sgr('27') + ' ');
const output = serializeAddon.serializeAsHTML();
assert.equal((output.match(new RegExp('<span style=\'color: #000000; background-color: #BFBFBF;\'>terminal<\/span>', 'g')) || []).length, 1, output);
});
it('cells with underline styling', async () => {
await writeP(terminal, ' ' + sgr('4') + 'terminal' + sgr('24') + ' ');
const output = serializeAddon.serializeAsHTML();
assert.equal((output.match(new RegExp('<span style=\'text-decoration: underline;\'>terminal<\/span>', 'g')) || []).length, 1, output);
});
it('cells with invisible styling', async () => {
await writeP(terminal, ' ' + sgr('8') + 'terminal' + sgr('28') + ' ');
const output = serializeAddon.serializeAsHTML();
assert.equal((output.match(new RegExp('<span style=\'visibility: hidden;\'>terminal<\/span>', 'g')) || []).length, 1, output);
});
it('cells with dim styling', async () => {
await writeP(terminal, ' ' + sgr('2') + 'terminal' + sgr('22') + ' ');
const output = serializeAddon.serializeAsHTML();
assert.equal((output.match(new RegExp('<span style=\'opacity: 0.5;\'>terminal<\/span>', 'g')) || []).length, 1, output);
});
it('cells with strikethrough styling', async () => {
await writeP(terminal, ' ' + sgr('9') + 'terminal' + sgr('29') + ' ');
const output = serializeAddon.serializeAsHTML();
assert.equal((output.match(new RegExp('<span style=\'text-decoration: line-through;\'>terminal<\/span>', 'g')) || []).length, 1, output);
});
it('cells with combined styling', async () => {
await writeP(terminal, sgr('1') + ' ' + sgr('9') + 'termi' + sgr('22') + 'nal' + sgr('29') + ' ');
const output = serializeAddon.serializeAsHTML();
assert.equal((output.match(new RegExp('<span style=\'font-weight: bold;\'> <\/span>', 'g')) || []).length, 1, output);
assert.equal((output.match(new RegExp('<span style=\'font-weight: bold; text-decoration: line-through;\'>termi<\/span>', 'g')) || []).length, 1, output);
assert.equal((output.match(new RegExp('<span style=\'text-decoration: line-through;\'>nal<\/span>', 'g')) || []).length, 1, output);
});
it('cells with color styling', async () => {
await writeP(terminal, ' ' + sgr('38;5;46') + 'terminal' + sgr('39') + ' ');
const output = serializeAddon.serializeAsHTML();
assert.equal((output.match(new RegExp('<span style=\'color: #00ff00;\'>terminal<\/span>', 'g')) || []).length, 1, output);
});
it('cells with background styling', async () => {
await writeP(terminal, ' ' + sgr('48;5;46') + 'terminal' + sgr('49') + ' ');
const output = serializeAddon.serializeAsHTML();
assert.equal((output.match(new RegExp('<span style=\'background-color: #00ff00;\'>terminal<\/span>', 'g')) || []).length, 1, output);
});
it('empty terminal with default options', async () => {
const output = serializeAddon.serializeAsHTML();
assert.equal((output.match(new RegExp('color: #000000; background-color: #ffffff; font-family: courier-new, courier, monospace; font-size: 15px;', 'g')) || []).length, 1, output);
});
it('empty terminal with custom options', async () => {
terminal.options.fontFamily = 'verdana';
terminal.options.fontSize = 20;
terminal.options.theme = {
foreground: '#ff00ff',
background: '#00ff00'
};
const output = serializeAddon.serializeAsHTML({
includeGlobalBackground: true
});
assert.equal((output.match(new RegExp('color: #ff00ff; background-color: #00ff00; font-family: verdana; font-size: 20px;', 'g')) || []).length, 1, output);
});
it('empty terminal with background included', async () => {
const output = serializeAddon.serializeAsHTML({
includeGlobalBackground: true
});
assert.equal((output.match(new RegExp('color: #ffffff; background-color: #000000; font-family: courier-new, courier, monospace; font-size: 15px;', 'g')) || []).length, 1, output);
});
});
@@ -5,8 +5,8 @@
* (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';
function constrain(value: number, low: number, high: number): number {
return Math.max(low, Math.min(value, high));
@@ -19,18 +19,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 +47,7 @@ abstract class BaseSerializeHandler {
oldCell = c;
}
}
this._rowEnd(row, row === endRow - 1);
this._rowEnd(row, row === endRow);
}
this._afterSerialize();
@@ -72,7 +79,8 @@ function equalFlags(cell1: IBufferCell, cell2: IBufferCell): boolean {
&& cell1.isBlink() === cell2.isBlink()
&& cell1.isInvisible() === cell2.isInvisible()
&& cell1.isItalic() === cell2.isItalic()
&& cell1.isDim() === cell2.isDim();
&& cell1.isDim() === cell2.isDim()
&& cell1.isStrikethrough() === cell2.isStrikethrough();
}
class StringSerializeHandler extends BaseSerializeHandler {
@@ -160,7 +168,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,
@@ -259,6 +267,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); }
}
}
}
@@ -401,7 +410,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 +470,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 = 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"
}
]
}
@@ -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.5.1",
"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",
@@ -10,7 +10,7 @@ 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,7 +29,11 @@ const strictUrlRegex = new RegExp(start + protocolClause + bodyClause + end);
function handleLink(event: MouseEvent, uri: string): void {
const newWindow = window.open();
if (newWindow) {
newWindow.opener = null;
try {
newWindow.opener = null;
} catch {
// no-op, Electron can throw
}
newWindow.location.href = uri;
} else {
console.warn('Opening link blocked as opener could not be cleared');
@@ -39,6 +43,7 @@ function handleLink(event: MouseEvent, uri: string): void {
interface ILinkProviderOptions {
hover?(event: MouseEvent, text: string, location: IViewportRange): void;
leave?(event: MouseEvent, text: string): void;
urlRegex?: RegExp;
}
export class WebLinksAddon implements ITerminalAddon {
@@ -58,7 +63,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;
+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.11.4",
"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",
+24 -19
View File
@@ -299,32 +299,37 @@ export class GlyphRenderer {
return this._colors.ansi[idx];
}
public onResize(): void {
public clear(force?: boolean): 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;
}
// Don't clear if not forced and the array length is correct
if (!force && this._vertices.count === newCount) {
return;
}
// 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;
}
}
}
public onResize(): void {
const gl = this._gl;
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
this.clear();
}
public setColors(): void {
}
@@ -8,6 +8,7 @@ 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';
export class WebglAddon implements ITerminalAddon {
private _terminal?: Terminal;
@@ -23,6 +24,9 @@ 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;
@@ -60,7 +60,7 @@ export class WebglRenderer extends Disposable implements IRenderer {
this._renderLayers = [
new LinkRenderLayer(this._core.screenElement!, 2, this._colors, this._core),
new CursorRenderLayer(this._core.screenElement!, 3, this._colors, this._core, this._onRequestRedraw)
new CursorRenderLayer(_terminal, this._core.screenElement!, 3, this._colors, this._core, this._onRequestRedraw)
];
this.dimensions = {
scaledCharWidth: 0,
@@ -247,6 +247,8 @@ export class WebglRenderer extends Disposable implements IRenderer {
}
public clear(): void {
this._model.clear();
this._glyphRenderer.clear(true);
for (const l of this._renderLayers) {
l.reset(this._terminal);
}
@@ -4,7 +4,7 @@
*/
import { ICharAtlasConfig } from './Types';
import { DIM_OPACITY } from 'browser/renderer/atlas/Constants';
import { DIM_OPACITY, TEXT_BASELINE } from 'browser/renderer/atlas/Constants';
import { IRasterizedGlyph, IBoundingBox, IRasterizedGlyphSet } from '../Types';
import { DEFAULT_COLOR, Attributes } from 'common/buffer/Constants';
import { throwIfFalsy } from '../WebglUtils';
@@ -14,8 +14,8 @@ import { AttributeData } from 'common/buffer/AttributeData';
import { channels, rgba } from 'browser/Color';
import { tryDrawCustomChar } from 'browser/renderer/CustomGlyphs';
// In practice we're probably never going to exhaust a texture this large. For debugging purposes,
// however, it can be useful to set this to a really tiny value, to verify that LRU eviction works.
// 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;
@@ -368,7 +368,7 @@ export class WebglCharAtlas implements IDisposable {
const fontStyle = italic ? 'italic' : '';
this._tmpCtx.font =
`${fontStyle} ${fontWeight} ${this._config.fontSize * this._config.devicePixelRatio}px ${this._config.fontFamily}`;
this._tmpCtx.textBaseline = 'ideographic';
this._tmpCtx.textBaseline = TEXT_BASELINE;
this._tmpCtx.fillStyle = this._getForegroundCss(bg, bgColorMode, bgColor, fg, fgColorMode, fgColor, inverse, bold);
@@ -463,7 +463,7 @@ export class WebglCharAtlas implements IDisposable {
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;
@@ -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(),
@@ -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,25 +68,18 @@ export class CursorRenderLayer extends BaseRenderLayer {
public reset(terminal: Terminal): void {
this._clearCursor();
if (this._cursorBlinkStateManager) {
this._cursorBlinkStateManager.dispose();
this.onOptionsChanged(terminal);
}
this._cursorBlinkStateManager?.restartBlinkAnimation(terminal);
this.onOptionsChanged(terminal);
}
public onBlur(terminal: Terminal): void {
if (this._cursorBlinkStateManager) {
this._cursorBlinkStateManager.pause();
}
this._cursorBlinkStateManager?.pause();
this._onRequestRefreshRowsEvent.fire({ start: terminal.buffer.active.cursorY, end: terminal.buffer.active.cursorY });
}
public onFocus(terminal: Terminal): void {
if (this._cursorBlinkStateManager) {
this._cursorBlinkStateManager.resume(terminal);
} else {
this._onRequestRefreshRowsEvent.fire({ start: terminal.buffer.active.cursorY, end: terminal.buffer.active.cursorY });
}
this._cursorBlinkStateManager?.resume(terminal);
this._onRequestRefreshRowsEvent.fire({ start: terminal.buffer.active.cursorY, end: terminal.buffer.active.cursorY });
}
public onOptionsChanged(terminal: Terminal): void {
@@ -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 {
@@ -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
+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.substr(a.search(/\d+$/)));
const bVersion = parseInt(b.substr(b.search(/\d+$/)));
return aVersion > bVersion ? -1 : 1;
})[0];
const latestTagVersion = parseInt(latestPublishedVersion.substr(latestPublishedVersion.search(/[0-9]+$/)), 10);
const latestTagVersion = parseInt(latestPublishedVersion.substr(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], {
+7 -1
View File
@@ -133,7 +133,8 @@
cursor: default;
}
.xterm.xterm-cursor-pointer {
.xterm.xterm-cursor-pointer,
.xterm .xterm-cursor-pointer {
cursor: pointer;
}
@@ -172,3 +173,8 @@
.xterm-strikethrough {
text-decoration: line-through;
}
.xterm-screen .xterm-decoration-container .xterm-decoration {
z-index: 6;
position: absolute;
}
+40 -12
View File
@@ -147,8 +147,10 @@ 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('add-decoration').addEventListener('click', addDecoration);
}
function createTerminal(): void {
@@ -293,11 +295,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 +316,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 +340,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 +349,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 +367,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;
});
});
}
@@ -444,6 +449,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 +542,11 @@ function loadTest() {
term._core._onData.fire('\x03');
});
}
function addDecoration() {
const marker = term.addMarker(1);
const decoration = term.registerDecoration({ marker });
decoration.onRender(() => {
decoration.element.style.backgroundColor = 'red';
});
}
+5
View File
@@ -49,6 +49,10 @@
<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">
@@ -64,6 +68,7 @@
<button id="dispose" title="This is used to testing memory leaks">Dispose terminal</button>
<button id="custom-glyph" title="Write custom box drawing and block element characters to the terminal">Test custom glyphs</button>
<button id="load-test" title="Write several MB of data to simulate a lot of data coming from the process">Load test</button>
<button id="add-decoration" title="Add a decoration to the terminal">Decoration</button>
</div>
</div>
</div>
+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.0",
"version": "4.17.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.16.2",
"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.0"
}
}
+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');
+8
View File
@@ -4,6 +4,7 @@
*/
import { IColor } from 'browser/Types';
import { IColorRGB } from 'common/Types';
/**
* Helper functions where the source type is "channels" (individual color channels as numbers).
@@ -17,6 +18,8 @@ export namespace channels {
}
export function toRgba(r: number, g: number, b: number, a: number = 0xFF): number {
// Note: The aggregated number is RGBA32 (BE), thus needs to be converted to ABGR32
// on LE systems, before it can be used for direct 32-bit buffer writes.
// >>> 0 forces an unsigned int
return (r << 24 | g << 16 | b << 8 | a) >>> 0;
}
@@ -81,6 +84,10 @@ export namespace color {
rgba: channels.toRgba(r, g, b, a)
};
}
export function toColorRGB(color: IColor): IColorRGB {
return [(color.rgba >> 24) & 0xFF, (color.rgba >> 16) & 0xFF, (color.rgba >> 8) & 0xFF];
}
}
/**
@@ -197,6 +204,7 @@ export namespace rgba {
return (fgR << 24 | fgG << 16 | fgB << 8 | 0xFF) >>> 0;
}
// FIXME: Move this to channels NS?
export function toChannels(value: number): [number, number, number, number] {
return [(value >> 24) & 0xFF, (value >> 16) & 0xFF, (value >> 8) & 0xFF, value & 0xFF];
}
+45
View File
@@ -7,6 +7,16 @@ import { IColorManager, IColor, IColorSet, IColorContrastCache } from 'browser/T
import { ITheme } from 'common/services/Services';
import { channels, color, css } from 'browser/Color';
import { ColorContrastCache } from 'browser/ColorContrastCache';
import { ColorIndex } 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');
@@ -96,6 +107,7 @@ export class ColorManager implements IColorManager {
ansi: DEFAULT_ANSI_COLORS.slice(),
contrastCache: this._contrastCache
};
this._updateRestoreColors();
}
public onOptionsChange(key: string): void {
@@ -142,6 +154,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]
};
}
private _parseColor(
+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 {
+17 -1
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;
@@ -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 {
+117 -52
View File
@@ -37,24 +37,25 @@ 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';
import { ColorManager } from 'browser/ColorManager';
import { RenderService } from 'browser/services/RenderService';
import { ICharSizeService, IRenderService, IMouseService, ISelectionService, ISoundService, ICoreBrowserService, ICharacterJoinerService } from 'browser/services/Services';
import { ICharSizeService, IRenderService, IMouseService, ISelectionService, ISoundService, ICoreBrowserService, ICharacterJoinerService, IDecorationService } from 'browser/services/Services';
import { CharSizeService } from 'browser/services/CharSizeService';
import { IBuffer } from 'common/buffer/Types';
import { MouseService } from 'browser/services/MouseService';
import { Linkifier2 } from 'browser/Linkifier2';
import { CoreBrowserService } from 'browser/services/CoreBrowserService';
import { CoreTerminal } from 'common/CoreTerminal';
import { ITerminalOptions as IInitializedTerminalOptions } from 'common/services/Services';
import { rgba } from 'browser/Color';
import { color, rgba } from 'browser/Color';
import { CharacterJoinerService } from 'browser/services/CharacterJoinerService';
import { toRgbString } from 'common/input/XParseColor';
import { DecorationService } from 'browser/services/DecorationService';
// Let it work inside Node.js for automated testing purposes.
const document: Document = (typeof window !== 'undefined') ? window.document : null as any;
@@ -74,9 +75,6 @@ export class Terminal extends CoreTerminal implements ITerminal {
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
@@ -111,6 +109,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
public linkifier: ILinkifier;
public linkifier2: ILinkifier2;
public viewport: IViewport | undefined;
public decorationService: IDecorationService;
private _compositionHelper: ICompositionHelper | undefined;
private _mouseZoneManager: IMouseZoneManager | undefined;
private _accessibilityManager: AccessibilityManager | undefined;
@@ -152,7 +151,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
* @alias module:xterm/src/xterm
*/
constructor(
options: ITerminalOptions = {}
options: Partial<ITerminalOptions> = {}
) {
super(options);
@@ -160,13 +159,15 @@ export class Terminal extends CoreTerminal implements ITerminal {
this.linkifier = this._instantiationService.createInstance(Linkifier);
this.linkifier2 = this.register(this._instantiationService.createInstance(Linkifier2));
this.decorationService = this.register(this._instantiationService.createInstance(DecorationService));
// 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 +177,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)}${C0.BEL}`);
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 +301,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 +312,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;
}
}
@@ -493,7 +532,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 +575,9 @@ 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.decorationService.attachToDom(this.screenElement, this._renderService, this._bufferService);
// This event listener must be registered aftre MouseZoneManager is created
this.register(addDisposableDomListener(this.element, 'mousedown', (e: MouseEvent) => this._selectionService!.onMouseDown(e)));
@@ -698,8 +739,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 +757,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 +834,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 +1002,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.
*/
@@ -1091,7 +1133,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);
}
@@ -1170,6 +1212,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;
}
@@ -1180,11 +1226,17 @@ export class Terminal extends CoreTerminal implements ITerminal {
* @param ev The input event to be handled.
*/
protected _inputEvent(ev: InputEvent): boolean {
if (ev.data && ev.inputType === 'insertText') {
// Only support emoji IMEs when screen reader mode is disabled as the event must bubble up to
// support reading out character input which can doubling up input characters
if (ev.data && ev.inputType === 'insertText' && !ev.composed && !this.optionsService.rawOptions.screenReaderMode) {
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);
@@ -1249,6 +1301,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
// Don't clear if it's already clear
return;
}
this.buffer.clearMarkers();
this.buffer.lines.set(0, this.buffer.lines.get(this.buffer.ybase + this.buffer.y)!);
this.buffer.lines.length = 1;
this.buffer.ydisp = 0;
@@ -1290,6 +1343,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;
+19 -1
View File
@@ -3,7 +3,7 @@
* @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';
@@ -102,6 +102,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 +198,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 +257,9 @@ export class MockBuffer implements IBuffer {
public getWhitespaceCell(attr?: IAttributeData): ICellData {
throw new Error('Method not implemented.');
}
public clearMarkers(): void {
throw new Error('Method not implemented.');
}
}
export class MockRenderer implements IRenderer {
@@ -277,6 +286,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 { }
@@ -374,6 +386,9 @@ export class MockRenderService implements IRenderService {
public refreshRows(start: number, end: number): void {
throw new Error('Method not implemented.');
}
public clearTextureAtlas(): void {
throw new Error('Method not implemented.');
}
public resize(cols: number, rows: number): void {
throw new Error('Method not implemented.');
}
@@ -413,6 +428,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 {
+4 -3
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 { 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;
@@ -62,6 +61,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 +79,7 @@ export interface IPublicTerminal extends IDisposable {
write(data: string | Uint8Array, callback?: () => void): void;
paste(data: string): void;
refresh(start: number, end: number): void;
clearTextureAtlas(): void;
reset(): void;
}
+11 -8
View File
@@ -42,6 +42,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,
@@ -109,14 +110,16 @@ export class Viewport extends Disposable implements IViewport {
}
// Update scroll bar width
if (this._optionsService.options.scrollback === 0) {
if (this._optionsService.rawOptions.scrollback === 0) {
this.scrollBarWidth = 0;
} else {
this.scrollBarWidth = (this._viewportElement.offsetWidth - this._scrollArea.offsetWidth) || FALLBACK_SCROLL_BAR_WIDTH;
}
this._lastHadScrollBar = this.scrollBarWidth > 0;
this._viewportElement.style.width = (this._renderService.dimensions.actualCellWidth * (this._bufferService.cols) + this.scrollBarWidth).toString() + 'px';
const elementStyle = window.getComputedStyle(this._element);
const elementPadding = parseInt(elementStyle.paddingLeft) + parseInt(elementStyle.paddingRight);
this._viewportElement.style.width = (this._renderService.dimensions.actualCellWidth * (this._bufferService.cols) + this.scrollBarWidth + (this._lastHadScrollBar ? elementPadding : 0)).toString() + 'px';
this._refreshAnimationFrame = null;
}
@@ -150,7 +153,7 @@ export class Viewport extends Disposable implements IViewport {
}
// If the scroll bar visibility changed
if (this._lastHadScrollBar !== (this._optionsService.options.scrollback > 0)) {
if (this._lastHadScrollBar !== (this._optionsService.rawOptions.scrollback > 0)) {
this._refresh(immediate);
}
}
@@ -217,7 +220,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 +241,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 +259,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;
}
/**
+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();
+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;
+61 -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');
}
}
@@ -89,6 +122,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 +166,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 +259,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 +269,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 +286,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');
}
}
}
}
+19 -20
View File
@@ -7,7 +7,7 @@ import { IRenderDimensions, IRenderLayer } from 'browser/renderer/Types';
import { ICellData } from 'common/Types';
import { DEFAULT_COLOR, WHITESPACE_CELL_CHAR, WHITESPACE_CELL_CODE, Attributes } from 'common/buffer/Constants';
import { IGlyphIdentifier } from 'browser/renderer/atlas/Types';
import { DIM_OPACITY, INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants';
import { DIM_OPACITY, INVERTED_DEFAULT_COLOR, TEXT_BASELINE } from 'browser/renderer/atlas/Constants';
import { BaseCharAtlas } from 'browser/renderer/atlas/BaseCharAtlas';
import { acquireCharAtlas } from 'browser/renderer/atlas/CharAtlasCache';
import { AttributeData } from 'common/buffer/AttributeData';
@@ -112,7 +112,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 +138,10 @@ export abstract class BaseRenderLayer implements IRenderLayer {
public abstract reset(): void;
public clearTextureAtlas(): void {
this._charAtlas?.clear();
}
/**
* Fills 1+ cells completely. This uses the existing fillStyle on the context.
* @param x The column to start at.
@@ -258,12 +262,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);
}
@@ -311,7 +315,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 +325,7 @@ 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
);
const atlasDidDraw = 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 +345,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 +356,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 +370,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 +386,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,14 +421,14 @@ 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) {
if (this._optionsService.rawOptions.minimumContrastRatio === 1) {
return undefined;
}
@@ -456,7 +455,7 @@ export abstract class BaseRenderLayer implements IRenderLayer {
const bgRgba = this._resolveBackgroundRgba(bgColorMode, bgColor, isInverse);
const fgRgba = this._resolveForegroundRgba(fgColorMode, fgColor, isInverse, isBold);
const result = rgba.ensureContrastRatio(bgRgba, fgRgba, this._optionsService.options.minimumContrastRatio);
const result = rgba.ensureContrastRatio(bgRgba, fgRgba, this._optionsService.rawOptions.minimumContrastRatio);
if (!result) {
this._colors.contrastCache.setColor(cell.bg, cell.fg, null);
@@ -496,7 +495,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;
+13 -23
View File
@@ -55,7 +55,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 +79,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 +110,7 @@ export class CursorRenderLayer extends BaseRenderLayer {
}
public onCursorMove(): void {
if (this._cursorBlinkStateManager) {
this._cursorBlinkStateManager.restartBlinkAnimation();
}
this._cursorBlinkStateManager?.restartBlinkAnimation();
}
public onGridChanged(startRow: number, endRow: number): void {
@@ -159,7 +148,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 +174,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 +182,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 +213,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 +302,7 @@ class CursorBlinkStateManager {
// Clear any existing interval
if (this._blinkInterval) {
window.clearInterval(this._blinkInterval);
this._blinkInterval = undefined;
}
// Setup the initial timeout which will hide the cursor, this is done before
+14 -7
View File
@@ -10,10 +10,11 @@ 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';
import { IDecorationOptions, IDecoration } from 'xterm';
let nextRendererId = 1;
@@ -39,7 +40,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),
@@ -149,6 +150,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 +179,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
+3 -3
View File
@@ -225,7 +225,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 +237,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 +271,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]
};
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;
+12 -2
View File
@@ -3,7 +3,7 @@
* @license MIT
*/
import { DIM_OPACITY, INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants';
import { DIM_OPACITY, INVERTED_DEFAULT_COLOR, TEXT_BASELINE } from 'browser/renderer/atlas/Constants';
import { IGlyphIdentifier, ICharAtlasConfig } from 'browser/renderer/atlas/Types';
import { BaseCharAtlas } from 'browser/renderer/atlas/BaseCharAtlas';
import { DEFAULT_ANSI_COLORS } from 'browser/ColorManager';
@@ -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;
+9 -9
View File
@@ -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;` +
@@ -356,7 +356,7 @@ export class DomRenderer extends Disposable implements IRenderer {
public renderRows(start: number, end: number): void {
const cursorAbsoluteY = this._bufferService.buffer.ybase + this._bufferService.buffer.y;
const cursorX = 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];
@@ -364,7 +364,7 @@ export class DomRenderer extends Disposable implements IRenderer {
const row = y + this._bufferService.buffer.ydisp;
const lineData = this._bufferService.buffer.lines.get(row);
const cursorStyle = this._optionsService.options.cursorStyle;
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,7 +10,7 @@ import { NULL_CELL_CODE, NULL_CELL_WIDTH, NULL_CELL_CHAR, DEFAULT_ATTR, FgFlags,
import { BufferLine, DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine';
import { IBufferLine } from 'common/Types';
import { CellData } from 'common/buffer/CellData';
import { MockOptionsService } from 'common/TestUtils.test';
import { MockCoreService, MockOptionsService } from 'common/TestUtils.test';
import { css } from 'browser/Color';
import { MockCharacterJoinerService } from 'browser/TestUtils.test';
@@ -21,30 +21,36 @@ 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()
);
lineData = createEmptyLineData(2);
});
@@ -7,7 +7,7 @@ import { IBufferLine } 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 { ICoreService, IOptionsService } from 'common/services/Services';
import { color, rgba } from 'browser/Color';
import { IColorSet, IColor } from 'browser/Types';
import { ICharacterJoinerService } from 'browser/services/Services';
@@ -31,7 +31,8 @@ export class DomRendererRowFactory {
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
) {
}
@@ -110,7 +111,7 @@ export class DomRendererRowFactory {
}
}
if (isCursorRow && x === cursorX) {
if (!this._coreService.isCursorHidden && isCursorRow && x === cursorX) {
charElement.classList.add(CURSOR_CLASS);
if (cursorBlink) {
@@ -174,7 +175,7 @@ export class DomRendererRowFactory {
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])) {
@@ -224,7 +225,7 @@ export class DomRendererRowFactory {
}
private _applyMinimumContrast(element: HTMLElement, bg: IColor, fg: IColor): boolean {
if (this._optionsService.options.minimumContrastRatio === 1) {
if (this._optionsService.rawOptions.minimumContrastRatio === 1) {
return false;
}
@@ -233,7 +234,7 @@ export class DomRendererRowFactory {
// Calculate and store in cache
if (adjustedColor === undefined) {
adjustedColor = color.ensureContrastRatio(bg, fg, this._optionsService.options.minimumContrastRatio);
adjustedColor = color.ensureContrastRatio(bg, fg, this._optionsService.rawOptions.minimumContrastRatio);
this._colors.contrastCache.setColor(this._workCell.bg, this._workCell.fg, adjustedColor ?? null);
}
@@ -122,5 +122,13 @@ describe('SelectionModel', () => {
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]);
});
});
});
+4
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]];
+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();
+15 -6
View File
@@ -176,16 +176,25 @@ export class CharacterJoinerService implements ICharacterJoinerService {
// At this point we already know that there is at least one joiner so
// we can just pull its value and assign it directly rather than
// merging it into an empty array, which incurs unnecessary writes.
const joinedRanges: [number, number][] = this._characterJoiners[0].handler(text);
let allJoinedRanges: [number, number][] = [];
try {
allJoinedRanges = this._characterJoiners[0].handler(text);
} catch (error) {
console.error(error);
}
for (let i = 1; i < this._characterJoiners.length; i++) {
// We merge any overlapping ranges across the different joiners
const joinerRanges = this._characterJoiners[i].handler(text);
for (let j = 0; j < joinerRanges.length; j++) {
CharacterJoinerService._mergeRanges(joinedRanges, joinerRanges[j]);
try {
const joinerRanges = this._characterJoiners[i].handler(text);
for (let j = 0; j < joinerRanges.length; j++) {
CharacterJoinerService._mergeRanges(allJoinedRanges, joinerRanges[j]);
}
} catch (error) {
console.error(error);
}
}
this._stringRangesToCellRanges(joinedRanges, lineData, startCol);
return joinedRanges;
this._stringRangesToCellRanges(allJoinedRanges, lineData, startCol);
return allJoinedRanges;
}
/**
+164
View File
@@ -0,0 +1,164 @@
/**
* Copyright (c) 2022 The xterm.js authors. All rights reserved.
* @license MIT
*/
import { IDecorationService, IRenderService } from 'browser/services/Services';
import { EventEmitter, IEvent } from 'common/EventEmitter';
import { Disposable } from 'common/Lifecycle';
import { IBufferService, IInstantiationService } from 'common/services/Services';
import { IDecorationOptions, IDecoration, IMarker } from 'xterm';
export class DecorationService extends Disposable implements IDecorationService {
private readonly _decorations: Decoration[] = [];
private _container: HTMLElement | undefined;
private _screenElement: HTMLElement | undefined;
private _renderService: IRenderService | undefined;
private _animationFrame: number | undefined;
constructor(@IInstantiationService private readonly _instantiationService: IInstantiationService) { super(); }
public attachToDom(screenElement: HTMLElement, renderService: IRenderService): void {
this._renderService = renderService;
this._screenElement = screenElement;
this._container = document.createElement('div');
this._container.classList.add('xterm-decoration-container');
screenElement.appendChild(this._container);
this.register(this._renderService.onRenderedBufferChange(() => this.refresh()));
this.register(this._renderService.onDimensionsChange(() => this.refresh(true)));
}
public registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined {
if (decorationOptions.marker.isDisposed || !this._container) {
return undefined;
}
const decoration = this._instantiationService.createInstance(Decoration, decorationOptions, this._container);
this._decorations.push(decoration);
decoration.onDispose(() => this._decorations.splice(this._decorations.indexOf(decoration), 1));
this._queueRefresh();
return decoration;
}
private _queueRefresh(): void {
if (this._animationFrame !== undefined) {
return;
}
this._animationFrame = window.requestAnimationFrame(() => {
this.refresh();
this._animationFrame = undefined;
});
}
public refresh(shouldRecreate?: boolean): void {
if (!this._renderService) {
return;
}
for (const decoration of this._decorations) {
decoration.render(this._renderService, shouldRecreate);
}
}
public dispose(): void {
for (const decoration of this._decorations) {
decoration.dispose();
}
if (this._screenElement && this._container && this._screenElement.contains(this._container)) {
this._screenElement.removeChild(this._container);
}
}
}
export class Decoration extends Disposable implements IDecoration {
private readonly _marker: IMarker;
private _element: HTMLElement | undefined;
public isDisposed: boolean = false;
public get element(): HTMLElement | undefined { return this._element; }
public get marker(): IMarker { return this._marker; }
private _onDispose = new EventEmitter<void>();
public get onDispose(): IEvent<void> { return this._onDispose.event; }
private _onRender = new EventEmitter<HTMLElement>();
public get onRender(): IEvent<HTMLElement> { return this._onRender.event; }
public x: number;
public anchor: 'left' | 'right';
public width: number;
public height: number;
constructor(
options: IDecorationOptions,
private readonly _container: HTMLElement,
@IBufferService private readonly _bufferService: IBufferService
) {
super();
this.x = options.x ?? 0;
this._marker = options.marker;
this._marker.onDispose(() => this.dispose());
this.anchor = options.anchor || 'left';
this.width = options.width || 1;
this.height = options.height || 1;
}
public render(renderService: IRenderService, shouldRecreate?: boolean): void {
if (!this._element || shouldRecreate) {
this._createElement(renderService, shouldRecreate);
}
if (this._container && this._element && !this._container.contains(this._element)) {
this._container.append(this._element);
}
this._refreshStyle(renderService);
if (this._element) {
this._onRender.fire(this._element);
}
}
private _createElement(renderService: IRenderService, shouldRecreate?: boolean): void {
if (shouldRecreate && this._element && this._container.contains(this._element)) {
this._container.removeChild(this._element);
}
this._element = document.createElement('div');
this._element.classList.add('xterm-decoration');
this._element.style.width = `${this.width * renderService.dimensions.actualCellWidth}px`;
this._element.style.height = `${this.height * renderService.dimensions.actualCellHeight}px`;
this._element.style.top = `${(this.marker.line - this._bufferService.buffers.active.ydisp) * renderService.dimensions.actualCellHeight}px`;
this._element.style.lineHeight = `${renderService.dimensions.actualCellHeight}px`;
if (this.x && this.x > this._bufferService.cols) {
// exceeded the container width, so hide
this._element.style.display = 'none';
}
if (this.anchor === 'right') {
this._element.style.right = this.x ? `${this.x * renderService.dimensions.actualCellWidth}px` : '';
} else {
this._element.style.left = this.x ? `${this.x * renderService.dimensions.actualCellWidth}px` : '';
}
}
private _refreshStyle(renderService: IRenderService): void {
if (!this._element) {
return;
}
const line = this.marker.line - this._bufferService.buffers.active.ydisp;
if (line < 0 || line > this._bufferService.rows) {
// outside of viewport
this._element.style.display = 'none';
} else {
this._element.style.top = `${line * renderService.dimensions.actualCellHeight}px`;
this._element.style.display = this._bufferService.buffer === this._bufferService.buffers.alt ? 'none' : 'block';
}
}
public override dispose(): void {
if (this.isDisposed) {
return;
}
if (this._element && this._container.contains(this._element)) {
this._container.removeChild(this._element);
}
this.isDisposed = true;
this._onDispose.fire();
}
}
+7 -1
View File
@@ -65,7 +65,8 @@ export class RenderService extends Disposable implements IRenderService {
this._screenDprMonitor.setListener(() => this.onDevicePixelRatioChange());
this.register(this._screenDprMonitor);
this.register(bufferService.onResize(e => this._fullRefresh()));
this.register(bufferService.onResize(() => this._fullRefresh()));
this.register(bufferService.buffers.onBufferActivate(() => this._renderer?.clear()));
this.register(optionsService.onOptionChange(() => this._renderer.onOptionsChanged()));
this.register(this._charSizeService.onCharSizeChange(() => this.onCharSizeChanged()));
@@ -168,6 +169,11 @@ export class RenderService extends Disposable implements IRenderService {
}
}
public clearTextureAtlas(): void {
this._renderer?.clearTextureAtlas?.();
this._fullRefresh();
}
public setColors(colors: IColorSet): void {
this._renderer.setColors(colors);
this._fullRefresh();
+5 -5
View File
@@ -220,7 +220,7 @@ export class SelectionService extends Disposable implements ISelectionService {
for (let i = start[1] + 1; i <= end[1] - 1; i++) {
const bufferLine = buffer.lines.get(i);
const lineText = buffer.translateBufferLineToString(i, true);
if (bufferLine && bufferLine.isWrapped) {
if (bufferLine?.isWrapped) {
result[result.length - 1] += lineText;
} else {
result.push(lineText);
@@ -414,7 +414,7 @@ export class SelectionService extends Disposable implements ISelectionService {
*/
public shouldForceSelection(event: MouseEvent): boolean {
if (Browser.isMac) {
return event.altKey && this._optionsService.options.macOptionClickForcesSelection;
return event.altKey && this._optionsService.rawOptions.macOptionClickForcesSelection;
}
return event.shiftKey;
@@ -567,7 +567,7 @@ export class SelectionService extends Disposable implements ISelectionService {
* @param event the mouse or keyboard event
*/
public shouldColumnSelect(event: KeyboardEvent | MouseEvent): boolean {
return event.altKey && !(Browser.isMac && this._optionsService.options.macOptionClickForcesSelection);
return event.altKey && !(Browser.isMac && this._optionsService.rawOptions.macOptionClickForcesSelection);
}
/**
@@ -924,7 +924,7 @@ export class SelectionService extends Disposable implements ISelectionService {
if (followWrappedLinesBelow) {
if (start + length === this._bufferService.cols && bufferLine.getCodePoint(this._bufferService.cols - 1) !== 32 /* ' ' */) {
const nextBufferLine = buffer.lines.get(coords[1] + 1);
if (nextBufferLine && nextBufferLine.isWrapped && nextBufferLine.getCodePoint(0) !== 32 /* ' ' */) {
if (nextBufferLine?.isWrapped && nextBufferLine.getCodePoint(0) !== 32 /* ' ' */) {
const nextLineWordPosition = this._getWordAt([0, coords[1] + 1], false, false, true);
if (nextLineWordPosition) {
length += nextLineWordPosition.length;
@@ -993,7 +993,7 @@ export class SelectionService extends Disposable implements ISelectionService {
if (cell.getWidth() === 0) {
return false;
}
return this._optionsService.options.wordSeparator.indexOf(cell.getChars()) >= 0;
return this._optionsService.rawOptions.wordSeparator.indexOf(cell.getChars()) >= 0;
}
/**
+11
View File
@@ -9,6 +9,8 @@ import { IColorSet } from 'browser/Types';
import { ISelectionRedrawRequestEvent as ISelectionRequestRedrawEvent, ISelectionRequestScrollLinesEvent } from 'browser/selection/Types';
import { createDecorator } from 'common/services/ServiceRegistry';
import { IDisposable } from 'common/Types';
import { IDecorationOptions, IDecoration } from 'xterm';
import { IBufferService } from 'common/services/Services';
export const ICharSizeService = createDecorator<ICharSizeService>('CharSizeService');
export interface ICharSizeService {
@@ -53,6 +55,7 @@ export interface IRenderService extends IDisposable {
dimensions: IRenderDimensions;
refreshRows(start: number, end: number): void;
clearTextureAtlas(): void;
resize(cols: number, rows: number): void;
changeOptions(): void;
setRenderer(renderer: IRenderer): void;
@@ -112,3 +115,11 @@ export interface ICharacterJoinerService {
deregister(joinerId: number): boolean;
getJoinedCharacters(row: number): [number, number][];
}
export const IDecorationService = createDecorator<IDecorationService>('DecorationService');
export interface IDecorationService extends IDisposable {
registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined;
refresh(): void;
attachToDom(screenElement: HTMLElement, renderService: IRenderService, bufferService: IBufferService): void;
}
+1 -1
View File
@@ -34,7 +34,7 @@ export class SoundService implements ISoundService {
return;
}
const bellAudioSource = ctx.createBufferSource();
ctx.decodeAudioData(this._base64ToArrayBuffer(this._removeMimeType(this._optionsService.options.bellSound)), (buffer) => {
ctx.decodeAudioData(this._base64ToArrayBuffer(this._removeMimeType(this._optionsService.rawOptions.bellSound)), (buffer) => {
bellAudioSource.buffer = buffer;
bellAudioSource.connect(ctx.destination);
bellAudioSource.start(0);
+11 -5
View File
@@ -22,12 +22,12 @@
*/
import { Disposable } from 'common/Lifecycle';
import { IInstantiationService, IOptionsService, IBufferService, ILogService, ICharsetService, ICoreService, ICoreMouseService, IUnicodeService, IDirtyRowService, LogLevelEnum } from 'common/services/Services';
import { IInstantiationService, IOptionsService, IBufferService, ILogService, ICharsetService, ICoreService, ICoreMouseService, IUnicodeService, IDirtyRowService, LogLevelEnum, ITerminalOptions } from 'common/services/Services';
import { InstantiationService } from 'common/services/InstantiationService';
import { LogService } from 'common/services/LogService';
import { BufferService, MINIMUM_COLS, MINIMUM_ROWS } from 'common/services/BufferService';
import { OptionsService } from 'common/services/OptionsService';
import { ITerminalOptions, IDisposable, IBufferLine, IAttributeData, ICoreTerminal, IKeyboardEvent, IScrollEvent, ScrollSource } from 'common/Types';
import { IDisposable, IBufferLine, IAttributeData, ICoreTerminal, IKeyboardEvent, IScrollEvent, ScrollSource, ITerminalOptions as IPublicTerminalOptions } from 'common/Types';
import { CoreService } from 'common/services/CoreService';
import { EventEmitter, IEvent, forwardEvent } from 'common/EventEmitter';
import { CoreMouseService } from 'common/services/CoreMouseService';
@@ -86,9 +86,15 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal {
public get cols(): number { return this._bufferService.cols; }
public get rows(): number { return this._bufferService.rows; }
public get buffers(): IBufferSet { return this._bufferService.buffers; }
public get options(): ITerminalOptions { return this.optionsService.options; }
public set options(options: ITerminalOptions) {
for (const key in options) {
this.optionsService.options[key] = options[key];
}
}
constructor(
options: ITerminalOptions
options: Partial<ITerminalOptions>
) {
super();
@@ -241,7 +247,7 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal {
}
protected _setup(): void {
if (this.optionsService.options.windowsMode) {
if (this.optionsService.rawOptions.windowsMode) {
this._enableWindowsMode();
}
}
@@ -261,7 +267,7 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal {
this.buffers.resize(this.cols, this.rows);
break;
case 'windowsMode':
if (this.optionsService.options.windowsMode) {
if (this.optionsService.rawOptions.windowsMode) {
this._enableWindowsMode();
} else {
this._windowsMode?.dispose();
+225 -90
View File
@@ -5,7 +5,7 @@
import { assert } from 'chai';
import { InputHandler } from 'common/InputHandler';
import { IBufferLine, IAttributeData, IAnsiColorChangeEvent } from 'common/Types';
import { IBufferLine, IAttributeData, IColorEvent, ColorIndex, ColorRequestType } from 'common/Types';
import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine';
import { CellData } from 'common/buffer/CellData';
import { Attributes, UnderlineStyle } from 'common/buffer/Constants';
@@ -17,7 +17,7 @@ import { DEFAULT_OPTIONS } from 'common/services/OptionsService';
import { clone } from 'common/Clone';
import { BufferService } from 'common/services/BufferService';
import { CoreService } from 'common/services/CoreService';
import { OscHandler } from 'common/parser/OscParser';
function getCursor(bufferService: IBufferService): number[] {
return [
@@ -41,7 +41,6 @@ class TestInputHandler extends InputHandler {
public get curAttrData(): IAttributeData { return (this as any)._curAttrData; }
public get windowTitleStack(): string[] { return this._windowTitleStack; }
public get iconNameStack(): string[] { return this._iconNameStack; }
public parseAnsiColorChange(data: string): IAnsiColorChangeEvent | null { return this._parseAnsiColorChange(data); }
/**
* Promise based parse call to await the full resolve of given input data.
@@ -77,54 +76,54 @@ describe('InputHandler', () => {
optionsService.options.scrollback = 1;
bufferService.reset();
});
it('SL (scrollLeft)', () => {
inputHandler.parseP('12345'.repeat(6));
inputHandler.parseP('\x1b[ @');
it('SL (scrollLeft)', async () => {
await inputHandler.parseP('12345'.repeat(6));
await inputHandler.parseP('\x1b[ @');
assert.deepEqual(getLines(bufferService, 6), ['12345', '2345', '2345', '2345', '2345', '2345']);
inputHandler.parseP('\x1b[0 @');
await inputHandler.parseP('\x1b[0 @');
assert.deepEqual(getLines(bufferService, 6), ['12345', '345', '345', '345', '345', '345']);
inputHandler.parseP('\x1b[2 @');
await inputHandler.parseP('\x1b[2 @');
assert.deepEqual(getLines(bufferService, 6), ['12345', '5', '5', '5', '5', '5']);
});
it('SR (scrollRight)', () => {
inputHandler.parseP('12345'.repeat(6));
inputHandler.parseP('\x1b[ A');
it('SR (scrollRight)', async () => {
await inputHandler.parseP('12345'.repeat(6));
await inputHandler.parseP('\x1b[ A');
assert.deepEqual(getLines(bufferService, 6), ['12345', ' 1234', ' 1234', ' 1234', ' 1234', ' 1234']);
inputHandler.parseP('\x1b[0 A');
await inputHandler.parseP('\x1b[0 A');
assert.deepEqual(getLines(bufferService, 6), ['12345', ' 123', ' 123', ' 123', ' 123', ' 123']);
inputHandler.parseP('\x1b[2 A');
await inputHandler.parseP('\x1b[2 A');
assert.deepEqual(getLines(bufferService, 6), ['12345', ' 1', ' 1', ' 1', ' 1', ' 1']);
});
it('insertColumns (DECIC)', () => {
inputHandler.parseP('12345'.repeat(6));
inputHandler.parseP('\x1b[3;3H');
inputHandler.parseP('\x1b[\'}');
it('insertColumns (DECIC)', async () => {
await inputHandler.parseP('12345'.repeat(6));
await inputHandler.parseP('\x1b[3;3H');
await inputHandler.parseP('\x1b[\'}');
assert.deepEqual(getLines(bufferService, 6), ['12345', '12 34', '12 34', '12 34', '12 34', '12 34']);
bufferService.reset();
inputHandler.parseP('12345'.repeat(6));
inputHandler.parseP('\x1b[3;3H');
inputHandler.parseP('\x1b[1\'}');
await inputHandler.parseP('12345'.repeat(6));
await inputHandler.parseP('\x1b[3;3H');
await inputHandler.parseP('\x1b[1\'}');
assert.deepEqual(getLines(bufferService, 6), ['12345', '12 34', '12 34', '12 34', '12 34', '12 34']);
bufferService.reset();
inputHandler.parseP('12345'.repeat(6));
inputHandler.parseP('\x1b[3;3H');
inputHandler.parseP('\x1b[2\'}');
await inputHandler.parseP('12345'.repeat(6));
await inputHandler.parseP('\x1b[3;3H');
await inputHandler.parseP('\x1b[2\'}');
assert.deepEqual(getLines(bufferService, 6), ['12345', '12 3', '12 3', '12 3', '12 3', '12 3']);
});
it('deleteColumns (DECDC)', () => {
inputHandler.parseP('12345'.repeat(6));
inputHandler.parseP('\x1b[3;3H');
inputHandler.parseP('\x1b[\'~');
it('deleteColumns (DECDC)', async () => {
await inputHandler.parseP('12345'.repeat(6));
await inputHandler.parseP('\x1b[3;3H');
await inputHandler.parseP('\x1b[\'~');
assert.deepEqual(getLines(bufferService, 6), ['12345', '1245', '1245', '1245', '1245', '1245']);
bufferService.reset();
inputHandler.parseP('12345'.repeat(6));
inputHandler.parseP('\x1b[3;3H');
inputHandler.parseP('\x1b[1\'~');
await inputHandler.parseP('12345'.repeat(6));
await inputHandler.parseP('\x1b[3;3H');
await inputHandler.parseP('\x1b[1\'~');
assert.deepEqual(getLines(bufferService, 6), ['12345', '1245', '1245', '1245', '1245', '1245']);
bufferService.reset();
inputHandler.parseP('12345'.repeat(6));
inputHandler.parseP('\x1b[3;3H');
inputHandler.parseP('\x1b[2\'~');
await inputHandler.parseP('12345'.repeat(6));
await inputHandler.parseP('\x1b[3;3H');
await inputHandler.parseP('\x1b[2\'~');
assert.deepEqual(getLines(bufferService, 6), ['12345', '125', '125', '125', '125', '125']);
});
});
@@ -137,41 +136,41 @@ describe('InputHandler', () => {
bufferService.reset();
});
describe('reverseWraparound set', () => {
it('should not reverse outside of scroll margins', () => {
it('should not reverse outside of scroll margins', async () => {
// prepare buffer content
inputHandler.parseP('#####abcdefghijklmnopqrstuvwxy');
await inputHandler.parseP('#####abcdefghijklmnopqrstuvwxy');
assert.deepEqual(getLines(bufferService, 6), ['#####', 'abcde', 'fghij', 'klmno', 'pqrst', 'uvwxy']);
assert.equal(bufferService.buffers.active.ydisp, 1);
assert.equal(bufferService.buffers.active.x, 5);
assert.equal(bufferService.buffers.active.y, 4);
inputHandler.parseP(ttyBS.repeat(100));
await inputHandler.parseP(ttyBS.repeat(100));
assert.deepEqual(getLines(bufferService, 6), ['#####', 'abcde', 'fghij', 'klmno', 'pqrst', ' y']);
inputHandler.parseP('\x1b[?45h');
inputHandler.parseP('uvwxy');
await inputHandler.parseP('\x1b[?45h');
await inputHandler.parseP('uvwxy');
// set top/bottom to 1/3 (0-based)
inputHandler.parseP('\x1b[2;4r');
await inputHandler.parseP('\x1b[2;4r');
// place cursor below scroll bottom
bufferService.buffers.active.x = 5;
bufferService.buffers.active.y = 4;
inputHandler.parseP(ttyBS.repeat(100));
await inputHandler.parseP(ttyBS.repeat(100));
assert.deepEqual(getLines(bufferService, 6), ['#####', 'abcde', 'fghij', 'klmno', 'pqrst', ' ']);
inputHandler.parseP('uvwxy');
await inputHandler.parseP('uvwxy');
// place cursor within scroll margins
bufferService.buffers.active.x = 5;
bufferService.buffers.active.y = 3;
inputHandler.parseP(ttyBS.repeat(100));
await inputHandler.parseP(ttyBS.repeat(100));
assert.deepEqual(getLines(bufferService, 6), ['#####', 'abcde', ' ', ' ', ' ', 'uvwxy']);
assert.equal(bufferService.buffers.active.x, 0);
assert.equal(bufferService.buffers.active.y, bufferService.buffers.active.scrollTop); // stops at 0, scrollTop
inputHandler.parseP('fghijklmnopqrst');
await inputHandler.parseP('fghijklmnopqrst');
// place cursor above scroll top
bufferService.buffers.active.x = 5;
bufferService.buffers.active.y = 0;
inputHandler.parseP(ttyBS.repeat(100));
await inputHandler.parseP(ttyBS.repeat(100));
assert.deepEqual(getLines(bufferService, 6), ['#####', ' ', 'fghij', 'klmno', 'pqrst', 'uvwxy']);
});
});
@@ -386,6 +385,56 @@ describe('InputHandler', () => {
assert.equal(bufferService.buffer.lines.get(2)!.translateToString(false), Array(bufferService.cols + 1).join(' '));
});
it('eraseInLine reflow', async () => {
const bufferService = new MockBufferService(80, 30);
const inputHandler = new TestInputHandler(
bufferService,
new MockCharsetService(),
new MockCoreService(),
new MockDirtyRowService(),
new MockLogService(),
new MockOptionsService(),
new MockCoreMouseService(),
new MockUnicodeService()
);
const resetToBaseState = async (): Promise<void> => {
// reset and add a wrapped line
bufferService.buffer.y = 0;
bufferService.buffer.x = 0;
await inputHandler.parseP(Array(bufferService.cols + 1).join('a')); // line 0
await inputHandler.parseP(Array(bufferService.cols + 10).join('a')); // line 1 and 2
for (let i = 3; i < bufferService.rows; ++i) await inputHandler.parseP(Array(bufferService.cols + 1).join('a'));
// confirm precondition that line 2 is wrapped
assert.equal(bufferService.buffer.lines.get(2)!.isWrapped, true);
};
// params[0] - erase from the cursor through the end of the row.
await resetToBaseState();
bufferService.buffer.y = 2;
bufferService.buffer.x = 40;
inputHandler.eraseInLine(Params.fromArray([0]));
assert.equal(bufferService.buffer.lines.get(2)!.isWrapped, true);
bufferService.buffer.y = 2;
bufferService.buffer.x = 0;
inputHandler.eraseInLine(Params.fromArray([0]));
assert.equal(bufferService.buffer.lines.get(2)!.isWrapped, false);
// params[1] - erase from the beginning of the line through the cursor
await resetToBaseState();
bufferService.buffer.y = 2;
bufferService.buffer.x = 40;
inputHandler.eraseInLine(Params.fromArray([1]));
assert.equal(bufferService.buffer.lines.get(2)!.isWrapped, true);
// params[2] - erase complete line
await resetToBaseState();
bufferService.buffer.y = 2;
bufferService.buffer.x = 40;
inputHandler.eraseInLine(Params.fromArray([2]));
assert.equal(bufferService.buffer.lines.get(2)!.isWrapped, false);
});
it('eraseInDisplay', async () => {
const bufferService = new MockBufferService(80, 7);
const inputHandler = new TestInputHandler(
@@ -1865,58 +1914,144 @@ describe('InputHandler', () => {
});
});
describe('OSC', () => {
it('4: should parse correct Ansi color change data', () => {
// this is testing a private method
const event = inputHandler.parseAnsiColorChange('19;rgb:a1/b2/c3');
assert.isNotNull(event);
assert.deepEqual(event!.colors[0], { colorIndex: 19, red: 0xa1, green: 0xb2, blue: 0xc3 });
it('4: query color events', async () => {
const stack: IColorEvent[] = [];
inputHandler.onColor(ev => stack.push(ev));
// single color query
await inputHandler.parseP('\x1b]4;0;?\x07');
assert.deepEqual(stack, [[{ type: ColorRequestType.REPORT, index: 0 }]]);
stack.length = 0;
await inputHandler.parseP('\x1b]4;123;?\x07');
assert.deepEqual(stack, [[{ type: ColorRequestType.REPORT, index: 123 }]]);
stack.length = 0;
// multiple queries
await inputHandler.parseP('\x1b]4;0;?;123;?\x07');
assert.deepEqual(stack, [[{ type: ColorRequestType.REPORT, index: 0 }, { type: ColorRequestType.REPORT, index: 123 }]]);
stack.length = 0;
});
it('4: set color events', async () => {
const stack: IColorEvent[] = [];
inputHandler.onColor(ev => stack.push(ev));
// single color query
await inputHandler.parseP('\x1b]4;0;rgb:01/02/03\x07');
assert.deepEqual(stack, [[{ type: ColorRequestType.SET, index: 0, color: [1, 2, 3] }]]);
stack.length = 0;
await inputHandler.parseP('\x1b]4;123;#aabbcc\x07');
assert.deepEqual(stack, [[{ type: ColorRequestType.SET, index: 123, color: [170, 187, 204] }]]);
stack.length = 0;
// multiple queries
await inputHandler.parseP('\x1b]4;0;rgb:aa/bb/cc;123;#001122\x07');
assert.deepEqual(stack, [[{ type: ColorRequestType.SET, index: 0, color: [170, 187, 204] }, { type: ColorRequestType.SET, index: 123, color: [0, 17, 34] }]]);
stack.length = 0;
});
it('4: should ignore invalid values', async () => {
const stack: IColorEvent[] = [];
inputHandler.onColor(ev => stack.push(ev));
await inputHandler.parseP('\x1b]4;0;rgb:aa/bb/cc;45;rgb:1/22/333;123;#001122\x07');
assert.deepEqual(stack, [[{ type: ColorRequestType.SET, index: 0, color: [170, 187, 204] }, { type: ColorRequestType.SET, index: 123, color: [0, 17, 34] }]]);
stack.length = 0;
});
it('104: restore events', async () => {
const stack: IColorEvent[] = [];
inputHandler.onColor(ev => stack.push(ev));
await inputHandler.parseP('\x1b]104;0\x07\x1b]104;43\x07');
assert.deepEqual(stack, [[{ type: ColorRequestType.RESTORE, index: 0 }], [{ type: ColorRequestType.RESTORE, index: 43 }]]);
stack.length = 0;
// multiple in one command
await inputHandler.parseP('\x1b]104;0;43\x07');
assert.deepEqual(stack, [[{ type: ColorRequestType.RESTORE, index: 0 }, { type: ColorRequestType.RESTORE, index: 43 }]]);
stack.length = 0;
// full ANSI table restore
await inputHandler.parseP('\x1b]104\x07');
assert.deepEqual(stack, [[{ type: ColorRequestType.RESTORE}]]);
});
it('4: should ignore incorrect Ansi color change data', () => {
// this is testing a private method
assert.isNull(inputHandler.parseAnsiColorChange('17;rgb:a/b/c'));
assert.isNull(inputHandler.parseAnsiColorChange('17;rgb:#aabbcc'));
assert.isNull(inputHandler.parseAnsiColorChange('17;rgba:aa/bb/cc'));
assert.isNull(inputHandler.parseAnsiColorChange('rgb:aa/bb/cc'));
it('10: FG set & query events', async () => {
const stack: IColorEvent[] = [];
inputHandler.onColor(ev => stack.push(ev));
// single foreground query --> color undefined
await inputHandler.parseP('\x1b]10;?\x07');
assert.deepEqual(stack, [[{ type: ColorRequestType.REPORT, index: ColorIndex.FOREGROUND }]]);
stack.length = 0;
// OSC with multiple values maps to OSC 10 & OSC 11 & OSC 12
await inputHandler.parseP('\x1b]10;?;?;?;?\x07');
assert.deepEqual(stack, [[{ type: ColorRequestType.REPORT, index: ColorIndex.FOREGROUND }], [{ type: ColorRequestType.REPORT, index: ColorIndex.BACKGROUND }], [{ type: ColorRequestType.REPORT, index: ColorIndex.CURSOR }]]);
stack.length = 0;
// set foreground color events
await inputHandler.parseP('\x1b]10;rgb:01/02/03\x07');
assert.deepEqual(stack, [[{ type: ColorRequestType.SET, index: ColorIndex.FOREGROUND, color: [1, 2, 3] }]]);
stack.length = 0;
await inputHandler.parseP('\x1b]10;#aabbcc\x07');
assert.deepEqual(stack, [[{ type: ColorRequestType.SET, index: ColorIndex.FOREGROUND, color: [170, 187, 204] }]]);
stack.length = 0;
// set FG, BG and cursor color at once
await inputHandler.parseP('\x1b]10;rgb:aa/bb/cc;#001122;rgb:12/34/56\x07');
assert.deepEqual(stack, [
[{ type: ColorRequestType.SET, index: ColorIndex.FOREGROUND, color: [170, 187, 204] }],
[{ type: ColorRequestType.SET, index: ColorIndex.BACKGROUND, color: [0, 17, 34] }],
[{ type: ColorRequestType.SET, index: ColorIndex.CURSOR, color: [18, 52, 86] }]
]);
});
it('4: should parse a list of Ansi color changes', () => {
// this is testing a private method
const event = inputHandler.parseAnsiColorChange('19;rgb:a1/b2/c3;17;rgb:00/11/22;255;rgb:01/ef/2d');
assert.isNotNull(event);
assert.equal(event!.colors.length, 3);
assert.deepEqual(event!.colors[0], { colorIndex: 19, red: 0xa1, green: 0xb2, blue: 0xc3 });
assert.deepEqual(event!.colors[1], { colorIndex: 17, red: 0x00, green: 0x11, blue: 0x22 });
assert.deepEqual(event!.colors[2], { colorIndex: 255, red: 0x01, green: 0xef, blue: 0x2d });
it('110: restore FG color', async () => {
const stack: IColorEvent[] = [];
inputHandler.onColor(ev => stack.push(ev));
await inputHandler.parseP('\x1b]110\x07');
assert.deepEqual(stack, [[{ type: ColorRequestType.RESTORE, index: ColorIndex.FOREGROUND }]]);
});
it('4: should ignore incorrect colors in a list of Ansi color changes', () => {
// this is testing a private method
const event = inputHandler.parseAnsiColorChange('19;rgb:a1/b2/c3;17;rgb:WR/ON/G;255;rgb:01/ef/2d');
assert.equal(event!.colors.length, 2);
assert.deepEqual(event!.colors[0], { colorIndex: 19, red: 0xa1, green: 0xb2, blue: 0xc3 });
assert.deepEqual(event!.colors[1], { colorIndex: 255, red: 0x01, green: 0xef, blue: 0x2d });
it('11: BG set & query events', async () => {
const stack: IColorEvent[] = [];
inputHandler.onColor(ev => stack.push(ev));
// single background query --> color undefined
await inputHandler.parseP('\x1b]11;?\x07');
assert.deepEqual(stack, [[{ type: ColorRequestType.REPORT, index: ColorIndex.BACKGROUND }]]);
stack.length = 0;
// OSC 11 with multiple values creates only BG and cursor event
await inputHandler.parseP('\x1b]11;?;?;?;?\x07');
assert.deepEqual(stack, [[{ type: ColorRequestType.REPORT, index: ColorIndex.BACKGROUND }], [{ type: ColorRequestType.REPORT, index: ColorIndex.CURSOR }]]);
stack.length = 0;
// set background color events
await inputHandler.parseP('\x1b]11;rgb:01/02/03\x07');
assert.deepEqual(stack, [[{ type: ColorRequestType.SET, index: ColorIndex.BACKGROUND, color: [1, 2, 3] }]]);
stack.length = 0;
await inputHandler.parseP('\x1b]11;#aabbcc\x07');
assert.deepEqual(stack, [[{ type: ColorRequestType.SET, index: ColorIndex.BACKGROUND, color: [170, 187, 204] }]]);
stack.length = 0;
// set BG and cursor color at once
await inputHandler.parseP('\x1b]11;#001122;rgb:12/34/56\x07');
assert.deepEqual(stack, [
[{ type: ColorRequestType.SET, index: ColorIndex.BACKGROUND, color: [0, 17, 34] }],
[{ type: ColorRequestType.SET, index: ColorIndex.CURSOR, color: [18, 52, 86] }]
]);
});
it('4: should be case insensitive when parsing Ansi color changes', () => {
// this is testing a private method
const event = inputHandler.parseAnsiColorChange('19;rGb:A1/b2/C3');
assert.equal(event!.colors.length, 1);
assert.deepEqual(event!.colors[0], { colorIndex: 19, red: 0xa1, green: 0xb2, blue: 0xc3 });
it('111: restore BG color', async () => {
const stack: IColorEvent[] = [];
inputHandler.onColor(ev => stack.push(ev));
await inputHandler.parseP('\x1b]111\x07');
assert.deepEqual(stack, [[{ type: ColorRequestType.RESTORE, index: ColorIndex.BACKGROUND }]]);
});
it('4: should fire event on Ansi color change', async () => {
return new Promise(async r => {
inputHandler.onAnsiColorChange(e => {
assert.isNotNull(e);
assert.isNotNull(e!.colors);
assert.deepEqual(e!.colors[0], { colorIndex: 17, red: 0x1a, green: 0x2b, blue: 0x3c });
assert.deepEqual(e!.colors[1], { colorIndex: 12, red: 0x11, green: 0x22, blue: 0x33 });
r();
});
await inputHandler.parseP('\x1b]4;17;rgb:1a/2b/3c;12;rgb:11/22/33\x1b\\');
});
it('12: cursor color set & query events', async () => {
const stack: IColorEvent[] = [];
inputHandler.onColor(ev => stack.push(ev));
// single cursor query --> color undefined
await inputHandler.parseP('\x1b]12;?\x07');
assert.deepEqual(stack, [[{ type: ColorRequestType.REPORT, index: ColorIndex.CURSOR }]]);
stack.length = 0;
// OSC 12 with multiple values creates only cursor event
await inputHandler.parseP('\x1b]12;?;?;?;?\x07');
assert.deepEqual(stack, [[{ type: ColorRequestType.REPORT, index: ColorIndex.CURSOR }]]);
stack.length = 0;
// set cursor color events
await inputHandler.parseP('\x1b]12;rgb:01/02/03\x07');
assert.deepEqual(stack, [[{ type: ColorRequestType.SET, index: ColorIndex.CURSOR, color: [1, 2, 3] }]]);
stack.length = 0;
await inputHandler.parseP('\x1b]12;#aabbcc\x07');
assert.deepEqual(stack, [[{ type: ColorRequestType.SET, index: ColorIndex.CURSOR, color: [170, 187, 204] }]]);
});
it('112: restore cursor color', async () => {
const stack: IColorEvent[] = [];
inputHandler.onColor(ev => stack.push(ev));
await inputHandler.parseP('\x1b]112\x07');
assert.deepEqual(stack, [[{ type: ColorRequestType.RESTORE, index: ColorIndex.CURSOR }]]);
});
});
+198 -59
View File
@@ -4,7 +4,7 @@
* @license MIT
*/
import { IInputHandler, IAttributeData, IDisposable, IWindowOptions, IAnsiColorChangeEvent, IParseStack } from 'common/Types';
import { IInputHandler, IAttributeData, IDisposable, IWindowOptions, IColorEvent, IParseStack, ColorIndex, ColorRequestType } from 'common/Types';
import { C0, C1 } from 'common/data/EscapeSequences';
import { CHARSETS, DEFAULT_CHARSET } from 'common/data/Charsets';
import { EscapeSequenceParser } from 'common/parser/EscapeSequenceParser';
@@ -21,11 +21,12 @@ import { ICoreService, IBufferService, IOptionsService, ILogService, IDirtyRowSe
import { OscHandler } from 'common/parser/OscParser';
import { DcsHandler } from 'common/parser/DcsParser';
import { IBuffer } from 'common/buffer/Types';
import { parseColor } from 'common/input/XParseColor';
/**
* Map collect to glevel. Used in `selectCharset`.
*/
const GLEVEL: {[key: string]: number} = { '(': 0, ')': 1, '*': 2, '+': 3, '-': 1, '.': 2 };
const GLEVEL: { [key: string]: number } = { '(': 0, ')': 1, '*': 2, '+': 3, '-': 1, '.': 2 };
/**
* VT commands done by the parser - FIXME: move this to the parser?
@@ -167,7 +168,7 @@ class DECRQSS implements IDcsHandler {
break;
case 'r': // DECSTBM
const pt = '' + (this._bufferService.buffer.scrollTop + 1) +
';' + (this._bufferService.buffer.scrollBottom + 1) + 'r';
';' + (this._bufferService.buffer.scrollBottom + 1) + 'r';
this._coreService.triggerDataEvent(`${C0.ESC}P1$r${pt}${C0.ESC}\\`);
break;
case 'm': // SGR
@@ -175,9 +176,9 @@ class DECRQSS implements IDcsHandler {
this._coreService.triggerDataEvent(`${C0.ESC}P1$r0m${C0.ESC}\\`);
break;
case ' q': // DECSCUSR
const STYLES: {[key: string]: number} = { 'block': 2, 'underline': 4, 'bar': 6 };
let style = STYLES[this._optionsService.options.cursorStyle];
style -= this._optionsService.options.cursorBlink ? 1 : 0;
const STYLES: { [key: string]: number } = { 'block': 2, 'underline': 4, 'bar': 6 };
let style = STYLES[this._optionsService.rawOptions.cursorStyle];
style -= this._optionsService.rawOptions.cursorBlink ? 1 : 0;
this._coreService.triggerDataEvent(`${C0.ESC}P1$r${style} q${C0.ESC}\\`);
break;
default:
@@ -243,6 +244,8 @@ export class InputHandler extends Disposable implements IInputHandler {
public get onRequestRefreshRows(): IEvent<number, number> { return this._onRequestRefreshRows.event; }
private _onRequestReset = new EventEmitter<void>();
public get onRequestReset(): IEvent<void> { return this._onRequestReset.event; }
private _onRequestSendFocus = new EventEmitter<void>();
public get onRequestSendFocus(): IEvent<void> { return this._onRequestSendFocus.event; }
private _onRequestSyncScrollBar = new EventEmitter<void>();
public get onRequestSyncScrollBar(): IEvent<void> { return this._onRequestSyncScrollBar.event; }
private _onRequestWindowsOptionsReport = new EventEmitter<WindowsOptionsReportType>();
@@ -260,8 +263,8 @@ export class InputHandler extends Disposable implements IInputHandler {
public get onScroll(): IEvent<number> { return this._onScroll.event; }
private _onTitleChange = new EventEmitter<string>();
public get onTitleChange(): IEvent<string> { return this._onTitleChange.event; }
private _onAnsiColorChange = new EventEmitter<IAnsiColorChangeEvent>();
public get onAnsiColorChange(): IEvent<IAnsiColorChangeEvent> { return this._onAnsiColorChange.event; }
private _onColor = new EventEmitter<IColorEvent>();
public get onColor(): IEvent<IColorEvent> { return this._onColor.event; }
private _parseStack: IParseStack = {
paused: false,
@@ -396,13 +399,16 @@ export class InputHandler extends Disposable implements IInputHandler {
this._parser.registerOscHandler(2, new OscHandler(data => this.setTitle(data)));
// 3 - set property X in the form "prop=value"
// 4 - Change Color Number
this._parser.registerOscHandler(4, new OscHandler(data => this.setAnsiColor(data)));
this._parser.registerOscHandler(4, new OscHandler(data => this.setOrReportIndexedColor(data)));
// 5 - Change Special Color Number
// 6 - Enable/disable Special Color Number c
// 7 - current directory? (not in xterm spec, see https://gitlab.com/gnachman/iterm2/issues/3939)
// 10 - Change VT100 text foreground color to Pt.
this._parser.registerOscHandler(10, new OscHandler(data => this.setOrReportFgColor(data)));
// 11 - Change VT100 text background color to Pt.
this._parser.registerOscHandler(11, new OscHandler(data => this.setOrReportBgColor(data)));
// 12 - Change text cursor color to Pt.
this._parser.registerOscHandler(12, new OscHandler(data => this.setOrReportCursorColor(data)));
// 13 - Change mouse foreground color to Pt.
// 14 - Change mouse background color to Pt.
// 15 - Change Tektronix foreground color to Pt.
@@ -415,11 +421,15 @@ export class InputHandler extends Disposable implements IInputHandler {
// 51 - reserved for Emacs shell.
// 52 - Manipulate Selection Data.
// 104 ; c - Reset Color Number c.
this._parser.registerOscHandler(104, new OscHandler(data => this.restoreIndexedColor(data)));
// 105 ; c - Reset Special Color Number c.
// 106 ; c; f - Enable/disable Special Color Number c.
// 110 - Reset VT100 text foreground color.
this._parser.registerOscHandler(110, new OscHandler(data => this.restoreFgColor(data)));
// 111 - Reset VT100 text background color.
this._parser.registerOscHandler(111, new OscHandler(data => this.restoreBgColor(data)));
// 112 - Reset text cursor color.
this._parser.registerOscHandler(112, new OscHandler(data => this.restoreCursorColor(data)));
// 113 - Reset mouse foreground color.
// 114 - Reset mouse background color.
// 115 - Reset Tektronix foreground color.
@@ -534,7 +544,13 @@ export class InputHandler extends Disposable implements IInputHandler {
}
}
this._logService.debug('parsing data', data);
// Log debug data, the log level gate is to prevent extra work in this hot path
if (this._logService.logLevel <= LogLevelEnum.DEBUG) {
this._logService.debug(`parsing data${typeof data === 'string' ? ` "${data}"` : ` "${Array.prototype.map.call(data, e => String.fromCharCode(e)).join('')}"`}`, typeof data === 'string'
? data.split('').map(e => e.charCodeAt(0))
: data
);
}
// resize input buffer if needed
if (this._parseBuffer.length < data.length) {
@@ -587,7 +603,7 @@ export class InputHandler extends Disposable implements IInputHandler {
let code: number;
let chWidth: number;
const charset = this._charsetService.charset;
const screenReaderMode = this._optionsService.options.screenReaderMode;
const screenReaderMode = this._optionsService.rawOptions.screenReaderMode;
const cols = this._bufferService.cols;
const wraparoundMode = this._coreService.decPrivateModes.wraparound;
const insertMode = this._coreService.modes.insertMode;
@@ -729,7 +745,7 @@ export class InputHandler extends Disposable implements IInputHandler {
if (id.final === 't' && !id.prefix && !id.intermediates) {
// security: always check whether window option is allowed
return this._parser.registerCsiHandler(id, params => {
if (!paramToWindowOption(params.params[0], this._optionsService.options.windowOptions)) {
if (!paramToWindowOption(params.params[0], this._optionsService.rawOptions.windowOptions)) {
return true;
}
return callback(params);
@@ -784,7 +800,7 @@ export class InputHandler extends Disposable implements IInputHandler {
*/
public lineFeed(): boolean {
this._dirtyRowService.markDirty(this._activeBuffer.y);
if (this._optionsService.options.convertEol) {
if (this._optionsService.rawOptions.convertEol) {
this._activeBuffer.x = 0;
}
this._activeBuffer.y++;
@@ -853,10 +869,9 @@ export class InputHandler extends Disposable implements IInputHandler {
* - any cursor movement sequence keeps working as expected
*/
if (this._activeBuffer.x === 0
&& this._activeBuffer.y > this._activeBuffer.scrollTop
&& this._activeBuffer.y <= this._activeBuffer.scrollBottom
&& this._activeBuffer.lines.get(this._activeBuffer.ybase + this._activeBuffer.y)?.isWrapped)
{
&& this._activeBuffer.y > this._activeBuffer.scrollTop
&& this._activeBuffer.y <= this._activeBuffer.scrollBottom
&& this._activeBuffer.lines.get(this._activeBuffer.ybase + this._activeBuffer.y)?.isWrapped) {
this._activeBuffer.lines.get(this._activeBuffer.ybase + this._activeBuffer.y)!.isWrapped = false;
this._activeBuffer.y--;
this._activeBuffer.x = this._bufferService.cols - 1;
@@ -889,7 +904,7 @@ export class InputHandler extends Disposable implements IInputHandler {
}
const originalX = this._activeBuffer.x;
this._activeBuffer.x = this._activeBuffer.nextStop();
if (this._optionsService.options.screenReaderMode) {
if (this._optionsService.rawOptions.screenReaderMode) {
this._onA11yTab.fire(this._activeBuffer.x - originalX);
}
return true;
@@ -1193,6 +1208,7 @@ export class InputHandler extends Disposable implements IInputHandler {
* @param y row index
* @param start first cell index to be erased
* @param end end - 1 is last erased cell
* @param cleanWrap clear the isWrapped flag
*/
private _eraseInBufferLine(y: number, start: number, end: number, clearWrap: boolean = false): void {
const line = this._activeBuffer.lines.get(this._activeBuffer.ybase + y)!;
@@ -1215,6 +1231,7 @@ export class InputHandler extends Disposable implements IInputHandler {
private _resetBufferLine(y: number): void {
const line = this._activeBuffer.lines.get(this._activeBuffer.ybase + y)!;
line.fill(this._activeBuffer.getNullCell(this._eraseAttrData()));
this._bufferService.buffer.clearMarkers(this._activeBuffer.ybase + y);
line.isWrapped = false;
}
@@ -1318,13 +1335,13 @@ export class InputHandler extends Disposable implements IInputHandler {
this._restrictCursor(this._bufferService.cols);
switch (params.params[0]) {
case 0:
this._eraseInBufferLine(this._activeBuffer.y, this._activeBuffer.x, this._bufferService.cols);
this._eraseInBufferLine(this._activeBuffer.y, this._activeBuffer.x, this._bufferService.cols, this._activeBuffer.x === 0);
break;
case 1:
this._eraseInBufferLine(this._activeBuffer.y, 0, this._activeBuffer.x + 1);
this._eraseInBufferLine(this._activeBuffer.y, 0, this._activeBuffer.x + 1, false);
break;
case 2:
this._eraseInBufferLine(this._activeBuffer.y, 0, this._bufferService.cols);
this._eraseInBufferLine(this._activeBuffer.y, 0, this._bufferService.cols, true);
break;
}
this._dirtyRowService.markDirty(this._activeBuffer.y);
@@ -1747,7 +1764,7 @@ export class InputHandler extends Disposable implements IInputHandler {
* @param term The terminal name to evaluate
*/
private _is(term: string): boolean {
return (this._optionsService.options.termName + '').indexOf(term) === 0;
return (this._optionsService.rawOptions.termName + '').indexOf(term) === 0;
}
/**
@@ -1913,7 +1930,7 @@ export class InputHandler extends Disposable implements IInputHandler {
* This is only active if 'SetWinLines' (24) is enabled
* through `options.windowsOptions`.
*/
if (this._optionsService.options.windowOptions.setWinLines) {
if (this._optionsService.rawOptions.windowOptions.setWinLines) {
this._bufferService.resize(132, this._bufferService.rows);
this._onRequestReset.fire();
}
@@ -1956,6 +1973,7 @@ export class InputHandler extends Disposable implements IInputHandler {
// focusin: ^[[I
// focusout: ^[[O
this._coreService.decPrivateModes.sendFocus = true;
this._onRequestSendFocus.fire();
break;
case 1005: // utf8 ext mode mouse - removed in #2507
this._logService.debug('DECSET 1005 not supported (see #2507)');
@@ -1974,7 +1992,7 @@ export class InputHandler extends Disposable implements IInputHandler {
break;
case 1049: // alt screen buffer cursor
this.saveCursor();
// FALL-THROUGH
// FALL-THROUGH
case 47: // alt screen buffer
case 1047: // alt screen buffer
this._bufferService.buffers.activateAltBuffer(this._eraseAttrData());
@@ -2146,7 +2164,7 @@ export class InputHandler extends Disposable implements IInputHandler {
* This is only active if 'SetWinLines' (24) is enabled
* through `options.windowsOptions`.
*/
if (this._optionsService.options.windowOptions.setWinLines) {
if (this._optionsService.rawOptions.windowOptions.setWinLines) {
this._bufferService.resize(80, this._bufferService.rows);
this._onRequestReset.fire();
}
@@ -2194,7 +2212,7 @@ export class InputHandler extends Disposable implements IInputHandler {
this.restoreCursor();
break;
case 1049: // alt screen buffer cursor
// FALL-THROUGH
// FALL-THROUGH
case 47: // normal screen buffer
case 1047: // normal screen buffer - clearing it first
// Ensure the selection manager has the correct buffer
@@ -2261,7 +2279,7 @@ export class InputHandler extends Disposable implements IInputHandler {
}
// exit early if can decide color mode with semicolons
if ((accu[1] === 5 && advance + cSpace >= 2)
|| (accu[1] === 2 && advance + cSpace >= 5)) {
|| (accu[1] === 2 && advance + cSpace >= 5)) {
break;
}
// offset colorSpace slot for semicolon mode
@@ -2680,7 +2698,7 @@ export class InputHandler extends Disposable implements IInputHandler {
const top = params.params[0] || 1;
let bottom: number;
if (params.length < 2 || (bottom = params.params[1]) > this._bufferService.rows || bottom === 0) {
if (params.length < 2 || (bottom = params.params[1]) > this._bufferService.rows || bottom === 0) {
bottom = this._bufferService.rows;
}
@@ -2723,7 +2741,7 @@ export class InputHandler extends Disposable implements IInputHandler {
* Ps >= 24 not implemented
*/
public windowOptions(params: IParams): boolean {
if (!paramToWindowOption(params.params[0], this._optionsService.options.windowOptions)) {
if (!paramToWindowOption(params.params[0], this._optionsService.rawOptions.windowOptions)) {
return true;
}
const second = (params.length > 1) ? params.params[1] : 0;
@@ -2837,46 +2855,167 @@ export class InputHandler extends Disposable implements IInputHandler {
return true;
}
protected _parseAnsiColorChange(data: string): IAnsiColorChangeEvent | null {
const result: IAnsiColorChangeEvent = { colors: [] };
// example data: 5;rgb:aa/bb/cc
const regex = /(\d+);rgb:([\da-f]{2})\/([\da-f]{2})\/([\da-f]{2})/gi;
let match;
while ((match = regex.exec(data)) !== null) {
result.colors.push({
colorIndex: parseInt(match[1]),
red: parseInt(match[2], 16),
green: parseInt(match[3], 16),
blue: parseInt(match[4], 16)
});
}
if (result.colors.length === 0) {
return null;
}
return result;
}
/**
* OSC 4; <num> ; <text> ST (set ANSI color <num> to <text>)
*
* @vt: #Y OSC 4 "Set ANSI color" "OSC 4 ; c ; spec BEL" "Change color number `c` to the color specified by `spec`."
* `c` is the color index between 0 and 255. `spec` color format is 'rgb:hh/hh/hh' where `h` are hexadecimal digits.
* There may be multipe c ; spec elements present in the same instruction, e.g. 1;rgb:10/20/30;2;rgb:a0/b0/c0.
* `c` is the color index between 0 and 255. The color format of `spec` is derived from `XParseColor` (see OSC 10 for supported formats).
* There may be multipe `c ; spec` pairs present in the same instruction.
* If `spec` contains `?` the terminal returns a sequence with the currently set color.
*/
public setAnsiColor(data: string): boolean {
const event = this._parseAnsiColorChange(data);
if (event) {
this._onAnsiColorChange.fire(event);
public setOrReportIndexedColor(data: string): boolean {
const event: IColorEvent = [];
const slots = data.split(';');
while (slots.length > 1) {
const idx = slots.shift() as string;
const spec = slots.shift() as string;
if (/^\d+$/.exec(idx)) {
const index = parseInt(idx);
if (0 <= index && index < 256) {
if (spec === '?') {
event.push({ type: ColorRequestType.REPORT, index });
} else {
const color = parseColor(spec);
if (color) {
event.push({ type: ColorRequestType.SET, index, color });
}
}
}
}
}
else {
this._logService.warn(`Expected format <num>;rgb:<rr>/<gg>/<bb> but got data: ${data}`);
if (event.length) {
this._onColor.fire(event);
}
return true;
}
// special colors - OSC 10 | 11 | 12
private _specialColors = [ColorIndex.FOREGROUND, ColorIndex.BACKGROUND, ColorIndex.CURSOR];
/**
* Apply colors requests for special colors in OSC 10 | 11 | 12.
* Since these commands are stacking from multiple parameters,
* we handle them in a loop with an entry offset to `_specialColors`.
*/
private _setOrReportSpecialColor(data: string, offset: number): boolean {
const slots = data.split(';');
for (let i = 0; i < slots.length; ++i, ++offset) {
if (offset >= this._specialColors.length) break;
if (slots[i] === '?') {
this._onColor.fire([{ type: ColorRequestType.REPORT, index: this._specialColors[offset] }]);
} else {
const color = parseColor(slots[i]);
if (color) {
this._onColor.fire([{ type: ColorRequestType.SET, index: this._specialColors[offset], color }]);
}
}
}
return true;
}
/**
* OSC 10 ; <xcolor name>|<?> ST - set or query default foreground color
*
* @vt: #Y OSC 10 "Set or query default foreground color" "OSC 10 ; Pt BEL" "Set or query default foreground color."
* To set the color, the following color specification formats are supported:
* - `rgb:<red>/<green>/<blue>` for `<red>, <green>, <blue>` in `h | hh | hhh | hhhh`, where
* `h` is a single hexadecimal digit (case insignificant). The different widths scale
* from 4 bit (`h`) to 16 bit (`hhhh`) and get converted to 8 bit (`hh`).
* - `#RGB` - 4 bits per channel, expanded to `#R0G0B0`
* - `#RRGGBB` - 8 bits per channel
* - `#RRRGGGBBB` - 12 bits per channel, truncated to `#RRGGBB`
* - `#RRRRGGGGBBBB` - 16 bits per channel, truncated to `#RRGGBB`
*
* **Note:** X11 named colors are currently unsupported.
*
* If `Pt` contains `?` instead of a color specification, the terminal
* returns a sequence with the current default foreground color
* (use that sequence to restore the color after changes).
*
* **Note:** Other than xterm, xterm.js does not support OSC 12 - 19.
* Therefore stacking multiple `Pt` separated by `;` only works for the first two entries.
*/
public setOrReportFgColor(data: string): boolean {
return this._setOrReportSpecialColor(data, 0);
}
/**
* OSC 11 ; <xcolor name>|<?> ST - set or query default background color
*
* @vt: #Y OSC 11 "Set or query default background color" "OSC 11 ; Pt BEL" "Same as OSC 10, but for default background."
*/
public setOrReportBgColor(data: string): boolean {
return this._setOrReportSpecialColor(data, 1);
}
/**
* OSC 12 ; <xcolor name>|<?> ST - set or query default cursor color
*
* @vt: #Y OSC 12 "Set or query default cursor color" "OSC 12 ; Pt BEL" "Same as OSC 10, but for default cursor color."
*/
public setOrReportCursorColor(data: string): boolean {
return this._setOrReportSpecialColor(data, 2);
}
/**
* OSC 104 ; <num> ST - restore ANSI color <num>
*
* @vt: #Y OSC 104 "Reset ANSI color" "OSC 104 ; c BEL" "Reset color number `c` to themed color."
* `c` is the color index between 0 and 255. This function restores the default color for `c` as
* specified by the loaded theme. Any number of `c` parameters may be given.
* If no parameters are given, the entire indexed color table will be reset.
*/
public restoreIndexedColor(data: string): boolean {
if (!data) {
this._onColor.fire([{ type: ColorRequestType.RESTORE }]);
return true;
}
const event: IColorEvent = [];
const slots = data.split(';');
for (let i = 0; i < slots.length; ++i) {
if (/^\d+$/.exec(slots[i])) {
const index = parseInt(slots[i]);
if (0 <= index && index < 256) {
event.push({ type: ColorRequestType.RESTORE, index });
}
}
}
if (event.length) {
this._onColor.fire(event);
}
return true;
}
/**
* OSC 110 ST - restore default foreground color
*
* @vt: #Y OSC 110 "Restore default foreground color" "OSC 110 BEL" "Restore default foreground to themed color."
*/
public restoreFgColor(data: string): boolean {
this._onColor.fire([{ type: ColorRequestType.RESTORE, index: ColorIndex.FOREGROUND }]);
return true;
}
/**
* OSC 111 ST - restore default background color
*
* @vt: #Y OSC 111 "Restore default background color" "OSC 111 BEL" "Restore default background to themed color."
*/
public restoreBgColor(data: string): boolean {
this._onColor.fire([{ type: ColorRequestType.RESTORE, index: ColorIndex.BACKGROUND }]);
return true;
}
/**
* OSC 112 ST - restore default cursor color
*
* @vt: #Y OSC 112 "Restore default cursor color" "OSC 112 BEL" "Restore default cursor to themed color."
*/
public restoreCursorColor(data: string): boolean {
this._onColor.fire([{ type: ColorRequestType.RESTORE, index: ColorIndex.CURSOR }]);
return true;
}
/**
* ESC E
* C1.NEL
+1
View File
@@ -18,6 +18,7 @@ const userAgent = (isNode) ? 'node' : navigator.userAgent;
const platform = (isNode) ? 'node' : navigator.platform;
export const isFirefox = userAgent.includes('Firefox');
export const isLegacyEdge = userAgent.includes('Edge');
export const isSafari = /^((?!chrome|android).)*safari/i.test(userAgent);
// Find the users platform. We use this to interpret the meta key
+10 -4
View File
@@ -3,7 +3,7 @@
* @license MIT
*/
import { IBufferService, ICoreService, ILogService, IOptionsService, ITerminalOptions, IPartialTerminalOptions, IDirtyRowService, ICoreMouseService, ICharsetService, IUnicodeService, IUnicodeVersionProvider, LogLevelEnum } from 'common/services/Services';
import { IBufferService, ICoreService, ILogService, IOptionsService, ITerminalOptions, IDirtyRowService, ICoreMouseService, ICharsetService, IUnicodeService, IUnicodeVersionProvider, LogLevelEnum } from 'common/services/Services';
import { IEvent, EventEmitter } from 'common/EventEmitter';
import { clone } from 'common/Clone';
import { DEFAULT_OPTIONS } from 'common/services/OptionsService';
@@ -120,15 +120,21 @@ export class MockLogService implements ILogService {
export class MockOptionsService implements IOptionsService {
public serviceBrand: any;
public options: ITerminalOptions = clone(DEFAULT_OPTIONS);
public readonly rawOptions: ITerminalOptions = clone(DEFAULT_OPTIONS);
public options: ITerminalOptions = this.rawOptions;
public onOptionChange: IEvent<string> = new EventEmitter<string>().event;
constructor(testOptions?: IPartialTerminalOptions) {
constructor(testOptions?: Partial<ITerminalOptions>) {
if (testOptions) {
for (const key of Object.keys(testOptions)) {
this.options[key] = (testOptions as any)[key];
this.rawOptions[key] = testOptions[key];
}
}
}
public setOptions(options: ITerminalOptions): void {
for (const key of Object.keys(options)) {
this.options[key] = options[key];
}
}
public setOption<T>(key: string, value: T): void {
throw new Error('Method not implemented.');
}
+96 -72
View File
@@ -16,6 +16,7 @@ export interface ICoreTerminal {
optionsService: IOptionsService;
unicodeService: IUnicodeService;
buffers: IBufferSet;
options: ITerminalOptions;
registerCsiHandler(id: IFunctionIdentifier, callback: (params: IParams) => boolean | Promise<boolean>): IDisposable;
registerDcsHandler(id: IFunctionIdentifier, callback: (data: string, param: IParams) => boolean | Promise<boolean>): IDisposable;
registerEscHandler(id: IFunctionIdentifier, callback: () => boolean | Promise<boolean>): IDisposable;
@@ -45,6 +46,7 @@ export interface IKeyboardEvent {
ctrlKey: boolean;
shiftKey: boolean;
metaKey: boolean;
/** @deprecated See KeyboardEvent.keyCode */
keyCode: number;
key: string;
type: string;
@@ -347,19 +349,32 @@ export interface IWindowOptions {
setWinLines?: boolean;
}
export interface IAnsiColorChangeEventColor {
colorIndex: number;
red: number;
green: number;
blue: number;
// color events from common, used for OSC 4/10/11/12 and 104/110/111/112
export const enum ColorRequestType {
REPORT = 0,
SET = 1,
RESTORE = 2
}
export const enum ColorIndex {
FOREGROUND = 256,
BACKGROUND = 257,
CURSOR = 258
}
export interface IColorReportRequest {
type: ColorRequestType.REPORT;
index: ColorIndex;
}
export interface IColorSetRequest {
type: ColorRequestType.SET;
index: ColorIndex;
color: IColorRGB;
}
export interface IColorRestoreRequest {
type: ColorRequestType.RESTORE;
index?: ColorIndex;
}
export type IColorEvent = (IColorReportRequest | IColorSetRequest | IColorRestoreRequest)[];
/**
* Event fired for OSC 4 command - to change ANSI color based on its index.
*/
export interface IAnsiColorChangeEvent {
colors: IAnsiColorChangeEventColor[];
}
/**
* Calls the parser and handles actions generated by the parser.
@@ -374,80 +389,89 @@ export interface IInputHandler {
registerEscHandler(id: IFunctionIdentifier, callback: () => boolean | Promise<boolean>): IDisposable;
registerOscHandler(ident: number, callback: (data: string) => boolean | Promise<boolean>): IDisposable;
/** C0 BEL */ bell(): void;
/** C0 LF */ lineFeed(): void;
/** C0 CR */ carriageReturn(): void;
/** C0 BS */ backspace(): void;
/** C0 HT */ tab(): void;
/** C0 SO */ shiftOut(): void;
/** C0 SI */ shiftIn(): void;
/** C0 BEL */ bell(): boolean;
/** C0 LF */ lineFeed(): boolean;
/** C0 CR */ carriageReturn(): boolean;
/** C0 BS */ backspace(): boolean;
/** C0 HT */ tab(): boolean;
/** C0 SO */ shiftOut(): boolean;
/** C0 SI */ shiftIn(): boolean;
/** CSI @ */ insertChars(params: IParams): boolean;
/** CSI SP @ */ scrollLeft(params: IParams): boolean;
/** CSI A */ cursorUp(params: IParams): boolean;
/** CSI SP A */ scrollRight(params: IParams): boolean;
/** CSI B */ cursorDown(params: IParams): boolean;
/** CSI C */ cursorForward(params: IParams): boolean;
/** CSI D */ cursorBackward(params: IParams): boolean;
/** CSI E */ cursorNextLine(params: IParams): boolean;
/** CSI F */ cursorPrecedingLine(params: IParams): boolean;
/** CSI G */ cursorCharAbsolute(params: IParams): boolean;
/** CSI H */ cursorPosition(params: IParams): boolean;
/** CSI I */ cursorForwardTab(params: IParams): boolean;
/** CSI J */ eraseInDisplay(params: IParams): boolean;
/** CSI K */ eraseInLine(params: IParams): boolean;
/** CSI L */ insertLines(params: IParams): boolean;
/** CSI M */ deleteLines(params: IParams): boolean;
/** CSI P */ deleteChars(params: IParams): boolean;
/** CSI S */ scrollUp(params: IParams): boolean;
/** CSI T */ scrollDown(params: IParams, collect?: string): boolean;
/** CSI X */ eraseChars(params: IParams): boolean;
/** CSI Z */ cursorBackwardTab(params: IParams): boolean;
/** CSI ` */ charPosAbsolute(params: IParams): boolean;
/** CSI a */ hPositionRelative(params: IParams): boolean;
/** CSI b */ repeatPrecedingCharacter(params: IParams): boolean;
/** CSI c */ sendDeviceAttributesPrimary(params: IParams): boolean;
/** CSI > c */ sendDeviceAttributesSecondary(params: IParams): boolean;
/** CSI d */ linePosAbsolute(params: IParams): boolean;
/** CSI e */ vPositionRelative(params: IParams): boolean;
/** CSI f */ hVPosition(params: IParams): boolean;
/** CSI g */ tabClear(params: IParams): boolean;
/** CSI h */ setMode(params: IParams, collect?: string): boolean;
/** CSI l */ resetMode(params: IParams, collect?: string): boolean;
/** CSI m */ charAttributes(params: IParams): boolean;
/** CSI n */ deviceStatus(params: IParams, collect?: string): boolean;
/** CSI p */ softReset(params: IParams, collect?: string): boolean;
/** CSI q */ setCursorStyle(params: IParams, collect?: string): boolean;
/** CSI r */ setScrollRegion(params: IParams, collect?: string): boolean;
/** CSI s */ saveCursor(params: IParams): boolean;
/** CSI u */ restoreCursor(params: IParams): boolean;
/** CSI ' } */ insertColumns(params: IParams): boolean;
/** CSI ' ~ */ deleteColumns(params: IParams): boolean;
/** CSI @ */ insertChars(params: IParams): void;
/** CSI SP @ */ scrollLeft(params: IParams): void;
/** CSI A */ cursorUp(params: IParams): void;
/** CSI SP A */ scrollRight(params: IParams): void;
/** CSI B */ cursorDown(params: IParams): void;
/** CSI C */ cursorForward(params: IParams): void;
/** CSI D */ cursorBackward(params: IParams): void;
/** CSI E */ cursorNextLine(params: IParams): void;
/** CSI F */ cursorPrecedingLine(params: IParams): void;
/** CSI G */ cursorCharAbsolute(params: IParams): void;
/** CSI H */ cursorPosition(params: IParams): void;
/** CSI I */ cursorForwardTab(params: IParams): void;
/** CSI J */ eraseInDisplay(params: IParams): void;
/** CSI K */ eraseInLine(params: IParams): void;
/** CSI L */ insertLines(params: IParams): void;
/** CSI M */ deleteLines(params: IParams): void;
/** CSI P */ deleteChars(params: IParams): void;
/** CSI S */ scrollUp(params: IParams): void;
/** CSI T */ scrollDown(params: IParams, collect?: string): void;
/** CSI X */ eraseChars(params: IParams): void;
/** CSI Z */ cursorBackwardTab(params: IParams): void;
/** CSI ` */ charPosAbsolute(params: IParams): void;
/** CSI a */ hPositionRelative(params: IParams): void;
/** CSI b */ repeatPrecedingCharacter(params: IParams): void;
/** CSI c */ sendDeviceAttributesPrimary(params: IParams): void;
/** CSI > c */ sendDeviceAttributesSecondary(params: IParams): void;
/** CSI d */ linePosAbsolute(params: IParams): void;
/** CSI e */ vPositionRelative(params: IParams): void;
/** CSI f */ hVPosition(params: IParams): void;
/** CSI g */ tabClear(params: IParams): void;
/** CSI h */ setMode(params: IParams, collect?: string): void;
/** CSI l */ resetMode(params: IParams, collect?: string): void;
/** CSI m */ charAttributes(params: IParams): void;
/** CSI n */ deviceStatus(params: IParams, collect?: string): void;
/** CSI p */ softReset(params: IParams, collect?: string): void;
/** CSI q */ setCursorStyle(params: IParams, collect?: string): void;
/** CSI r */ setScrollRegion(params: IParams, collect?: string): void;
/** CSI s */ saveCursor(params: IParams): void;
/** CSI u */ restoreCursor(params: IParams): void;
/** CSI ' } */ insertColumns(params: IParams): void;
/** CSI ' ~ */ deleteColumns(params: IParams): void;
/** OSC 0
OSC 2 */ setTitle(data: string): void;
/** OSC 4 */ setAnsiColor(data: string): void;
/** ESC E */ nextLine(): void;
/** ESC = */ keypadApplicationMode(): void;
/** ESC > */ keypadNumericMode(): void;
OSC 2 */ setTitle(data: string): boolean;
/** OSC 4 */ setOrReportIndexedColor(data: string): boolean;
/** OSC 10 */ setOrReportFgColor(data: string): boolean;
/** OSC 11 */ setOrReportBgColor(data: string): boolean;
/** OSC 12 */ setOrReportCursorColor(data: string): boolean;
/** OSC 104 */ restoreIndexedColor(data: string): boolean;
/** OSC 110 */ restoreFgColor(data: string): boolean;
/** OSC 111 */ restoreBgColor(data: string): boolean;
/** OSC 112 */ restoreCursorColor(data: string): boolean;
/** ESC E */ nextLine(): boolean;
/** ESC = */ keypadApplicationMode(): boolean;
/** ESC > */ keypadNumericMode(): boolean;
/** ESC % G
ESC % @ */ selectDefaultCharset(): void;
ESC % @ */ selectDefaultCharset(): boolean;
/** ESC ( C
ESC ) C
ESC * C
ESC + C
ESC - C
ESC . C
ESC / C */ selectCharset(collectAndFlag: string): void;
/** ESC D */ index(): void;
/** ESC H */ tabSet(): void;
/** ESC M */ reverseIndex(): void;
/** ESC c */ fullReset(): void;
ESC / C */ selectCharset(collectAndFlag: string): boolean;
/** ESC D */ index(): boolean;
/** ESC H */ tabSet(): boolean;
/** ESC M */ reverseIndex(): boolean;
/** ESC c */ fullReset(): boolean;
/** ESC n
ESC o
ESC |
ESC }
ESC ~ */ setgLevel(level: number): void;
/** ESC # 8 */ screenAlignmentPattern(): void;
ESC ~ */ setgLevel(level: number): boolean;
/** ESC # 8 */ screenAlignmentPattern(): boolean;
}
interface IParseStack {
+31 -5
View File
@@ -43,6 +43,7 @@ export class Buffer implements IBuffer {
private _whitespaceCell: ICellData = CellData.fromCharData([0, WHITESPACE_CELL_CHAR, WHITESPACE_CELL_WIDTH, WHITESPACE_CELL_CODE]);
private _cols: number;
private _rows: number;
private _isClearing: boolean = false;
constructor(
private _hasScrollback: boolean,
@@ -107,7 +108,7 @@ export class Buffer implements IBuffer {
return rows;
}
const correctBufferLength = rows + this._optionsService.options.scrollback;
const correctBufferLength = rows + this._optionsService.rawOptions.scrollback;
return correctBufferLength > MAX_BUFFER_SIZE ? MAX_BUFFER_SIZE : correctBufferLength;
}
@@ -172,7 +173,7 @@ export class Buffer implements IBuffer {
if (this._rows < newRows) {
for (let y = this._rows; y < newRows; y++) {
if (this.lines.length < newRows + this.ybase) {
if (this._optionsService.options.windowsMode) {
if (this._optionsService.rawOptions.windowsMode) {
// Just add the new missing rows on Windows as conpty reprints the screen with it's
// view of the world. Once a line enters scrollback for conpty it remains there
this.lines.push(new BufferLine(newCols, nullCell));
@@ -252,7 +253,7 @@ export class Buffer implements IBuffer {
}
private get _isReflowEnabled(): boolean {
return this._hasScrollback && !this._optionsService.options.windowsMode;
return this._hasScrollback && !this._optionsService.rawOptions.windowsMode;
}
private _reflow(newCols: number, newRows: number): void {
@@ -367,6 +368,11 @@ export class Buffer implements IBuffer {
let srcCol = lastLineLength;
while (srcLineIndex >= 0) {
const cellsToCopy = Math.min(srcCol, destCol);
if (wrappedLines[destLineIndex] === undefined) {
// Sanity check that the line exists, this has been known to fail for an unknown reason
// which would stop the reflow from happening if an exception would throw.
break;
}
wrappedLines[destLineIndex].copyCellsFrom(wrappedLines[srcLineIndex], srcCol - cellsToCopy, destCol - cellsToCopy, cellsToCopy, true);
destCol -= cellsToCopy;
if (destCol === 0) {
@@ -550,7 +556,7 @@ export class Buffer implements IBuffer {
i = 0;
}
for (; i < this._cols; i += this._optionsService.options.tabStopWidth) {
for (; i < this._cols; i += this._optionsService.rawOptions.tabStopWidth) {
this.tabs[i] = true;
}
}
@@ -579,6 +585,24 @@ export class Buffer implements IBuffer {
return x >= this._cols ? this._cols - 1 : x < 0 ? 0 : x;
}
public clearMarkers(y?: number): void {
this._isClearing = true;
if (y !== undefined) {
for (let i = 0; i < this.markers.length; i++) {
if (this.markers[i].line === y) {
this.markers[i].dispose();
this.markers.splice(i--, 1);
}
}
} else {
for (const marker of this.markers) {
marker.dispose();
}
this.markers = [];
}
this._isClearing = false;
}
public addMarker(y: number): Marker {
const marker = new Marker(y);
this.markers.push(marker);
@@ -610,7 +634,9 @@ export class Buffer implements IBuffer {
}
private _removeMarker(marker: Marker): void {
this.markers.splice(this.markers.indexOf(marker), 1);
if (!this._isClearing) {
this.markers.splice(this.markers.indexOf(marker), 1);
}
}
public iterator(trimRight: boolean, startIndex?: number, endIndex?: number, startOverscan?: number, endOverscan?: number): IBufferStringIterator {
+1
View File
@@ -45,6 +45,7 @@ export interface IBuffer {
getNullCell(attr?: IAttributeData): ICellData;
getWhitespaceCell(attr?: IAttributeData): ICellData;
addMarker(y: number): IMarker;
clearMarkers(y?: number): void;
}
export interface IBufferSet extends IDisposable {
+1 -1
View File
@@ -349,7 +349,7 @@ export function evaluateKeyboardEvent(
} else if ((!isMac || macOptionIsMeta) && ev.altKey && !ev.metaKey) {
// On macOS this is a third level shift when !macOptionIsMeta. Use <Esc> instead.
const keyMapping = KEYCODE_KEY_MAPPINGS[ev.keyCode];
const key = keyMapping && keyMapping[!ev.shiftKey ? 0 : 1];
const key = keyMapping?.[!ev.shiftKey ? 0 : 1];
if (key) {
result.key = C0.ESC + key;
} else if (ev.keyCode >= 65 && ev.keyCode <= 90) {
+1 -1
View File
@@ -162,7 +162,7 @@ it('wcwidth should match all values from the old implementation', function(): vo
// we are only interested in 2 LSBs, cut off higher bits
// ==> n = n & 3 e.g. 000000000000000000000000000000XX
return (num: number): number => {
num = num | 0; // get asm.js like optimization under V8
num |= 0; // get asm.js like optimization under V8
if (num < 32) {
return control | 0;
}
+93
View File
@@ -0,0 +1,93 @@
/**
* Copyright (c) 2021 The xterm.js authors. All rights reserved.
* @license MIT
*/
import { assert } from 'chai';
import { parseColor, toRgbString } from 'common/input/XParseColor';
describe('XParseColor', () => {
describe('parseColor', () => {
it('rgb:<r>/<g>/<b> scheme in 4/8/12/16 bit', () => {
// 4 bit
assert.deepEqual(parseColor('rgb:0/0/0'), [0, 0, 0]);
assert.deepEqual(parseColor('rgb:f/f/f'), [255, 255, 255]);
assert.deepEqual(parseColor('rgb:1/2/3'), [17, 34, 51]);
// 8 bit
assert.deepEqual(parseColor('rgb:00/00/00'), [0, 0, 0]);
assert.deepEqual(parseColor('rgb:ff/ff/ff'), [255, 255, 255]);
assert.deepEqual(parseColor('rgb:11/22/33'), [17, 34, 51]);
// 12 bit
assert.deepEqual(parseColor('rgb:000/000/000'), [0, 0, 0]);
assert.deepEqual(parseColor('rgb:fff/fff/fff'), [255, 255, 255]);
assert.deepEqual(parseColor('rgb:111/222/333'), [17, 34, 51]);
// 16 bit
assert.deepEqual(parseColor('rgb:0000/0000/0000'), [0, 0, 0]);
assert.deepEqual(parseColor('rgb:ffff/ffff/ffff'), [255, 255, 255]);
assert.deepEqual(parseColor('rgb:1111/2222/3333'), [17, 34, 51]);
});
it('#RGB scheme in 4/8/12/16 bit', () => {
// 4 bit
assert.deepEqual(parseColor('#000'), [0, 0, 0]);
assert.deepEqual(parseColor('#fff'), [240, 240, 240]);
assert.deepEqual(parseColor('#123'), [16, 32, 48]);
// 8 bit
assert.deepEqual(parseColor('#000000'), [0, 0, 0]);
assert.deepEqual(parseColor('#ffffff'), [255, 255, 255]);
assert.deepEqual(parseColor('#112233'), [17, 34, 51]);
// 12 bit
assert.deepEqual(parseColor('#000000000'), [0, 0, 0]);
assert.deepEqual(parseColor('#fffffffff'), [255, 255, 255]);
assert.deepEqual(parseColor('#111222333'), [17, 34, 51]);
// 16 bit
assert.deepEqual(parseColor('#000000000000'), [0, 0, 0]);
assert.deepEqual(parseColor('#ffffffffffff'), [255, 255, 255]);
assert.deepEqual(parseColor('#111122223333'), [17, 34, 51]);
});
it('supports upper case', () => {
assert.deepEqual(parseColor('RGB:0/A/F'), [0, 170, 255]);
assert.deepEqual(parseColor('#FFF'), [240, 240, 240]);
});
it('does not parse illegal combinations', () => {
// shifting bit width
assert.equal(parseColor('rgb:0/11/222'), undefined);
// unsupported scheme
assert.equal(parseColor('rgbi:00/11/22'), undefined);
// broken # specifier
assert.equal(parseColor('#aabbbcc'), undefined);
// out of range
assert.equal(parseColor('#aabbgg'), undefined);
assert.equal(parseColor('rgb:aa/bb/gg'), undefined);
});
});
describe('toXColorRgb', () => {
it('rgb:<r>/<g>/<b> scheme in 4/8/12/16 bit', () => {
// 4 bit
assert.equal(toRgbString(parseColor('rgb:0/0/0')!, 4), 'rgb:0/0/0');
assert.equal(toRgbString(parseColor('rgb:f/f/f')!, 4), 'rgb:f/f/f');
assert.equal(toRgbString(parseColor('rgb:1/2/3')!, 4), 'rgb:1/2/3');
// 8 bit
assert.equal(toRgbString(parseColor('rgb:00/00/00')!, 8), 'rgb:00/00/00');
assert.equal(toRgbString(parseColor('rgb:ff/ff/ff')!, 8), 'rgb:ff/ff/ff');
assert.equal(toRgbString(parseColor('rgb:11/22/33')!, 8), 'rgb:11/22/33');
// 12 bit
assert.equal(toRgbString(parseColor('rgb:000/000/000')!, 12), 'rgb:000/000/000');
assert.equal(toRgbString(parseColor('rgb:fff/fff/fff')!, 12), 'rgb:fff/fff/fff');
assert.equal(toRgbString(parseColor('rgb:111/222/333')!, 12), 'rgb:111/222/333');
// 16 bit
assert.equal(toRgbString(parseColor('rgb:0000/0000/0000')!, 16), 'rgb:0000/0000/0000');
assert.equal(toRgbString(parseColor('rgb:ffff/ffff/ffff')!, 16), 'rgb:ffff/ffff/ffff');
assert.equal(toRgbString(parseColor('rgb:1111/2222/3333')!, 16), 'rgb:1111/2222/3333');
});
it('defaults to 16 bit output', () => {
assert.equal(toRgbString(parseColor('rgb:1/2/3')!), 'rgb:1111/2222/3333');
assert.equal(toRgbString(parseColor('rgb:11/22/33')!), 'rgb:1111/2222/3333');
assert.equal(toRgbString(parseColor('rgb:111/222/333')!), 'rgb:1111/2222/3333');
assert.equal(toRgbString(parseColor('rgb:123/123/123')!), 'rgb:1212/1212/1212');
});
it('reduces colors to 8 bit resolution', () => {
assert.equal(toRgbString(parseColor('rgb:123/123/123')!, 12), 'rgb:121/121/121');
assert.equal(toRgbString(parseColor('rgb:1234/1234/1234')!, 16), 'rgb:1212/1212/1212');
});
});
});
+80
View File
@@ -0,0 +1,80 @@
/**
* Copyright (c) 2021 The xterm.js authors. All rights reserved.
* @license MIT
*/
// 'rgb:' rule - matching: r/g/b | rr/gg/bb | rrr/ggg/bbb | rrrr/gggg/bbbb (hex digits)
const RGB_REX = /^([\da-f]{1})\/([\da-f]{1})\/([\da-f]{1})$|^([\da-f]{2})\/([\da-f]{2})\/([\da-f]{2})$|^([\da-f]{3})\/([\da-f]{3})\/([\da-f]{3})$|^([\da-f]{4})\/([\da-f]{4})\/([\da-f]{4})$/;
// '#...' rule - matching any hex digits
const HASH_REX = /^[\da-f]+$/;
/**
* Parse color spec to RGB values (8 bit per channel).
* See `man xparsecolor` for details about certain format specifications.
*
* Supported formats:
* - rgb:<red>/<green>/<blue> with <red>, <green>, <blue> in h | hh | hhh | hhhh
* - #RGB, #RRGGBB, #RRRGGGBBB, #RRRRGGGGBBBB
*
* All other formats like rgbi: or device-independent string specifications
* with float numbering are not supported.
*/
export function parseColor(data: string): [number, number, number] | undefined {
if (!data) return;
// also handle uppercases
let low = data.toLowerCase();
if (low.indexOf('rgb:') === 0) {
// 'rgb:' specifier
low = low.slice(4);
const m = RGB_REX.exec(low);
if (m) {
const base = m[1] ? 15 : m[4] ? 255 : m[7] ? 4095 : 65535;
return [
Math.round(parseInt(m[1] || m[4] || m[7] || m[10], 16) / base * 255),
Math.round(parseInt(m[2] || m[5] || m[8] || m[11], 16) / base * 255),
Math.round(parseInt(m[3] || m[6] || m[9] || m[12], 16) / base * 255)
];
}
} else if (low.indexOf('#') === 0) {
// '#' specifier
low = low.slice(1);
if (HASH_REX.exec(low) && [3, 6, 9, 12].includes(low.length)) {
const adv = low.length / 3;
const result: [number, number, number] = [0, 0, 0];
for (let i = 0; i < 3; ++i) {
const c = parseInt(low.slice(adv * i, adv * i + adv), 16);
result[i] = adv === 1 ? c << 4 : adv === 2 ? c : adv === 3 ? c >> 4 : c >> 8;
}
return result;
}
}
// Named colors are currently not supported due to the large addition to the xterm.js bundle size
// they would add. In order to support named colors, we would need some way of optionally loading
// additional payloads so startup/download time is not bloated (see #3530).
}
// pad hex output to requested bit width
function pad(n: number, bits: number): string {
const s = n.toString(16);
const s2 = s.length < 2 ? '0' + s : s;
switch (bits) {
case 4:
return s[0];
case 8:
return s2;
case 12:
return (s2 + s2).slice(0, 3);
default:
return s2 + s2;
}
}
/**
* Convert a given color to rgb:../../.. string of `bits` depth.
*/
export function toRgbString(color: [number, number, number], bits: number = 16): string {
const [r, g, b] = color;
return `rgb:${pad(r, bits)}/${pad(g, bits)}/${pad(b, bits)}`;
}
+2 -2
View File
@@ -52,9 +52,9 @@ export class Params implements IParams {
return params;
}
// skip leading sub params
for (let i = (values[0] instanceof Array) ? 1 : 0; i < values.length; ++i) {
for (let i = (Array.isArray(values[0])) ? 1 : 0; i < values.length; ++i) {
const value = values[i];
if (value instanceof Array) {
if (Array.isArray(value)) {
for (let k = 0; k < value.length; ++k) {
params.addSubParam(value[k]);
}
+2 -2
View File
@@ -36,8 +36,8 @@ export class BufferService extends Disposable implements IBufferService {
@IOptionsService private _optionsService: IOptionsService
) {
super();
this.cols = Math.max(_optionsService.options.cols || 0, MINIMUM_COLS);
this.rows = Math.max(_optionsService.options.rows || 0, MINIMUM_ROWS);
this.cols = Math.max(_optionsService.rawOptions.cols || 0, MINIMUM_COLS);
this.rows = Math.max(_optionsService.rawOptions.rows || 0, MINIMUM_ROWS);
this.buffers = new BufferSet(_optionsService, this);
}
+2 -2
View File
@@ -62,7 +62,7 @@ export class CoreService extends Disposable implements ICoreService {
public triggerDataEvent(data: string, wasUserInput: boolean = false): void {
// Prevents all events to pty process if stdin is disabled
if (this._optionsService.options.disableStdin) {
if (this._optionsService.rawOptions.disableStdin) {
return;
}
@@ -83,7 +83,7 @@ export class CoreService extends Disposable implements ICoreService {
}
public triggerBinaryEvent(data: string): void {
if (this._optionsService.options.disableStdin) {
if (this._optionsService.rawOptions.disableStdin) {
return;
}
this._logService.debug(`sending binary "${data}"`, () => data.split('').map(e => e.charCodeAt(0)));
+1 -1
View File
@@ -46,7 +46,7 @@ export class LogService implements ILogService {
}
private _updateLogLevel(): void {
this.logLevel = optionsKeyToLogLevel[this._optionsService.options.logLevel];
this.logLevel = optionsKeyToLogLevel[this._optionsService.rawOptions.logLevel];
}
private _evalLazyOptionalParams(optionalParams: any[]): void {
+6 -2
View File
@@ -10,7 +10,7 @@ describe('OptionsService', () => {
describe('constructor', () => {
const originalError = console.error;
beforeEach(() => {
console.error = () => {};
console.error = () => { };
});
afterEach(() => {
console.error = originalError;
@@ -26,7 +26,11 @@ describe('OptionsService', () => {
assert.equal(optionsService.getOption('cols'), 80);
});
it('uses default value if invalid constructor option value passed', () => {
assert.equal(new OptionsService({tabStopWidth: 0}).getOption('tabStopWidth'), DEFAULT_OPTIONS.tabStopWidth);
assert.equal(new OptionsService({ tabStopWidth: 0 }).getOption('tabStopWidth'), DEFAULT_OPTIONS.tabStopWidth);
});
it('object.keys return the correct number of options', () => {
const optionsService = new OptionsService({ cols: 80, rows: 25 });
assert.notEqual(Object.keys(optionsService.options).length, 0);
});
});
describe('setOption', () => {
+47 -38
View File
@@ -3,10 +3,9 @@
* @license MIT
*/
import { IOptionsService, ITerminalOptions, IPartialTerminalOptions, FontWeight } from 'common/services/Services';
import { IOptionsService, ITerminalOptions, FontWeight } from 'common/services/Services';
import { EventEmitter, IEvent } from 'common/EventEmitter';
import { isMac } from 'common/Platform';
import { clone } from 'common/Clone';
// Source: https://freesound.org/people/altemark/sounds/45759/
// This sound is released under the Creative Commons Attribution 3.0 Unported
@@ -14,15 +13,14 @@ import { clone } from 'common/Clone';
// made, apart from the conversion to base64.
export const DEFAULT_BELL_SOUND = 'data:audio/mp3;base64,SUQzBAAAAAAAI1RTU0UAAAAPAAADTGF2ZjU4LjMyLjEwNAAAAAAAAAAAAAAA//tQxAADB8AhSmxhIIEVCSiJrDCQBTcu3UrAIwUdkRgQbFAZC1CQEwTJ9mjRvBA4UOLD8nKVOWfh+UlK3z/177OXrfOdKl7pyn3Xf//WreyTRUoAWgBgkOAGbZHBgG1OF6zM82DWbZaUmMBptgQhGjsyYqc9ae9XFz280948NMBWInljyzsNRFLPWdnZGWrddDsjK1unuSrVN9jJsK8KuQtQCtMBjCEtImISdNKJOopIpBFpNSMbIHCSRpRR5iakjTiyzLhchUUBwCgyKiweBv/7UsQbg8isVNoMPMjAAAA0gAAABEVFGmgqK////9bP/6XCykxBTUUzLjEwMKqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq';
// TODO: Freeze?
export const DEFAULT_OPTIONS: ITerminalOptions = Object.freeze({
export const DEFAULT_OPTIONS: Readonly<ITerminalOptions> = {
cols: 80,
rows: 24,
cursorBlink: false,
cursorStyle: 'block',
cursorWidth: 1,
customGlyphs: true,
bellSound: DEFAULT_BELL_SOUND,
bellSound: DEFAULT_BELL_SOUND,
bellStyle: 'none',
drawBoldTextInBrightColors: true,
fastScrollModifier: 'alt',
@@ -55,57 +53,71 @@ export const DEFAULT_OPTIONS: ITerminalOptions = Object.freeze({
convertEol: false,
termName: 'xterm',
cancelEvents: false
});
};
const FONT_WEIGHT_OPTIONS: Extract<FontWeight, string>[] = ['normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900'];
/**
* The set of options that only have an effect when set in the Terminal constructor.
*/
const CONSTRUCTOR_ONLY_OPTIONS = ['cols', 'rows'];
export class OptionsService implements IOptionsService {
public serviceBrand: any;
public readonly rawOptions: ITerminalOptions;
public options: ITerminalOptions;
private _onOptionChange = new EventEmitter<string>();
public get onOptionChange(): IEvent<string> { return this._onOptionChange.event; }
constructor(options: IPartialTerminalOptions) {
this.options = clone(DEFAULT_OPTIONS);
for (const k of Object.keys(options)) {
if (k in this.options) {
constructor(options: Partial<ITerminalOptions>) {
// set the default value of each option
const defaultOptions = { ...DEFAULT_OPTIONS };
for (const key in options) {
if (key in defaultOptions) {
try {
const newValue = options[k as keyof IPartialTerminalOptions] as any;
this.options[k] = this._sanitizeAndValidateOption(k, newValue);
const newValue = options[key];
defaultOptions[key] = this._sanitizeAndValidateOption(key, newValue);
} catch (e) {
console.error(e);
}
}
}
// set up getters and setters for each option
this.rawOptions = defaultOptions;
this.options = { ... defaultOptions };
this._setupOptions();
}
private _setupOptions(): void {
const getter = (propName: string): any => {
if (!(propName in DEFAULT_OPTIONS)) {
throw new Error(`No option with key "${propName}"`);
}
return this.rawOptions[propName];
};
const setter = (propName: string, value: any): void => {
if (!(propName in DEFAULT_OPTIONS)) {
throw new Error(`No option with key "${propName}"`);
}
value = this._sanitizeAndValidateOption(propName, value);
// Don't fire an option change event if they didn't change
if (this.rawOptions[propName] !== value) {
this.rawOptions[propName] = value;
this._onOptionChange.fire(propName);
}
};
for (const propName in this.rawOptions) {
const desc = {
get: getter.bind(this, propName),
set: setter.bind(this, propName)
};
Object.defineProperty(this.options, propName, desc);
}
}
public setOption(key: string, value: any): void {
if (!(key in DEFAULT_OPTIONS)) {
throw new Error('No option with key "' + key + '"');
}
if (CONSTRUCTOR_ONLY_OPTIONS.includes(key)) {
throw new Error(`Option "${key}" can only be set in the constructor`);
}
if (this.options[key] === value) {
return;
}
value = this._sanitizeAndValidateOption(key, value);
// Don't fire an option change event if they didn't change
if (this.options[key] === value) {
return;
}
this.options[key] = value;
this._onOptionChange.fire(key);
}
private _sanitizeAndValidateOption(key: string, value: any): any {
@@ -160,9 +172,6 @@ export class OptionsService implements IOptionsService {
}
public getOption(key: string): any {
if (!(key in DEFAULT_OPTIONS)) {
throw new Error(`No option with key "${key}"`);
}
return this.options[key];
}
}
+16 -43
View File
@@ -164,6 +164,14 @@ export interface IInstantiationService {
createInstance<Ctor extends new (...args: any[]) => any, R extends InstanceType<Ctor>>(t: Ctor, ...args: GetLeadingNonServiceArgs<ConstructorParameters<Ctor>>): R;
}
export enum LogLevelEnum {
DEBUG = 0,
INFO = 1,
WARN = 2,
ERROR = 3,
OFF = 4
}
export const ILogService = createDecorator<ILogService>('LogService');
export interface ILogService {
serviceBrand: undefined;
@@ -180,6 +188,12 @@ export const IOptionsService = createDecorator<IOptionsService>('OptionsService'
export interface IOptionsService {
serviceBrand: undefined;
/**
* Read only access to the raw options object, this is an internal-only fast path for accessing
* single options without any validation as we trust TypeScript to enforce correct usage
* internally.
*/
readonly rawOptions: Readonly<ITerminalOptions>;
readonly options: ITerminalOptions;
readonly onOptionChange: IEvent<string>;
@@ -190,49 +204,8 @@ export interface IOptionsService {
export type FontWeight = 'normal' | 'bold' | '100' | '200' | '300' | '400' | '500' | '600' | '700' | '800' | '900' | number;
export type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'off';
export enum LogLevelEnum {
DEBUG = 0,
INFO = 1,
WARN = 2,
ERROR = 3,
OFF = 4
}
export type RendererType = 'dom' | 'canvas';
export interface IPartialTerminalOptions {
altClickMovesCursor?: boolean;
allowTransparency?: boolean;
bellSound?: string;
bellStyle?: 'none' | 'sound' /* | 'visual' | 'both' */;
cols?: number;
cursorBlink?: boolean;
cursorStyle?: 'block' | 'underline' | 'bar';
cursorWidth?: number;
disableStdin?: boolean;
drawBoldTextInBrightColors?: boolean;
fastScrollModifier?: 'alt' | 'ctrl' | 'shift';
fastScrollSensitivity?: number;
fontSize?: number;
fontFamily?: string;
fontWeight?: FontWeight;
fontWeightBold?: FontWeight;
letterSpacing?: number;
lineHeight?: number;
logLevel?: LogLevel;
macOptionIsMeta?: boolean;
macOptionClickForcesSelection?: boolean;
rendererType?: RendererType;
rightClickSelectsWord?: boolean;
rows?: number;
screenReaderMode?: boolean;
scrollback?: number;
scrollSensitivity?: number;
tabStopWidth?: number;
theme?: ITheme;
windowsMode?: boolean;
wordSeparator?: string;
windowOptions?: IWindowOptions;
}
export type RendererType = 'dom' | 'canvas';
export interface ITerminalOptions {
allowProposedApi: boolean;
@@ -241,6 +214,7 @@ export interface ITerminalOptions {
bellSound: string;
bellStyle: 'none' | 'sound' /* | 'visual' | 'both' */;
cols: number;
convertEol: boolean;
cursorBlink: boolean;
cursorStyle: 'block' | 'underline' | 'bar';
cursorWidth: number;
@@ -274,7 +248,6 @@ export interface ITerminalOptions {
[key: string]: any;
cancelEvents: boolean;
convertEol: boolean;
termName: string;
}
+35 -4
View File
@@ -5,10 +5,11 @@
import { deepStrictEqual, strictEqual, throws } from 'assert';
import { Terminal } from 'headless/public/Terminal';
import { ITerminalOptions } from 'xterm-headless';
let term: Terminal;
describe('Headless API Tests', function(): void {
describe('Headless API Tests', function (): void {
beforeEach(() => {
// Create default terminal to be used by most tests
term = new Terminal();
@@ -119,13 +120,43 @@ describe('Headless API Tests', function(): void {
strictEqual(term.getOption('scrollback'), 50);
});
describe('options', () => {
const termOptions = {
cols: 80,
rows: 24
};
beforeEach(async () => {
term = new Terminal(termOptions);
});
it('get options', () => {
const options: ITerminalOptions = term.options;
strictEqual(options.cols, 80);
strictEqual(options.rows, 24);
});
it('set options', async () => {
const options: ITerminalOptions = term.options;
throws(() => options.cols = 40);
throws(() => options.rows = 20);
term.options.scrollback = 1;
strictEqual(term.options.scrollback, 1);
term.options= {
fontSize: 12,
fontFamily: 'Arial'
};
strictEqual(term.options.fontSize, 12);
strictEqual(term.options.fontFamily, 'Arial');
});
});
describe('loadAddon', () => {
it('constructor', async () => {
term = new Terminal({ cols: 5 });
let cols = 0;
term.loadAddon({
activate: (t) => cols = t.cols,
dispose: () => {}
dispose: () => { }
});
strictEqual(cols, 5);
});
@@ -133,7 +164,7 @@ describe('Headless API Tests', function(): void {
it('dispose (addon)', async () => {
let disposeCalled = false;
const addon = {
activate: () => {},
activate: () => { },
dispose: () => disposeCalled = true
};
term.loadAddon(addon);
@@ -145,7 +176,7 @@ describe('Headless API Tests', function(): void {
it('dispose (terminal)', async () => {
let disposeCalled = false;
term.loadAddon({
activate: () => {},
activate: () => { },
dispose: () => disposeCalled = true
});
strictEqual(disposeCalled, false);

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