Compare commits

..
202 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
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 9557b9132b Merge branch 'master' into options3 2021-12-17 11:13:23 -08:00
Simon Lamon d6d540e748 use a proxy 2021-11-20 14:58:07 +00:00
62 changed files with 1299 additions and 247 deletions
+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
+5 -1
View File
@@ -185,7 +185,11 @@ Xterm.js is used in several world-class applications to provide great terminal e
- [**gdbgui**](https://github.com/cs01/gdbgui): Browser-based frontend to gdb (gnu debugger)
- [**goormIDE**](https://ide.goorm.io/): Run almost every programming languages with real-time collaboration, live pair programming, and built-in messenger.
- [**FleetDeck**](https://fleetdeck.io): Remote desktop & virtual terminal
- [And much more...](https://github.com/xtermjs/xterm.js/network/dependents)
- [**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.
+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",
+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",
+4 -4
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,14 +25,14 @@
"keywords": [
"font",
"ligature",
"terminal",
"xterm",
"xterm.js",
"terminal"
"xterm.js"
],
"license": "MIT",
"dependencies": {
"font-finder": "^1.1.0",
"font-ligatures": "^1.4.0"
"font-ligatures": "^1.4.1"
},
"devDependencies": {
"@types/sinon": "^5.0.1",
+7 -7
View File
@@ -67,9 +67,9 @@ fd-slicer@~1.1.0:
pend "~1.2.0"
follow-redirects@^1.14.0:
version "1.14.3"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.3.tgz#6ada78118d8d24caee595595accdc0ac6abd022e"
integrity sha512-3MkHxknWMUtb23apkgz/83fDoe+y+qr0TdgacGIA7bew+QLBo3vdgEN2xEsuXNivpFy4CyDhBBZnNZOtalmenw==
version "1.14.8"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.8.tgz#016996fb9a11a100566398b1c6839337d7bfa8fc"
integrity sha512-1x0S9UVJHsQprFcEC/qnNzBLcIxsjAV905f/UkQxbclCsoTWlacCNOpQa/anodLl2uaEKFhfWOvM2Qg77+15zA==
font-finder@^1.0.3:
version "1.0.4"
@@ -86,10 +86,10 @@ font-finder@^1.1.0:
get-system-fonts "^2.0.0"
promise-stream-reader "^1.0.1"
font-ligatures@^1.4.0:
version "1.4.0"
resolved "https://registry.npmjs.org/font-ligatures/-/font-ligatures-1.4.0.tgz#6a7b370d96be1358dddfad67830e82fbfd59e6dc"
integrity sha512-n7DFnnEpJ0NrVoLqZIL4tMGVs+CnFwQc92m80LWyrbgAFO4x234+t2/H9o4eOYA1eh6ta9dZAEEsJAwsBdNezA==
font-ligatures@^1.4.1:
version "1.4.1"
resolved "https://registry.npmjs.org/font-ligatures/-/font-ligatures-1.4.1.tgz#e8c9df7cf6bee80c0cca4a9a578618c7b1f5d8de"
integrity sha512-7W6zlfyhvCqShZ5ReUWqmSd9vBaUudW0Hxis+tqUjtHhsPU+L3Grf8mcZAtCiXHTzorhwdRTId2WeH/88gdFkw==
dependencies:
font-finder "^1.0.3"
lru-cache "^6.0.0"
+5
View File
@@ -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",
@@ -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();
@@ -403,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 {
@@ -460,6 +495,14 @@ export class SerializeAddon implements ITerminalAddon {
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 { }
}
@@ -469,3 +512,150 @@ interface ISerializeOptions {
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"
}
]
}
@@ -24,14 +24,23 @@ 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.
*
*
* 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(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.
*/
@@ -56,4 +65,24 @@ declare module 'xterm-addon-serialize' {
*/
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;
}
}
@@ -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",
+6 -1
View File
@@ -1,6 +1,6 @@
{
"name": "xterm-addon-web-links",
"version": "0.5.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})';
+6
View File
@@ -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 {
}
@@ -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);
}
+18 -17
View File
@@ -105,22 +105,23 @@ 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:
@@ -143,7 +144,7 @@ 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:
+5
View File
@@ -173,3 +173,8 @@
.xterm-strikethrough {
text-decoration: line-through;
}
.xterm-screen .xterm-decoration-container .xterm-decoration {
z-index: 6;
position: absolute;
}
+25
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 {
@@ -447,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');
@@ -525,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>
+17 -2
View File
@@ -1,12 +1,27 @@
{
"name": "xterm",
"description": "Full xterm terminal, in your browser",
"version": "4.16.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",
@@ -57,7 +72,7 @@
"express-ws": "^5.0.2",
"glob": "^7.2.0",
"jsdom": "^18.0.1",
"mocha": "^9.1.3",
"mocha": "^9.2.0",
"mustache": "^4.2.0",
"node-pty": "^0.10.1",
"nyc": "^15.1.0",
-1
View File
@@ -53,7 +53,6 @@ export class AccessibilityManager extends Disposable {
) {
super();
this._accessibilityTreeRoot = document.createElement('div');
this._accessibilityTreeRoot.setAttribute('role', 'document');
this._accessibilityTreeRoot.classList.add('xterm-accessibility');
this._accessibilityTreeRoot.tabIndex = 0;
+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 {
+16 -7
View File
@@ -37,7 +37,7 @@ 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 { KeyboardResultType, CoreMouseEventType, CoreMouseButton, CoreMouseAction, ITerminalOptions, ScrollSource, IColorEvent, ColorIndex, ColorRequestType } from 'common/Types';
import { evaluateKeyboardEvent } from 'common/input/Keyboard';
@@ -45,7 +45,7 @@ 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';
@@ -55,6 +55,7 @@ import { CoreTerminal } from 'common/CoreTerminal';
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;
@@ -108,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;
@@ -157,6 +159,7 @@ 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()));
@@ -298,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);
}
@@ -309,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;
}
}
@@ -574,6 +577,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
this.linkifier.attachToDom(this.element, this._mouseZoneManager);
this.linkifier2.attachToDom(this.screenElement, this._mouseService, this._renderService);
this.decorationService.attachToDom(this.screenElement, this._renderService, this._bufferService);
// This event listener must be registered aftre MouseZoneManager is created
this.register(addDisposableDomListener(this.element, 'mousedown', (e: MouseEvent) => this._selectionService!.onMouseDown(e)));
@@ -753,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');
@@ -998,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.
*/
@@ -1125,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);
}
@@ -1220,7 +1228,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
protected _inputEvent(ev: InputEvent): boolean {
// Only support emoji IMEs when screen reader mode is disabled as the event must bubble up to
// support reading out character input which can doubling up input characters
if (ev.data && ev.inputType === 'insertText' && !ev.composed && !this.optionsService.options.screenReaderMode) {
if (ev.data && ev.inputType === 'insertText' && !ev.composed && !this.optionsService.rawOptions.screenReaderMode) {
if (this._keyPressHandled) {
return false;
}
@@ -1293,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;
+13 -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.');
}
@@ -254,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 {
@@ -280,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 { }
@@ -419,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 {
+3 -2
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 {
@@ -61,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;
+5 -5
View File
@@ -110,7 +110,7 @@ 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;
@@ -153,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);
}
}
@@ -259,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();
+30 -13
View File
@@ -3,7 +3,7 @@
* @license MIT
*/
import { Terminal as ITerminalApi, 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';
@@ -30,17 +30,21 @@ export class Terminal implements ITerminalApi {
this._core = new TerminalCore(options);
this._addonManager = new AddonManager();
this._publicOptions = {};
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) {
Object.defineProperty(this._publicOptions, propName, {
get: () => {
return this._core.options[propName];
},
set: (value: any) => {
this._checkReadonlyOptions(propName);
this._core.options[propName] = value;
}
});
const desc = {
get: getter.bind(this, propName),
set: setter.bind(this, propName)
};
Object.defineProperty(this._publicOptions, propName, desc);
}
}
@@ -54,7 +58,7 @@ export class Terminal implements ITerminalApi {
}
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');
}
}
@@ -162,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);
}
@@ -277,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');
}
}
}
}
+11 -11
View File
@@ -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();
}
@@ -267,7 +267,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);
}
@@ -315,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;
@@ -356,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;
@@ -370,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;
@@ -386,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);
}
@@ -421,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;
}
@@ -455,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);
@@ -495,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;
+6 -6
View File
@@ -94,7 +94,7 @@ export class CursorRenderLayer extends BaseRenderLayer {
}
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);
@@ -148,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 {
@@ -174,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;
}
@@ -182,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();
}
@@ -213,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();
}
+8 -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),
@@ -178,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);
}
/**
+3 -3
View File
@@ -3,13 +3,13 @@
* @license MIT
*/
import { isFirefox } from 'common/Platform';
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 would
// 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 ? 'bottom' : 'ideographic';
export const TEXT_BASELINE: CanvasTextBaseline = isFirefox || isLegacyEdge ? 'bottom' : 'ideographic';
export const CHAR_ATLAS_CELL_SPACING = 1;
+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));
}
}
@@ -175,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])) {
@@ -225,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;
}
@@ -234,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);
}
+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();
+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();
}
}
+2 -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()));
+3 -3
View File
@@ -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);
}
/**
@@ -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;
}
/**
+10
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 {
@@ -113,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);
+2 -2
View File
@@ -247,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();
}
}
@@ -267,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();
+12 -11
View File
@@ -177,8 +177,8 @@ class DECRQSS implements IDcsHandler {
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;
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:
@@ -546,7 +546,7 @@ export class InputHandler extends Disposable implements IInputHandler {
// 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}"` : ''}`, typeof data === 'string'
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
);
@@ -603,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;
@@ -745,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);
@@ -800,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++;
@@ -904,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;
@@ -1231,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;
}
@@ -1763,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;
}
/**
@@ -1929,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();
}
@@ -2163,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();
}
@@ -2740,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;
+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
+3 -2
View File
@@ -120,12 +120,13 @@ 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?: Partial<ITerminalOptions>) {
if (testOptions) {
for (const key of Object.keys(testOptions)) {
this.options[key] = testOptions[key];
this.rawOptions[key] = testOptions[key];
}
}
}
+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 {
+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', () => {
+33 -28
View File
@@ -60,7 +60,7 @@ const FONT_WEIGHT_OPTIONS: Extract<FontWeight, string>[] = ['normal', 'bold', '1
export class OptionsService implements IOptionsService {
public serviceBrand: any;
private _options: ITerminalOptions;
public readonly rawOptions: ITerminalOptions;
public options: ITerminalOptions;
private _onOptionChange = new EventEmitter<string>();
@@ -68,12 +68,12 @@ export class OptionsService implements IOptionsService {
constructor(options: Partial<ITerminalOptions>) {
// set the default value of each option
this._options = { ...DEFAULT_OPTIONS };
const defaultOptions = { ...DEFAULT_OPTIONS };
for (const key in options) {
if (key in this._options) {
if (key in defaultOptions) {
try {
const newValue = options[key];
this._options[key] = this._sanitizeAndValidateOption(key, newValue);
defaultOptions[key] = this._sanitizeAndValidateOption(key, newValue);
} catch (e) {
console.error(e);
}
@@ -81,34 +81,39 @@ export class OptionsService implements IOptionsService {
}
// set up getters and setters for each option
this.options = this._setupOptions(this._options);
this.rawOptions = defaultOptions;
this.options = { ... defaultOptions };
this._setupOptions();
}
private _setupOptions(options: ITerminalOptions): ITerminalOptions {
const copiedOptions = { ... options };
for (const propName in copiedOptions) {
Object.defineProperty(copiedOptions, propName, {
get: () => {
if (!(propName in DEFAULT_OPTIONS)) {
throw new Error(`No option with key "${propName}"`);
}
return this._options[propName];
},
set: (value: any) => {
if (!(propName in DEFAULT_OPTIONS)) {
throw new Error(`No option with key "${propName}"`);
}
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];
};
value = this._sanitizeAndValidateOption(propName, value);
// Don't fire an option change event if they didn't change
if (this._options[propName] !== value) {
this._options[propName] = value;
this._onOptionChange.fire(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);
}
return copiedOptions;
}
public setOption(key: string, value: any): void {
+6
View File
@@ -188,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>;
+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);
+52 -2
View File
@@ -7,19 +7,61 @@ import { IEvent } from 'common/EventEmitter';
import { BufferNamespaceApi } from 'common/public/BufferNamespaceApi';
import { ParserApi } from 'common/public/ParserApi';
import { UnicodeApi } from 'common/public/UnicodeApi';
import { IBufferNamespace as IBufferNamespaceApi, IMarker, IModes, IParser, ITerminalAddon, ITerminalOptions, IUnicodeHandling, Terminal as ITerminalApi } from 'xterm-headless';
import { IBufferNamespace as IBufferNamespaceApi, IMarker, IModes, IParser, ITerminalAddon, IUnicodeHandling, Terminal as ITerminalApi } from 'xterm-headless';
import { Terminal as TerminalCore } from 'headless/Terminal';
import { AddonManager } from 'common/public/AddonManager';
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: TerminalCore;
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) {
Object.defineProperty(this._publicOptions, propName, {
get: () => {
return this._core.options[propName];
},
set: (value: any) => {
this._checkReadonlyOptions(propName);
this._core.options[propName] = value;
}
});
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 {
@@ -82,11 +124,19 @@ 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 resize(columns: number, rows: number): void {
this._verifyIntegers(columns, rows);
this._core.resize(columns, rows);
}
public registerMarker(cursorYOffset: number): IMarker | undefined {
public registerMarker(cursorYOffset: number = 0): IMarker | undefined {
this._checkProposedApi();
this._verifyIntegers(cursorYOffset);
return this._core.addMarker(cursorYOffset);
+73
View File
@@ -189,6 +189,10 @@ describe('API Integration Tests', function(): void {
assert.equal(await page.evaluate(`window.term.options.fontSize`), 30);
assert.equal(await page.evaluate(`window.term.options.fontFamily`), 'Arial');
});
it('object.keys return the correct number of options', async () => {
await openTerminal(page);
assert.notEqual(await page.evaluate(`Object.keys(window.term.options).length`), 0);
});
});
describe('renderer', () => {
@@ -555,6 +559,29 @@ describe('API Integration Tests', function(): void {
assert.equal(await page.evaluate(`window.term.buffer.active.getLine(0).getCell(2).getChars()`), '');
assert.equal(await page.evaluate(`window.term.buffer.active.getLine(0).getCell(2).getWidth()`), 0);
});
it('clearMarkers', async () => {
await openTerminal(page, { cols: 5 });
await page.evaluate(`
window.disposeStack = [];
`);
await writeSync(page, '\\n\\n\\n\\n');
await writeSync(page, '\\n\\n\\n\\n');
await writeSync(page, '\\n\\n\\n\\n');
await writeSync(page, '\\n\\n\\n\\n');
await page.evaluate(`window.term.addMarker(1)`);
await page.evaluate(`window.term.addMarker(2)`);
await page.evaluate(`window.term.scrollLines(10)`);
await page.evaluate(`window.term.addMarker(3)`);
await page.evaluate(`window.term.addMarker(4)`);
await page.evaluate(`
for (let i = 0; i < window.term.markers.length; ++i) {
const marker = window.term.markers[i];
marker.onDispose(() => window.disposeStack.push(marker));
}`);
await page.evaluate(`window.term.clear()`);
assert.equal(await page.evaluate(`window.disposeStack.length`), 4);
});
});
it('active, normal, alternate', async () => {
@@ -704,6 +731,52 @@ describe('API Integration Tests', function(): void {
await pollFor(page, `window.term._core._renderService.dimensions.actualCellWidth > 0`, true);
});
describe('registerDecoration', () => {
it('should register decorations and render them', async () => {
await openTerminal(page);
await writeSync(page, '\\n\\n\\n\\n');
await writeSync(page, '\\n\\n\\n\\n');
await writeSync(page, '\\n\\n\\n\\n');
await page.evaluate(`window.marker1 = window.term.addMarker(1)`);
await page.evaluate(`window.marker2 = window.term.addMarker(2)`);
await page.evaluate(`window.term.registerDecoration({ marker: window.marker1 })`);
await page.evaluate(`window.term.registerDecoration({ marker: window.marker2 })`);
await page.evaluate(`window.term.resize(10, 5)`);
assert.equal(await page.evaluate(`document.querySelectorAll('.xterm-screen .xterm-decoration').length`), 2);
});
it('on resize should dispose of the old decoration and create a new one', async () => {
await openTerminal(page);
await writeSync(page, '\\n\\n\\n\\n');
await writeSync(page, '\\n\\n\\n\\n');
await page.evaluate(`window.marker = window.term.addMarker(1)`);
await page.evaluate(`window.decoration = window.term.registerDecoration({ marker: window.marker })`);
await page.evaluate(`window.term.resize(10, 5)`);
assert.equal(await page.evaluate(`document.querySelectorAll('.xterm-screen .xterm-decoration').length`), 1);
});
it('should return undefined when the marker has already been disposed of', async () => {
await openTerminal(page);
await writeSync(page, '\\n\\n\\n\\n');
await writeSync(page, '\\n\\n\\n\\n');
await page.evaluate(`window.marker = window.term.addMarker(1)`);
await page.evaluate(`window.marker.dispose()`);
assert.equal(await page.evaluate(`window.decoration = window.term.registerDecoration({ marker: window.marker });`), undefined);
});
it('should throw when a negative x offset is provided', async () => {
await openTerminal(page);
await writeSync(page, '\\n\\n\\n\\n');
await writeSync(page, '\\n\\n\\n\\n');
await page.evaluate(`window.marker = window.term.addMarker(1)`);
await page.evaluate(`
try {
window.decoration = window.term.registerDecoration({ marker: window.marker, x: -2 });
} catch (e) {
window.throwMessage = e.message;
}
`);
await pollFor(page, 'window.throwMessage', 'This API only accepts positive integers');
});
});
describe('registerLinkProvider', () => {
it('should fire provideLinks when hovering cells', async () => {
await openTerminal(page, { rendererType: 'dom' });
+24 -1
View File
@@ -534,6 +534,29 @@ declare module 'xterm-headless' {
*/
readonly modes: IModes;
/**
* Gets or sets the terminal options. This supports setting multiple options.
*
* @example Get a single option
* ```typescript
* console.log(terminal.options.fontSize);
* ```
*
* @example Set a single option
* ```typescript
* terminal.options.fontSize = 12;
* ```
*
* @example Set multiple options
* ```typescript
* terminal.options = {
* fontSize: 12,
* fontFamily: 'Arial',
* };
* ```
*/
options: ITerminalOptions;
/**
* Natural language strings that can be localized.
*/
@@ -620,7 +643,7 @@ declare module 'xterm-headless' {
* @param cursorYOffset The y position offset of the marker from the cursor.
* @returns The new marker or undefined.
*/
registerMarker(cursorYOffset: number): IMarker | undefined;
registerMarker(cursorYOffset?: number): IMarker | undefined;
/**
* @deprecated use `registerMarker` instead.
+86 -10
View File
@@ -380,29 +380,96 @@ declare module 'xterm' {
* is trimmed and lines are added or removed. This is a single line that may
* be part of a larger wrapped line.
*/
export interface IMarker extends IDisposable {
export interface IMarker extends IDisposableWithEvent {
/**
* A unique identifier for this marker.
*/
readonly id: number;
/**
* Whether this marker is disposed.
*/
readonly isDisposed: boolean;
/**
* The actual line index in the buffer at this point in time. This is set to
* -1 if the marker has been disposed.
*/
readonly line: number;
}
/**
* Represents a disposable with an
* @param onDispose event listener and
* @param isDisposed property.
*/
export interface IDisposableWithEvent extends IDisposable {
/**
* Event listener to get notified when the marker gets disposed. Automatic disposal
* might happen for a marker, that got invalidated by scrolling out or removal of
* a line from the buffer.
* Event listener to get notified when this gets disposed.
*/
onDispose: IEvent<void>;
/**
* Whether this is disposed.
*/
readonly isDisposed: boolean;
}
/**
* Represents a decoration in the terminal that is associated with a particular marker and DOM element.
*/
export interface IDecoration extends IDisposableWithEvent {
/*
* The marker for the decoration in the terminal.
*/
readonly marker: IMarker;
/**
* An event fired when the decoration
* is rendered, returns the dom element
* associated with the decoration.
*/
readonly onRender: IEvent<HTMLElement>;
/**
* The HTMLElement that gets created after the
* first _onRender call, or undefined if accessed before
* that.
*/
readonly element: HTMLElement | undefined;
}
/**
* Options provided when registering a decoration
* containing a @param marker, @param anchor,
* @param x offset from the anchor, @param width in cells
* and @param height in cells.
*/
export interface IDecorationOptions {
/**
* The line in the terminal where
* the decoration will be displayed
*/
marker: IMarker;
/*
* Where the decoration will be anchored -
* defaults to the left edge
*/
anchor?: 'right' | 'left';
/**
* The x position offset relative to the anchor
*/
x?: number;
/**
* The width of the decoration in cells, which defaults to
* cell width
*/
width?: number;
/**
* The height of the decoration in cells, which defaults to
* cell height
*/
height?: number;
}
/**
@@ -863,13 +930,22 @@ declare module 'xterm' {
* @param cursorYOffset The y position offset of the marker from the cursor.
* @returns The new marker or undefined.
*/
registerMarker(cursorYOffset: number): IMarker | undefined;
registerMarker(cursorYOffset?: number): IMarker | undefined;
/**
* @deprecated use `registerMarker` instead.
*/
addMarker(cursorYOffset: number): IMarker | undefined;
/**
* (EXPERIMENTAL) Adds a decoration to the terminal using
* @param decorationOptions, which takes a marker and an optional anchor,
* width, height, and x offset from the anchor. Returns the decoration or
* undefined if the alt buffer is active or the marker has already been disposed of.
* @throws when options include a negative x offset.
*/
registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined;
/**
* Gets whether the terminal has an active selection.
*/
+37 -30
View File
@@ -1153,10 +1153,10 @@ chokidar@3.5.1:
optionalDependencies:
fsevents "~2.3.1"
chokidar@3.5.2:
version "3.5.2"
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.2.tgz#dba3976fcadb016f66fd365021d91600d01c1e75"
integrity sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==
chokidar@3.5.3:
version "3.5.3"
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd"
integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==
dependencies:
anymatch "~3.1.2"
braces "~3.0.2"
@@ -1398,7 +1398,14 @@ debug@4.3.1:
dependencies:
ms "2.1.2"
debug@4.3.2, debug@^4.3.1, debug@^4.3.2:
debug@4.3.3:
version "4.3.3"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.3.tgz#04266e0b70a98d4462e6e288e38259213332b664"
integrity sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==
dependencies:
ms "2.1.2"
debug@^4.3.1, debug@^4.3.2:
version "4.3.2"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.2.tgz#f0a49c18ac8779e31d4a0c6029dfb76873c7428b"
integrity sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==
@@ -2140,10 +2147,10 @@ glob@7.1.6, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6:
once "^1.3.0"
path-is-absolute "^1.0.0"
glob@7.1.7, glob@^7.1.1:
version "7.1.7"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.7.tgz#3b193e9233f01d42d0b3f78294bbeeb418f94a90"
integrity sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==
glob@7.2.0, glob@^7.2.0:
version "7.2.0"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023"
integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==
dependencies:
fs.realpath "^1.0.0"
inflight "^1.0.4"
@@ -2152,10 +2159,10 @@ glob@7.1.7, glob@^7.1.1:
once "^1.3.0"
path-is-absolute "^1.0.0"
glob@^7.2.0:
version "7.2.0"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023"
integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==
glob@^7.1.1:
version "7.1.7"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.7.tgz#3b193e9233f01d42d0b3f78294bbeeb418f94a90"
integrity sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==
dependencies:
fs.realpath "^1.0.0"
inflight "^1.0.4"
@@ -2983,32 +2990,32 @@ mocha@^8.3.2:
yargs-parser "20.2.4"
yargs-unparser "2.0.0"
mocha@^9.1.3:
version "9.1.3"
resolved "https://registry.yarnpkg.com/mocha/-/mocha-9.1.3.tgz#8a623be6b323810493d8c8f6f7667440fa469fdb"
integrity sha512-Xcpl9FqXOAYqI3j79pEtHBBnQgVXIhpULjGQa7DVb0Po+VzmSIK9kanAiWLHoRR/dbZ2qpdPshuXr8l1VaHCzw==
mocha@^9.2.0:
version "9.2.0"
resolved "https://registry.yarnpkg.com/mocha/-/mocha-9.2.0.tgz#2bfba73d46e392901f877ab9a47b7c9c5d0275cc"
integrity sha512-kNn7E8g2SzVcq0a77dkphPsDSN7P+iYkqE0ZsGCYWRsoiKjOt+NvXfaagik8vuDa6W5Zw3qxe8Jfpt5qKf+6/Q==
dependencies:
"@ungap/promise-all-settled" "1.1.2"
ansi-colors "4.1.1"
browser-stdout "1.3.1"
chokidar "3.5.2"
debug "4.3.2"
chokidar "3.5.3"
debug "4.3.3"
diff "5.0.0"
escape-string-regexp "4.0.0"
find-up "5.0.0"
glob "7.1.7"
glob "7.2.0"
growl "1.10.5"
he "1.2.0"
js-yaml "4.1.0"
log-symbols "4.1.0"
minimatch "3.0.4"
ms "2.1.3"
nanoid "3.1.25"
nanoid "3.2.0"
serialize-javascript "6.0.0"
strip-json-comments "3.1.1"
supports-color "8.1.1"
which "2.0.2"
workerpool "6.1.5"
workerpool "6.2.0"
yargs "16.2.0"
yargs-parser "20.2.4"
yargs-unparser "2.0.0"
@@ -3048,10 +3055,10 @@ nanoid@3.1.20:
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.20.tgz#badc263c6b1dcf14b71efaa85f6ab4c1d6cfc788"
integrity sha512-a1cQNyczgKbLX9jwbS/+d7W8fX/RfgYR7lVWwWOGIPNgK2m0MWvrGF6/m4kk6U3QcFMnZf3RIhL0v2Jgh/0Uxw==
nanoid@3.1.25:
version "3.1.25"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.25.tgz#09ca32747c0e543f0e1814b7d3793477f9c8e152"
integrity sha512-rdwtIXaXCLFAQbnfqDRnI6jaRHp9fTcYBjtFKE8eezcZ7LuLjhUaQGNeMXf1HmRoCH32CLz6XwX0TtxEOS/A3Q==
nanoid@3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.2.0.tgz#62667522da6673971cca916a6d3eff3f415ff80c"
integrity sha512-fmsZYa9lpn69Ad5eDn7FMcnnSR+8R34W9qJEijxYhTbfOWzr22n1QxCMzXLK+ODyW2973V3Fux959iQoUxzUIA==
natural-compare@^1.4.0:
version "1.4.0"
@@ -4421,10 +4428,10 @@ workerpool@6.1.0:
resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.1.0.tgz#a8e038b4c94569596852de7a8ea4228eefdeb37b"
integrity sha512-toV7q9rWNYha963Pl/qyeZ6wG+3nnsyvolaNUS8+R5Wtw6qJPTxIlOP1ZSvcGhEJw+l3HMMmtiNo9Gl61G4GVg==
workerpool@6.1.5:
version "6.1.5"
resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.1.5.tgz#0f7cf076b6215fd7e1da903ff6f22ddd1886b581"
integrity sha512-XdKkCK0Zqc6w3iTxLckiuJ81tiD/o5rBE/m+nXpRCB+/Sq4DqkfXZ/x0jW02DG1tGsfUGXbTJyZDP+eu67haSw==
workerpool@6.2.0:
version "6.2.0"
resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.2.0.tgz#827d93c9ba23ee2019c3ffaff5c27fccea289e8b"
integrity sha512-Rsk5qQHJ9eowMH28Jwhe8HEbmdYDX4lwoMWshiCXugjtHqMD9ZbiqSDLxcsfdqsETPzVUtX5s1Z5kStiIM6l4A==
wrap-ansi@^6.2.0:
version "6.2.0"