Compare commits

...
788 Commits
Author SHA1 Message Date
Daniel Imms 4f35c21355 v3.14.1 2019-05-31 09:30:49 -07:00
Daniel Imms 13133d699f Make sure xterm.css gets built to dist/ 2019-05-31 09:30:40 -07:00
Daniel Imms 0ed75dc100 Merge pull request #2145 from xtermjs/Tyriar-patch-1
v3.14.0
2019-05-30 16:37:12 -07:00
Daniel Imms b3a631750d v3.14.0 2019-05-30 16:26:37 -07:00
Daniel Imms 9643de4c01 Merge pull request #2141 from Tyriar/ts_3.5
typescript@3.5
2019-05-29 16:38:37 -07:00
Daniel Imms ab1263f669 typescript@3.5 2019-05-29 16:10:54 -07:00
Daniel Imms 9ebe094a2f Merge pull request #2140 from Tyriar/2136_fix_dom_renderer
Fix DOM renderer
2019-05-28 11:56:31 -07:00
Daniel Imms 0c3f2d7222 Revert "Revert "fix: don't use Math.floor in DomRenderer charWidth computation""
This reverts commit 1feb0a27e9.
2019-05-28 09:49:14 -07:00
Daniel Imms a39da0f0d5 Ensure DOM renderer has styles injected on ctor
Fixes #2136
2019-05-28 09:49:03 -07:00
Daniel Imms adac12eded Merge pull request #2134 from Tyriar/fix_underline
Draw underline over char glyph
2019-05-28 08:49:01 -07:00
Daniel Imms 030fb78712 Merge branch 'master' into fix_underline 2019-05-26 21:19:03 -07:00
Daniel Imms 96b3eb982b Draw underline over char glyph 2019-05-26 21:12:36 -07:00
Daniel Imms 5276ad8146 Merge pull request #2133 from Tyriar/remove_scripts
Remove release scripts
2019-05-26 13:17:19 -07:00
Daniel Imms 4c42393aa4 Remove release scripts
They're no longer used, azure-pipelines.yml does this now
2019-05-26 13:07:37 -07:00
Daniel Imms 1942b4ad51 Merge pull request #2130 from Tyriar/2117_J_tests
Tests for erase in display
2019-05-25 21:34:11 -07:00
Daniel Imms 05ca1cb7dc Up timeouts 2019-05-25 21:25:56 -07:00
Daniel Imms 2f4c5b93fa Merge pull request #2131 from Tyriar/72149_npe
Fix NPE in reflow
2019-05-25 21:23:19 -07:00
Daniel Imms de8d9a720a Fix NPE in reflow
See microsoft/vscode#72149
2019-05-25 21:17:36 -07:00
Daniel Imms 7c2bc70aac Tests for erase in display
Part of #2117
2019-05-25 21:13:47 -07:00
Daniel Imms 44e01ba006 Merge pull request #2128 from Tyriar/1965_optimize_a11y
Only update a11y announcements on animation frame
2019-05-25 19:03:22 -07:00
Daniel Imms 8ffdc14143 Only update a11y announcements on animation frame
Fixes #1965
2019-05-25 11:46:02 -07:00
Daniel Imms fda964b3ea Merge pull request #2127 from jerch/2126_fix
fix losing data after RIS command
2019-05-24 16:39:40 -07:00
Jörg Breitbart b748eca980 fix losing data behind RIS 2019-05-24 23:17:40 +02:00
jerch bbefedb6eb Merge pull request #2125 from jerch/2124_fix_colormode_switches
fix colormode switches
2019-05-24 21:13:59 +02:00
Jörg Breitbart c387180a47 add color mode transition tests 2019-05-24 21:09:11 +02:00
Jörg Breitbart 0e1704f563 cleanup 2019-05-24 20:50:25 +02:00
Daniel Imms b8f55f1c8f Merge pull request #2123 from Tyriar/revert_floor
Revert "fix: don't use Math.floor in DomRenderer charWidth computation"
2019-05-24 06:55:26 -07:00
Daniel Imms a428193424 Merge branch 'master' into revert_floor 2019-05-24 06:50:37 -07:00
Daniel Imms 1feb0a27e9 Revert "fix: don't use Math.floor in DomRenderer charWidth computation"
This reverts commit 23bdb2a6c2.
2019-05-24 06:48:48 -07:00
jerch a92e4adfe2 Merge pull request #2120 from jerch/2108_memory_exhaustion
trim processed chunks from write buffer
2019-05-22 22:28:01 +02:00
Jörg Breitbart 7716e9abda trim already processed chunks 2019-05-22 14:12:15 +02:00
Daniel Imms 6e196da59c Merge pull request #2118 from Tyriar/2117_csi_tests
Add tests for CSI @-I
2019-05-21 22:53:48 -07:00
Daniel Imms 69e8f62149 Make tests way faster by avoiding page reload 2019-05-21 22:43:02 -07:00
Daniel Imms b46757010f Boost timeout of xterm output tests 2019-05-21 22:34:04 -07:00
Daniel Imms a74173adf2 Add tests for CSI @-I
Part of #2117
2019-05-21 22:29:39 -07:00
Daniel Imms c10b18113e Merge pull request #2116 from Tyriar/2097_fix_addon_dispose
Fix new addon dispose
2019-05-21 16:35:28 -07:00
Daniel Imms 7786e59c77 Fix new addon dispose
Fixes #2097
2019-05-21 16:26:50 -07:00
Daniel Imms 9dfac7e674 Merge pull request #2114 from Tyriar/consolidate_api_test_timeout
Move API test timeout to top-level describe
2019-05-21 14:58:37 -07:00
Daniel Imms 922e594fcf Fix lint 2019-05-21 14:51:48 -07:00
Daniel Imms c73aa07135 Merge branch 'master' into consolidate_api_test_timeout 2019-05-21 14:48:22 -07:00
Daniel Imms acea979fe0 Move API test timeout to top-level describe
I learned a thing.
2019-05-21 14:48:03 -07:00
Daniel Imms e4f626e3b6 Merge pull request #2110 from Tyriar/split_out_integration
Split out integration tests into its own job
2019-05-21 14:39:35 -07:00
Daniel Imms 9a4f419820 Merge branch 'master' into split_out_integration 2019-05-21 14:33:27 -07:00
Daniel Imms 209c44898d Merge pull request #2113 from Tyriar/2111_automate_patch_release
Run release against builds on release/* branches
2019-05-21 14:32:12 -07:00
Daniel Imms d3ff32da72 Run release against builds on release/* branches
Fixes #2111
2019-05-21 14:26:28 -07:00
Daniel Imms f81d1cb1cf Increase integration test sleep 2019-05-21 14:20:58 -07:00
Daniel Imms 8f09f58119 Merge branch 'master' into split_out_integration 2019-05-21 12:39:22 -07:00
Daniel Imms ab0eedc513 Fix job name 2019-05-21 11:19:54 -07:00
Daniel Imms 8e460dfe55 Merge pull request #2109 from Tyriar/2107_cursor_report
Fix status reporting
2019-05-21 11:13:32 -07:00
Daniel Imms 37ed1e4796 Split out integration tests into its own job
This will make Linux finish faster and release trigger faster
2019-05-21 11:12:34 -07:00
Daniel Imms d0a3164e99 Fix lint 2019-05-21 11:01:40 -07:00
Daniel Imms 968dd93ba7 Fix status reporting
Fixes #2107
2019-05-21 10:57:26 -07:00
Daniel Imms 739723fe92 Merge pull request #2101 from Tyriar/bad_comment_
Remove old comment
2019-05-18 21:21:34 -07:00
Daniel Imms 2f08b87d95 Remove old comment 2019-05-18 21:21:02 -07:00
Daniel Imms eb70ddcba5 Merge pull request #2100 from Tyriar/2099_pausing
Support pausing in RenderCoordinator
2019-05-18 21:19:51 -07:00
Daniel Imms 43015f85f4 Move Renderer ownership to RenderCoordinator 2019-05-18 21:15:18 -07:00
Daniel Imms 87dca56dee Support pausing in RenderCoordinator
This actually removes the pause rendering functionality when the resize
and theme changes but simplifies it as a result, they also rarely happen
especially when off screen/hidden.

Fixes #2099
2019-05-18 20:20:46 -07:00
Daniel Imms 72e6863a7f Merge pull request #2098 from Tyriar/2093_logo_demo
Use logo in demo
2019-05-18 20:05:33 -07:00
Daniel Imms e83aa64794 Merge pull request #2096 from Tyriar/render_coordinator
Introduce RenderCoordinator
2019-05-18 20:03:20 -07:00
Daniel Imms c7416a6eaa Use logo in demo
Fixes #2093
2019-05-18 19:56:28 -07:00
Daniel Imms 9ed5920275 Add some basic fg/bg renderer tests 2019-05-18 19:51:44 -07:00
Daniel Imms 497d31bec3 onThemeChange -> setColors 2019-05-18 19:31:35 -07:00
Daniel Imms 24de302d2f onWindowResize -> onDevicePixelRatioChange 2019-05-18 19:27:06 -07:00
Daniel Imms 5f70b3f3be Move window resize listener to render coordinator 2019-05-18 19:22:51 -07:00
Daniel Imms b64cc0a9de Fix ui project build 2019-05-18 19:16:06 -07:00
Daniel Imms 3e4c1b5a21 Move ScreenDprMonitor to be owned by RenderCoordinator
(and a11y manager)
2019-05-18 19:12:37 -07:00
Daniel Imms f698cafccb Move the renderer events to RenderCoordinator 2019-05-18 19:02:42 -07:00
Daniel Imms 8792bb5f36 Introduce a RenderCoordinator that owns common Renderer logic
Part of #2005
2019-05-18 18:47:44 -07:00
Daniel Imms ac48ef977b Merge pull request #2094 from Tyriar/2005_colormanager
Move ColorManager to ui
2019-05-18 18:16:47 -07:00
Daniel Imms 4119c7d1d7 Move ColorManager ownership to Terminal 2019-05-18 18:10:04 -07:00
Daniel Imms 000fefdead Move ColorManager to ui
Part of #2005
Part of #1507
2019-05-18 17:49:48 -07:00
Daniel Imms 33955398e7 Merge pull request #2092 from Tyriar/layering
Move BufferReflow and Marker to core
2019-05-18 09:59:54 -07:00
Daniel Imms dbafc793da Move Marker to core 2019-05-17 23:41:26 -07:00
Daniel Imms 88c1ff6f11 Move BufferReflow to core 2019-05-17 23:37:44 -07:00
Daniel Imms 610c4c9726 Merge pull request #2088 from Tyriar/fix_coveralls
Fix what branch coverage is sent with
2019-05-16 10:47:34 -07:00
Daniel Imms e2214b0239 Fix what branch coverage is sent with 2019-05-16 10:39:53 -07:00
Daniel Imms 23d3a27a6f Merge pull request #2085 from Tyriar/a11y_fix
Add sanity check in AccessibilityManager
2019-05-16 09:58:02 -07:00
Daniel Imms d04016d51f Merge branch 'master' into a11y_fix 2019-05-16 09:52:54 -07:00
Daniel Imms fede301f5f Merge pull request #2081 from Tyriar/xterm-tests
Support debugging integration tests
2019-05-16 09:52:42 -07:00
Daniel Imms a62ec679bd Move readFileSync out of test 2019-05-15 12:49:13 -07:00
Daniel Imms da79fec5c2 Disable failing test on Linux 2019-05-15 11:57:32 -07:00
Daniel Imms 80025804b3 Add sanity check in AccessibilityManager
Fixes #2082
2019-05-15 11:47:15 -07:00
Daniel Imms 955c45906b Merge branch 'master' into xterm-tests 2019-05-13 21:30:33 -07:00
Daniel Imms f331269ef4 Support debugging integration tests 2019-05-13 21:11:56 -07:00
Daniel Imms 9cb6706e2d Merge pull request #2080 from jerch/demo_update
apply attach changes to demo
2019-05-13 21:07:48 -07:00
Daniel Imms b9cfbbea8f xterm-addon-attach@0.1.0-beta8 2019-05-13 20:34:14 -07:00
Jörg Breitbart 58d461bb90 optimize Buffer.concat 2019-05-13 15:33:54 +02:00
Jörg Breitbart 69411916cb make linter happy 2019-05-13 01:45:21 +02:00
Jörg Breitbart 1a596f9608 default to string transport 2019-05-13 01:41:51 +02:00
Jörg Breitbart 67be8c36e7 utf8 switch in server.js 2019-05-13 01:40:54 +02:00
Jörg Breitbart 0b50c06810 Merge branch 'master' into demo_update 2019-05-13 01:25:51 +02:00
Jörg Breitbart 53167df1a8 document utf8 switch in client.ts 2019-05-13 01:24:41 +02:00
jerch eef855601b Merge pull request #1904 from jerch/utf8_input
Support for raw UTF8 input
2019-05-13 01:23:39 +02:00
Jörg Breitbart 6fd5cdd8f1 apply attach changes to demo 2019-05-13 00:58:14 +02:00
Jörg Breitbart c0e9bbe87f Revert "change demo to utf8 input"
This reverts commit e6e5ecc0f2.
2019-05-12 22:17:06 +02:00
Jörg Breitbart 06c292c11d Merge branch 'master' into utf8_input 2019-05-12 22:16:59 +02:00
Jörg Breitbart bda068c5f0 docs 2019-05-12 22:07:14 +02:00
Daniel Imms 031a30b007 Merge pull request #2065 from Tyriar/1128_addons
New addon API
2019-05-12 12:54:45 -07:00
Daniel Imms 3475764946 Types/doc polish 2019-05-12 10:59:26 -07:00
Daniel Imms f90239377b Fix possible infinite loop if addon disposes terminal 2019-05-12 10:47:35 -07:00
Daniel Imms 1502fea431 Remove unused fullscreen addon from demo 2019-05-12 10:41:01 -07:00
Daniel Imms caf8f9d35e Update search addon 2019-05-12 10:39:39 -07:00
Daniel Imms d230f93bde Remove old search addon from demo 2019-05-12 10:25:41 -07:00
Daniel Imms bae125b789 Merge branch 'master' into utf8_input 2019-05-12 10:24:39 -07:00
Daniel Imms eda04bcb23 Add new search addon, move AddonManager to public
Because addons build on top of the API it needs to live in public, the main
reason for this is because the implementation of buffer differs on
public/Terminal and src/Terminal.
2019-05-12 10:22:29 -07:00
Jörg Breitbart 523d562f73 fix missing types 2019-05-12 18:49:23 +02:00
Daniel Imms 0c588a7333 Fix missed merge conflict 2019-05-12 09:45:09 -07:00
Daniel Imms f7612a49d2 Merge remote-tracking branch 'ups/master' into 1128_addons 2019-05-12 09:44:09 -07:00
Daniel Imms 34244248bf Merge pull request #2078 from Tyriar/1443_selection_api
select and getSelectionPosition APIs
2019-05-12 09:40:03 -07:00
Daniel Imms f654044e5e Fix search addon unit tests 2019-05-12 00:17:59 -07:00
Daniel Imms a7fa58cc60 Add API test for getSelectionPosition 2019-05-11 23:59:51 -07:00
Daniel Imms 9ca9f7296e Use new APIs in selection addon 2019-05-11 23:56:34 -07:00
Daniel Imms 5bbbbf7348 Expose getSelectionPosition and select APIs instead 2019-05-11 23:52:38 -07:00
Daniel Imms da7c39e526 Add API test 2019-05-11 23:37:42 -07:00
Daniel Imms 6674f05cc8 Implement setSelection API
Fixes #1443
2019-05-11 23:35:31 -07:00
Daniel Imms 161c7f9339 Use API clearSelection in search addon 2019-05-11 23:22:46 -07:00
Daniel Imms 780e924b21 Use stable API for buffer in search addon 2019-05-11 23:15:04 -07:00
Daniel Imms 1f000054c2 Merge remote-tracking branch 'ups/master' into 1128_addons 2019-05-11 22:48:49 -07:00
Daniel Imms c13d6e5eeb Make write and writeln consistent with writeUtf8 test 2019-05-11 22:29:01 -07:00
Daniel Imms e5dfc56030 Add api test for writeUtf8 2019-05-11 22:24:33 -07:00
Daniel Imms 00423407c1 Clean up 2019-05-11 22:18:18 -07:00
Daniel Imms 2340fcd071 Fix compile and update yarn.lock with utf8 2019-05-11 21:21:36 -07:00
Daniel Imms 59a1749d44 Merge branch 'master' into utf8_input 2019-05-11 21:16:05 -07:00
Daniel Imms 0fae9bb5a8 Merge pull request #2074 from Tyriar/1994_buffer_api
Buffer API
2019-05-11 21:15:55 -07:00
Daniel Imms 70ba349a85 Fix invalid index check, add api tests 2019-05-11 20:58:51 -07:00
Daniel Imms 49574be510 Add warnings to getCell and getLine 2019-05-11 20:49:48 -07:00
Daniel Imms f41e00ed97 Return undefined if cell or line doesn't exist 2019-05-11 20:46:55 -07:00
Jörg Breitbart ce079f399c fix docstring 2019-05-12 00:07:07 +02:00
Jörg Breitbart 7a567d2717 apply time-based limit 2019-05-11 23:10:18 +02:00
Daniel Imms cd3f2609d6 Make tests use buffer API over _core 2019-05-11 12:12:53 -07:00
Daniel Imms 8a929ed142 Add API tests for loadAddon 2019-05-11 11:02:15 -07:00
Daniel Imms 46995fdbc1 Add tests for buffer.getLine 2019-05-11 10:46:00 -07:00
Daniel Imms d694eae6b5 Add some buffer tests 2019-05-11 10:32:53 -07:00
Daniel Imms bce2eae66f Update yarn.lock 2019-05-11 10:15:29 -07:00
Daniel Imms 3554d3e075 Merge branch 'master' into 1128_addons 2019-05-11 10:14:48 -07:00
Daniel Imms eb6deb67f9 Fix build issues 2019-05-11 10:10:31 -07:00
Daniel Imms 7c55e1be9e Merge remote-tracking branch 'ups/master' into 1994_buffer_api 2019-05-11 10:05:31 -07:00
Daniel Imms 7549ca59be Merge branch 'master' into utf8_input 2019-05-11 10:02:14 -07:00
Daniel Imms af81acc50e Merge pull request #2068 from Tyriar/api_tests
API integration tests
2019-05-11 10:00:36 -07:00
Jörg Breitbart f033800554 fix imports 2019-05-11 17:56:26 +02:00
Daniel Imms 623dd56190 Merge pull request #2073 from jerch/2072_remove_instanceof
remove instanceof check
2019-05-11 08:46:22 -07:00
Jörg Breitbart 740604f035 remove instanceof check 2019-05-11 17:21:16 +02:00
Daniel Imms 3f68a60e73 Merge pull request #2067 from starpit/floor
fix: don't use Math.floor in DomRenderer charWidth computation
2019-05-11 00:50:20 -07:00
Daniel Imms 556c56ff94 Merge branch 'master' into api_tests 2019-05-11 00:10:33 -07:00
Daniel Imms dfb5d8e663 Add focus, blur test 2019-05-11 00:00:57 -07:00
Daniel Imms 7562948259 Add tests for all API events 2019-05-10 23:47:28 -07:00
Daniel Imms 34ac1bc502 Fix clearSelection not firing onSelectionChange
Fixes #2070
2019-05-10 23:38:45 -07:00
Daniel Imms 97b176e701 Add selection tests 2019-05-10 23:14:16 -07:00
Daniel Imms 8a5b6b0356 Move puppeteer to devDependencies 2019-05-10 22:58:23 -07:00
Daniel Imms 55d590348c API integration tests
Part of #1247
2019-05-10 22:57:27 -07:00
Nick Mitchell 23bdb2a6c2 fix: don't use Math.floor in DomRenderer charWidth computation
Fixes #2063
2019-05-11 00:59:59 -04:00
Daniel Imms 16aaf6c58c Fix build 2019-05-10 21:08:06 -07:00
Daniel Imms 22c9ad7145 Merge remote-tracking branch 'origin/master' into 1994_buffer_api 2019-05-10 21:04:26 -07:00
Daniel Imms 41a7cabbc2 Merge branch 'master' into 1128_addons 2019-05-10 21:02:55 -07:00
Daniel Imms 724baf22f9 Merge pull request #2066 from Tyriar/lib_import
Fix bad import from lib
2019-05-10 20:52:20 -07:00
Daniel Imms c04ebe974f Fix bad import from lib
Related #1996
2019-05-10 20:51:18 -07:00
Daniel Imms fcee890819 Merge branch 'master' into 1128_addons 2019-05-10 20:47:17 -07:00
Daniel Imms d696046921 Simplify and mark new addon API as experimental 2019-05-10 20:38:08 -07:00
Daniel Imms 518e2734ba Re-introduce original attach and webLinks addons back
We could release this in v3 if we don't break addons like this
2019-05-10 20:30:47 -07:00
Daniel Imms eedcbccb15 Merge remote-tracking branch 'origin/master' into 1128_addons 2019-05-10 20:24:43 -07:00
Daniel Imms d7e6cb17a5 Merge pull request #2062 from Tyriar/windowsMode_typings
Add typings for windowsMode
2019-05-10 19:41:49 -07:00
Daniel Imms b658baf921 Add typings for windowsMode 2019-05-10 16:26:53 -07:00
Daniel Imms 10c6e900ad Merge pull request #2059 from Tyriar/buffer_core
Move BufferLine into core
2019-05-10 15:17:14 -07:00
Daniel Imms b608c9d0ef Merge branch 'master' into buffer_core 2019-05-10 14:57:32 -07:00
Daniel Imms 0e0bcde957 Merge pull request #2061 from Tyriar/fix_beta_off_patch
Make sure beta builds are always on patch version 0
2019-05-10 07:57:33 -07:00
Daniel Imms d735196a30 Make sure beta builds are always on patch version 0 2019-05-10 07:39:42 -07:00
Daniel Imms 59aec69379 Add BufferLine into core and strict null check 2019-05-09 22:19:33 -07:00
Daniel Imms 228ec7b818 Move BufferLine into core 2019-05-09 22:15:32 -07:00
Daniel Imms 7b7fc53157 Move more BufferLine dependencies into core 2019-05-09 22:10:07 -07:00
Daniel Imms f381cdfc33 Move a bunch of types to core 2019-05-09 21:57:33 -07:00
Daniel Imms ac6bced6e7 Merge pull request #2056 from xtermjs/Tyriar-patch-1
Add missing space to typings
2019-05-09 10:12:29 -07:00
Daniel Imms 1a61401cbe Merge pull request #2057 from Tyriar/fix_cd_after_release
Fix beta CD for new major/minor beta versions
2019-05-09 10:12:11 -07:00
Daniel Imms 6d55ab23fd Fix beta CD for new major/minor beta versions 2019-05-09 10:11:25 -07:00
Daniel Imms 3abc090412 Add missing space to typings 2019-05-09 10:02:42 -07:00
Daniel Imms 21f1498977 Merge pull request #2053 from xtermjs/Tyriar-patch-1
Update year in README
2019-05-09 10:00:09 -07:00
Daniel Imms 16c0550d7c Merge pull request #2051 from Tyriar/2019_prepublish
Replace prepublish
2019-05-09 09:59:58 -07:00
Daniel Imms 517d5cfd0e Merge pull request #2050 from Tyriar/2048_demo_line_height
Update terminal dimensions after line height changes
2019-05-09 09:59:49 -07:00
Daniel Imms d363416fb4 Merge pull request #2046 from NizamLZ/fix/2045
Allow + char in URI path
2019-05-09 09:59:36 -07:00
Daniel Imms ffc868a4b8 Merge pull request #2055 from xtermjs/Tyriar-patch-2
v3.13.0
2019-05-09 09:42:56 -07:00
Daniel Imms e40f924913 v3.13.0 2019-05-09 09:37:07 -07:00
Daniel Imms ca1c647442 Merge pull request #2054 from Tyriar/fix_beta_cd
Actually fix beta CD
2019-05-09 08:20:58 -07:00
Daniel Imms 79e182d63f Actually fix beta CD
Part of #2044
2019-05-09 08:20:19 -07:00
Daniel Imms c1b56722d3 Merge pull request #2052 from jflatow/unit-separator
Send the unit separator for C-_, like some other keyboards
2019-05-09 07:56:30 -07:00
Daniel Imms b959fc8d6a Update year in README 2019-05-09 07:49:50 -07:00
Khaja Nizamuddin 48753625f3 Merge branch 'master' into fix/2045 2019-05-09 12:43:25 +05:30
Jared Flatow dc236aeb20 Send the unit separator for C-_, like some other keyboards 2019-05-09 00:06:32 -07:00
Daniel Imms 71886167be Replace prepublish
On prepublishOnly we want to do a full build, on prepare we want
to only build the ts projects so the .d.ts files are avilable.

Fixes #2019
2019-05-08 23:08:39 -07:00
Daniel Imms 4b1c54d634 Update terminal dimensions after line height changes
Fixes #2048
2019-05-08 20:53:24 -07:00
Daniel Imms 2d0937ac1d Merge pull request #2047 from Tyriar/2044_cd_fix
Base the next beta version on the correct current beta
2019-05-08 18:08:18 -07:00
Daniel Imms 0d5cd9582f Base the next beta version on the correct current beta
Fixes #2044
2019-05-08 18:03:06 -07:00
Khaja Nizamuddin e831bdf1ef Allow + char in URI path 2019-05-08 21:06:22 +05:30
Daniel Imms 4729bfaa2d Merge pull request #2043 from cubiclesoft/add_php_app_server
Added PHP App Server to Real-world uses section.
2019-05-07 18:30:04 -07:00
cubiclesoft a88469aac6 Added PHP App Server to Real-world uses section. 2019-05-07 12:07:00 -07:00
jerch d6da010c7e Merge pull request #2035 from jerch/2028_handle_joined_cells
cell representation for joined cells
2019-05-06 22:42:05 +02:00
Daniel Imms c6138edec2 Merge branch 'master' into 2028_handle_joined_cells 2019-05-05 18:55:53 -07:00
Daniel Imms 15a6c9846d Merge pull request #2040 from Tyriar/devcontainer
Add a dev container
2019-05-05 16:06:55 -07:00
Jörg Breitbart ca193ed00d Merge branch 'master' into 2028_handle_joined_cells 2019-05-05 21:54:16 +02:00
Jörg Breitbart ffcdba8d5b simplify JoinedCellData 2019-05-05 21:48:35 +02:00
Daniel Imms d68c441b86 Merge pull request #2038 from roottool/fix-that-webLinks-not-interpreted#1954
Fix that webLinks not interpreted
2019-05-03 21:13:00 -07:00
Daniel Imms 613f877954 Add a dev container
This lets users using VS Code/Docker avoid installing node and the C++
dependencies necessary for running xterm.js.
2019-05-03 21:08:25 -07:00
Daniel Imms a1353a8fb4 Merge pull request #2039 from roottool/refacter-the-css-code-of-cursor-blink
Refactored the CSS code of cursor blink
2019-05-03 12:59:04 -07:00
roottool 4269777f1c Fix: Refactored the css code of keyframes blink 2019-05-04 02:54:38 +09:00
roottool 23eed1b3ea Fix: Changed pathClause 2019-05-04 02:02:26 +09:00
Daniel Imms ebc0083531 Merge pull request #2036 from Tyriar/protect_markers
Make Terminal.markers readonly
2019-05-02 09:45:15 -07:00
Daniel Imms 7d3e2d7da4 Merge branch 'master' into protect_markers 2019-05-02 09:33:24 -07:00
Daniel Imms ea09c6af09 Fix build 2019-05-02 09:23:13 -07:00
Daniel Imms 267357c4de Merge pull request #2032 from Tyriar/task
Add build and test task
2019-05-02 07:54:42 -07:00
Daniel Imms f416bd155a Make compound task the default 2019-05-02 07:53:28 -07:00
Daniel Imms c6a3fb3b42 Merge branch 'master' into task 2019-05-02 07:47:29 -07:00
Daniel Imms cb33c897d2 Merge branch 'master' into protect_markers 2019-04-30 12:20:30 -07:00
Daniel Imms 469d5158a0 Make Terminal.markers readonly 2019-04-30 19:19:41 +00:00
Daniel Imms 766b1745d3 Merge pull request #2027 from Tyriar/auto_release
Automatically release stable builds
2019-04-29 22:50:16 -07:00
Daniel Imms 68182ed162 Merge branch 'master' into auto_release 2019-04-29 22:42:20 -07:00
Daniel Imms d261862ccb Merge pull request #2026 from Tyriar/ui_project
Create UI project, strict null check some more files
2019-04-29 22:41:54 -07:00
Daniel Imms 8d6682a2bc Merge branch 'master' into ui_project 2019-04-29 22:26:28 -07:00
Daniel Imms 63833b59ed Fix lint 2019-04-29 22:24:44 -07:00
Jörg Breitbart 4fd9b3f249 jix joined cell representation as overloaded CellData type 2019-04-30 00:58:18 +02:00
jerch 3f145b8a0c Merge pull request #2034 from jerch/2031_defaultcolor_handling
apply default fg color for uncached chars and underline in canvas renderer
2019-04-29 23:40:18 +02:00
Jörg Breitbart 1f42e7c06a fix fg color for underline 2019-04-29 23:19:16 +02:00
Jörg Breitbart 42b1dca7db add handling for default fg color 2019-04-29 22:33:01 +02:00
Daniel Imms f0c3bab156 Add build and test task 2019-04-29 11:35:42 -07:00
Daniel Imms 4453a1cbf7 Automatically release stable builds
Part of #1921
2019-04-28 03:52:43 +00:00
Daniel Imms 362152004b Move files not in ui project back to root 2019-04-28 02:28:20 +00:00
Daniel Imms 88d5cf46f8 Strict null check ScreenDprMonitor 2019-04-28 02:16:28 +00:00
Daniel Imms ebfa57b71c Strict null check RenderDebouncer 2019-04-28 02:03:41 +00:00
Daniel Imms 4c3082c924 Remove ITerminal dependency in RenderDebouncer 2019-04-28 01:59:40 +00:00
Daniel Imms 672db9437f Create ui project, add Lifecycle 2019-04-28 01:55:56 +00:00
Daniel Imms eb10652e46 Merge branch 'master' into utf8_input 2019-04-27 18:49:46 -07:00
Daniel Imms f4bbfc30f3 Merge pull request #1872 from Tyriar/cd
Setup continuous deployment beta builds
2019-04-27 18:33:46 -07:00
Daniel Imms 65eaea39d3 Base the beta build off of the next minor version 2019-04-28 00:45:51 +00:00
Daniel Imms 70db48b533 Add notice about beta builds in readme 2019-04-28 00:34:44 +00:00
Daniel Imms 779ebe87e5 Merge branch 'master' into cd 2019-04-27 16:52:09 -07:00
Daniel Imms 6237eaa86f Merge pull request #2025 from thebradbain/fix-terminal-resize
Clear line cache on terminal resize
2019-04-27 16:42:30 -07:00
Bradley Bain 736bd53e49 Clear line cache on terminal resize 2019-04-25 21:24:07 -07:00
Daniel Imms 69d43d4a40 Merge branch 'master' into cd 2019-04-14 16:04:49 -07:00
Daniel Imms 017a5b1218 Merge branch 'master' into utf8_input 2019-04-14 12:54:26 -07:00
Daniel Imms c6f785f51a Merge pull request #2014 from starpit/underline
feat: add underline support to DOM renderer
2019-04-14 11:24:25 -07:00
Nick Mitchell 8718d3a4ca feat: add underline support to DOM renderer
part of #1896
2019-04-14 13:47:58 -04:00
Daniel Imms 7aaa1ad0bb Merge pull request #2009 from Tyriar/single_quote
Add vscode setting for single quote style
2019-04-12 09:36:35 -07:00
Daniel Imms fa70aa0983 Merge pull request #2008 from Tyriar/event_emitter_2
Explicit event API
2019-04-11 09:08:15 -07:00
Daniel Imms 89a3931a68 Merge branch 'master' into event_emitter_2 2019-04-11 08:15:19 -07:00
Daniel Imms a0ee09a7bc Add vscode setting for single quote style
This will alwayts use single quotes when TS server does auto imports,
implement interface, etc.
2019-04-11 08:14:18 -07:00
Daniel Imms 94415a227d Merge pull request #1866 from ntchjb/master
Fix search addons: Fix problem of skipping some results and incorrect text selection after resize window
2019-04-10 03:45:58 -04:00
Daniel Imms 0b4ff4bb6b Merge branch 'master' into event_emitter_2 2019-04-10 03:34:37 -04:00
Daniel Imms 4be376d397 Merge branch 'master' into master 2019-04-10 03:31:40 -04:00
Daniel Imms 5362ea195c Add first API test 2019-04-10 00:24:50 -07:00
Daniel Imms b55bedc6af Add terminal API interface to Types.ts
This is needed since the API/public buffer now differs from the TerminalCore's
2019-04-09 21:29:08 -07:00
Daniel Imms cf4b59567b Add tests for new events 2019-04-09 21:01:55 -07:00
Daniel Imms b9bc219ecc Make events in d.ts alphabetical 2019-04-09 20:52:18 -07:00
Daniel Imms f4c7e3fd7d Remove extends EventEmitter from Linkifier 2019-04-09 20:43:42 -07:00
Daniel Imms 36567e60df Remove extends EventEmitter from more classes 2019-04-09 20:36:08 -07:00
Daniel Imms 2e0e24c382 Remove extends EventEmitter from SelectinoManager 2019-04-09 20:26:18 -07:00
Daniel Imms 290bde6163 Convert refresh to EventEmitter2 2019-04-09 20:16:26 -07:00
Daniel Imms db78b75e8b Convert selection to EventEmitter2 2019-04-09 20:05:13 -07:00
Daniel Imms 87328fe293 Convert data to EventEmitter2 2019-04-09 19:53:02 -07:00
Daniel Imms 2c96c262f4 Convert scroll to EventEmitter2 2019-04-09 19:42:53 -07:00
Daniel Imms f111aa27dc Convert linefeed to EventEmitter2 2019-04-09 19:35:41 -07:00
Daniel Imms 8e5d372b81 Convert cursormove to EventEmitter2 2019-04-09 19:31:59 -07:00
Daniel Imms 8b5b64605e Start find previous from the current viewport 2019-04-09 19:53:22 -04:00
Daniel Imms 0c388c69bc Merge branch 'master' into pr/jerch/1904 2019-04-09 19:38:39 -04:00
Daniel Imms 660466d244 Merge pull request #1923 from jerch/fix_1879
signal sequence was finally handled
2019-04-09 11:15:02 -04:00
Daniel Imms dee4429151 Remove unneeded interfaces 2019-04-09 11:11:16 -04:00
Daniel Imms c3d1b0703c Merge branch 'master' into fix_1879 2019-04-09 11:10:17 -04:00
Daniel Imms 71827f7981 Merge branch 'master' into master 2019-04-09 11:01:02 -04:00
Daniel Imms 2d80abe801 Merge branch 'master' into 1128_addons 2019-04-09 10:57:32 -04:00
Daniel Imms 6b0f2170ea Merge branch 'master' into 1994_buffer_api 2019-04-09 10:57:22 -04:00
Daniel Imms a86c2943b8 Merge branch 'master' into event_emitter_2 2019-04-09 10:57:11 -04:00
Daniel Imms cb591f212a Merge pull request #2006 from skavanagh/patch-1
Added Bastillion to Real-world uses
2019-04-09 10:53:08 -04:00
Sean Kavanagh e5f94391ae Update README.md 2019-04-09 09:10:29 -04:00
Daniel Imms 5476f4cb93 Merge pull request #2002 from Tyriar/1932_broken_reflow
Fix reflow case where wrapped line portions ended in \t
2019-04-08 18:59:21 -04:00
Daniel Imms aaf8b80292 Merge branch 'master' into 1932_broken_reflow 2019-04-08 18:51:02 -04:00
Daniel Imms 9ab3d319d1 Merge pull request #2004 from Tyriar/dim_dom_renderer_test
Add a test for dim flag in dom renderer
2019-04-08 10:56:15 -04:00
Daniel Imms efeca49705 Mostly implemented buffer API
Part of #1994
2019-04-07 15:29:37 -04:00
Daniel Imms 267071ead6 Add deprecation message to applyAddon 2019-04-07 15:00:27 -04:00
Daniel Imms 26a80d0d35 Convert to much simpler model 2019-04-07 14:53:09 -04:00
Daniel Imms 49f58f5d25 Merge branch 'master' into 1128_addons 2019-04-07 14:24:29 -04:00
Daniel Imms a0b4ee56f6 Add a test for dim flag in dom renderer 2019-04-07 14:17:38 -04:00
Daniel Imms 6ff81c43f6 Convert marker dispose, selection manager newselection 2019-04-07 14:09:31 -04:00
Daniel Imms 625e168e1b Convert title and resize events 2019-04-07 14:02:11 -04:00
Daniel Imms b2ab89ddaa Merge branch 'master' into event_emitter_2 2019-04-07 13:56:50 -04:00
Daniel Imms 4ace81d994 Merge branch 'master' into 1932_broken_reflow 2019-04-07 13:53:23 -04:00
Daniel Imms 25745777c6 Merge pull request #2003 from Tyriar/fix_merge_problem
Fix compile error from conflicting PRs
2019-04-07 13:53:09 -04:00
Daniel Imms 74b68ac1c7 Fix compile error from conflicting PRs 2019-04-07 13:49:56 -04:00
Daniel Imms 2a4208cf3e Merge branch 'master' into 1932_broken_reflow 2019-04-07 13:48:31 -04:00
Daniel Imms e58033bbac Merge pull request #1895 from jerch/true_color
True color support
2019-04-07 13:45:08 -04:00
Daniel Imms bd92aaf4dc Merge branch 'master' into 1932_broken_reflow 2019-04-07 13:44:29 -04:00
Daniel Imms 3ac313f8e1 Fix reflow larger case
Fixes #1932
2019-04-07 13:18:05 -04:00
Daniel Imms ab44650d87 Fix reflow smaller case related to ended \t chars
Part of #1932
2019-04-07 13:16:20 -04:00
Daniel Imms 11ab4dbd5b Merge pull request #2000 from starpit/dim
feat: add DIM support to DomRenderer
2019-04-07 09:47:51 -04:00
Daniel Imms 92c435a16d Use same dim opacity as canvas renderer 2019-04-07 09:45:54 -04:00
Nick Mitchell ddd02b4968 feat: add DIM support to DomRenderer
part of #1896
2019-04-06 16:04:50 -04:00
Daniel Imms 7e36bf47c6 Have undefined | true mean success 2019-04-06 15:21:20 -04:00
Daniel Imms d6daf40250 Merge branch 'master' into fix_1879 2019-04-06 15:08:58 -04:00
Daniel Imms 09a5fd4e61 Fix capitalization in color mode 2019-04-06 15:06:42 -04:00
Daniel Imms 171b9337cb Make BufferLine enums const 2019-04-06 14:56:15 -04:00
Daniel Imms bb324ba9c1 Move width shift back into enum 2019-04-06 14:55:43 -04:00
Daniel Imms 27283e45e9 Merge branch 'master' into true_color 2019-04-06 14:13:14 -04:00
Daniel Imms 23ce6ce19e Merge pull request #1999 from asadm/master
add CodeInterview.io in README
2019-04-05 11:12:03 -04:00
Asad Memon e8a9994583 add CodeInterview.io in README 2019-04-04 11:58:19 -07:00
Daniel Imms 46f3b3fa62 Merge branch 'master' into true_color 2019-04-04 01:06:26 -04:00
Daniel Imms 2f8d49c74d Convert internal key event usages 2019-04-04 00:45:57 -04:00
Daniel Imms 629238ae70 Use onResize in demo 2019-04-04 00:25:03 -04:00
Daniel Imms 3f9d90ecf6 Remove unnecessary paste listener from demo 2019-04-04 00:23:12 -04:00
Daniel Imms f35dfb496a Use onKey in demo 2019-04-04 00:22:34 -04:00
Daniel Imms 4e6f4be6cc Convert Listener to an interface 2019-04-04 00:14:04 -04:00
Daniel Imms 798cf8abc3 Merge branch 'master' into event_emitter_2 2019-04-04 00:12:07 -04:00
Daniel Imms b591db8c02 Merge pull request #1997 from Tyriar/1989_backspace_demo
Fix backspace on demo
2019-04-04 00:06:09 -04:00
Daniel Imms b18829963c Fix backspace on demo
buffer.x will probably be public API soon (#1994)

Fixes #1989
2019-04-04 00:01:43 -04:00
Daniel Imms e402dba635 Merge pull request #1995 from termius/fix_csi_scroll_down
Fix CSI scroll down handler
2019-04-03 23:57:01 -04:00
Daniel Imms 2023b5b677 Merge pull request #1992 from Tyriar/1990_layering_libs
Ensure DOM APIs cannot be used in common and core
2019-04-03 18:28:53 -04:00
Vadim Zakondyrin 68e3d7fa16 Fix CSI scroll down handler
The issue has been introduced in cd8477a942
2019-04-03 12:10:32 +06:00
Daniel Imms 3f993d675f Fix conflicts with new stuff 2019-04-02 20:17:33 -04:00
Daniel Imms 7ddcf1a2a2 Merge branch 'master' into 1128_addons 2019-04-02 20:16:09 -04:00
Daniel Imms e6333bde2d Merge branch 'master' into true_color 2019-04-02 17:13:21 -07:00
Daniel Imms 51c7337099 Merge branch 'master' into 1990_layering_libs 2019-04-02 17:10:49 -07:00
Daniel Imms 6476429077 Merge pull request #1993 from Tyriar/windows_mode
Replace winptyCompat addon with windowsMode option
2019-04-02 17:08:31 -07:00
Daniel Imms 7b9a718af0 Fix some imports to point at ts files, not lib/ 2019-04-02 16:45:43 -07:00
Daniel Imms 60113fa155 Replace EventEmitter usage in CharMeasure 2019-04-02 11:38:55 -07:00
Daniel Imms 60c2b20d68 Replace EventEmitter usage in BufferSet 2019-04-02 11:35:20 -07:00
Daniel Imms f8fb4da9f4 Replace EventEmitter with EventEmitter2 in CircularList 2019-04-02 11:30:47 -07:00
Daniel Imms 5f014be057 Add onCursorMove, replace .on usage in addons 2019-04-02 11:12:57 -07:00
Daniel Imms b12d2c218e Remove reference to winptyCompat tsconfig 2019-04-02 11:03:24 -07:00
Daniel Imms ba4662ac14 Introduce new onEvent APIs and deprecate on/off/etc.
Part of #1505
2019-04-02 11:01:16 -07:00
Daniel Imms ca1f7f1392 Add EventEmitter2 2019-04-02 10:11:59 -07:00
Daniel Imms fa505111a2 Replace winptyCompat addon with windowsMode option 2019-04-02 08:51:23 -07:00
Daniel Imms 2e10c8c25e Clean up 2019-04-02 08:04:39 -07:00
Daniel Imms 8d4da3c2ac Merge branch 'master' into pr/jerch/1895 2019-04-02 07:53:22 -07:00
Daniel Imms 81dda13218 Merge branch 'master' into 1990_layering_libs 2019-04-02 06:35:59 -07:00
Daniel Imms 0a8d57ce74 Merge pull request #1878 from jerch/utf32_buffer
Utf32 buffer
2019-04-01 17:50:09 -07:00
Daniel Imms 5c8c680dac Fix feedback 2019-04-01 00:01:41 -07:00
Daniel Imms 69f8700557 Merge branch 'master' into pr/jerch/1878 2019-03-31 23:34:46 -07:00
Daniel Imms 4a3609f805 Move platform to common and remove dom dependence in common
Because common/ imports 'xterm', it also imported dom accidentally.

Fixes #1990
2019-03-31 23:24:29 -07:00
Daniel Imms fa2cbb0aca Merge branch 'master' into fix_1879 2019-03-30 15:38:53 -07:00
Daniel Imms b05c66b132 Merge pull request #1987 from Tyriar/1986_localhost
Recommend 127.0.0.1:3000 on mac and linux too
2019-03-30 15:36:42 -07:00
Daniel Imms 75046235af Merge pull request #1984 from Tyriar/project_references
Build using TypeScript project references
2019-03-30 15:09:11 -07:00
Daniel Imms bab94ebbdc Merge pull request #1958 from nikonso/1947-weblinks-with-selection
WebLinks plugin don't follow link on selection
2019-03-30 15:07:34 -07:00
Daniel Imms 4d008c66f1 Recommend 127.0.0.1:3000 on mac and linux too
Fixes #1986
2019-03-30 15:06:25 -07:00
Daniel Imms bf9d879efa Fix typo 2019-03-30 14:59:31 -07:00
Daniel Imms 636afa11d6 Merge branch 'master' into 1947-weblinks-with-selection 2019-03-30 14:57:24 -07:00
Daniel Imms 1283123d4d Merge pull request #1985 from Tyriar/fix_demo_non_win
Fix demo on non-Windows
2019-03-30 14:55:00 -07:00
Daniel Imms e182e3d4e4 Fix demo on non-Windows
Broke in #1978
2019-03-30 14:52:48 -07:00
Daniel Imms 51033596ef Merge branch 'master' into 1947-weblinks-with-selection 2019-03-30 14:47:46 -07:00
Daniel Imms 0960a7ae14 Merge branch 'master' into project_references 2019-03-30 14:43:11 -07:00
Daniel Imms 84d5963e7e Merge pull request #1976 from Tyriar/1975_renderer_pause_fix
Fix renderer pausing to not full refresh every time
2019-03-30 14:42:38 -07:00
Daniel Imms 10b7b467b4 Merge pull request #1978 from Tyriar/move_windows_check
Let consumers decide whether winptyCompat should be active
2019-03-30 14:41:58 -07:00
Daniel Imms 2317a319d0 Merge pull request #1982 from Tyriar/1981_prevent_scroll_on_focus
Prevent scroll on focus
2019-03-30 14:40:26 -07:00
Daniel Imms f80924fb8d Remove zmodem demo 2019-03-30 14:12:15 -07:00
Daniel Imms 18f4dc6b3e Remove concurrently 2019-03-30 14:09:51 -07:00
Daniel Imms b400f9ca3b Don't clear terminal on yarn watch 2019-03-30 14:08:57 -07:00
Daniel Imms 0b78011fa3 Adopt project references
Recent versions of TypeScript has improved the performance of project
references so they are now viable to switch over to.
2019-03-30 14:05:24 -07:00
Daniel Imms 2be0a5819b Merge branch 'master' into 1981_prevent_scroll_on_focus 2019-03-29 19:57:33 -07:00
Daniel Imms e346f8bba3 Prevent scroll on focus
Fixes #1981
2019-03-29 19:46:24 -07:00
Daniel Imms 1451bcb6ae Merge pull request #1961 from nikonso:1773-take-drawBoldTextInBrightColors-into-account
Fix #1773 - drawBoldTextInBrightColors is not taken into account with DOM renderer
2019-03-29 12:59:20 -07:00
Daniel Imms f1ef9abc77 Merge pull request #1979 from JesseStolwijk:master
#1977 Add blinking cursor to DomRenderer
2019-03-29 12:51:49 -07:00
Jesse Stolwijk f9df863ee0 Add blinking cursor to DomRenderer 2019-03-25 22:17:16 +01:00
Daniel Imms e6ac5634bb Merge branch 'master' into move_windows_check 2019-03-23 11:38:19 -07:00
Daniel Imms 4dbe8ec1db Let consumers decide whether winptyCompat should be active 2019-03-23 10:58:26 -07:00
Daniel Imms 8b6d564598 Merge pull request #1968 from nikonso/new-feature
Use new vscode serverReadyAction
2019-03-23 09:37:15 -07:00
Daniel Imms 131e4f78bd Fix renderer pausing to not full refresh every time
Fixes #1975
2019-03-23 09:08:55 -07:00
Daniel Imms 99beeb2270 Merge pull request #1973 from zhaojh329/master
Modify uses
2019-03-19 10:46:23 -07:00
Jianhui Zhao 0d63cecc6b Modify uses
Signed-off-by: Jianhui Zhao <jianhuizhao329@gmail.com>
2019-03-19 14:04:16 +08:00
ntchjb 3c444ca39a Fixed coding format 2019-03-15 19:20:23 +07:00
ntchjb 03e648a0fb Fixed the case that, if findPrevious is run without any selection, then start scan at the last row 2019-03-15 19:15:41 +07:00
Max 48a98fa29f Merge pull request #1 from xtermjs/master
update branch
2019-03-15 17:26:55 +07:00
Bruno Ribeito 27ebe8c353 Use new vscode serverReadyAction 2019-03-11 20:58:42 +00:00
Bruno Ribeito 62cfa642cb Better aproach, check i a selection is being performed 2019-03-11 20:54:17 +00:00
Bruno Ribeito c16e3e54e8 Merge branch 'master' of https://github.com/xtermjs/xterm.js into 1947-weblinks-with-selection 2019-03-11 20:41:23 +00:00
Daniel Imms bc86a61a0c Merge pull request #1966 from turtle0x1/master
Add LxdMosaic to list of applications that use xterm.js
2019-03-11 10:29:22 -07:00
turtle0x1 19d36f9228 Update readme with link for lxdmosaic 2019-03-11 13:47:25 +00:00
Bruno Ribeito b4bef0986c Remove circular dependency 2019-03-10 22:03:38 +00:00
Daniel Imms f63bd3d218 Merge branch 'master' into master 2019-03-08 11:47:41 -08:00
Daniel Imms 5cf2a21ddd Merge branch 'master' into cd 2019-03-08 11:44:55 -08:00
Daniel Imms 7e1b5cc7e9 Merge pull request #1952 from NickShaffner/master
Fix for issue #812: Xterm.js's encoding of mouse coordinate
2019-03-08 09:50:24 -08:00
Daniel Imms 4046d682c9 v3.12.0 2019-03-08 09:21:01 -08:00
jerch 127a2ca27a Merge branch 'master' into master 2019-03-07 23:28:26 +01:00
Bruno Ribeito 525af9c36a Fix #1773 2019-03-07 21:30:32 +00:00
Bruno Ribeito 5878aa099d Cleaner aproach 2019-03-07 21:00:07 +00:00
Daniel Imms 9597ad20d3 Merge pull request #1950 from Tyriar/1943_winpty_compat_reflow
Disable reflow when winptyCompat is on
2019-03-07 12:38:30 -08:00
Daniel Imms 6deb3e13d2 Merge pull request #1938 from Tyriar/1937_uncached_char_top_fix
Align y draw coord with how cache draws it
2019-03-07 12:38:00 -08:00
Daniel Imms ecb7f429bc Merge branch 'master' into 1937_uncached_char_top_fix 2019-03-07 12:34:57 -08:00
Daniel Imms ce09ae4ff8 Merge pull request #1959 from nikonso/1908-leave-callback
Fix #1908 - leaveCallback does not fire when links are on the edge of the terminal
2019-03-07 11:17:49 -08:00
Bruno Ribeito bc41cc7d27 Fix #1908 2019-03-07 00:15:03 +00:00
Bruno Ribeito 51e2cdfafb WIP: First draft 2019-03-06 21:45:53 +00:00
Daniel Imms f41146fb40 Merge pull request #1934 from ahtshamraziq/fix-docker-compose-variable-substitution
Compose file: fix variable substitution
2019-03-05 08:31:59 -08:00
Daniel Imms 8fa7f177a6 Merge pull request #1956 from JesseStolwijk/master
Replace array shift with offset
2019-03-05 08:18:06 -08:00
Jesse Stolwijk 07430a4892 Replace array shift with offset (#1955) 2019-03-05 00:00:54 +01:00
Daniel Imms ce1c45fb18 Merge pull request #1818 from juancampa/perf3
Use a time-based limit in Terminal._innerWrite
2019-03-04 10:50:27 -08:00
Daniel Imms 55f3aeeff5 Merge branch 'master' into perf3 2019-03-04 10:39:00 -08:00
Daniel Imms a99364eae9 Merge branch 'master' into fix_1879 2019-03-04 10:37:51 -08:00
Daniel Imms 7b41b002c6 Merge branch 'master' into master 2019-03-04 10:05:18 -08:00
Daniel Imms 32e157bfaa Remove unnecessary else 2019-03-04 10:04:52 -08:00
Daniel Imms 655c595011 Merge pull request #1927 from pfitzseb/sp/mousehandlerfix
*actually* fix mouse handler before term attached
2019-03-04 10:01:42 -08:00
Daniel Imms 064a372c71 Merge branch 'master' into sp/mousehandlerfix 2019-03-04 10:00:38 -08:00
Daniel Imms c63f15a9b2 Fix lint 2019-03-04 10:00:04 -08:00
Daniel Imms 1aa40eb58b Merge branch 'master' into fix-docker-compose-variable-substitution 2019-03-04 09:57:33 -08:00
Daniel Imms d8b973b126 Merge pull request #1929 from coderaiser/patch-1
fix: Renderer: IntersectionObserver can produce more then 1 entry
2019-03-04 09:54:53 -08:00
Daniel Imms 05b162eefd Merge branch 'master' into patch-1 2019-03-04 09:53:12 -08:00
Daniel Imms 46aa6a40f9 Merge branch 'master' into fix-docker-compose-variable-substitution 2019-03-04 09:49:12 -08:00
Daniel Imms 9f2aec1944 Merge branch 'master' into 1937_uncached_char_top_fix 2019-03-04 09:47:33 -08:00
Daniel Imms 42b66d98b2 Merge branch 'master' into 1943_winpty_compat_reflow 2019-03-04 09:46:15 -08:00
Daniel Imms 1eee182860 Merge pull request #1930 from Tyriar/1926_reflow_larger_fix
Make sure the viewport is filled when reflowing a row change
2019-03-04 09:45:43 -08:00
Daniel Imms bd602fb078 Merge branch 'master' into 1926_reflow_larger_fix 2019-03-04 07:23:06 -08:00
Daniel Imms 8ea254bdc7 Merge pull request #1953 from cherouvim/patch-1
docs: style improvements
2019-03-01 11:26:06 -08:00
Ioannis Cherouvim 09754fddf8 xterm.js references to the library should not be backticked 2019-03-01 21:15:20 +02:00
Ioannis Cherouvim a8a0344d1a docs: style improvements
- Added a trailing dot at the end of each list line.
- Consistent usage of `xterm.js` in "Real-world uses".
2019-03-01 14:51:19 +02:00
Ioannis Cherouvim 4e479b455a docs: Consistent style on lists.
Added some missing dots, capitalized a couple of lines.
2019-03-01 14:40:21 +02:00
Nick Shaffner e178139907 Fix for issue #812: Xterm.js's encoding of mouse coordinate
See: https://github.com/xtermjs/xterm.js/issues/812
Changed the utf-8 mouse encoding to match iTerm
2019-02-27 22:37:22 -08:00
Sebastian Pfitzner 0854f84653 actually fix mouse handler before term attached 2019-02-26 11:31:22 +01:00
Daniel Imms a35cd43395 Merge branch 'master' into 1943_winpty_compat_reflow 2019-02-20 10:02:54 -08:00
Daniel Imms 3285374618 Disable reflow when winptyCompat is on
Fixes #1943
2019-02-20 07:03:40 -08:00
Daniel Imms 6ad6a01c3f Merge pull request #1949 from liuzheng/master
Update README.md
2019-02-20 06:21:08 -08:00
夏虫 e1c1c7a4f2 Update README.md
move it to the bottom
2019-02-20 14:57:16 +08:00
Daniel Imms 437a59d4cd Merge branch 'master' into 1937_uncached_char_top_fix 2019-02-19 07:19:33 -08:00
夏虫 9f29eeed23 Update README.md
(Jumpserver)[https://github.com/jumpserver/] use xterm.js
2019-02-19 17:19:19 +08:00
coderaiser b0f01beb0f Merge branch 'master' into patch-1 2019-02-15 16:57:58 +02:00
Daniel Imms 857ae4b702 Merge pull request #1936 from Tyriar/1935_remove_font_family
Remove font-family from .css file
2019-02-11 07:24:11 -08:00
Daniel Imms c934200c86 Also set font size 2019-02-11 06:07:31 -08:00
Daniel Imms 78426d8a12 Make the composition view use the same font as the terminal 2019-02-11 06:05:14 -08:00
Daniel Imms 817401bbcd Align y draw coord with how cache draws it
Fixes #1937
2019-02-11 05:32:47 -08:00
Daniel Imms 84d7bfeacc Remove font-family from .css file
Fixes #1935
2019-02-11 05:14:55 -08:00
Ahtsham Raziq 92be01dd81 Compose file: fix variable substitution 2019-02-09 23:24:32 +05:00
Daniel Imms d17dfbc73f Cover a case when resizing smaller making y go out of bounds 2019-02-08 12:13:00 -08:00
Daniel Imms ba9ed43acd Merge branch 'master' into utf32_buffer 2019-02-05 11:39:08 -08:00
Daniel Imms b88230db8b Make sure the viewport is filled when reflowing a row change
Fixes #1926
2019-02-05 08:20:16 -08:00
coderaiser 69d3a4667f fix: Renderer: IntersectionObserver can produce more then 1 entry 2019-02-04 13:28:14 +02:00
Daniel Imms abf6a9fa58 Merge pull request #1925 from xtermjs/Tyriar-patch-1
Add roadmap wiki link to README
2019-02-03 15:37:50 -08:00
Daniel Imms ea9d1d7ef0 Merge pull request #1928 from xtermjs/Tyriar-patch-2
Update license year
2019-02-03 15:37:40 -08:00
Daniel Imms f3aac10bbd Update license year 2019-02-03 10:46:20 -08:00
Daniel Imms f30012b985 Merge branch 'master' into fix_1879 2019-02-01 20:03:40 -08:00
Daniel Imms db9aa5c88b Merge pull request #1916 from skyway777:master
Clear selection state if selection doesn't exists
2019-02-01 19:39:25 -08:00
Daniel Imms 1829cb7609 Add roadmap wiki link to README 2019-02-01 19:28:07 -08:00
Daniel Imms 6d6be9bed8 Merge pull request #1924 from xtermjs/Tyriar-patch-1
v3.11.0
2019-02-01 18:29:09 -08:00
Daniel Imms 54a1319f57 v3.11.0 2019-02-01 18:23:10 -08:00
Jörg Breitbart 65dbf88460 apply bright shift for uncached draws 2019-02-01 21:44:51 +01:00
Jörg Breitbart 0c54b1a29e Merge branch 'master' into true_color 2019-02-01 20:59:31 +01:00
Jörg Breitbart 179e5d5892 Merge branch 'master' into utf32_buffer 2019-02-01 20:59:02 +01:00
Jörg Breitbart 6993daef15 Merge branch 'master' into fix_1879 2019-02-01 20:38:45 +01:00
Jörg Breitbart 801db3831d type add methods 2019-02-01 20:32:07 +01:00
Jörg Breitbart e813ae02fc return true from inputhandler methods to signal sequence was handled 2019-02-01 20:28:45 +01:00
Daniel Imms 73ec8a89a0 Merge pull request #1912 from mofux/contextmenu_fix
Make textarea positioning work with css transformations
2019-02-01 09:53:16 -08:00
Jörg Breitbart c2446df966 Merge branch 'utf32_buffer' into true_color 2019-02-01 01:30:29 +01:00
Jörg Breitbart 2de39024be Merge branch 'utf32_buffer' into utf8_input 2019-02-01 01:11:58 +01:00
Jörg Breitbart 079729f1b4 change property getter into methods 2019-02-01 01:10:20 +01:00
Jörg Breitbart ec3a95f727 Merge branch 'utf32_buffer' into utf8_input 2019-01-31 23:32:48 +01:00
Jörg Breitbart c4a5e0862c Merge branch 'utf32_buffer' into true_color 2019-01-31 23:16:23 +01:00
Jörg Breitbart 594797e05b Merge branch 'master' into utf32_buffer 2019-01-31 23:15:50 +01:00
jerch 682efc7206 Merge pull request #1887 from jerch/fix_linkifier_offset
account empty cells in stringIndexToBufferIndex
2019-01-31 23:07:24 +01:00
jerch 9791d8ab49 Merge branch 'master' into fix_linkifier_offset 2019-01-31 23:02:53 +01:00
Daniel Imms 8e88ce8da5 Merge pull request #1919 from xtermjs/Tyriar-patch-1
Improve docs on Terminal.resize
2019-01-31 14:02:12 -08:00
Daniel Imms 828f14f2ab Improve docs on Terminal.resize 2019-01-31 14:00:39 -08:00
Jörg Breitbart 7b7f85e3cf add test for correct tab handling, docs 2019-01-31 22:56:55 +01:00
Jörg Breitbart 7da40b9d56 Merge branch 'utf32_buffer' into true_color 2019-01-31 22:13:33 +01:00
Jörg Breitbart a1b61e05f9 Merge branch 'master' into utf32_buffer 2019-01-31 21:51:58 +01:00
jerch 21cb9872f3 Merge branch 'master' into fix_linkifier_offset 2019-01-31 20:33:26 +01:00
Daniel Imms 93d3842156 Merge pull request #1917 from Tyriar/vscode67364
Fix various problems with reflow
2019-01-31 09:07:51 -08:00
Daniel Imms db31a44da3 Fix tests 2019-01-30 22:14:47 -08:00
Daniel Imms 33e46682b1 Fix various problems with reflow
- No longer reflow lines where the full unwrapped line contains the cursor
- Add guards to prevent y and ybase becoming invalid values

Part of Microsoft/vscode#67364
Fixes #1910
2019-01-30 21:47:39 -08:00
Nikita Chuklinov fc1692b3eb clear state if selection doesn't exists 2019-01-30 20:34:16 +03:00
Daniel Imms d48982b60e Merge branch 'master' into cd 2019-01-28 07:25:55 -08:00
Daniel Imms 9c72254d3a Bump to 3.11.0 2019-01-28 07:23:58 -08:00
Daniel Imms af6544d6d9 Improve package.json modifications 2019-01-28 07:23:46 -08:00
Jörg Breitbart 39fd6c427d name polishing, docs 2019-01-28 10:42:44 +01:00
Jörg Breitbart ef3c0c0e72 Merge branch 'fix_linkifier_offset' of git+ssh://github.com/jerch/xterm.js into fix_linkifier_offset 2019-01-27 22:23:08 +01:00
Jörg Breitbart 13f0fd0503 Merge branch 'master' into fix_linkifier_offset 2019-01-27 22:22:52 +01:00
Jörg Breitbart c60f9b1468 code formatting 2019-01-27 20:06:58 +01:00
Jörg Breitbart e77e36cbb7 fix typo in HAS_CONTENT 2019-01-27 19:59:13 +01:00
Jörg Breitbart b20730ee4d add more docs 2019-01-27 19:40:28 +01:00
Jörg Breitbart acea73c8ac Merge branch 'master' into utf32_buffer 2019-01-27 19:02:09 +01:00
jerch cc531f1933 Merge pull request #1796 from jerch/typedarray_parser
support typed array in parser
2019-01-27 18:22:08 +01:00
Daniel Imms c1b0c9bbf1 Merge branch 'master' into fix_linkifier_offset 2019-01-27 08:45:57 -08:00
Jörg Breitbart 358d70d721 cleanup gitignore 2019-01-27 17:34:58 +01:00
Jörg Breitbart 975dfc4a5e Merge branch 'typedarray_parser' of git+ssh://github.com/jerch/xterm.js into typedarray_parser 2019-01-27 17:29:41 +01:00
Jörg Breitbart fdf784ea96 Merge branch 'master' into typedarray_parser 2019-01-27 17:29:12 +01:00
Jörg Breitbart e67bef69f4 cleanup types 2019-01-27 17:28:15 +01:00
Daniel Imms 8b6327b563 Merge branch 'master' into master 2019-01-27 08:18:40 -08:00
Daniel Imms 8e37883b90 Merge branch 'master' into typedarray_parser 2019-01-27 08:11:37 -08:00
Daniel Imms ace855d0fe Merge branch 'master' into contextmenu_fix 2019-01-27 07:49:03 -08:00
Daniel Imms 007cac22d2 Merge branch 'master' into cd 2019-01-27 07:29:34 -08:00
Daniel Imms b047045949 Replace hardcoded version 2019-01-27 07:28:04 -08:00
Daniel Imms c7cfca395c Merge pull request #1871 from Tyriar/1867_readonly_api_props
Make Terminal API properties readonly
2019-01-27 07:25:15 -08:00
Daniel Imms a7f2348c63 Merge branch 'master' into 1867_readonly_api_props 2019-01-27 07:17:07 -08:00
Daniel Imms 51a148508e Merge pull request #1900 from Tyriar/cleanup
Remove unused var and unneeded defensive check
2019-01-27 07:16:53 -08:00
Jörg Breitbart 90a41a113a review changes 2019-01-27 13:22:34 +01:00
Thomas Zilz 2a1f25e7dc Make textarea positioning work with css transformations on parent elements 2019-01-27 13:15:37 +01:00
Daniel Imms 7e3b315d91 Merge branch 'master' into perf3 2019-01-26 13:20:32 -08:00
Daniel Imms 3a32a5714c Merge branch 'master' into cleanup 2019-01-26 13:17:32 -08:00
Daniel Imms 273cc45357 Merge branch 'master' into cd 2019-01-26 13:12:40 -08:00
Daniel Imms 4dab31dbe0 Merge branch 'master' into 1867_readonly_api_props 2019-01-26 12:58:13 -08:00
Jörg Breitbart 87e9789748 more explicit docs for DCS parsers 2019-01-25 18:16:01 +01:00
Jörg Breitbart 4855b60ffe move utf32ToString to TextDecoder.ts 2019-01-25 17:35:12 +01:00
Jörg Breitbart 21165a4286 Merge branch 'master' into typedarray_parser 2019-01-25 16:53:06 +01:00
Daniel Imms c908da351b Merge pull request #1864 from Tyriar/622_reflow3
Reflow text on resize
2019-01-24 20:04:33 -08:00
Daniel Imms 52a429f584 Merge branch 'master' into 622_reflow3 2019-01-24 19:01:21 -08:00
Jörg Breitbart e6447bc91f Merge branch 'master' into true_color 2019-01-25 02:43:52 +01:00
Jörg Breitbart b587c2f30f Merge branch 'master' into utf8_input 2019-01-25 02:43:07 +01:00
Jörg Breitbart 4f531f8490 Merge branch 'master' into utf32_buffer 2019-01-25 02:42:40 +01:00
Jörg Breitbart 19931e75c0 Merge branch 'master' into typedarray_parser 2019-01-25 02:41:56 +01:00
Daniel Imms 2edf02e468 Merge pull request #1905 from pfitzseb/sp/mousehandler
fix mouse event listener change before term attached
2019-01-24 11:49:09 -08:00
Daniel Imms e1363e9fe7 Replace if/add/remove with a toggle 2019-01-24 11:32:09 -08:00
Daniel Imms 109e3a50e8 jsdoc 2019-01-24 10:20:18 -08:00
Daniel Imms 99ac78031c Remove out param from reflow large method 2019-01-24 09:11:16 -08:00
Daniel Imms d4bd8ae2be Pull more parts out of reflow larger 2019-01-23 23:58:38 -08:00
Daniel Imms dfff04cf3d Pull toRemove step into BufferReflow 2019-01-23 23:51:28 -08:00
Daniel Imms deb04ca162 Merge branch 'master' into 622_reflow3 2019-01-23 22:48:53 -08:00
Daniel Imms 178c513407 Clean up 2019-01-23 22:48:32 -08:00
Daniel Imms df7cd9c319 Get reflow smaller working for wide chars 2019-01-23 22:40:40 -08:00
Daniel Imms ce69dceee7 Progress on reflow smaller with wide chars 2019-01-23 21:46:35 -08:00
Daniel Imms 4843ca5bb8 Fix reflow larger with wide chars 2019-01-21 09:08:33 -08:00
Sebastian Pfitzner 8bdb606fd1 fix mouse event listener change before term attached 2019-01-21 11:09:00 +01:00
Jörg Breitbart e6e5ecc0f2 change demo to utf8 input 2019-01-20 23:06:51 +01:00
Jörg Breitbart 3a2076805c Merge branch 'master' into utf8_input 2019-01-20 22:44:15 +01:00
Jörg Breitbart d7ea0edfcf add utf8 input to terminal 2019-01-20 22:19:50 +01:00
Daniel Imms 50abb43e9c Remove request term info handler
We're not supporting it anyway, no point having it.
2019-01-20 12:56:49 -08:00
Jörg Breitbart 6bdd380890 add utf8 decoder 2019-01-20 21:54:58 +01:00
Daniel Imms 07d3407892 Remove unused var and unneeded defensive check
...items as an arg will always be an array
2019-01-17 09:48:18 -08:00
Daniel Imms 68197ed47b Merge branch 'master' into 622_reflow3 2019-01-16 19:41:46 -08:00
Jörg Breitbart b13b737a07 Merge branch 'master' into true_color 2019-01-17 02:09:44 +01:00
Jörg Breitbart 200f7bf33d Merge branch 'master' into utf32_buffer 2019-01-17 02:09:19 +01:00
Jörg Breitbart 9549896ea5 Merge branch 'master' into typedarray_parser 2019-01-17 02:08:58 +01:00
Jörg Breitbart 2370038523 always draw RGB uncached 2019-01-17 01:41:48 +01:00
Jörg Breitbart df739ab814 remove old color and flag shims 2019-01-17 01:31:59 +01:00
Jörg Breitbart 8d850d6c8b apply CellData to canvas renderer 2019-01-17 01:25:16 +01:00
Jörg Breitbart ae50d84c5a fix characterjoiner to support AttrData 2019-01-17 00:07:25 +01:00
Daniel Imms daaed5ac8f Merge pull request #1897 from juancampa/char-measure-fix
Prevent charsizechanged from firing unnecessarily
2019-01-16 13:07:54 -08:00
Juan Campa e02a9ff09e Prevent charsizechanged from firing unnecessarily 2019-01-15 11:00:34 -05:00
Jörg Breitbart 1536e79542 remove printf remnant 2019-01-15 03:24:47 +01:00
Jörg Breitbart 94c19fb92a preliminarly RGB support in canvas renderer 2019-01-15 03:13:44 +01:00
Jörg Breitbart 9cf479bcfa cleanup rgb channel conversion 2019-01-14 08:51:18 +01:00
Jörg Breitbart 0802582c9b test cases for DOM renderer style changes 2019-01-14 01:00:07 +01:00
Jörg Breitbart 3515c0b220 DOM renderer with RGB support 2019-01-14 00:46:44 +01:00
Jörg Breitbart 70ded42bee follow naming scheme 2019-01-13 23:50:58 +01:00
Jörg Breitbart c444eebd89 RGB support in buffer and attributes 2019-01-13 23:19:56 +01:00
Jörg Breitbart 7c6dad5805 test cases 2019-01-12 19:06:09 +01:00
Jörg Breitbart 40b231edd1 tests for CellData 2019-01-12 18:44:10 +01:00
Jörg Breitbart 35a6aa8326 some docs 2019-01-12 18:04:52 +01:00
Jörg Breitbart 711ae65947 remove get CharData from codebase and deprecate method 2019-01-12 17:50:19 +01:00
Jörg Breitbart ece7192db7 remove set with CharData from codebase and deprecate method 2019-01-12 16:15:24 +01:00
Jörg Breitbart 3e456971b1 change buffer line ctor to new interface 2019-01-12 15:47:50 +01:00
Jörg Breitbart 60a591e8c8 use getNullChar in InputHandler.print 2019-01-12 15:37:20 +01:00
Jörg Breitbart 56dec2849f change resize, fill to new interface, remove _cell on buffer line 2019-01-12 15:21:55 +01:00
Jörg Breitbart fe6919b52a change replaceCells to new interface 2019-01-12 15:05:52 +01:00
Jörg Breitbart 4bf7f3eb89 change deleteCells to new interface 2019-01-12 14:58:30 +01:00
Jörg Breitbart 523ff6e5fc add null and whitespace placeholder cells to buffer 2019-01-12 14:54:54 +01:00
Jörg Breitbart 88a037b309 change insertCells to new interface 2019-01-12 14:34:29 +01:00
Jörg Breitbart ad67f47acb Merge branch 'typedarray_parser' into utf32_buffer 2019-01-12 14:08:53 +01:00
Jörg Breitbart 329d40ab14 cleanup DCS handler 2019-01-12 13:53:43 +01:00
Jörg Breitbart a1f8749411 Merge branch 'typedarray_parser' of git+ssh://github.com/jerch/xterm.js into typedarray_parser 2019-01-12 13:43:55 +01:00
Jörg Breitbart e7e0f67ea8 Merge branch 'master' into typedarray_parser 2019-01-12 13:43:40 +01:00
Jörg Breitbart 907a6b9732 Merge branch 'master' into utf32_buffer 2019-01-12 13:43:11 +01:00
Daniel Imms 7fe3f0a4e3 Improve BufferLine test
Ensure combined is cleared when shrinking and enlarging
2019-01-11 12:58:41 -08:00
ntchjb 9236934586 _findInLine() function should not run multiple times on the same row 2019-01-09 07:01:34 +07:00
Daniel Imms 932d5c7c21 Merge branch 'master' into master 2019-01-08 10:19:38 -08:00
Daniel Imms 509ce5fa3a Merge pull request #1881 from Tyriar/1880_key_repeat
Don't print keys that print more than a single char
2019-01-07 14:43:20 -08:00
Jörg Breitbart fed430b78c Merge branch 'master' into fix_linkifier_offset 2019-01-07 11:39:52 +01:00
Jörg Breitbart 883ad01bd5 make linter happy 2019-01-07 11:39:16 +01:00
Jörg Breitbart 249f8800af account empty cells in stringIndexToBufferIndex 2019-01-07 11:30:26 +01:00
Daniel Imms 0b8c320579 Merge pull request #1886 from epicfaace/typos
Fix typos
2019-01-06 19:01:35 -08:00
Ashwin Ramaswami f04f2efc52 doc: fix more typos 2019-01-06 15:09:57 -08:00
Ashwin Ramaswami f090fa822d doc: fix typos 2019-01-06 15:07:36 -08:00
jerch 9bcf06f658 Merge branch 'master' into typedarray_parser 2019-01-05 05:04:42 +01:00
jerch 6581eb7d88 fix leftover BufferLineConstructor 2019-01-05 01:47:44 +01:00
jerch 90950e2b00 Merge branch 'master' into 622_reflow3 2019-01-05 01:24:28 +01:00
Jörg Breitbart 62dfa84d5d remove get CharData calls from codebase (beside tests) 2019-01-05 01:01:10 +01:00
Jörg Breitbart b93b774971 direct cell attrs getter 2019-01-04 19:14:56 +01:00
Jörg Breitbart 6552a023c1 remove get from linkifier 2019-01-04 18:46:45 +01:00
Jörg Breitbart 13ffed392d remove get calls from renderer 2019-01-04 18:36:07 +01:00
Jörg Breitbart d7e5977d9a remove leftover 2019-01-04 16:19:01 +01:00
Daniel Imms 7a416aa18f Don't print keys that print more than a single char
Fixes #1880
2019-01-03 22:49:03 -08:00
Daniel Imms 7ba17ca770 Convert web links and attach to modules 2019-01-03 22:19:30 -08:00
Daniel Imms 290394b401 New addon prototype
Part of #1128
2019-01-03 20:22:53 -08:00
Jörg Breitbart 802543f7cf replace set(CharData) with setCell 2019-01-04 02:58:15 +01:00
Jörg Breitbart f3619ab61c fix linter error 2019-01-04 01:33:49 +01:00
Jörg Breitbart 2f66efa49a first try to optimize read access 2019-01-04 01:32:56 +01:00
Jörg Breitbart 75fbda1fe6 further opt for InputHandler.print 2019-01-04 01:32:20 +01:00
Jörg Breitbart b0eecea45a partially apply fast data access in InputHandler.print 2019-01-03 21:40:34 +01:00
Jörg Breitbart f63223f8d8 Merge branch 'master' into utf32_buffer 2019-01-03 21:10:40 +01:00
Jörg Breitbart a2a8c3d47f extend buffer line with more direct access methods 2019-01-03 21:08:18 +01:00
jerch f98572cf71 Merge pull request #1877 from jerch/remove_jsarray_buffer
remove js array based buffer line
2019-01-03 20:55:09 +01:00
Jörg Breitbart ba1582cf3c apply utf32 buffer layout 2019-01-03 19:01:43 +01:00
Jörg Breitbart c668bb1243 remove js array based buffer line 2019-01-03 16:08:40 +01:00
Max b4b6bab000 Merge branch 'master' into master 2019-01-03 16:12:19 +07:00
ntchjb c784a012c7 Fix: update findingRow and cumulativeCols before running _findInLine
to be able to scan unwrapped line at the last row
2019-01-03 15:55:27 +07:00
ntchjb efe22d8cf3 Revert "Support wide characters and combined characters"
This reverts commit f961f90bc8.
2019-01-03 15:32:25 +07:00
Jörg Breitbart 27b91f6b00 speedup utf32ToString conversion 2019-01-03 02:55:11 +01:00
Jörg Breitbart 1fb1f556b1 Merge branch 'master' into typedarray_parser 2019-01-03 00:54:12 +01:00
Jörg Breitbart 8fc66a39e3 switch parse buffer to UTF32 2019-01-03 00:48:00 +01:00
Daniel Imms 740ca9ce6d v3.10.0 2019-01-02 14:23:09 -08:00
Jörg Breitbart 2918a6763f fix wrongly changed test case 2019-01-02 22:55:50 +01:00
Jörg Breitbart dfc853aef4 add TextDecoder for string to UTF32 2019-01-02 22:50:15 +01:00
Daniel Imms 4db95f253e Build to dist dir in release build 2019-01-02 13:40:06 -08:00
Daniel Imms 805c02fb18 Merge branch 'master' into cd 2019-01-02 12:56:07 -08:00
Daniel Imms d120ef5301 Re-enable condition and dependsOn 2019-01-02 12:55:44 -08:00
Daniel Imms 68fbb2f8c9 Tweak more 2019-01-02 11:53:51 -08:00
Daniel Imms 515cf7878e Tweak var 2019-01-02 11:49:20 -08:00
Daniel Imms a35ad6aa81 Inherit exit code from publish call 2019-01-02 11:39:44 -08:00
Daniel Imms aa2d28e619 Disable dependsOn 2019-01-02 11:37:34 -08:00
Daniel Imms a6c3f94860 Publish to npm as beta 2019-01-02 11:24:06 -08:00
Daniel Imms ff81ce078b Add a release pipelines job 2019-01-02 09:38:40 -08:00
Jörg Breitbart 5fd764a89d dont pullin everything from .vscode folder 2019-01-02 18:10:51 +01:00
Jörg Breitbart 83c734eae3 Merge branch 'typedarray_parser' of git+ssh://github.com/jerch/xterm.js into typedarray_parser 2019-01-02 18:00:31 +01:00
Jörg Breitbart 570cd786cb Merge branch 'master' into typedarray_parser 2019-01-02 18:00:05 +01:00
Daniel Imms 29ff74e069 Merge pull request #1859 from Tyriar/baseline
Fix text being top aligned in latest Chrome and Firefox
2019-01-01 18:58:32 -08:00
Daniel Imms c66a24fab4 Merge branch 'master' into baseline 2019-01-01 15:33:57 -08:00
jerch 7a579a374a Merge pull request #1853 from PerBothner/control-seq-handler
hooks for custom CSI/OSC control sequences
2019-01-01 21:25:52 +01:00
jerch f534758e29 Merge branch 'master' into control-seq-handler 2019-01-01 21:19:34 +01:00
Daniel Imms dbd9b2af37 Merge branch 'master' into baseline 2019-01-01 11:50:09 -08:00
Daniel Imms 68b2b79880 Merge pull request #1861 from Tyriar/1860_style_npe
Add sanity checks to dom renderer underline code
2019-01-01 11:47:17 -08:00
Per Bothner 8fbeadd5f9 Add missing deleteCount argument to Array.splice calls. 2019-01-01 09:45:23 -08:00
ntchjb f961f90bc8 Support wide characters and combined characters
- Convert buffer index to string index before searching
- Convert string index to buffer index after searching
- Fix bug when search selection skipped some result
2019-01-01 16:39:23 +07:00
Daniel Imms 338558d86c Make Terminal API properties readonly
Fixes #1867
2018-12-31 20:09:48 -08:00
Daniel Imms 8b3513f009 Merge branch 'master' into 1860_style_npe 2018-12-31 19:15:15 -08:00
Daniel Imms 9b0b323957 Merge branch 'master' into baseline 2018-12-31 19:14:53 -08:00
Daniel Imms 48c1d36d77 Merge branch 'master' into control-seq-handler 2018-12-31 19:07:58 -08:00
Daniel Imms 2ce67b89bc Remove unneeded MockTerminal member 2018-12-31 14:43:50 -08:00
Daniel Imms 6559931f34 Add lots of tests 2018-12-31 14:28:52 -08:00
Daniel Imms 840970eac7 Update markers after a reflow 2018-12-31 13:15:23 -08:00
Daniel Imms 40e8618cf5 Discard cut off combined data when resizing BufferLines 2018-12-31 10:26:10 -08:00
Daniel Imms db488ebcc9 Reflow combined chars 2018-12-31 10:18:38 -08:00
Daniel Imms 1612cec3e0 Fix reflow larger bug, add regression test 2018-12-31 09:54:24 -08:00
Daniel Imms 135e31f2ca Speed up reflow larger by batching removals 2018-12-31 08:58:04 -08:00
Daniel Imms b7081abfce Move loop into reflowLarger (adjust indent) 2018-12-31 07:54:16 -08:00
Daniel Imms c9f4a650c6 Clean up comments and todos 2018-12-31 07:26:08 -08:00
Daniel Imms 478742a22e Make reflow small crazy fast
This messy but this drops 100000 scrollback reflow from 87 cols to 40 cols
go from ~18 seconds to < 1 second, 10000 takes around 70ms
2018-12-30 12:14:48 -08:00
Per Bothner bbfe149397 Merge pull request #1 from Tyriar/hooks_changes
Move to array-based instead of linked list, simplify, add tests
2018-12-29 07:01:39 -08:00
ntchjb d94c897754 Fix search addons: be able to search wrapped lines from unwrapped line
by managing column range that is needed to be scanned.
2018-12-29 13:55:14 +07:00
Daniel Imms dde96187fa Keep track of cols/rows inside Buffer 2018-12-28 19:27:38 -08:00
Daniel Imms 4a9f10d062 Remove some of Buffer's dependency on Terminal 2018-12-28 19:22:05 -08:00
Daniel Imms 7435971c32 Merge remote-tracking branch 'origin/master' into 622_reflow3 2018-12-28 19:13:12 -08:00
Max 1b68682b79 Merge branch 'master' into master 2018-12-29 10:12:17 +07:00
ntchjb 565cba9c1a Fix search addons:
- Changed to use `this._terminal.cols` for buffer line length instead.
- Let `_findInLine` check on wrapped line
- Added conditions in `_findInLine`
  - For reverse search, if there is no  selection at given row, then start scan at the end of the string
2018-12-29 09:38:17 +07:00
Daniel Imms 4375b3cdb9 Merge pull request #1865 from ntchjb/master
Fix search addons: searchTerm should not be matched if the beginning of matching index < 0
2018-12-28 15:02:51 -08:00
Daniel Imms 72369e060e Only enable reflow on the normal buffer 2018-12-28 14:55:58 -08:00
Daniel Imms 2a0da173f2 Add a bunch of reflow tests 2018-12-28 14:55:15 -08:00
Daniel Imms a33e8a6af7 Properly shrink rows to cols every time 2018-12-28 12:57:10 -08:00
ntchjb 913150fe13 Fix search addons:
searchTerm should not be matched if the beginning of matching index < 0
2018-12-29 03:03:23 +07:00
Daniel Imms 358898daf9 Fix some tests 2018-12-28 11:02:08 -08:00
Daniel Imms 7684f93773 Fix ydisp/ybase after trimming buffer 2018-12-28 01:32:19 -08:00
Daniel Imms ae29dbb133 Fix scrollbar when wrapping beyond single viewport of data 2018-12-28 01:26:49 -08:00
Daniel Imms 3311ed509a Do shrink in reverse, fix up row remove count again 2018-12-28 00:40:30 -08:00
Daniel Imms 8bc04c2fa7 Tidy up 2018-12-27 23:58:57 -08:00
Daniel Imms fa47036982 Fix row removal in reflowLarger 2018-12-27 23:49:59 -08:00
Daniel Imms 314f98f2a6 Mostly working for reflowing to smaller 2018-12-27 23:35:52 -08:00
Daniel Imms 8a50729a98 Reflow wider 2018-12-27 21:58:54 -08:00
Daniel Imms c133c644a6 Merge pull request #1834 from Tyriar/debugging
Add VS Code debug targets for demo server, client and unit tests
2018-12-27 14:18:51 -08:00
Daniel Imms 951f281bb0 Merge branch 'master' into debugging 2018-12-27 14:09:48 -08:00
Daniel Imms d5cacbaf4a Merge pull request #1863 from vincentwoo/patch-2
Small indent fix
2018-12-27 14:02:53 -08:00
Vincent Woo bb198a980c Small indent fix 2018-12-27 13:54:11 -08:00
Daniel Imms 60bc1bb873 Merge branch 'master' into perf3 2018-12-27 13:53:01 -08:00
Daniel Imms d5bffb5395 Merge branch 'master' into typedarray_parser 2018-12-27 13:20:48 -08:00
Daniel Imms 2611a9b1d1 Merge pull request #1846 from linrock/no-end-quotes-in-weblinks
Weblinks should not allow quotes at end of urls enclosed in quotes
2018-12-27 11:32:40 -08:00
Daniel Imms 689ac3e073 Merge branch 'master' into no-end-quotes-in-weblinks 2018-12-27 11:29:33 -08:00
Daniel Imms 51e1f49bf3 Make dispose more resilient 2018-12-27 10:26:40 -08:00
Daniel Imms 838d727a89 Merge pull request #1849 from epicfaace/feature/hold-key
Allow holding key on mac to send multiple keys to terminal
2018-12-27 10:08:12 -08:00
Daniel Imms 9bcfb4d722 Tidy up wrapping style 2018-12-27 10:04:48 -08:00
Ashwin Ramaswami 0d9487c10f Merge branch 'master' into feature/hold-key 2018-12-26 20:36:53 -05:00
Daniel Imms b48f9108c8 Merge pull request #1763 from noamyogev84/issue_1654
Finding multiple instances on the same line
2018-12-26 16:13:24 -08:00
Daniel Imms d698faa183 Comment how the regex reverse search while works 2018-12-26 16:12:53 -08:00
Daniel Imms 1a1f7e984f Remove ISearchIndex object 2018-12-26 16:03:31 -08:00
Daniel Imms e0d535e0da Fix previous search, remove incremental previous search 2018-12-26 15:49:08 -08:00
Daniel Imms 76302e2146 Get multiple matches working after incremental changes 2018-12-26 15:32:34 -08:00
Daniel Imms 204e42a151 Fix incremental search 2018-12-26 15:15:12 -08:00
Daniel Imms bb9732599b Merge remote-tracking branch 'origin/master' into pr/noamyogev84/1763 2018-12-26 15:08:16 -08:00
Daniel Imms adbb929f78 Wrap .d.ts comments to 80 chars 2018-12-26 14:49:49 -08:00
Daniel Imms c045c803d6 Add tests for dispose 2018-12-26 14:48:35 -08:00
Daniel Imms 38796a0f74 Add tests, fix NPE 2018-12-26 14:40:23 -08:00
Daniel Imms d01efdda27 Use array instead of linkedlist, add typings 2018-12-26 14:08:53 -08:00
Daniel Imms 9e3e724f63 Add sanity checks to dom renderer underline code
Fixes #1860
2018-12-26 10:53:40 -08:00
Daniel Imms a80baa71e8 Merge branch 'master' into control-seq-handler 2018-12-23 22:24:26 -08:00
Daniel Imms 272cb08472 Merge pull request #1847 from linrock/update-natives
Fix npm scripts crashing when using node 10+
2018-12-23 22:19:12 -08:00
Per Bothner 48ff841d6a Be more paranoid about cleaning up escape sequence handlers. 2018-12-23 12:27:26 -08:00
Daniel Imms 0e8a102e17 Merge pull request #1856 from Tyriar/readme
Remove gitter and improve README
2018-12-23 10:57:49 -08:00
Daniel Imms c6e6c51929 Use textBaseline middle to draw instead of top
Chrome and Firefox behavior differs, see:
- https://bugzilla.mozilla.org/show_bug.cgi?id=737852
- https://bugs.chromium.org/p/chromium/issues/detail?id=607053

Fixes #1858
2018-12-23 10:51:41 -08:00
Daniel Imms 81ada39989 Remove Gitter link from issue template
Fixes #1838
2018-12-21 23:07:25 -08:00
Daniel Imms 78e7e39bb4 Improve the readme 2018-12-21 23:04:38 -08:00
Ashwin Ramaswami 548d8cd764 Merge branch 'feature/hold-key' of https://github.com/epicfaace/xterm.js into feature/hold-key 2018-12-21 07:38:19 -05:00
Ashwin Ramaswami a4b4d082fb Don't include num lock and scroll lock 2018-12-21 07:38:14 -05:00
Per Bothner 29cc0bf933 Merge remote-tracking branch 'upstream/master' into control-seq-handler 2018-12-20 18:12:53 -08:00
Daniel Imms 2896f56ebb Merge branch 'master' into feature/hold-key 2018-12-20 12:13:43 -08:00
Daniel Imms fcef4d5d28 Merge pull request #1851 from robins1212/bindfullscreen
bind classList function before calling in toggleFullScreen
2018-12-20 12:11:53 -08:00
robins1212 701f50f47b Merge branch 'master' into bindfullscreen 2018-12-20 12:03:43 -08:00
Daniel Imms 61d0e99ffd Merge pull request #1850 from Tyriar/vscode65485
Fix winptyCompat wrapped line heuristic
2018-12-20 11:58:55 -08:00
Robin Sturm 3eac446a85 bind function before calling 2018-12-20 11:54:26 -08:00
Daniel Imms 71d8a4d1ed Merge branch 'master' into vscode65485 2018-12-20 11:48:51 -08:00
Daniel Imms ff73f705ae Fix winptyCompat wrapped line heuristic
Part of Microsoft/vscode#65485
2018-12-20 11:47:51 -08:00
Ashwin Ramaswami 64dd9d46fb Change keyCode cutoff 2018-12-20 14:21:35 -05:00
Ashwin Ramaswami 0782fbb828 Fix check 2018-12-20 09:57:24 -05:00
Ashwin Ramaswami b820f23da6 Add additional test 2018-12-20 09:50:57 -05:00
Ashwin Ramaswami ec1060187d Add tests 2018-12-20 09:50:18 -05:00
Ashwin Ramaswami 1104bcbccb Allow holding key on mac to send multiple keys to terminal 2018-12-20 09:41:44 -05:00
Daniel Imms 90ec90746e Merge pull request #1848 from linrock/fix-addons-typo
Fix missing quotes in import statement in readme
2018-12-19 11:09:25 -08:00
Daniel Imms b39e8cfbb9 Merge pull request #1843 from Tyriar/endcol_null
Fix translateToString endCol default arg
2018-12-19 11:04:55 -08:00
Linmiao Xu 5ebd543b8b Fix missing quotes in import statement in readme 2018-12-19 14:32:15 +09:00
Linmiao Xu 86f194a6d3 Fix npm scripts when using node 10+ 2018-12-19 13:50:41 +09:00
Linmiao Xu 9cd7bf2af1 Weblinks should not allow quotes at end of urls enclosed in quotes 2018-12-19 13:28:16 +09:00
Daniel Imms c961175c03 Merge branch 'master' into debugging 2018-12-18 16:28:26 -08:00
Daniel Imms 04ac36fc16 Fix translateToString endCol default arg 2018-12-18 16:22:41 -08:00
Daniel Imms d27b43d515 Merge pull request #1841 from jerch/fix/1840
fix invalid string and buffer index in Linkifier
2018-12-18 10:57:00 -08:00
Jörg Breitbart 0da6300102 make linter happy 2018-12-18 10:50:30 +01:00
Jörg Breitbart e5ff04e798 Merge branch 'master' into fix/1840 2018-12-18 00:04:48 +01:00
Jörg Breitbart 5460068d52 fix invalid string and buffer index 2018-12-17 23:59:48 +01:00
Daniel Imms a53c6f1728 Merge pull request #1826 from nojvek/master
Fix #1660: Search as you type
2018-12-17 11:56:22 -08:00
Daniel Imms 6a5b5c23f8 Merge branch 'master' into master 2018-12-17 11:46:33 -08:00
Jörg Breitbart ef171bf29f Merge branch 'master' into typedarray_parser 2018-12-15 23:42:08 +01:00
Per Bothner 6b65ebd4aa Various typing and API fixes, doc comments, typing test etc. 2018-12-15 11:34:15 -08:00
Per Bothner 8a5a03238f New method _linkHandler used by both addCsiHandler and addOscHandler. 2018-12-15 09:42:19 -08:00
jerch 4feed2dabc Merge pull request #1775 from jerch/trim_behavior
explicit null char representation and fix for right side trimming
2018-12-15 00:11:41 +01:00
Jörg Breitbart 415e760a8a Merge branch 'trim_behavior' of git+ssh://github.com/jerch/xterm.js into trim_behavior 2018-12-15 00:04:15 +01:00
Jörg Breitbart 3927364f0e remove nonsense comments 2018-12-15 00:03:54 +01:00
Jörg Breitbart 62be3047d8 infer types fix 2018-12-15 00:01:18 +01:00
Jörg Breitbart d18c947854 Merge branch 'master' into trim_behavior 2018-12-14 23:59:40 +01:00
Per Bothner 5af4626ec7 Change addCsiHandler/addOscHandler to not use Object.assign. 2018-12-13 17:33:33 -08:00
Daniel Imms 4446fa34de Merge branch 'master' into master 2018-12-13 10:55:51 -08:00
Daniel Imms 4a7a19c368 Merge branch 'master' into trim_behavior 2018-12-13 10:19:58 -08:00
Daniel Imms 6351a5b748 Merge branch 'master' into master 2018-12-13 10:14:15 -08:00
Daniel Imms dc5c2381a8 Merge branch 'master' into perf3 2018-12-12 11:24:34 -08:00
Daniel Imms 551feb849e Merge pull request #1832 from Tyriar/remove_jsdoc
Remove jsdoc dependency
2018-12-12 09:44:40 -08:00
Daniel Imms 98285d3a84 Merge pull request #1831 from Tyriar/1830_addon_types_fix
Remove triple slash directive error in addon types
2018-12-12 09:30:34 -08:00
Daniel Imms 98ccc23934 Merge pull request #1827 from Tyriar/1605_remove_authors
Remove authors
2018-12-12 09:11:24 -08:00
Jörg Breitbart d0d94584ad Merge branch 'master' into trim_behavior 2018-12-12 18:10:24 +01:00
Noam Yogev e00c7d535d Merge branch 'master' into issue_1654 2018-12-12 11:59:48 +02:00
Per Bothner 8ceea112f7 hooks for custom control sequences
This re-implements addCsiHandler/addOscHandler to return an IDisposable.
2018-12-11 17:18:10 -08:00
Per Bothner 53fd04a886 Revert "hooks for custom control sequences"
This reverts commit 4d660deff3.
2018-12-11 16:50:40 -08:00
Per Bothner ffb2708a81 Revert "Cleanups required by tslink."
This reverts commit b68974517e.
2018-12-11 16:50:14 -08:00
Per Bothner 8ad2d1bf43 Merge remote-tracking branch 'upstream/master' 2018-12-11 16:30:18 -08:00
Daniel Imms a758f8c82f Merge pull request #1822 from PerBothner/optimize-osc-parse
Optimize parsing of OSC_STRING to minimize string concatenation.
2018-12-11 11:09:27 -08:00
Daniel Imms bbbfc04fa5 Merge branch 'master' into optimize-osc-parse 2018-12-11 11:07:12 -08:00
Daniel Imms d7f284c676 Add debug target for demo server, merge 2 start processes into one 2018-12-11 09:56:33 -08:00
Daniel Imms 943def1391 Add debug target for client, fix source maps in demo to point to ts 2018-12-11 09:37:20 -08:00
Daniel Imms a0a8003d60 Add unit tests debug target 2018-12-11 09:06:50 -08:00
Daniel Imms 6cc0a1447d Merge branch 'master' into remove_jsdoc 2018-12-11 08:58:28 -08:00
Daniel Imms 4d3345182f Remove jsdoc dependency 2018-12-11 08:57:44 -08:00
Daniel Imms 3a5d07a8f2 Merge branch 'master' into 1830_addon_types_fix 2018-12-11 07:21:34 -08:00
Daniel Imms 1e94fce545 Remove triple slash directive error in addon types
Fixes #1830
2018-12-11 07:19:37 -08:00
Daniel Imms da8546725f Merge branch 'master' into master 2018-12-11 06:28:08 -08:00
Daniel Imms 53f3b4e368 Merge pull request #1828 from Tyriar/1507_remove_layering
Remove utils/ folder
2018-12-11 06:27:49 -08:00
Noj Vek a0d583b075 handle non-cached scenario in tests land 2018-12-10 17:23:28 -08:00
Noj Vek 9005de1c42 adding a linesCache ttl and cursor move invalidation 2018-12-10 17:13:28 -08:00
Daniel Imms b9a22eb468 Remove utils/ folder
Part of #1507
2018-12-10 16:46:57 -08:00
Daniel Imms b51c07c408 Merge branch 'master' into 1605_remove_authors 2018-12-10 16:41:04 -08:00
Daniel Imms 3397ecf977 Remove authors
Fixes #1605
2018-12-10 16:40:04 -08:00
Noj Vek 801dcc3b75 Merge branch 'master' of github.com:xtermjs/xterm.js 2018-12-10 15:54:38 -08:00
Noj Vek 91ef7f24a6 Fixes #1660: Search as you type 2018-12-10 15:54:35 -08:00
Daniel Imms 65877d491e Merge pull request #1788 from gou4shi1/fix-get-mouse-coord
getCoordsRelativeToElement: use getBoundingClientRect and clientX/Y.
2018-12-10 14:46:33 -08:00
Daniel Imms f34c3d509d Merge pull request #1821 from Tyriar/dispose_atlas
Call dispose when atlas is no longer used in cache
2018-12-10 14:45:59 -08:00
Daniel Imms e1b3c7233a Merge pull request #1820 from Tyriar/add_missing_break
Add missing break in options code
2018-12-10 14:45:49 -08:00
Daniel Imms 08dc8a3e5f Merge pull request #1819 from Tyriar/fix_demo_line_height
Allow decimal line heights in demo
2018-12-10 14:45:41 -08:00
jerch 6a2e6a5edc Merge branch 'master' into optimize-osc-parse 2018-12-10 20:17:51 +01:00
jerch 2d8f420db8 fix default transition for 0xa0 2018-12-10 16:37:48 +01:00
Per Bothner 2cb293ceb2 Further tweaks to optimize parsing of OSC_STRING. 2018-12-09 19:03:58 -08:00
Juan Campa 3d2ae2b01e Removing redundant condition. Clearer variable name 2018-12-09 17:58:44 -05:00
Per Bothner 1c4a71f0df Optimize parsing of OSC_STRING to minimize string concatenation. 2018-12-09 13:29:09 -08:00
Per Bothner 30a667c3be Revert "Optimize parsing of OSC_STRING to minimize string concatenation."
This reverts commit 03115639a9.
2018-12-09 13:11:17 -08:00
Per Bothner 03115639a9 Optimize parsing of OSC_STRING to minimize string concatenation. 2018-12-09 10:56:34 -08:00
Daniel Imms 48629e7eb2 Merge remote-tracking branch 'origin/master' into pr/gou4shi1/1788 2018-12-09 10:02:55 -08:00
Daniel Imms 6b9fb48898 Fix tests/lint, use element instead of event.target 2018-12-09 09:59:33 -08:00
Daniel Imms fe9eb7d31e Call dispose when atlas is no longer used in cache
Only runtime change is that this clears the bitmap commit timeout
in DynamicCharAtlas
2018-12-09 09:19:56 -08:00
Daniel Imms 693e17a5cb Add missing break in options code
Atlas is reset as part of onResize, this should be safe
2018-12-09 09:13:30 -08:00
Daniel Imms b5b2bcd0c9 Allow decimal line heights in demo 2018-12-09 09:08:11 -08:00
Juan Campa fd34caee23 Use a time-based limit to Terminal._innerWrite
The idea is that it should run for a bit and then let the renderer draw
a frame so that the terminal look responsive. The existing approach
limits the work done using a fixed number elements from the write
buffer so the duration of a frame can vary widely.

This approach looks at the clock to determine when to stop, we basically
allocate an amount of time each frame to write, while the rest can be
used for rendering.

From my tests this change makes the terminal feel a lot smoother.
2018-12-08 00:44:05 -05:00
Noam 278d696709 remove reverseSearch from ISearchOptions
add isReverseSearch argument to findInLine
modify unit tests
2018-12-07 22:56:57 +02:00
gou4shi1 7f18199985 Fix build fail 2018-12-06 14:37:27 +08:00
Per Bothner b68974517e Cleanups required by tslink. 2018-12-05 14:45:57 -08:00
Per Bothner 4d660deff3 hooks for custom control sequences
This fixes (at least partially) issue #1176
"Add a way to plugin a custom control sequence handler".
2018-12-04 19:00:44 -08:00
Noam Yogev 38adfcd81c Merge branch 'master' into issue_1654 2018-11-30 20:49:12 +02:00
Noam 376c790e1d implement find multiple matches in line.
start search from current selection.
add unit tests.
2018-11-30 20:46:47 +02:00
Jörg Breitbart 9d4beb024a keep whitespace chars 2018-11-29 22:11:38 +01:00
Jörg Breitbart 99e4a9649f fix failing test cases 2018-11-29 21:48:48 +01:00
Jörg Breitbart eb27641928 Merge branch 'master' into trim_behavior 2018-11-29 21:26:45 +01:00
Jörg Breitbart 2483a98f63 use faster string conversion 2018-11-29 21:13:18 +01:00
Jörg Breitbart 5d3ef759b4 Merge branch 'master' into typedarray_parser 2018-11-29 21:11:07 +01:00
Jörg Breitbart a6ef64dcb6 always clear DCS buffer on HOOK 2018-11-29 16:39:17 +01:00
Jörg Breitbart a4e3646640 Merge branch 'master' into typedarray_parser 2018-11-29 16:35:12 +01:00
Jörg Breitbart 591dfd506d fix DCS handler 2018-11-29 16:33:56 +01:00
Jörg Breitbart 14d6960873 Merge remote-tracking branch 'upstream/master' into typedarray_parser 2018-11-23 23:33:56 +01:00
Jörg Breitbart 06e89549a8 typed array support for parser 2018-11-23 23:30:37 +01:00
gou4shi1 3ca0598279 fix EventTarget type 2018-11-15 11:56:41 +08:00
gou4shi1 3d2035e1a0 getCoordsRelativeToElement: use getBoundingClientRect and clientX/Y. 2018-11-15 11:43:38 +08:00
Jörg Breitbart a15b866013 test cases for getTrimLength and translateToString 2018-11-08 06:13:05 +01:00
Jörg Breitbart 2f9c04c3af cleanup test cases 2018-11-08 05:00:08 +01:00
Jörg Breitbart 67df9a5cdc cleanup 2018-11-08 04:03:48 +01:00
Jörg Breitbart 5395503031 apply code and char replacement to renderer 2018-10-31 15:47:35 +01:00
Jörg Breitbart 200b59b47c fix canvas renderer 2018-10-31 15:35:30 +01:00
Jörg Breitbart cabd4a721c fix dom renderer 2018-10-31 15:30:29 +01:00
Jörg Breitbart 745e228e98 fix input handler and terminal tests 2018-10-31 15:26:40 +01:00
Jörg Breitbart 8ffcd65cff fix buffer tests 2018-10-31 15:11:18 +01:00
Jörg Breitbart 1c111d3e60 skip empty cells after fullwidth 2018-10-31 14:53:55 +01:00
Jörg Breitbart 62f1d64af4 fix trimmedLength for TypedArray 2018-10-31 14:26:54 +01:00
Jörg Breitbart ee6fa6ca00 change NULL_CHAR and trim 2018-10-31 12:42:56 +01:00
148 changed files with 8819 additions and 4181 deletions
+22
View File
@@ -0,0 +1,22 @@
FROM node:10
# Configure apt
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update \
&& apt-get -y install --no-install-recommends apt-utils 2>&1
# Verify git and process tools are installed
RUN apt-get install -y git procps
# Install yarn
RUN apt-get install -y curl apt-transport-https lsb-release \
&& curl -sS https://dl.yarnpkg.com/$(lsb_release -is | tr '[:upper:]' '[:lower:]')/pubkey.gpg | apt-key add - 2>/dev/null \
&& echo "deb https://dl.yarnpkg.com/$(lsb_release -is | tr '[:upper:]' '[:lower:]')/ stable main" | tee /etc/apt/sources.list.d/yarn.list \
&& apt-get update \
&& apt-get -y install --no-install-recommends yarn
# Clean up
RUN apt-get autoremove -y \
&& apt-get clean -y \
&& rm -rf /var/lib/apt/lists/*
ENV DEBIAN_FRONTEND=dialog
+9
View File
@@ -0,0 +1,9 @@
{
"name": "xterm.js",
"dockerFile": "Dockerfile",
"appPort": 3000,
"extensions": [
"editorconfig.editorconfig",
"ms-vscode.vscode-typescript-tslint-plugin"
]
}
+2 -2
View File
@@ -1,8 +1,8 @@
---
name: Question
about: The issue tracker is not for questions. Please ask questions on https://stackoverflow.com/questions/tagged/xtermjs or https://gitter.im/sourcelair/xterm.js.
about: The issue tracker is not for questions. Please ask questions on https://stackoverflow.com/questions/tagged/xtermjs
---
🛑 The issue tracker is not for questions 🛑
If you have a question, please ask it on https://stackoverflow.com/questions/tagged/xtermjs or https://gitter.im/sourcelair/xterm.js.
If you have a question, please ask it on https://stackoverflow.com/questions/tagged/xtermjs.
-1
View File
@@ -12,7 +12,6 @@ npm-debug.log
/.idea/
.env
build/
.vscode/
.DS_Store
fixtures/typings-test/*.js
package-lock.json
+1
View File
@@ -42,6 +42,7 @@ lib/test/
docs/
/.idea/
.vscode/
bin/
build/
fixtures/
coverage/
+63
View File
@@ -0,0 +1,63 @@
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Unit Tests",
"cwd": "${workspaceRoot}",
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/mocha",
"windows": {
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/mocha.cmd"
},
"runtimeArgs": [
"--colors",
"--recursive",
"${workspaceRoot}/lib/**/*.test.js"
],
"sourceMaps": true,
"outFiles": [ "${workspaceRoot}/lib/**/*.js" ],
"internalConsoleOptions": "openOnSessionStart"
},
{
"type": "node",
"request": "launch",
"name": "Integration Tests",
"cwd": "${workspaceRoot}",
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/mocha",
"windows": {
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/mocha.cmd"
},
"runtimeArgs": [
"--colors",
"--recursive",
"${workspaceRoot}/lib/**/*.api.js"
],
"sourceMaps": true,
"outFiles": [ "${workspaceRoot}/lib/**/*.js" ],
"internalConsoleOptions": "openOnSessionStart"
},
{
"type": "chrome",
"request": "launch",
"name": "Demo Client",
"url": "http://127.0.0.1:3000",
"webRoot": "${workspaceFolder}/"
},
{
"type": "node",
"request": "launch",
"name": "Demo Server",
"runtimeExecutable": "npm",
"runtimeArgs": [
"run",
"start-debug"
],
"port": 9229,
"serverReadyAction": {
"action": "openExternally",
"pattern": "App listening to (http://.*?:[0-9]+)"
}
}
]
}
+3
View File
@@ -0,0 +1,3 @@
{
"typescript.preferences.quoteStyle": "single"
}
+51
View File
@@ -0,0 +1,51 @@
{
"version": "2.0.0",
"presentation": {
"echo": false,
"reveal": "always",
"focus": false,
"panel": "dedicated",
"showReuseMessage": true
},
"tasks": [
{
"type": "npm",
"script": "test",
"group": "test",
"problemMatcher": []
},
{
"type": "npm",
"script": "watch",
"group": "build",
"isBackground": true,
"problemMatcher": [],
"presentation": {
"group": "vscode"
}
},
{
"type": "npm",
"script": "start",
"group": "build",
"isBackground": true,
"problemMatcher": [],
"presentation": {
"group": "vscode"
}
},
{
"label": "Start demo",
"dependsOn": ["npm: watch", "npm: start"],
"group": {
"kind": "build",
"isDefault": true
},
"isBackground": true,
"problemMatcher": [],
"presentation": {
"group": "vscode"
}
}
]
}
-134
View File
@@ -1,134 +0,0 @@
List of xterm.js contributors. Updated before every release.
7PH <b.raymond@cern.ch>
7PH <b.raymond@protonmail.com>
Adrian Labbé <adrilabbelol@gmail.com>
Aleksandr Andrienko <aandrienko@codenvy.com>
Aleksandr Andriienko <oandriie@redhat.com>
Alessandro Nadalin <alessandro.nadalin@gmail.com>
Alexander Olsson <noseglid@gmail.com>
Alexandre Petit-Pas <petitpasalexandre@gmail.com>
Alexey Kontsevoy <alexey@gravitational.com>
Andres Mejia <amejia004@gmail.com>
Anish Athalye <me@anishathalye.com>
Anthony Lapenna <lapenna.anthony@gmail.com>
Antonin Stefanutti <antonin@stefanutti.fr>
Antonis Kalipetis <akalipetis@sourcelair.com>
Anton Skshidlevsky <meefik@gmail.com>
Anton Yurovskykh <anton.yurovskykh@gmail.com>
Artem Arbatskiy <artyom.arbatskiy@gmail.com>
Austin Robertson <austinrobertson@gmail.com>
ayapi <colors.aya@gmail.com>
Ben Hall <ben@benhall.me.uk>
Benjamin Fischer <benjamin.fischer@rwth-aachen.de>
Benjamin Raymond <b.raymond@cern.ch>
Benjamin Woodruff <github@benjam.info>
Bill Church <billchurch@users.noreply.github.com>
Bob Reid <bobreid@Bobs-MacBook-Pro.local>
bottleofwater <nison.mael+bottleofwater@gmail.com>
Brandon Bayer <b@bayer.ws>
Brian Mock <brian@mockbrian.com>
Bruno Ribeiro <b.m.fernandes.ribeiro@gmail.com>
Bruno Ribeito <b.m.fernandes.ribeiro@gmail.com>
Carson Anderson <carson@betterservers.com>
CHaBou <chabup@delean.fr>
Christian Budde Christensen <budde377@gmail.com>
Christof Marti <chrmarti@microsoft.com>
Christopher Jeffrey <chjjeffrey@gmail.com>
coderaiser <mnemonic.enemy@gmail.com>
Damien Tournoud <damien@platform.sh>
Dan Brown <jdanbrown@gmail.com>
Daniel Griffen <dagriffe@microsoft.com>
Daniel Griffen <daniel@griffen.io>
Daniel Imms <daimms@microsoft.com>
Daniel Risacher <drisacher@gmail.com>
Dan Kaplun <dbkaplun@twitch.tv>
Darin Morrison <freebroccolo@users.noreply.github.com>
dcylabs <dcylabs@gmail.com>
Dominik Csapak <d.csapak@proxmox.com>
Edgar Andrés Margffoy Tuay <andfoy@gmail.com>
Elliot Saba <staticfloat@gmail.com>
Exile <exile@chamber.ee>
Felipe Gasper <felipe@felipegasper.com>
Felix <30559812+felixse@users.noreply.github.com>
ficristo <ficristo.work@gmail.com>
Gary Ritchie <gary@rstudio.com>
hiro-su <h.sugipon@gmail.com>
Ian Lewis <ianlewis@google.com>
imoses <ido@twiggle.com>
InDieTasten <indietasten@gmail.com>
irokas <akasidiari@inf.uth.gr>
Jakob Gillich <jakob@gillich.me>
Jan Kuri <jkuri88@gmail.com>
Jean Bruenn <himself@jeanbruenn.info>
Jeff Principe <princjef@gmail.com>
Jeremy Danyow <jedanyow@microsoft.com>
Jianhui Zhao <jianhuizhao329@gmail.com>
Joao Moreno <jomo@microsoft.com>
Joao Moreno <mail@joaomoreno.com>
Johannes Zellner <johannes@nebulon.de>
Jon Austin <jon.i.austin@gmail.com>
Jon Kohler <jon@nutanix.com>
Jon Masters <jon.masters@sky.com>
Jörg Breitbart <jerch@rockborn.de>
Jose Anton <roke22@gmail.com>
jpoth <poth.john@gmail.com>
Justin Luk <jluk@users.noreply.github.com>
Justin Mecham <justin@mecham.me>
Kirill Merkushev <lanwen@yandex.ru>
Krasimir Tsonev <krasimir@outset.ws>
Ledion Bitincka <lbitincka@gmail.com>
Linus Unnebäck <linus@folkdatorn.se>
Luca <LucaT1@users.noreply.github.com>
Lucian Buzzo <lucian.buzzo@gmail.com>
Lukas Drgon <lukas.drgon@gmail.com>
Lukas Geiger <lukas.geiger94@gmail.com>
Maël Nison <nison.mael@gmail.com>
Marc Dumais <marc.dumais@ericsson.com>
Marek Libra <mlibra@redhat.com>
Markus F.X.J. Oberhumer <markus@oberhumer.com>
Martin Chloride <i@martincl2.me>
Martin Koppehel <martin.koppehel@st.ovgu.de>
Martin Wang <jiahaow@ca.ibm.com>
Matt Bierner <matb@microsoft.com>
Matthew James <matthew.d.james87@gmail.com>
Michael Irwin <mikesir87@gmail.com>
Mikko Karvonen <mikko.karvonen@arm.com>
mofux <t.zilz@mofux.org>
muji <muji@tmpfs.org>
Nicolas Ramz <nicolas.ramz@gmail.com>
npezza93 <npezza93@gmail.com>
Oleksandr Andriienko <oandriie@redhat.com>
Paris Kasidiaris <pariskasidiaris@gmail.com>
Paris Kasidiaris <paris@sourcelair.com>
Peng Xiao <pengxiao@outlook.com>
Peter Baumgarten <me@peterbaumgarten.com>
Philip Olson <philip.olson@protonmail.ch>
PowerHat <31401273+7PH@users.noreply.github.com>
PowerHat <b.raymond@protonmail.com>
pro-src <34285059+pro-src@users.noreply.github.com>
pro-src <rodneyd.teal@gmail.com>
Rick Baker <rick@ricktbaker.com>
runarberg <runar@greenqloud.com>
Saad Malik <simfox3@gmail.com>
Samuel Williams <samuel.williams@oriontransfer.co.nz>
Saswat Das <saswatds@users.noreply.github.com>
Saul Costa <saul@codevolve.com>
Segev Finer <segev208@gmail.com>
Shuanglei Tao <tsl0922@gmail.com>
sitzmar <sitzmar@gmail.com>
Steven Silvester <steven.silvester@ieee.org>
stuicey <stuicey@users.noreply.github.com>
Sven-Hendrik Haase <svenstaro@gmail.com>
t-amqi <umberos@gmail.com>
Thanasis Daglis <thanasis@sourcelair.com>
thomas <thomas@kreativkonzentrat.de>
Thomas Zilz <t.zilz@mofux.org>
Tine Jozelj <tine.jozelj@outlook.com>
Tyler Jewell <tjewell@codenvy.com>
Vincent Woo <me@vincentwoo.com>
Viraj Sinha <virajosinha@gmail.com>
yutaka <shigure@refy.net>
YuviPanda <yuvipanda@gmail.com>
ZHAO Xudong <zxdong@gmail.com>
赵雪珂 <zhaoxueke@onething.net>
+6 -6
View File
@@ -33,21 +33,21 @@ opening an issue, read these pointers.
You can find issues to work on by looking at the [help wanted](https://github.com/xtermjs/xterm.js/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22) or [good first issue](https://github.com/xtermjs/xterm.js/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) issues. It's a good idea to comment on the issue saying that you're taking it, just in case someone else comes along and you duplicate work. Once you have your issue, here are the steps to contribute:
- Fork [xterm.js](https://github.com/sourcelair/xterm.js/)
([how to fork a repo](https://help.github.com/articles/fork-a-repo))
- Get the [xterm.js demo](https://github.com/xtermjs/xterm.js/wiki/Contributing#running-the-demo) running
- Make your changes
([how to fork a repo](https://help.github.com/articles/fork-a-repo)).
- Get the [xterm.js demo](https://github.com/xtermjs/xterm.js/wiki/Contributing#running-the-demo) running.
- Make your changes.
- If your changes are easy to test or likely to regress, add tests. Tests go into `test`, directory.
- Follow the general code style of the rest of the project (see below).
- Submit a pull request
([how to create a pull request](https://help.github.com/articles/fork-a-repo)).
Don't put more than one feature/fix in a single pull request.
By contributing code to xterm.js you
By contributing code to xterm.js you:
- agree to license the contributed code under xterm.js' [MIT
- Agree to license the contributed code under xterm.js' [MIT
license](LICENSE).
- confirm that you have the right to contribute and license the code
- Confirm that you have the right to contribute and license the code
in question. (Either you hold all rights on the code, or the rights
holder has explicitly granted the right to use it like this,
through a compatible open source license or through a direct
+1 -1
View File
@@ -1,4 +1,4 @@
Copyright (c) 2017-2018, The xterm.js authors (https://github.com/xtermjs/xterm.js)
Copyright (c) 2017-2019, The xterm.js authors (https://github.com/xtermjs/xterm.js)
Copyright (c) 2014-2016, SourceLair Private Company (https://www.sourcelair.com)
Copyright (c) 2012-2013, Christopher Jeffrey (https://github.com/chjj/)
+53 -58
View File
@@ -2,25 +2,22 @@
[![Build Status](https://dev.azure.com/xtermjs/xterm.js/_apis/build/status/xtermjs.xterm.js)](https://dev.azure.com/xtermjs/xterm.js/_build/latest?definitionId=3)
[![Coverage Status](https://coveralls.io/repos/github/xtermjs/xterm.js/badge.svg?branch=master)](https://coveralls.io/github/xtermjs/xterm.js?branch=master)
[![Gitter](https://badges.gitter.im/sourcelair/xterm.js.svg)](https://gitter.im/sourcelair/xterm.js?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
[![jsDelivr Hits](https://data.jsdelivr.com/v1/package/npm/xterm/badge?style=rounded)](https://www.jsdelivr.com/package/npm/xterm)
Xterm.js is a terminal front-end component written in JavaScript that works in the browser.
It enables applications to provide fully featured terminals to their users and create great development experiences.
Xterm.js is a front-end component written in TypeScript that lets applications bring fully-featured terminals to their users in the browser. It's used by popular projects such as VS Code, Hyper and Theia.
## Features
- **Text-based application support**: Use xterm.js to work with applications like `bash`, `git` etc.
- **Curses-based application support**: Use xterm.js to work with applications like `vim`, `tmux` etc.
- **Mouse events support**: Xterm.js captures mouse events like click and scroll and passes them to the terminal's back-end controlling process
- **CJK (Chinese, Japanese, Korean) character support**: Xterm.js renders CJK characters seamlessly
- **IME support**: Insert international (including CJK) characters using IME input with your keyboard
- **Self-contained library**: Xterm.js works on its own. It does not require any external libraries like jQuery or React to work
- **Modular, event-based API**: Lets you build addons and themes with ease
- **Terminal apps just work**: Xterm.js works with most terminal apps such as `bash`, `vim` and `tmux`, this includes support for curses-based apps and mouse event support.
- **Perfomant**: Xterm.js is *really* fast, it even includes a GPU-accelerated renderer.
- **Rich unicode support**: Supports CJK, emojis and IMEs.
- **Self-contained**: Requires zero dependencies to work.
- **Accessible**: Screen reader support can be turned on using the `screenReaderMode` option.
- **And much more**: Links, theming, addons, well documented API, etc.
## What xterm.js is not
- Xterm.js is not a terminal application that you can download and use on your computer
- Xterm.js is not `bash`. Xterm.js can be connected to processes like `bash` and let you interact with them (provide input, receive output)
- Xterm.js is not a terminal application that you can download and use on your computer.
- Xterm.js is not `bash`. Xterm.js can be connected to processes like `bash` and let you interact with them (provide input, receive output).
## Getting Started
@@ -30,7 +27,7 @@ First you need to install the module, we ship exclusively through [npm](https://
npm install xterm
```
To start using xterm.js on your browser, add the `xterm.js` and `xterm.css` to the head of your html page. Then create a `<div id="terminal"></div>` onto which xterm can attach itself.
To start using xterm.js on your browser, add the `xterm.js` and `xterm.css` to the head of your html page. Then create a `<div id="terminal"></div>` onto which xterm can attach itself. Finally instantiate the `Terminal` object and then call the `open` function with the DOM object of the `div`.
```html
<!doctype html>
@@ -44,39 +41,30 @@ To start using xterm.js on your browser, add the `xterm.js` and `xterm.css` to t
<script>
var term = new Terminal();
term.open(document.getElementById('terminal'));
      term.write('Hello from \x1B[1;3;31mxterm.js\x1B[0m $ ')
term.write('Hello from \x1B[1;3;31mxterm.js\x1B[0m $ ')
</script>
</body>
</html>
```
Finally instantiate the `Terminal` object and then call the `open` function with the DOM object of the `div`.
### Importing
The proposed way to load xterm.js is via the ES6 module syntax.
The recommended way to load xterm.js is via the ES6 module syntax:
```javascript
import { Terminal } from 'xterm';
```
### API
The full API for xterm.js is contained within the [TypeScript declaration file](https://github.com/xtermjs/xterm.js/blob/master/typings/xterm.d.ts), use the branch/tag picker in GitHub (`w`) to navigate to the correct version of the API.
Note that some APIs are marked *experimental*, these are added so we can experiment with new ideas without committing to support it like a normal semver API. Note that these APIs can change radically between versions so be sure to read release notes if you plan on using experimental APIs.
### Addons
Addons are JavaScript modules that extend the `Terminal` prototype with new methods and attributes to provide additional functionality. There are a handful available in the main repository in the `src/addons` directory and you can even write your own, by using xterm.js' public API.
Addons are JavaScript modules that extend the `Terminal` prototype with new methods and attributes to provide additional functionality. There are a handful available in the main repository in the `src/addons` directory and you can even write your own by using the [public API](https://github.com/xtermjs/xterm.js/blob/master/typings/xterm.d.ts).
To use an addon, just import the JavaScript module and pass it to `Terminal`'s `applyAddon` method:
```javascript
import { Terminal } from xterm;
import { Terminal } from 'xterm';
import * as fit from 'xterm/lib/addons/fit/fit';
Terminal.applyAddon(fit);
var xterm = new Terminal(); // Instantiate the terminal
@@ -87,25 +75,16 @@ You will also need to include the addon's CSS file if it has one in the folder.
#### Importing Addons in TypeScript
There are currently no typings for addons if they are accessed via extending Terminal prototype, so you will need to upcast if using TypeScript, eg. `(<any>xterm).fit()`.
Alternatively, you can import addon function and enhance the terminal on demand. This would have better typing support and is friendly to treeshaking. E.g.:
There are currently no typings for addons if they are accessed via extending Terminal prototype, so you will need to upcast if using TypeScript, eg. `(xterm as any).fit()`. Alternatively, you can import the addon function and enhance the terminal on demand. This has better typing support and is friendly to treeshaking.
```typescript
import { Terminal } from 'xterm';
import { fit } from 'xterm/lib/addons/fit/fit';
const xterm = new Terminal();
// Fit the terminal when necessary:
fit(xterm);
fit(xterm); // Fit the terminal when necessary
```
#### Third party addons
There are also the following third party addons available:
- [xterm-webfont](https://www.npmjs.com/package/xterm-webfont)
## Browser Support
Since xterm.js is typically implemented as a developer tool, only modern browsers are supported officially. Here is a list of the versions we aim to support:
@@ -116,26 +95,28 @@ Since xterm.js is typically implemented as a developer tool, only modern browser
- Safari latest
- IE11
Xterm.js works seamlessly in Electron apps and may even work on earlier versions of the browsers but these are the browsers we strive to keep working.
Xterm.js works seamlessly in [Electron](https://electronjs.org/) apps and may even work on earlier versions of the browsers, these are the versions we strive to keep working.
## API
The current full API documentation is available in the [TypeScript declaration file on the repository](https://github.com/xtermjs/xterm.js/blob/master/typings/xterm.d.ts), switch the tag (press `w` when viewing the file) to point at the specific version tag you're using.
The full API for xterm.js is contained within the [TypeScript declaration file](https://github.com/xtermjs/xterm.js/blob/master/typings/xterm.d.ts), use the branch/tag picker in GitHub (`w`) to navigate to the correct version of the API.
Note that some APIs are marked *experimental*, these are added to enable experimentation with new ideas without committing to support it like a normal [semver](https://semver.org/) API. Note that these APIs can change radically between versions so be sure to read release notes if you plan on using experimental APIs.
## Real-world uses
Xterm.js is used in several world-class applications to provide great terminal experiences.
- [**SourceLair**](https://www.sourcelair.com/): In-browser IDE that provides its users with fully-featured Linux terminals based on xterm.js
- [**Microsoft Visual Studio Code**](http://code.visualstudio.com/): Modern, versatile and powerful open source code editor that provides an integrated terminal based on xterm.js
- [**ttyd**](https://github.com/tsl0922/ttyd): A command-line tool for sharing terminal over the web, with fully-featured terminal emulation based on xterm.js
- [**SourceLair**](https://www.sourcelair.com/): In-browser IDE that provides its users with fully-featured Linux terminals based on xterm.js.
- [**Microsoft Visual Studio Code**](http://code.visualstudio.com/): Modern, versatile and powerful open source code editor that provides an integrated terminal based on xterm.js.
- [**ttyd**](https://github.com/tsl0922/ttyd): A command-line tool for sharing terminal over the web, with fully-featured terminal emulation based on xterm.js.
- [**Katacoda**](https://www.katacoda.com/): Katacoda is an Interactive Learning Platform for software developers, covering the latest Cloud Native technologies.
- [**Eclipse Che**](http://www.eclipse.org/che): Developer workspace server, cloud IDE, and Eclipse next-generation IDE.
- [**Codenvy**](http://www.codenvy.com): Cloud workspaces for development teams.
- [**CoderPad**](https://coderpad.io): Online interviewing platform for programmers. Run code in many programming languages, with results displayed by `xterm.js`.
- [**WebSSH2**](https://github.com/billchurch/WebSSH2): A web based SSH2 client using `xterm.js`, socket.io, and ssh2.
- [**CoderPad**](https://coderpad.io): Online interviewing platform for programmers. Run code in many programming languages, with results displayed by xterm.js.
- [**WebSSH2**](https://github.com/billchurch/WebSSH2): A web based SSH2 client using xterm.js, socket.io, and ssh2.
- [**Spyder Terminal**](https://github.com/spyder-ide/spyder-terminal): A full fledged system terminal embedded on Spyder IDE.
- [**Cloud Commander**](https://cloudcmd.io "Cloud Commander"): Orthodox web file manager with console and editor.
- [**Codevolve**](https://www.codevolve.com "Codevolve"): Online platform for interactive coding and web development courses. Live container-backed terminal uses `xterm.js`.
- [**Codevolve**](https://www.codevolve.com "Codevolve"): Online platform for interactive coding and web development courses. Live container-backed terminal uses xterm.js.
- [**RStudio**](https://www.rstudio.com/products/RStudio "RStudio"): RStudio is an integrated development environment (IDE) for R.
- [**Terminal for Atom**](https://github.com/jsmecham/atom-terminal-tab): A simple terminal for the Atom text editor.
- [**Eclipse Orion**](https://orionhub.org): A modern, open source software development environment that runs in the cloud. Code, deploy and run in the cloud.
@@ -143,9 +124,8 @@ Xterm.js is used in several world-class applications to provide great terminal e
- [**Hexlet**](https://en.hexlet.io): Practical programming courses (JavaScript, PHP, Unix, databases, functional programming). A steady path from the first line of code to the first job.
- [**Selenoid UI**](https://github.com/aerokube/selenoid-ui): Simple UI for the scallable golang implementation of Selenium Hub named Selenoid. We use XTerm for streaming logs over websockets from docker containers.
- [**Portainer**](https://portainer.io): Simple management UI for Docker.
- [**SSHy**](https://github.com/stuicey/SSHy): HTML5 Based SSHv2 Web Client with E2E encryption utilising `xterm.js`, SJCL & websockets.
- [**JupyterLab**](https://github.com/jupyterlab/jupyterlab): An extensible
computational environment for Jupyter, supporting interactive data science and scientific computing across all programming languages.
- [**SSHy**](https://github.com/stuicey/SSHy): HTML5 Based SSHv2 Web Client with E2E encryption utilising xterm.js, SJCL & websockets.
- [**JupyterLab**](https://github.com/jupyterlab/jupyterlab): An extensible computational environment for Jupyter, supporting interactive data science and scientific computing across all programming languages.
- [**Theia**](https://github.com/theia-ide/theia): Theia is a cloud & desktop IDE framework implemented in TypeScript.
- [**Opshell**](https://github.com/ricktbaker/opshell) Ops Helper tool to make life easier working with AWS instances across multiple organizations.
- [**Proxmox VE**](https://www.proxmox.com/en/proxmox-ve): Proxmox VE is a complete open-source platform for enterprise virtualization. It uses xterm.js for container terminals and the host shell.
@@ -156,23 +136,28 @@ computational environment for Jupyter, supporting interactive data science and s
- [**Kubebox**](https://github.com/astefanutti/kubebox): Terminal console for Kubernetes clusters.
- [**Azure Cloud Shell**](https://shell.azure.com): Azure Cloud Shell is a Microsoft-managed admin machine built on Azure, for Azure.
- [**atom-xterm**](https://atom.io/packages/atom-xterm): Atom plugin for providing terminals inside your Atom workspace.
- [**rtty**](https://github.com/zhaojh329/rtty): A reverse proxy WebTTY. It is composed of the client and the server.
- [**Pisth**](https://github.com/ColdGrub1384/Pisth): An SFTP and SSH client for iOS
- [**rtty**](https://github.com/zhaojh329/rtty): Access your terminals from anywhere via the web.
- [**Pisth**](https://github.com/ColdGrub1384/Pisth): An SFTP and SSH client for iOS.
- [**abstruse**](https://github.com/bleenco/abstruse): Abstruse CI is a continuous integration platform based on Node.JS and Docker.
- [**Azure Data Studio**](https://github.com/Microsoft/azuredatastudio): A data management tool that enables working with SQL Server, Azure SQL DB and SQL DW from Windows, macOS and Linux.
- [**FreeMAN**](https://github.com/matthew-matvei/freeman): A free, cross-platform file manager for power users
- [**FreeMAN**](https://github.com/matthew-matvei/freeman): A free, cross-platform file manager for power users.
- [**Fluent Terminal**](https://github.com/felixse/FluentTerminal): A terminal emulator based on UWP and web technologies.
- [**Hyper**](https://hyper.is): A terminal built on web technologies
- [**Hyper**](https://hyper.is): A terminal built on web technologies.
- [**Diag**](https://diag.ai): A better way to troubleshoot problems faster. Capture, share and reapply troubleshooting knowledge so you can focus on solving problems that matter.
- [**GoTTY**](https://github.com/yudai/gotty): A simple command line tool that shares your terminal as a web application based on xterm.js.
- [**genact**](https://github.com/svenstaro/genact): A nonsense activity generator.
- [**cPanel & WHM**](https://cpanel.com): The hosting platform of choice.
- [**Nutanix**](https://github.com/nutanix): Nutanix Enterprise Cloud uses xterm in the webssh functionality within Nutanix Calm, and is also looking to move our old noserial (termjs) functionality to xterm.js
- [**Nutanix**](https://github.com/nutanix): Nutanix Enterprise Cloud uses xterm in the webssh functionality within Nutanix Calm, and is also looking to move our old noserial (termjs) functionality to xterm.js.
- [**SSH Web Client**](https://github.com/roke22/PHP-SSH2-Web-Client): SSH Web Client with PHP.
- [**Shellvault**](https://www.shellvault.io): The cloud-based SSH terminal you can access from anywhere.
- [**Juno**](http://junolab.org/): A flexible Julia IDE, based on Atom.
- [**webssh**](https://github.com/huashengdun/webssh): Web based ssh client.
- [**info-beamer hosted**](https://info-beamer.com): Uses Xterm.js to manage digital signage devices from the web dashboard.
- [**info-beamer hosted**](https://info-beamer.com): Uses xterm.js to manage digital signage devices from the web dashboard.
- [**Jumpserver**](https://github.com/jumpserver/luna): Jumpserver Luna project, Jumpserver is a bastion server project, Luna use xterm.js for web terminal emulation.
- [**LxdMosaic**](https://github.com/turtle0x1/LxdMosaic): Uses xterm.js to give terminal access to containers through LXD
- [**CodeInterview.io**](https://codeinterview.io): A coding interview platform in 25+ languages and many web frameworks. Uses xterm.js to provide shell access.
- [**Bastillion**](https://www.bastillion.io): Bastillion is an open-source web-based SSH console that centrally manages administrative access to systems.
- [**PHP App Server**](https://github.com/cubiclesoft/php-app-server/): Create lightweight, installable almost-native applications for desktop OSes. ExecTerminal (nicely wraps the xterm.js Terminal), TerminalManager, and RunProcessSDK are self-contained, reusable ES5+ compliant Javascript components.
[And much more...](https://github.com/xtermjs/xterm.js/network/dependents)
@@ -182,7 +167,17 @@ Do you use xterm.js in your application as well? Please [open a Pull Request](ht
Xterm.js follows a monthly release cycle roughly.
The existing releases are available at this GitHub repo's [Releases](https://github.com/sourcelair/xterm.js/releases), while the roadmap is available as [Milestones](https://github.com/sourcelair/xterm.js/milestones).
All current and past releases are available on this repo's [Releases page](https://github.com/sourcelair/xterm.js/releases), you can view the [high-level roadmap on the wiki](https://github.com/xtermjs/xterm.js/wiki/Roadmap) and see what we're working on now by looking through [Milestones](https://github.com/sourcelair/xterm.js/milestones).
### Beta builds
Our CI releases beta builds to npm for every change that goes into master, install the latest beta build with:
```
npm install -S xterm@beta
```
These should generally be stable but some bugs may slip in, we recommend using the beta build primarily to test out new features and for verifying bug fixes.
## Contributing
@@ -192,6 +187,6 @@ You can read the [guide on the wiki](https://github.com/xtermjs/xterm.js/wiki/Co
If you contribute code to this project, you are implicitly allowing your code to be distributed under the MIT license. You are also implicitly verifying that all code is your original work.
Copyright (c) 2017-2018, [The xterm.js authors](https://github.com/xtermjs/xterm.js/graphs/contributors) (MIT License)<br>
Copyright (c) 2017-2019, [The xterm.js authors](https://github.com/xtermjs/xterm.js/graphs/contributors) (MIT License)<br>
Copyright (c) 2014-2017, SourceLair, Private Company ([www.sourcelair.com](https://www.sourcelair.com/home)) (MIT License)<br>
Copyright (c) 2012-2013, Christopher Jeffrey (MIT License)
+51 -4
View File
@@ -21,7 +21,7 @@ jobs:
displayName: 'Install dependencies and build'
- script: |
yarn mocha
displayName: 'Test'
displayName: 'Unit tests'
- script: |
yarn lint
displayName: 'Lint'
@@ -39,13 +39,13 @@ jobs:
displayName: 'Install dependencies and build'
- script: |
yarn mocha
displayName: 'Test'
displayName: 'Unit tests'
- script: |
yarn lint
displayName: 'Lint'
- script: |
yarn test-coverage
export COVERALLS_GIT_BRANCH=$BUILD_SOURCEBRANCH
export COVERALLS_GIT_BRANCH=$BUILD_SOURCEBRANCHNAME
yarn coveralls
displayName: 'Generate and publish coverage'
@@ -62,7 +62,54 @@ jobs:
displayName: 'Install dependencies and build'
- script: |
yarn mocha
displayName: 'Test'
displayName: 'Unit tests'
- script: |
yarn lint
displayName: 'Lint'
- job: IntegrationTests
pool:
vmImage: 'ubuntu-16.04'
steps:
- task: NodeTool@0
inputs:
versionSpec: '8.x'
displayName: 'Install Node.js'
- task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@2
inputs:
versionSpec: "1.9.4"
displayName: 'Install Yarn'
- script: |
yarn
displayName: 'Install dependencies and build'
- script: |
yarn start &
sleep 10
yarn test-api --headless
displayName: 'Integration tests'
- job: Release
dependsOn:
- Linux
- macOS
- Windows
- IntegrationTests
condition: and(succeeded(), or(eq(variables['Build.SourceBranch'], 'refs/heads/master'), eq(variables['Build.SourceBranch'], 'refs/heads/release/*')))
pool:
vmImage: 'ubuntu-16.04'
steps:
- task: NodeTool@0
inputs:
versionSpec: '8.x'
displayName: 'Install Node.js'
- task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@2
inputs:
versionSpec: "1.9.4"
displayName: 'Install Yarn'
- script: |
yarn
BUILD_DIR=dist npm run build
displayName: 'Install dependencies and build'
- script: |
NPM_AUTH_TOKEN="$(NPM_AUTH_TOKEN)" node ./bin/publish.js
displayName: 'Publish to npm'
-7
View File
@@ -1,7 +0,0 @@
#! /usr/bin/env sh
tail --lines=+3 AUTHORS > AUTHORS.tmp
git log --format='%aN <%aE>' >> AUTHORS.tmp
echo "List of xterm.js contributors. Updated before every release.\n" > AUTHORS
sort -u AUTHORS.tmp >> AUTHORS
rm -f AUTHORS.tmp
-27
View File
@@ -1,27 +0,0 @@
#! /usr/bin/env sh
# Usage: ./bin/prepare-release x.y.z
# x.y.z should be semver (e.g. 1.0.0)
set -e
NEW_VERSION=$1
CURRENT_PACKAGE_JSON_VERSION=$(cat package.json \
| grep version \
| head -1 \
| awk -F: '{ print $2 }' \
| sed 's/[",]//g' \
| tr -d '[[:space:]]')
# Build xterm.js into `dist`
export BUILD_DIR=dist
npm run build
# Update AUTHORS file
sh bin/generate-authors
# Update version in package.json
sed -i "s/\"version\": \"$CURRENT_PACKAGE_JSON_VERSION\"/\"version\": \"$NEW_VERSION\"/g" package.json
git commit -S -s -a -m "Bump version to $NEW_VERSION"
git tag $NEW_VERSION
+63
View File
@@ -0,0 +1,63 @@
/**
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
* @license MIT
*/
const cp = require('child_process');
const fs = require('fs');
const path = require('path');
const packageJson = require('../package.json');
// Setup auth
fs.writeFileSync(`${process.env['HOME']}/.npmrc`, `//registry.npmjs.org/:_authToken=${process.env['NPM_AUTH_TOKEN']}`);
// Determine if this is a stable or beta release
const publishedVersions = getPublishedVersions();
const isStableRelease = publishedVersions.indexOf(packageJson.version) === -1;
// Get the next version
let nextVersion = isStableRelease ? packageJson.version : getNextBetaVersion();
console.log(`Publishing version: ${nextVersion}`);
// Set the version in package.json
const packageJsonFile = path.resolve(__dirname, '..', 'package.json');
packageJson.version = nextVersion;
fs.writeFileSync(packageJsonFile, JSON.stringify(packageJson, null, 2));
// Publish
const args = ['publish'];
if (!isStableRelease) {
args.push('--tag', 'beta');
}
const result = cp.spawn('npm', args, { stdio: 'inherit' });
result.on('exit', code => process.exit(code));
function getNextBetaVersion() {
if (!/^[0-9]+\.[0-9]+\.[0-9]+$/.exec(packageJson.version)) {
console.error('The package.json version must be of the form x.y.z');
process.exit(1);
}
const tag = 'beta';
const stableVersion = packageJson.version.split('.');
const nextStableVersion = `${stableVersion[0]}.${parseInt(stableVersion[1]) + 1}.0`;
const publishedVersions = getPublishedVersions(nextStableVersion, tag);
if (publishedVersions.length === 0) {
return `${nextStableVersion}-${tag}1`;
}
const latestPublishedVersion = publishedVersions.sort((a, b) => {
const aVersion = parseInt(a.substr(a.search(/[0-9]+$/)));
const bVersion = parseInt(b.substr(b.search(/[0-9]+$/)));
return aVersion > bVersion ? -1 : 1;
})[0];
const latestTagVersion = parseInt(latestPublishedVersion.substr(latestPublishedVersion.search(/[0-9]+$/)), 10);
return `${nextStableVersion}-${tag}${latestTagVersion + 1}`;
}
function getPublishedVersions(version, tag) {
const versionsProcess = cp.spawnSync('npm', ['view', packageJson.name, 'versions', '--json']);
const versionsJson = JSON.parse(versionsProcess.stdout);
if (tag) {
return versionsJson.filter(v => !v.search(new RegExp(`${version}-${tag}[0-9]+`)));
}
return versionsJson;
}
-22
View File
@@ -1,22 +0,0 @@
#! /usr/bin/env sh
# Usage: ./bin/release x.y.z
# x.y.z should be semver (e.g. 1.0.0)
set -e
if [ -z "$1" ];
then
echo "No version supplied. Please a version argument\n"
echo "Usage: $0 VERSION\n"
echo "Example: $0 1.0.0"
exit
fi
NEW_VERSION=$1
./bin/prepare-release $NEW_VERSION
git push && \
git push --tags && \
npm publish
+63 -53
View File
@@ -8,31 +8,26 @@
/// <reference path="../typings/xterm.d.ts"/>
import { Terminal } from '../lib/public/Terminal';
import * as attach from '../lib/addons/attach/attach';
import { AttachAddon } from 'xterm-addon-attach';
import { SearchAddon, ISearchOptions } from 'xterm-addon-search';
import { WebLinksAddon } from 'xterm-addon-web-links';
import * as fit from '../lib/addons/fit/fit';
import * as fullscreen from '../lib/addons/fullscreen/fullscreen';
import * as search from '../lib/addons/search/search';
import * as webLinks from '../lib/addons/webLinks/webLinks';
import * as winptyCompat from '../lib/addons/winptyCompat/winptyCompat';
// Pulling in the module's types relies on the <reference> above, it's looks a
// little weird here as we're importing "this" module
import { Terminal as TerminalType } from 'xterm';
import { Terminal as TerminalType, ITerminalOptions } from 'xterm';
export interface IWindowWithTerminal extends Window {
term: TerminalType;
Terminal?: typeof TerminalType;
}
declare let window: IWindowWithTerminal;
Terminal.applyAddon(attach);
Terminal.applyAddon(fit);
Terminal.applyAddon(fullscreen);
Terminal.applyAddon(search);
Terminal.applyAddon(webLinks);
Terminal.applyAddon(winptyCompat);
let term;
let searchAddon: SearchAddon;
let protocol;
let socketURL;
let socket;
@@ -50,7 +45,13 @@ function setPadding(): void {
term.fit();
}
createTerminal();
function getSearchOptions(): ISearchOptions {
return {
regex: (document.getElementById('regex') as HTMLInputElement).checked,
wholeWord: (document.getElementById('whole-word') as HTMLInputElement).checked,
caseSensitive: (document.getElementById('case-sensitive') as HTMLInputElement).checked
};
}
const disposeRecreateButtonHandler = () => {
// If the terminal exists dispose of it, otherwise recreate it
@@ -67,16 +68,32 @@ const disposeRecreateButtonHandler = () => {
}
};
document.getElementById('dispose').addEventListener('click', disposeRecreateButtonHandler);
if (document.location.pathname === '/test') {
window.Terminal = Terminal;
} else {
createTerminal();
document.getElementById('dispose').addEventListener('click', disposeRecreateButtonHandler);
}
function createTerminal(): void {
// Clean terminal
while (terminalContainer.children.length) {
terminalContainer.removeChild(terminalContainer.children[0]);
}
term = new Terminal({});
const isWindows = ['Windows', 'Win16', 'Win32', 'WinCE'].indexOf(navigator.platform) >= 0;
term = new Terminal({
windowsMode: isWindows
} as ITerminalOptions);
// Load addons
const typedTerm = term as TerminalType;
typedTerm.loadAddon(new WebLinksAddon());
searchAddon = new SearchAddon();
typedTerm.loadAddon(searchAddon);
window.term = term; // Expose `term` to window for debugging purposes
term.on('resize', (size: { cols: number, rows: number }) => {
term.onResize((size: { cols: number, rows: number }) => {
if (!pid) {
return;
}
@@ -90,33 +107,20 @@ function createTerminal(): void {
socketURL = protocol + location.hostname + ((location.port) ? (':' + location.port) : '') + '/terminals/';
term.open(terminalContainer);
term.winptyCompatInit();
term.webLinksInit();
term.fit();
term.focus();
addDomListener(paddingElement, 'change', setPadding);
addDomListener(actionElements.findNext, 'keypress', (e) => {
if (e.key === 'Enter') {
e.preventDefault();
const searchOptions = {
regex: (document.getElementById('regex') as HTMLInputElement).checked,
wholeWord: (document.getElementById('whole-word') as HTMLInputElement).checked,
caseSensitive: (document.getElementById('case-sensitive') as HTMLInputElement).checked
};
term.findNext(actionElements.findNext.value, searchOptions);
}
addDomListener(actionElements.findNext, 'keyup', (e) => {
const searchOptions = getSearchOptions();
searchOptions.incremental = e.key !== `Enter`;
searchAddon.findNext(actionElements.findNext.value, searchOptions);
});
addDomListener(actionElements.findPrevious, 'keypress', (e) => {
if (e.key === 'Enter') {
e.preventDefault();
const searchOptions = {
regex: (document.getElementById('regex') as HTMLInputElement).checked,
wholeWord: (document.getElementById('whole-word') as HTMLInputElement).checked,
caseSensitive: (document.getElementById('case-sensitive') as HTMLInputElement).checked
};
term.findPrevious(actionElements.findPrevious.value, searchOptions);
addDomListener(actionElements.findPrevious, 'keyup', (e) => {
if (e.key === `Enter`) {
searchAddon.findPrevious(actionElements.findPrevious.value, getSearchOptions());
}
});
@@ -145,7 +149,14 @@ function createTerminal(): void {
}
function runRealTerminal(): void {
term.attach(socket);
/**
* The demo defaults to string transport by default.
* To run it with UTF8 binary transport, swap comment on
* the lines below. (Must also be switched in server.js)
*/
term.loadAddon(new AttachAddon(socket));
// term.loadAddon(new AttachAddon(socket, {inputUtf8: true}));
term._initialized = true;
}
@@ -166,24 +177,21 @@ function runFakeTerminal(): void {
term.writeln('');
term.prompt();
term._core.register(term.addDisposableListener('key', (key, ev) => {
const printable = !ev.altKey && !ev.altGraphKey && !ev.ctrlKey && !ev.metaKey;
term.onKey((e: { key: string, domEvent: KeyboardEvent }) => {
const ev = e.domEvent;
const printable = !ev.altKey && !ev.ctrlKey && !ev.metaKey;
if (ev.keyCode === 13) {
term.prompt();
} else if (ev.keyCode === 8) {
// Do not delete the prompt
if (term.x > 2) {
if (term._core.buffer.x > 2) {
term.write('\b \b');
}
} else if (printable) {
term.write(key);
term.write(e.key);
}
}));
term._core.register(term.addDisposableListener('paste', (data, ev) => {
term.write(data);
}));
});
}
function initOptions(term: TerminalType): void {
@@ -207,8 +215,7 @@ function initOptions(term: TerminalType): void {
fontFamily: null,
fontWeight: ['normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900'],
fontWeightBold: ['normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900'],
rendererType: ['dom', 'canvas'],
experimentalBufferLineImpl: ['JsArray', 'TypedArray']
rendererType: ['dom', 'canvas']
};
const options = Object.keys((<any>term)._core.options);
const booleanOptions = [];
@@ -235,7 +242,7 @@ function initOptions(term: TerminalType): void {
});
html += '</div><div class="option-group">';
numberOptions.forEach(o => {
html += `<div class="option"><label>${o} <input id="opt-${o}" type="number" value="${term.getOption(o)}"/></label></div>`;
html += `<div class="option"><label>${o} <input id="opt-${o}" type="number" value="${term.getOption(o)}" step="${o === 'lineHeight' ? '0.1' : '1'}"/></label></div>`;
});
html += '</div><div class="option-group">';
Object.keys(stringOptions).forEach(o => {
@@ -264,8 +271,11 @@ function initOptions(term: TerminalType): void {
console.log('change', o, input.value);
if (o === 'cols' || o === 'rows') {
updateTerminalSize();
} else if (o === 'lineHeight') {
term.setOption(o, parseFloat(input.value));
updateTerminalSize();
} else {
term.setOption(o, parseInt(input.value, 10));
term.setOption(o, parseInt(input.value));
}
});
});
@@ -286,8 +296,8 @@ function addDomListener(element: HTMLElement, type: string, handler: (...args: a
function updateTerminalSize(): void {
const cols = parseInt((<HTMLInputElement>document.getElementById(`opt-cols`)).value, 10);
const rows = parseInt((<HTMLInputElement>document.getElementById(`opt-rows`)).value, 10);
const width = (cols * term._core.renderer.dimensions.actualCellWidth + term._core.viewport.scrollBarWidth).toString() + 'px';
const height = (rows * term._core.renderer.dimensions.actualCellHeight).toString() + 'px';
const width = (cols * term._core._renderCoordinator.dimensions.actualCellWidth + term._core.viewport.scrollBarWidth).toString() + 'px';
const height = (rows * term._core._renderCoordinator.dimensions.actualCellHeight).toString() + 'px';
terminalContainer.style.width = width;
terminalContainer.style.height = height;
term.fit();
+3 -3
View File
@@ -2,8 +2,8 @@
<html>
<head>
<title>xterm.js demo</title>
<link rel="stylesheet" href="/build/xterm.css" />
<link rel="stylesheet" href="/build/addons/fullscreen/fullscreen.css" />
<link rel="shortcut icon" type="image/png" href="/logo.png">
<link rel="stylesheet" href="/src/xterm.css" />
<link rel="stylesheet" href="style.css" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/es6-promise/4.1.1/es6-promise.auto.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fetch/1.0.0/fetch.min.js"></script>
@@ -16,7 +16,7 @@
<p>
<label>Find next <input id="find-next"/></label>
<label>Find previous <input id="find-previous"/></label>
<label>Use regex<input type="checkbox" id="regex"/></label>
<label>Use regex<input type="checkbox" id="regex"/></label>
<label>Case sensitive<input type="checkbox" id="case-sensitive"/></label>
<label>Whole word<input type="checkbox" id="whole-word"/></label>
</p>
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

+120 -82
View File
@@ -1,100 +1,138 @@
var express = require('express');
var app = express();
var expressWs = require('express-ws')(app);
var expressWs = require('express-ws');
var os = require('os');
var pty = require('node-pty');
var terminals = {},
logs = {};
/**
* Whether to use UTF8 binary transport.
* (Must also be switched in client.ts)
*/
const USE_BINARY_UTF8 = false;
app.use('/build', express.static(__dirname + '/../build'));
app.get('/', function(req, res){
res.sendFile(__dirname + '/index.html');
});
function startServer() {
var app = express();
expressWs(app);
app.get('/style.css', function(req, res){
res.sendFile(__dirname + '/style.css');
});
var terminals = {},
logs = {};
app.get('/dist/client-bundle.js', function(req, res){
res.sendFile(__dirname + '/dist/client-bundle.js');
});
app.use('/src', express.static(__dirname + '/../src'));
app.get('/logo.png', (req, res) => res.sendFile(__dirname + '/logo.png'));
app.post('/terminals', function (req, res) {
var cols = parseInt(req.query.cols),
rows = parseInt(req.query.rows),
term = pty.spawn(process.platform === 'win32' ? 'cmd.exe' : 'bash', [], {
name: 'xterm-color',
cols: cols || 80,
rows: rows || 24,
cwd: process.env.PWD,
env: process.env
});
console.log('Created terminal with PID: ' + term.pid);
terminals[term.pid] = term;
logs[term.pid] = '';
term.on('data', function(data) {
logs[term.pid] += data;
app.get('/', function(req, res){
res.sendFile(__dirname + '/index.html');
});
res.send(term.pid.toString());
res.end();
});
app.post('/terminals/:pid/size', function (req, res) {
var pid = parseInt(req.params.pid),
cols = parseInt(req.query.cols),
rows = parseInt(req.query.rows),
term = terminals[pid];
app.get('/test', function(req, res){
res.sendFile(__dirname + '/test.html');
});
term.resize(cols, rows);
console.log('Resized terminal ' + pid + ' to ' + cols + ' cols and ' + rows + ' rows.');
res.end();
});
app.get('/style.css', function(req, res){
res.sendFile(__dirname + '/style.css');
});
app.ws('/terminals/:pid', function (ws, req) {
var term = terminals[parseInt(req.params.pid)];
console.log('Connected to terminal ' + term.pid);
ws.send(logs[term.pid]);
app.get('/dist/client-bundle.js', function(req, res){
res.sendFile(__dirname + '/dist/client-bundle.js');
});
function buffer(socket, timeout) {
let s = '';
let sender = null;
return (data) => {
s += data;
if (!sender) {
sender = setTimeout(() => {
socket.send(s);
s = '';
sender = null;
}, timeout);
}
};
}
const send = buffer(ws, 5);
app.post('/terminals', function (req, res) {
var cols = parseInt(req.query.cols),
rows = parseInt(req.query.rows),
term = pty.spawn(process.platform === 'win32' ? 'cmd.exe' : 'bash', [], {
name: 'xterm-color',
cols: cols || 80,
rows: rows || 24,
cwd: process.env.PWD,
env: process.env,
encoding: USE_BINARY_UTF8 ? null : 'utf8'
});
term.on('data', function(data) {
try {
send(data);
} catch (ex) {
// The WebSocket is not open, ignore
console.log('Created terminal with PID: ' + term.pid);
terminals[term.pid] = term;
logs[term.pid] = '';
term.on('data', function(data) {
logs[term.pid] += data;
});
res.send(term.pid.toString());
res.end();
});
app.post('/terminals/:pid/size', function (req, res) {
var pid = parseInt(req.params.pid),
cols = parseInt(req.query.cols),
rows = parseInt(req.query.rows),
term = terminals[pid];
term.resize(cols, rows);
console.log('Resized terminal ' + pid + ' to ' + cols + ' cols and ' + rows + ' rows.');
res.end();
});
app.ws('/terminals/:pid', function (ws, req) {
var term = terminals[parseInt(req.params.pid)];
console.log('Connected to terminal ' + term.pid);
ws.send(logs[term.pid]);
// string message buffering
function buffer(socket, timeout) {
let s = '';
let sender = null;
return (data) => {
s += data;
if (!sender) {
sender = setTimeout(() => {
socket.send(s);
s = '';
sender = null;
}, timeout);
}
};
}
});
ws.on('message', function(msg) {
term.write(msg);
});
ws.on('close', function () {
term.kill();
console.log('Closed terminal ' + term.pid);
// Clean things up
delete terminals[term.pid];
delete logs[term.pid];
});
});
// binary message buffering
function bufferUtf8(socket, timeout) {
let buffer = [];
let sender = null;
let length = 0;
return (data) => {
buffer.push(data);
length += data.length;
if (!sender) {
sender = setTimeout(() => {
socket.send(Buffer.concat(buffer, length));
buffer = [];
sender = null;
length = 0;
}, timeout);
}
};
}
const send = USE_BINARY_UTF8 ? bufferUtf8(ws, 5) : buffer(ws, 5);
var port = process.env.PORT || 3000,
host = os.platform() === 'win32' ? '127.0.0.1' : '0.0.0.0';
term.on('data', function(data) {
try {
send(data);
} catch (ex) {
// The WebSocket is not open, ignore
}
});
ws.on('message', function(msg) {
term.write(msg);
});
ws.on('close', function () {
term.kill();
console.log('Closed terminal ' + term.pid);
// Clean things up
delete terminals[term.pid];
delete logs[term.pid];
});
});
console.log('App listening to http://' + host + ':' + port);
app.listen(port, host);
var port = process.env.PORT || 3000,
host = os.platform() === 'win32' ? '127.0.0.1' : '0.0.0.0';
console.log('App listening to http://127.0.0.1:' + port);
app.listen(port, host);
}
module.exports = startServer;
+9 -3
View File
@@ -5,12 +5,11 @@
* This file is the entry point for browserify.
*/
const cp = require('child_process');
const path = require('path');
const webpack = require('webpack');
const startServer = require('./server.js');
// Launch server
cp.spawn('node', [path.resolve(__dirname, 'server.js')], { stdio: 'inherit' });
startServer();
// Build/watch client source
const clientConfig = {
@@ -22,10 +21,17 @@ const clientConfig = {
test: /\.tsx?$/,
use: 'ts-loader',
exclude: /node_modules/
},
{
test: /\.js$/,
use: ["source-map-loader"],
enforce: "pre",
exclude: /node_modules/
}
]
},
resolve: {
modules: [path.resolve(__dirname, '..'), 'node_modules'],
extensions: [ '.tsx', '.ts', '.js' ]
},
output: {
+12
View File
@@ -0,0 +1,12 @@
<!doctype html>
<html>
<head>
<title>xterm.js integration test fixture</title>
<link rel="stylesheet" href="/src/xterm.css" />
<link rel="stylesheet" href="style.css" />
</head>
<body>
<div id="terminal-container"></div>
<script src="dist/client-bundle.js" defer ></script>
</body>
</html>
-87
View File
@@ -1,87 +0,0 @@
var express = require('express');
var app = express();
var expressWs = require('express-ws')(app);
var os = require('os');
var pty = require('node-pty');
var terminals = {},
logs = {};
app.use('/build', express.static(__dirname + '/../../build'));
app.use('/demo', express.static(__dirname + '/../../demo'));
app.use('/zmodemjs', express.static(__dirname + '/../../node_modules/zmodem.js/dist'));
app.get('/', function(req, res){
res.sendFile(__dirname + '/index.html');
});
app.get('/style.css', function(req, res){
res.sendFile(__dirname + '../style.css');
});
app.get('/main.js', function(req, res){
res.sendFile(__dirname + '/main.js');
});
app.post('/terminals', function (req, res) {
var cols = parseInt(req.query.cols),
rows = parseInt(req.query.rows),
term = pty.spawn(process.platform === 'win32' ? 'cmd.exe' : 'bash', [], {
encoding: null,
name: 'xterm-color',
cols: cols || 80,
rows: rows || 24,
cwd: process.env.PWD,
env: process.env
});
console.log('Created terminal with PID: ' + term.pid);
terminals[term.pid] = term;
logs[term.pid] = '';
term.on('data', function(data) {
logs[term.pid] += data;
});
res.send(term.pid.toString());
res.end();
});
app.post('/terminals/:pid/size', function (req, res) {
var pid = parseInt(req.params.pid),
cols = parseInt(req.query.cols),
rows = parseInt(req.query.rows),
term = terminals[pid];
term.resize(cols, rows);
console.log('Resized terminal ' + pid + ' to ' + cols + ' cols and ' + rows + ' rows.');
res.end();
});
app.ws('/terminals/:pid', function (ws, req) {
var term = terminals[parseInt(req.params.pid)];
console.log('Connected to terminal ' + term.pid);
ws.send(logs[term.pid]);
term.on('data', function(data) {
try {
ws.send(data);
} catch (ex) {
// The WebSocket is not open, ignore
}
});
ws.on('message', function(msg) {
term.write(msg);
});
ws.on('close', function () {
term.kill();
console.log('Closed terminal ' + term.pid);
// Clean things up
delete terminals[term.pid];
delete logs[term.pid];
});
});
var port = process.env.PORT || 3000,
host = os.platform() === 'win32' ? '127.0.0.1' : '0.0.0.0';
console.log('App listening to http://' + host + ':' + port);
app.listen(port, host);
-128
View File
@@ -1,128 +0,0 @@
<!doctype html>
<html>
<head>
<title>xterm.js demo</title>
<link rel="stylesheet" href="/build/xterm.css" />
<link rel="stylesheet" href="/build/addons/fullscreen/fullscreen.css" />
<link rel="stylesheet" href="/demo/style.css" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/es6-promise/4.1.1/es6-promise.auto.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fetch/1.0.0/fetch.min.js"></script>
<script src="/build/xterm.js" ></script>
<script src="/build/addons/attach/attach.js" ></script>
<script src="/zmodemjs/zmodem.js"></script>
<script src="/build/addons/zmodem/zmodem.js" ></script>
<script src="/build/addons/fit/fit.js" ></script>
<script src="/build/addons/fullscreen/fullscreen.js" ></script>
<script src="/build/addons/search/search.js" ></script>
</head>
<body>
<h1>xterm.js: xterm, in the browser</h1>
<div id="terminal-container"></div>
<div id="zmodem_controls">
<form id="zm_start" style="display: none" action="javascript:void(0)">
ZMODEM detected: Start ZMODEM session?
<label><input id="zmstart_yes" name="zmstart" type=radio checked value="1"> Yes</label>
&nbsp;
<label><input name="zmstart" type=radio value=""> No</label>
<button type="submit">Submit</button>
</form>
<form id="zm_offer" style="display: none" action="javascript:void(0)">
<p>ZMODEM File offered!</p>
<label><input id="zmaccept_yes" name="zmaccept" type=radio checked value="1"> Accept</label>
&nbsp;
<label><input name="zmaccept" type=radio value=""> Skip</label>
<button type="submit">Submit</button>
</form>
<div id="zm_file" style="display: none">
<div>Name: <span id="name"></span></div>
<div>Size: <span id="size"></span></div>
<div>Last modified: <span id="mtime"></span></div>
<div>Mode: <span id="mode"></span></div>
<br>
<div>Conversion: <span id="zfile_conversion"></span></div>
<div>Management: <span id="zfile_management"></span></div>
<div>Transport: <span id="zfile_transport"></span></div>
<div>Sparse? <span id="zfile_sparse"></span></div>
<br>
<div>Files remaining in batch: <span id="files_remaining"></span></div>
<div>Bytes remaining in batch: <span id="bytes_remaining"></span></div>
</div>
<form id="zm_progress" style="display: none" action="javascript:void(0)">
<div><span id="percent_received"></span>% (<span id="bytes_received"></span> bytes) received</div>
<button id="zm_progress_skipper" type="button" onclick="skip_current_file();">Skip File</button>
</form>
<form id="zm_choose" style="display: none" action="javascript:void(0)">
<label>Choose file(s): <input id="zm_files" type="file" multiple></label>
</form>
</div>
<div>
<h2>Actions</h2>
<p>
<label>Find next <input id="find-next"/></label>
<label>Find previous <input id="find-previous"/></label>
</p>
</div>
<div>
<h2>Options</h2>
<p>
<label><input type="checkbox" id="option-cursor-blink"> cursorBlink</label>
</p>
<p>
<label><input type="checkbox" checked id="zmodem-auto"> Accept all ZMODEM prompts<sup>*</sup></label>
</p>
<p>
<label>
cursorStyle
<select id="option-cursor-style">
<option value="block">block</option>
<option value="underline">underline</option>
<option value="bar">bar</option>
</select>
</label>
</p>
<p>
<label>
bellStyle
<select id="option-bell-style">
<option value="">none</option>
<option value="sound">sound</option>
<option value="visual">visual</option>
<option value="both">both</option>
</select>
</label>
</p>
<p>
<label>scrollback <input type="number" id="option-scrollback" value="1000" /></label>
</p>
<p>
<label>tabStopWidth <input type="number" id="option-tabstopwidth" value="8" /></label>
</p>
<div>
<h3>Size</h3>
<div>
<div style="display: inline-block; margin-right: 16px;">
<label for="cols">Columns</label>
<input type="number" id="cols" />
</div>
<div style="display: inline-block; margin-right: 16px;">
<label for="rows">Rows</label>
<input type="number" id="rows" />
</div>
</div>
</div>
</div>
<p><strong>Attention:</strong> The demo is a barebones implementation and is designed for xterm.js evaluation purposes only. Exposing the demo to the public as is would introduce security risks for the host.</p>
<p><sup>*</sup> ZMODEM file transfers are supported via an addon. To try it out, install <a href="https://ohse.de/uwe/software/lrzsz.html"><code>lrzsz</code></a> onto the remote peer, then run <code>rz</code> to send from your browser or <code>sz &lt;file&gt;</code> to send from the remote peer.</p>
<script src="main.js" defer ></script>
</body>
</html>
-388
View File
@@ -1,388 +0,0 @@
"use strict";
var term,
protocol,
socketURL,
socket,
pid;
Terminal.applyAddon(fit);
Terminal.applyAddon(attach);
Terminal.applyAddon(zmodem);
Terminal.applyAddon(search);
var terminalContainer = document.getElementById('terminal-container'),
actionElements = {
findNext: document.querySelector('#find-next'),
findPrevious: document.querySelector('#find-previous')
},
optionElements = {
cursorBlink: document.querySelector('#option-cursor-blink'),
cursorStyle: document.querySelector('#option-cursor-style'),
scrollback: document.querySelector('#option-scrollback'),
tabstopwidth: document.querySelector('#option-tabstopwidth'),
bellStyle: document.querySelector('#option-bell-style')
},
colsElement = document.getElementById('cols'),
rowsElement = document.getElementById('rows');
function setTerminalSize() {
var cols = parseInt(colsElement.value, 10);
var rows = parseInt(rowsElement.value, 10);
var viewportElement = document.querySelector('.xterm-viewport');
var scrollBarWidth = viewportElement.offsetWidth - viewportElement.clientWidth;
var width = (cols * term.charMeasure.width + 20 /*room for scrollbar*/).toString() + 'px';
var height = (rows * term.charMeasure.height).toString() + 'px';
terminalContainer.style.width = width;
terminalContainer.style.height = height;
term.resize(cols, rows);
}
colsElement.addEventListener('change', setTerminalSize);
rowsElement.addEventListener('change', setTerminalSize);
actionElements.findNext.addEventListener('keypress', function (e) {
if (e.key === "Enter") {
e.preventDefault();
term.findNext(actionElements.findNext.value);
}
});
actionElements.findPrevious.addEventListener('keypress', function (e) {
if (e.key === "Enter") {
e.preventDefault();
term.findPrevious(actionElements.findPrevious.value);
}
});
optionElements.cursorBlink.addEventListener('change', function () {
term.setOption('cursorBlink', optionElements.cursorBlink.checked);
});
optionElements.cursorStyle.addEventListener('change', function () {
term.setOption('cursorStyle', optionElements.cursorStyle.value);
});
optionElements.bellStyle.addEventListener('change', function () {
term.setOption('bellStyle', optionElements.bellStyle.value);
});
optionElements.scrollback.addEventListener('change', function () {
term.setOption('scrollback', parseInt(optionElements.scrollback.value, 10));
});
optionElements.tabstopwidth.addEventListener('change', function () {
term.setOption('tabStopWidth', parseInt(optionElements.tabstopwidth.value, 10));
});
createTerminal();
function createTerminal() {
// Clean terminal
while (terminalContainer.children.length) {
terminalContainer.removeChild(terminalContainer.children[0]);
}
term = new Terminal({
cursorBlink: optionElements.cursorBlink.checked,
scrollback: parseInt(optionElements.scrollback.value, 10),
tabStopWidth: parseInt(optionElements.tabstopwidth.value, 10)
});
term.on('resize', function (size) {
if (!pid) {
return;
}
var cols = size.cols,
rows = size.rows,
url = '/terminals/' + pid + '/size?cols=' + cols + '&rows=' + rows;
fetch(url, {method: 'POST'});
});
protocol = (location.protocol === 'https:') ? 'wss://' : 'ws://';
socketURL = protocol + location.hostname + ((location.port) ? (':' + location.port) : '') + '/terminals/';
term.open(terminalContainer);
term.fit();
// fit is called within a setTimeout, cols and rows need this.
setTimeout(function () {
colsElement.value = term.cols;
rowsElement.value = term.rows;
// Set terminal size again to set the specific dimensions on the demo
setTerminalSize();
fetch('/terminals?cols=' + term.cols + '&rows=' + term.rows, {method: 'POST'}).then(function (res) {
res.text().then(function (pid) {
window.pid = pid;
socketURL += pid;
socket = new WebSocket(socketURL);
socket.onopen = runRealTerminal;
socket.onclose = runFakeTerminal;
socket.onerror = runFakeTerminal;
term.zmodemAttach(socket, {
noTerminalWriteOutsideSession: true,
} );
term.on("zmodemRetract", () => {
start_form.style.display = "none";
start_form.onsubmit = null;
});
term.on("zmodemDetect", (detection) => {
function do_zmodem() {
term.detach();
let zsession = detection.confirm();
var promise;
if (zsession.type === "receive") {
promise = _handle_receive_session(zsession);
}
else {
promise = _handle_send_session(zsession);
}
promise.catch( console.error.bind(console) ).then( () => {
term.attach(socket);
} );
}
if (_auto_zmodem()) {
do_zmodem();
}
else {
start_form.style.display = "";
start_form.onsubmit = function(e) {
start_form.style.display = "none";
if (document.getElementById("zmstart_yes").checked) {
do_zmodem();
}
else {
detection.deny();
}
};
}
});
});
});
}, 0);
}
//----------------------------------------------------------------------
// UI STUFF
function _show_file_info(xfer) {
var file_info = xfer.get_details();
document.getElementById("name").textContent = file_info.name;
document.getElementById("size").textContent = file_info.size;
document.getElementById("mtime").textContent = file_info.mtime;
document.getElementById("files_remaining").textContent = file_info.files_remaining;
document.getElementById("bytes_remaining").textContent = file_info.bytes_remaining;
document.getElementById("mode").textContent = "0" + file_info.mode.toString(8);
var xfer_opts = xfer.get_options();
["conversion", "management", "transport", "sparse"].forEach( (lbl) => {
document.getElementById(`zfile_${lbl}`).textContent = xfer_opts[lbl];
} );
document.getElementById("zm_file").style.display = "";
}
function _hide_file_info() {
document.getElementById("zm_file").style.display = "none";
}
function _save_to_disk(xfer, buffer) {
return Zmodem.Browser.save_to_disk(buffer, xfer.get_details().name);
}
var skipper_button = document.getElementById("zm_progress_skipper");
var skipper_button_orig_text = skipper_button.textContent;
function _show_progress() {
skipper_button.disabled = false;
skipper_button.textContent = skipper_button_orig_text;
document.getElementById("bytes_received").textContent = 0;
document.getElementById("percent_received").textContent = 0;
document.getElementById("zm_progress").style.display = "";
}
function _update_progress(xfer) {
var total_in = xfer.get_offset();
document.getElementById("bytes_received").textContent = total_in;
var percent_received = 100 * total_in / xfer.get_details().size;
document.getElementById("percent_received").textContent = percent_received.toFixed(2);
}
function _hide_progress() {
document.getElementById("zm_progress").style.display = "none";
}
var start_form = document.getElementById("zm_start");
function _auto_zmodem() {
return document.getElementById("zmodem-auto").checked;
}
// END UI STUFF
//----------------------------------------------------------------------
function _handle_receive_session(zsession) {
zsession.on("offer", function(xfer) {
current_receive_xfer = xfer;
_show_file_info(xfer);
var offer_form = document.getElementById("zm_offer");
function on_form_submit() {
offer_form.style.display = "none";
//START
//if (offer_form.zmaccept.value) {
if (_auto_zmodem() || document.getElementById("zmaccept_yes").checked) {
_show_progress();
var FILE_BUFFER = [];
xfer.on("input", (payload) => {
_update_progress(xfer);
FILE_BUFFER.push( new Uint8Array(payload) );
});
xfer.accept().then(
() => {
_save_to_disk(xfer, FILE_BUFFER);
},
console.error.bind(console)
);
}
else {
xfer.skip();
}
//END
}
if (_auto_zmodem()) {
on_form_submit();
}
else {
offer_form.onsubmit = on_form_submit;
offer_form.style.display = "";
}
} );
var promise = new Promise( (res) => {
zsession.on("session_end", () => {
_hide_file_info();
_hide_progress();
res();
} );
} );
zsession.start();
return promise;
}
function _handle_send_session(zsession) {
var choose_form = document.getElementById("zm_choose");
choose_form.style.display = "";
var file_el = document.getElementById("zm_files");
var promise = new Promise( (res) => {
file_el.onchange = function(e) {
choose_form.style.display = "none";
var files_obj = file_el.files;
Zmodem.Browser.send_files(
zsession,
files_obj,
{
on_offer_response(obj, xfer) {
if (xfer) _show_progress();
//console.log("offer", xfer ? "accepted" : "skipped");
},
on_progress(obj, xfer) {
_update_progress(xfer);
},
on_file_complete(obj) {
//console.log("COMPLETE", obj);
_hide_progress();
},
}
).then(_hide_progress).then(
zsession.close.bind(zsession),
console.error.bind(console)
).then( () => {
_hide_file_info();
_hide_progress();
res();
} );
};
} );
return promise;
}
//This is here to allow canceling of an in-progress ZMODEM transfer.
var current_receive_xfer;
//Called from HTML directly.
function skip_current_file() {
current_receive_xfer.skip();
skipper_button.disabled = true;
skipper_button.textContent = "Waiting for server to acknowledge skip …";
}
function runRealTerminal() {
term.attach(socket);
term._initialized = true;
}
function runFakeTerminal() {
if (term._initialized) {
return;
}
term._initialized = true;
var shellprompt = '$ ';
term.prompt = function () {
term.write('\r\n' + shellprompt);
};
term.writeln('Welcome to xterm.js');
term.writeln('This is a local terminal emulation, without a real terminal in the back-end.');
term.writeln('Type some keys and commands to play around.');
term.writeln('');
term.prompt();
term.on('key', function (key, ev) {
var printable = (
!ev.altKey && !ev.altGraphKey && !ev.ctrlKey && !ev.metaKey
);
if (ev.keyCode == 13) {
term.prompt();
} else if (ev.keyCode == 8) {
// Do not delete the prompt
if (term.x > 2) {
term.write('\b \b');
}
} else if (printable) {
term.write(key);
}
});
term.on('paste', function (data, ev) {
term.write(data);
});
}
+1 -1
View File
@@ -7,7 +7,7 @@ services:
volumes:
- ./:/usr/src/app
ports:
- ${XTERMJS_PORT:3000}:3000
- ${XTERMJS_PORT:-3000}:3000
command: ["npm", "start"]
watch:
+10 -2
View File
@@ -4,7 +4,7 @@
/// <reference path="../../typings/xterm.d.ts" />
import { Terminal } from 'xterm';
import { Terminal, IDisposable } from 'xterm';
namespace constructor {
{
@@ -21,7 +21,7 @@ namespace constructor {
'disableStdin': false,
'rows': 1,
'scrollback': 10,
'tabStopWidth': 2,
'tabStopWidth': 2
});
}
}
@@ -119,6 +119,12 @@ namespace methods_core {
const t: Terminal = new Terminal();
t.attachCustomKeyEventHandler((e: KeyboardEvent) => true);
t.attachCustomKeyEventHandler((e: KeyboardEvent) => false);
const d1: IDisposable = t.addCsiHandler('x',
(params: number[], collect: string): boolean => params[0] === 1);
d1.dispose();
const d2: IDisposable = t.addOscHandler(199,
(data: string): boolean => true);
d2.dispose();
}
namespace options {
{
@@ -149,6 +155,7 @@ namespace methods_core {
const r25: string = t.getOption('fontWeightBold');
const r26: boolean = t.getOption('allowTransparency');
const r27: boolean = t.getOption('rightClickSelectsWord');
const r28: boolean = t.getOption('windowsMode');
}
{
const t: Terminal = new Terminal();
@@ -171,6 +178,7 @@ namespace methods_core {
t.setOption('useFlowControl', true);
t.setOption('allowTransparency', true);
t.setOption('visualBell', true);
t.setOption('windowsMode', true);
t.setOption('colors', ['a', 'b']);
t.setOption('letterSpacing', 1);
t.setOption('cols', 1);
+9 -5
View File
@@ -16,9 +16,9 @@ const ts = require('gulp-typescript');
const util = require('gulp-util');
const buildDir = process.env.BUILD_DIR || 'build';
const tsProject = ts.createProject('tsconfig.json');
let srcDir = tsProject.config.compilerOptions.rootDir;
let outDir = tsProject.config.compilerOptions.outDir;
const tsProject = ts.createProject('src/tsconfig.json');
let srcDir = './src';
let outDir = './lib';
const addons = fs.readdirSync(`${__dirname}/src/addons`);
@@ -39,6 +39,10 @@ gulp.task('css', function() {
return gulp.src(`${srcDir}/**/*.css`).pipe(gulp.dest(outDir));
});
gulp.task('css-build', function() {
return gulp.src(`${srcDir}/**/*.css`).pipe(gulp.dest(buildDir));
});
gulp.task('watch-css', function() {
return gulp.watch(`${srcDir}/**/*.css`, ['css']);
});
@@ -61,7 +65,7 @@ gulp.task('browserify', function() {
};
let bundleStream = browserify(browserifyOptions)
.bundle()
.pipe(source('xterm.js'))
.pipe(source(`xterm.js`))
.pipe(buffer())
.pipe(sourcemaps.init({loadMaps: true, sourceRoot: '..'}))
.pipe(sourcemaps.write('./'))
@@ -136,6 +140,6 @@ gulp.task('sorcery-addons', ['browserify-addons'], function () {
})
});
gulp.task('build', ['sorcery', 'sorcery-addons']);
gulp.task('build', ['css', 'css-build', 'sorcery', 'sorcery-addons']);
gulp.task('test', ['mocha']);
gulp.task('default', ['build']);
+16 -13
View File
@@ -1,7 +1,7 @@
{
"name": "xterm",
"description": "Full xterm terminal, in your browser",
"version": "3.9.0",
"version": "3.14.1",
"main": "lib/public/Terminal.js",
"types": "typings/xterm.d.ts",
"repository": "https://github.com/xtermjs/xterm.js",
@@ -12,10 +12,11 @@
"@types/jsdom": "11.0.1",
"@types/mocha": "^2.2.33",
"@types/node": "6.0.108",
"@types/puppeteer": "^1.12.4",
"@types/utf8": "^2.1.6",
"@types/webpack": "^4.4.11",
"browserify": "^13.3.0",
"chai": "3.5.0",
"concurrently": "^3.5.1",
"coveralls": "^3.0.1",
"express": "4.13.4",
"express-ws": "2.0.0-rc.1",
@@ -28,43 +29,45 @@
"gulp-sourcemaps": "1.9.1",
"gulp-typescript": "^3.1.3",
"gulp-util": "3.0.8",
"jsdoc": "3.4.3",
"jsdom": "^11.11.0",
"merge-stream": "^1.0.1",
"node-pty": "0.7.6",
"nodemon": "1.10.2",
"nyc": "^11.8.0",
"puppeteer": "^1.15.0",
"sorcery": "^0.10.0",
"source-map-loader": "^0.2.3",
"source-map-loader": "^0.2.4",
"ts-loader": "^4.5.0",
"tslint": "^5.9.1",
"tslint-consistent-codestyle": "^1.13.0",
"typescript": "3.1",
"typescript": "3.5",
"utf8": "^3.0.0",
"vinyl-buffer": "^1.0.0",
"vinyl-source-stream": "^1.1.0",
"webpack": "^4.17.1",
"webpack-cli": "^3.1.0",
"xterm-addon-attach": "0.1.0-beta8",
"xterm-addon-search": "0.1.0-beta4",
"xterm-addon-web-links": "0.1.0-beta6",
"zmodem.js": "^0.1.5"
},
"scripts": {
"start": "node demo/start",
"start-debug": "node --inspect-brk demo/start",
"start-zmodem": "node demo/zmodem/app",
"lint": "tslint 'src/**/*.ts' './demo/**/*.ts'",
"pretest": "npm run layering",
"test": "npm run mocha",
"posttest": "npm run lint",
"test-debug": "node --inspect-brk node_modules/.bin/gulp test",
"test-suite": "gulp mocha-suite --test",
"test-coverage": "nyc -x gulpfile.js -x '**/*test*' npm run mocha",
"test-api": "mocha \"**/*.api.js\"",
"mocha": "gulp test",
"build:docs": "jsdoc -c jsdoc.json",
"tsc": "tsc",
"prebuild": "concurrently --kill-others-on-fail --names \"lib,attach,fit,fullscreen,search,terminado,webLinks,winptyCompat,zmodem,css\" \"tsc\" \"tsc -p ./src/addons/attach\" \"tsc -p ./src/addons/fit\" \"tsc -p ./src/addons/fullscreen\" \"tsc -p ./src/addons/search\" \"tsc -p ./src/addons/terminado\" \"tsc -p ./src/addons/webLinks\" \"tsc -p ./src/addons/winptyCompat\" \"tsc -p ./src/addons/zmodem\" \"gulp css\"",
"prebuild": "tsc -b ./src/tsconfig.all.json",
"build": "gulp build",
"prepublish": "npm run build",
"prepare": "npm run prebuild",
"prepublishOnly": "npm run build",
"coveralls": "nyc report --reporter=text-lcov | coveralls",
"watch": "concurrently --kill-others-on-fail --names \"lib,css\" \"tsc -w\" \"gulp watch-css\"",
"watch-addons": "concurrently --kill-others-on-fail --names \"attach,fit,fullscreen,search,terminado,webLinks,winptyCompat,zmodem\" \"tsc -w -p ./src/addons/attach\" \"tsc -w -p ./src/addons/fit\" \"tsc -w -p ./src/addons/fullscreen\" \"tsc -w -p ./src/addons/search\" \"tsc -w -p ./src/addons/terminado\" \"tsc -w -p ./src/addons/webLinks\" \"tsc -w -p ./src/addons/winptyCompat\" \"tsc -w -p ./src/addons/zmodem\"",
"layering": "concurrently --kill-others-on-fail --names \"common,core\" \"tsc -p ./src/common\" \"tsc -p ./src/core\""
"watch": "tsc -b -w ./src/tsconfig.all.json --preserveWatchOutput"
}
}
+43 -30
View File
@@ -5,10 +5,12 @@
import * as Strings from './Strings';
import { ITerminal, IBuffer } from './Types';
import { isMac } from './core/Platform';
import { isMac } from './common/Platform';
import { RenderDebouncer } from './ui/RenderDebouncer';
import { addDisposableDomListener } from './ui/Lifecycle';
import { Disposable } from './common/Lifecycle';
import { ScreenDprMonitor } from './ui/ScreenDprMonitor';
import { IRenderDimensions } from './renderer/Types';
const MAX_ROWS_TO_READ = 20;
@@ -25,6 +27,7 @@ export class AccessibilityManager extends Disposable {
private _liveRegionLineCount: number = 0;
private _renderRowsDebouncer: RenderDebouncer;
private _screenDprMonitor: ScreenDprMonitor;
private _topBoundaryFocusListener: (e: FocusEvent) => void;
private _bottomBoundaryFocusListener: (e: FocusEvent) => void;
@@ -40,7 +43,12 @@ export class AccessibilityManager extends Disposable {
*/
private _charsToConsume: string[] = [];
constructor(private _terminal: ITerminal) {
private _charsToAnnounce: string = '';
constructor(
private _terminal: ITerminal,
private _dimensions: IRenderDimensions
) {
super();
this._accessibilityTreeRoot = document.createElement('div');
this._accessibilityTreeRoot.classList.add('xterm-accessibility');
@@ -61,7 +69,7 @@ export class AccessibilityManager extends Disposable {
this._refreshRowsDimensions();
this._accessibilityTreeRoot.appendChild(this._rowContainer);
this._renderRowsDebouncer = new RenderDebouncer(this._terminal, this._renderRows.bind(this));
this._renderRowsDebouncer = new RenderDebouncer(this._renderRows.bind(this));
this._refreshRows();
this._liveRegion = document.createElement('div');
@@ -72,22 +80,21 @@ export class AccessibilityManager extends Disposable {
this._terminal.element.insertAdjacentElement('afterbegin', this._accessibilityTreeRoot);
this.register(this._renderRowsDebouncer);
this.register(this._terminal.addDisposableListener('resize', data => this._onResize(data.rows)));
this.register(this._terminal.addDisposableListener('refresh', data => this._refreshRows(data.start, data.end)));
this.register(this._terminal.addDisposableListener('scroll', data => this._refreshRows()));
this.register(this._terminal.onResize(e => this._onResize(e.rows)));
this.register(this._terminal.onRender(e => this._refreshRows(e.start, e.end)));
this.register(this._terminal.onScroll(() => this._refreshRows()));
// Line feed is an issue as the prompt won't be read out after a command is run
this.register(this._terminal.addDisposableListener('a11y.char', (char) => this._onChar(char)));
this.register(this._terminal.addDisposableListener('linefeed', () => this._onChar('\n')));
this.register(this._terminal.onLineFeed(() => this._onChar('\n')));
this.register(this._terminal.addDisposableListener('a11y.tab', spaceCount => this._onTab(spaceCount)));
this.register(this._terminal.addDisposableListener('key', keyChar => this._onKey(keyChar)));
this.register(this._terminal.onKey(e => this._onKey(e.key)));
this.register(this._terminal.addDisposableListener('blur', () => this._clearLiveRegion()));
// TODO: Maybe renderer should fire an event on terminal when the characters change and that
// should be listened to instead? That would mean that the order of events are always
// guarenteed
this.register(this._terminal.addDisposableListener('dprchange', () => this._refreshRowsDimensions()));
this.register(this._terminal.renderer.addDisposableListener('resize', () => this._refreshRowsDimensions()));
this._screenDprMonitor = new ScreenDprMonitor();
this.register(this._screenDprMonitor);
this._screenDprMonitor.setListener(() => this._refreshRowsDimensions());
// This shouldn't be needed on modern browsers but is present in case the
// media query that drives the dprchange event isn't supported
// media query that drives the ScreenDprMonitor isn't supported
this.register(addDisposableDomListener(window, 'resize', () => this._refreshRowsDimensions()));
}
@@ -197,10 +204,10 @@ export class AccessibilityManager extends Disposable {
// Have the screen reader ignore the char if it was just input
const shiftedChar = this._charsToConsume.shift();
if (shiftedChar !== char) {
this._announceCharacter(char);
this._charsToAnnounce += char;
}
} else {
this._announceCharacter(char);
this._charsToAnnounce += char;
}
if (char === '\n') {
@@ -239,7 +246,7 @@ export class AccessibilityManager extends Disposable {
}
private _refreshRows(start?: number, end?: number): void {
this._renderRowsDebouncer.refresh(start, end);
this._renderRowsDebouncer.refresh(start, end, this._terminal.rows);
}
private _renderRows(start: number, end: number): void {
@@ -249,14 +256,17 @@ export class AccessibilityManager extends Disposable {
const lineData = buffer.translateBufferLineToString(buffer.ydisp + i, true);
const posInSet = (buffer.ydisp + i + 1).toString();
const element = this._rowElements[i];
element.textContent = lineData.length === 0 ? Strings.blankLine : lineData;
element.setAttribute('aria-posinset', posInSet);
element.setAttribute('aria-setsize', setSize);
if (element) {
element.textContent = lineData.length === 0 ? Strings.blankLine : lineData;
element.setAttribute('aria-posinset', posInSet);
element.setAttribute('aria-setsize', setSize);
}
}
this._announceCharacters();
}
private _refreshRowsDimensions(): void {
if (!this._terminal.renderer.dimensions.actualCellHeight) {
if (!this._dimensions.actualCellHeight) {
return;
}
if (this._rowElements.length !== this._terminal.rows) {
@@ -267,17 +277,20 @@ export class AccessibilityManager extends Disposable {
}
}
private _refreshRowDimensions(element: HTMLElement): void {
element.style.height = `${this._terminal.renderer.dimensions.actualCellHeight}px`;
public setDimensions(dimensions: IRenderDimensions): void {
this._dimensions = dimensions;
this._refreshRowsDimensions();
}
private _announceCharacter(char: string): void {
if (char === ' ') {
// Always use nbsp for spaces in order to preserve the space between characters in
// voiceover's caption window
this._liveRegion.innerHTML += '&nbsp;';
} else {
this._liveRegion.textContent += char;
private _refreshRowDimensions(element: HTMLElement): void {
element.style.height = `${this._dimensions.actualCellHeight}px`;
}
private _announceCharacters(): void {
if (this._charsToAnnounce.length === 0) {
return;
}
this._liveRegion.textContent += this._charsToAnnounce;
this._charsToAnnounce = '';
}
}
+894 -43
View File
File diff suppressed because it is too large Load Diff
+312 -150
View File
@@ -3,24 +3,15 @@
* @license MIT
*/
import { CircularList } from './common/CircularList';
import { CharData, ITerminal, IBuffer, IBufferLine, BufferIndex, IBufferStringIterator, IBufferStringIteratorResult, IBufferLineConstructor } from './Types';
import { EventEmitter } from './common/EventEmitter';
import { IMarker } from 'xterm';
import { BufferLine, BufferLineJSArray } from './BufferLine';
import { DEFAULT_COLOR } from './renderer/atlas/Types';
import { CircularList, IInsertEvent } from './common/CircularList';
import { ITerminal, IBuffer, BufferIndex, IBufferStringIterator, IBufferStringIteratorResult } from './Types';
import { IBufferLine, ICellData, IAttributeData } from './core/Types';
import { BufferLine, CellData, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE, WHITESPACE_CELL_CHAR, WHITESPACE_CELL_WIDTH, WHITESPACE_CELL_CODE, CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CHAR_INDEX, DEFAULT_ATTR_DATA } from './core/buffer/BufferLine';
import { reflowLargerApplyNewLayout, reflowLargerCreateNewLayout, reflowLargerGetLinesToRemove, reflowSmallerGetNewLineLengths, getWrappedLineTrimmedLength } from './core/buffer/BufferReflow';
import { Marker } from './core/buffer/Marker';
export const DEFAULT_ATTR = (0 << 18) | (DEFAULT_COLOR << 9) | (256 << 0);
export const CHAR_DATA_ATTR_INDEX = 0;
export const CHAR_DATA_CHAR_INDEX = 1;
export const CHAR_DATA_WIDTH_INDEX = 2;
export const CHAR_DATA_CODE_INDEX = 3;
export const MAX_BUFFER_SIZE = 4294967295; // 2^32 - 1
export const NULL_CELL_CHAR = ' ';
export const NULL_CELL_WIDTH = 1;
export const NULL_CELL_CODE = 32;
/**
* This class represents a terminal buffer (an internal state of the terminal), where the
* following information is stored (in high-level):
@@ -39,9 +30,12 @@ export class Buffer implements IBuffer {
public tabs: any;
public savedY: number;
public savedX: number;
public savedCurAttr: number;
public savedCurAttrData = DEFAULT_ATTR_DATA.clone();
public markers: Marker[] = [];
private _bufferLineConstructor: IBufferLineConstructor;
private _nullCell: ICellData = CellData.fromCharData([0, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]);
private _whitespaceCell: ICellData = CellData.fromCharData([0, WHITESPACE_CELL_CHAR, WHITESPACE_CELL_WIDTH, WHITESPACE_CELL_CODE]);
private _cols: number;
private _rows: number;
/**
* Create a new Buffer.
@@ -53,48 +47,45 @@ export class Buffer implements IBuffer {
private _terminal: ITerminal,
private _hasScrollback: boolean
) {
this._cols = this._terminal.cols;
this._rows = this._terminal.rows;
this.clear();
}
public setBufferLineFactory(type: string): void {
if (type === 'JsArray') {
if (this._bufferLineConstructor !== BufferLineJSArray) {
this._bufferLineConstructor = BufferLineJSArray;
this._recreateLines();
}
public getNullCell(attr?: IAttributeData): ICellData {
if (attr) {
this._nullCell.fg = attr.fg;
this._nullCell.bg = attr.bg;
} else {
if (this._bufferLineConstructor !== BufferLine) {
this._bufferLineConstructor = BufferLine;
this._recreateLines();
}
this._nullCell.fg = 0;
this._nullCell.bg = 0;
}
return this._nullCell;
}
private _recreateLines(): void {
if (!this.lines) return;
for (let i = 0; i < this.lines.length; ++i) {
const oldLine = this.lines.get(i);
const newLine = new this._bufferLineConstructor(oldLine.length);
for (let j = 0; j < oldLine.length; ++j) {
newLine.set(j, oldLine.get(j));
}
this.lines.set(i, newLine);
public getWhitespaceCell(attr?: IAttributeData): ICellData {
if (attr) {
this._whitespaceCell.fg = attr.fg;
this._whitespaceCell.bg = attr.bg;
} else {
this._whitespaceCell.fg = 0;
this._whitespaceCell.bg = 0;
}
return this._whitespaceCell;
}
public getBlankLine(attr: number, isWrapped?: boolean): IBufferLine {
const fillCharData: CharData = [attr, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE];
return new this._bufferLineConstructor(this._terminal.cols, fillCharData, isWrapped);
public getBlankLine(attr: IAttributeData, isWrapped?: boolean): IBufferLine {
return new BufferLine(this._terminal.cols, this.getNullCell(attr), isWrapped);
}
public get hasScrollback(): boolean {
return this._hasScrollback && this.lines.maxLength > this._terminal.rows;
return this._hasScrollback && this.lines.maxLength > this._rows;
}
public get isCursorInViewport(): boolean {
const absoluteY = this.ybase + this.y;
const relativeY = absoluteY - this.ydisp;
return (relativeY >= 0 && relativeY < this._terminal.rows);
return (relativeY >= 0 && relativeY < this._rows);
}
/**
@@ -115,12 +106,12 @@ export class Buffer implements IBuffer {
/**
* Fills the buffer's viewport with blank lines.
*/
public fillViewportRows(fillAttr?: number): void {
public fillViewportRows(fillAttr?: IAttributeData): void {
if (this.lines.length === 0) {
if (fillAttr === undefined) {
fillAttr = DEFAULT_ATTR;
fillAttr = DEFAULT_ATTR_DATA;
}
let i = this._terminal.rows;
let i = this._rows;
while (i--) {
this.lines.push(this.getBlankLine(fillAttr));
}
@@ -131,14 +122,13 @@ export class Buffer implements IBuffer {
* Clears the buffer to it's initial state, discarding all previous data.
*/
public clear(): void {
this.setBufferLineFactory(this._terminal.options.experimentalBufferLineImpl);
this.ydisp = 0;
this.ybase = 0;
this.y = 0;
this.x = 0;
this.lines = new CircularList<IBufferLine>(this._getCorrectBufferLength(this._terminal.rows));
this.lines = new CircularList<IBufferLine>(this._getCorrectBufferLength(this._rows));
this.scrollTop = 0;
this.scrollBottom = this._terminal.rows - 1;
this.scrollBottom = this._rows - 1;
this.setupTabStops();
}
@@ -148,6 +138,9 @@ export class Buffer implements IBuffer {
* @param newRows The new number of rows.
*/
public resize(newCols: number, newRows: number): void {
// store reference to null cell with default attrs
const nullCell = this.getNullCell(DEFAULT_ATTR_DATA);
// Increase max length if needed before adjustments to allow space to fill
// as required.
const newMaxLength = this._getCorrectBufferLength(newRows);
@@ -158,18 +151,17 @@ export class Buffer implements IBuffer {
// The following adjustments should only happen if the buffer has been
// initialized/filled.
if (this.lines.length > 0) {
// Deal with columns increasing (we don't do anything when columns reduce)
if (this._terminal.cols < newCols) {
const ch: CharData = [DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]; // does xterm use the default attr?
// Deal with columns increasing (reducing needs to happen after reflow)
if (this._cols < newCols) {
for (let i = 0; i < this.lines.length; i++) {
this.lines.get(i).resize(newCols, ch);
this.lines.get(i).resize(newCols, nullCell);
}
}
// Resize rows in both directions as needed
let addToY = 0;
if (this._terminal.rows < newRows) {
for (let y = this._terminal.rows; y < newRows; y++) {
if (this._rows < newRows) {
for (let y = this._rows; y < newRows; y++) {
if (this.lines.length < newRows + this.ybase) {
if (this.ybase > 0 && this.lines.length <= this.ybase + this.y + addToY + 1) {
// There is room above the buffer and there are no empty elements below the line,
@@ -183,13 +175,12 @@ export class Buffer implements IBuffer {
} else {
// Add a blank line if there is no buffer left at the top to scroll to, or if there
// are blank lines after the cursor
const fillCharData: CharData = [DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE];
this.lines.push(new this._bufferLineConstructor(newCols, fillCharData));
this.lines.push(new BufferLine(newCols, nullCell));
}
}
}
} else { // (this._terminal.rows >= newRows)
for (let y = this._terminal.rows; y > newRows; y--) {
} else { // (this._rows >= newRows)
for (let y = this._rows; y > newRows; y--) {
if (this.lines.length > newRows + this.ybase) {
if (this.lines.length > this.ybase + this.y + 1) {
// The line is a blank line below the cursor, remove it
@@ -229,8 +220,238 @@ export class Buffer implements IBuffer {
}
this.scrollBottom = newRows - 1;
if (this._isReflowEnabled) {
this._reflow(newCols, newRows);
// Trim the end of the line off if cols shrunk
if (this._cols > newCols) {
for (let i = 0; i < this.lines.length; i++) {
this.lines.get(i).resize(newCols, nullCell);
}
}
}
this._cols = newCols;
this._rows = newRows;
}
private get _isReflowEnabled(): boolean {
return this._hasScrollback && !this._terminal.options.windowsMode;
}
private _reflow(newCols: number, newRows: number): void {
if (this._cols === newCols) {
return;
}
// Iterate through rows, ignore the last one as it cannot be wrapped
if (newCols > this._cols) {
this._reflowLarger(newCols, newRows);
} else {
this._reflowSmaller(newCols, newRows);
}
}
private _reflowLarger(newCols: number, newRows: number): void {
const toRemove: number[] = reflowLargerGetLinesToRemove(this.lines, this._cols, newCols, this.ybase + this.y, this.getNullCell(DEFAULT_ATTR_DATA));
if (toRemove.length > 0) {
const newLayoutResult = reflowLargerCreateNewLayout(this.lines, toRemove);
reflowLargerApplyNewLayout(this.lines, newLayoutResult.layout);
this._reflowLargerAdjustViewport(newCols, newRows, newLayoutResult.countRemoved);
}
}
private _reflowLargerAdjustViewport(newCols: number, newRows: number, countRemoved: number): void {
const nullCell = this.getNullCell(DEFAULT_ATTR_DATA);
// Adjust viewport based on number of items removed
let viewportAdjustments = countRemoved;
while (viewportAdjustments-- > 0) {
if (this.ybase === 0) {
if (this.y > 0) {
this.y--;
}
if (this.lines.length < newRows) {
// Add an extra row at the bottom of the viewport
this.lines.push(new BufferLine(newCols, nullCell));
}
} else {
if (this.ydisp === this.ybase) {
this.ydisp--;
}
this.ybase--;
}
}
}
private _reflowSmaller(newCols: number, newRows: number): void {
const nullCell = this.getNullCell(DEFAULT_ATTR_DATA);
// Gather all BufferLines that need to be inserted into the Buffer here so that they can be
// batched up and only committed once
const toInsert = [];
let countToInsert = 0;
// Go backwards as many lines may be trimmed and this will avoid considering them
for (let y = this.lines.length - 1; y >= 0; y--) {
// Check whether this line is a problem
let nextLine = this.lines.get(y) as BufferLine;
if (!nextLine || !nextLine.isWrapped && nextLine.getTrimmedLength() <= newCols) {
continue;
}
// Gather wrapped lines and adjust y to be the starting line
const wrappedLines: BufferLine[] = [nextLine];
while (nextLine.isWrapped && y > 0) {
nextLine = this.lines.get(--y) as BufferLine;
wrappedLines.unshift(nextLine);
}
// If these lines contain the cursor don't touch them, the program will handle fixing up
// wrapped lines with the cursor
const absoluteY = this.ybase + this.y;
if (absoluteY >= y && absoluteY < y + wrappedLines.length) {
continue;
}
const lastLineLength = wrappedLines[wrappedLines.length - 1].getTrimmedLength();
const destLineLengths = reflowSmallerGetNewLineLengths(wrappedLines, this._cols, newCols);
const linesToAdd = destLineLengths.length - wrappedLines.length;
let trimmedLines: number;
if (this.ybase === 0 && this.y !== this.lines.length - 1) {
// If the top section of the buffer is not yet filled
trimmedLines = Math.max(0, this.y - this.lines.maxLength + linesToAdd);
} else {
trimmedLines = Math.max(0, this.lines.length - this.lines.maxLength + linesToAdd);
}
// Add the new lines
const newLines: BufferLine[] = [];
for (let i = 0; i < linesToAdd; i++) {
const newLine = this.getBlankLine(DEFAULT_ATTR_DATA, true) as BufferLine;
newLines.push(newLine);
}
if (newLines.length > 0) {
toInsert.push({
// countToInsert here gets the actual index, taking into account other inserted items.
// using this we can iterate through the list forwards
start: y + wrappedLines.length + countToInsert,
newLines
});
countToInsert += newLines.length;
}
wrappedLines.push(...newLines);
// Copy buffer data to new locations, this needs to happen backwards to do in-place
let destLineIndex = destLineLengths.length - 1; // Math.floor(cellsNeeded / newCols);
let destCol = destLineLengths[destLineIndex]; // cellsNeeded % newCols;
if (destCol === 0) {
destLineIndex--;
destCol = destLineLengths[destLineIndex];
}
let srcLineIndex = wrappedLines.length - linesToAdd - 1;
let srcCol = lastLineLength;
while (srcLineIndex >= 0) {
const cellsToCopy = Math.min(srcCol, destCol);
wrappedLines[destLineIndex].copyCellsFrom(wrappedLines[srcLineIndex], srcCol - cellsToCopy, destCol - cellsToCopy, cellsToCopy, true);
destCol -= cellsToCopy;
if (destCol === 0) {
destLineIndex--;
destCol = destLineLengths[destLineIndex];
}
srcCol -= cellsToCopy;
if (srcCol === 0) {
srcLineIndex--;
const wrappedLinesIndex = Math.max(srcLineIndex, 0);
srcCol = getWrappedLineTrimmedLength(wrappedLines, wrappedLinesIndex, this._cols);
}
}
// Null out the end of the line ends if a wide character wrapped to the following line
for (let i = 0; i < wrappedLines.length; i++) {
if (destLineLengths[i] < newCols) {
wrappedLines[i].setCell(destLineLengths[i], nullCell);
}
}
// Adjust viewport as needed
let viewportAdjustments = linesToAdd - trimmedLines;
while (viewportAdjustments-- > 0) {
if (this.ybase === 0) {
if (this.y < newRows - 1) {
this.y++;
this.lines.pop();
} else {
this.ybase++;
this.ydisp++;
}
} else {
// Ensure ybase does not exceed its maximum value
if (this.ybase < Math.min(this.lines.maxLength, this.lines.length + countToInsert) - newRows) {
if (this.ybase === this.ydisp) {
this.ydisp++;
}
this.ybase++;
}
}
}
}
// Rearrange lines in the buffer if there are any insertions, this is done at the end rather
// than earlier so that it's a single O(n) pass through the buffer, instead of O(n^2) from many
// costly calls to CircularList.splice.
if (toInsert.length > 0) {
// Record buffer insert events and then play them back backwards so that the indexes are
// correct
const insertEvents: IInsertEvent[] = [];
// Record original lines so they don't get overridden when we rearrange the list
const originalLines: BufferLine[] = [];
for (let i = 0; i < this.lines.length; i++) {
originalLines.push(this.lines.get(i) as BufferLine);
}
const originalLinesLength = this.lines.length;
let originalLineIndex = originalLinesLength - 1;
let nextToInsertIndex = 0;
let nextToInsert = toInsert[nextToInsertIndex];
this.lines.length = Math.min(this.lines.maxLength, this.lines.length + countToInsert);
let countInsertedSoFar = 0;
for (let i = Math.min(this.lines.maxLength - 1, originalLinesLength + countToInsert - 1); i >= 0; i--) {
if (nextToInsert && nextToInsert.start > originalLineIndex + countInsertedSoFar) {
// Insert extra lines here, adjusting i as needed
for (let nextI = nextToInsert.newLines.length - 1; nextI >= 0; nextI--) {
this.lines.set(i--, nextToInsert.newLines[nextI]);
}
i++;
// Create insert events for later
insertEvents.push({
index: originalLineIndex + 1,
amount: nextToInsert.newLines.length
});
countInsertedSoFar += nextToInsert.newLines.length;
nextToInsert = toInsert[++nextToInsertIndex];
} else {
this.lines.set(i, originalLines[originalLineIndex--]);
}
}
// Update markers
let insertCountEmitted = 0;
for (let i = insertEvents.length - 1; i >= 0; i--) {
insertEvents[i].index += insertCountEmitted;
this.lines.onInsertEmitter.fire(insertEvents[i]);
insertCountEmitted += insertEvents[i].amount;
}
const amountToTrim = Math.max(0, originalLinesLength + countToInsert - this.lines.maxLength);
if (amountToTrim > 0) {
this.lines.onTrimEmitter.fire(amountToTrim);
}
}
}
// private _reflowSmallerGetLinesNeeded()
/**
* Translates a string index back to a BufferIndex.
* To get the correct buffer position the string must start at `startCol` 0
@@ -244,14 +465,19 @@ export class Buffer implements IBuffer {
* @param stringIndex index within the string
* @param startCol column offset the string was retrieved from
*/
public stringIndexToBufferIndex(lineIndex: number, stringIndex: number): BufferIndex {
public stringIndexToBufferIndex(lineIndex: number, stringIndex: number, trimRight: boolean = false): BufferIndex {
while (stringIndex) {
const line = this.lines.get(lineIndex);
if (!line) {
[-1, -1];
return [-1, -1];
}
for (let i = 0; i < line.length; ++i) {
stringIndex -= line.get(i)[CHAR_DATA_CHAR_INDEX].length;
const length = (trimRight) ? line.getTrimmedLength() : line.length;
for (let i = 0; i < length; ++i) {
if (line.get(i)[CHAR_DATA_WIDTH_INDEX]) {
// empty cells report a string length of 0, but get replaced
// with a whitespace in translateToString, thus replace with 1
stringIndex -= line.get(i)[CHAR_DATA_CHAR_INDEX].length || 1;
}
if (stringIndex < 0) {
return [lineIndex, i];
}
@@ -271,65 +497,12 @@ export class Buffer implements IBuffer {
* @param startCol The column to start at.
* @param endCol The column to end at.
*/
public translateBufferLineToString(lineIndex: number, trimRight: boolean, startCol: number = 0, endCol: number = null): string {
// Get full line
let lineString = '';
public translateBufferLineToString(lineIndex: number, trimRight: boolean, startCol: number = 0, endCol?: number): string {
const line = this.lines.get(lineIndex);
if (!line) {
return '';
}
// Initialize column and index values. Column values represent the actual
// cell column, indexes represent the index in the string. Indexes are
// needed here because some chars are 0 characters long (eg. after wide
// chars) and some chars are longer than 1 characters long (eg. emojis).
let startIndex = startCol;
// Only set endCol to the line length when it is null. 0 is a valid column.
if (endCol === null) {
endCol = line.length;
}
let endIndex = endCol;
for (let i = 0; i < line.length; i++) {
const char = line.get(i);
lineString += char[CHAR_DATA_CHAR_INDEX];
// Adjust start and end cols for wide characters if they affect their
// column indexes
if (char[CHAR_DATA_WIDTH_INDEX] === 0) {
if (startCol >= i) {
startIndex--;
}
if (endCol > i) {
endIndex--;
}
} else {
// Adjust the columns to take glyphs that are represented by multiple
// code points into account.
if (char[CHAR_DATA_CHAR_INDEX].length > 1) {
if (startCol > i) {
startIndex += char[CHAR_DATA_CHAR_INDEX].length - 1;
}
if (endCol > i) {
endIndex += char[CHAR_DATA_CHAR_INDEX].length - 1;
}
}
}
}
// Calculate the final end col by trimming whitespace on the right of the
// line if needed.
if (trimRight) {
const rightWhitespaceIndex = lineString.search(/\s+$/);
if (rightWhitespaceIndex !== -1) {
endIndex = Math.min(endIndex, rightWhitespaceIndex);
}
// Return the empty string if only trimmed whitespace is selected
if (endIndex <= startIndex) {
return '';
}
}
return lineString.substring(startIndex, endIndex);
return line.translateToString(trimRight, startCol, endCol);
}
public getWrappedRangeForLine(y: number): { first: number, last: number } {
@@ -360,7 +533,7 @@ export class Buffer implements IBuffer {
i = 0;
}
for (; i < this._terminal.cols; i += this._terminal.options.tabStopWidth) {
for (; i < this._cols; i += this._terminal.options.tabStopWidth) {
this.tabs[i] = true;
}
}
@@ -374,7 +547,7 @@ export class Buffer implements IBuffer {
x = this.x;
}
while (!this.tabs[--x] && x > 0);
return x >= this._terminal.cols ? this._terminal.cols - 1 : x < 0 ? 0 : x;
return x >= this._cols ? this._cols - 1 : x < 0 ? 0 : x;
}
/**
@@ -385,26 +558,41 @@ export class Buffer implements IBuffer {
if (x === null || x === undefined) {
x = this.x;
}
while (!this.tabs[++x] && x < this._terminal.cols);
return x >= this._terminal.cols ? this._terminal.cols - 1 : x < 0 ? 0 : x;
while (!this.tabs[++x] && x < this._cols);
return x >= this._cols ? this._cols - 1 : x < 0 ? 0 : x;
}
public addMarker(y: number): Marker {
const marker = new Marker(y);
this.markers.push(marker);
marker.register(this.lines.addDisposableListener('trim', amount => {
marker.register(this.lines.onTrim(amount => {
marker.line -= amount;
// The marker should be disposed when the line is trimmed from the buffer
if (marker.line < 0) {
marker.dispose();
}
}));
marker.register(marker.addDisposableListener('dispose', () => this._removeMarker(marker)));
marker.register(this.lines.onInsert(event => {
if (marker.line >= event.index) {
marker.line += event.amount;
}
}));
marker.register(this.lines.onDelete(event => {
// Delete the marker if it's within the range
if (marker.line >= event.index && marker.line < event.index + event.amount) {
marker.dispose();
}
// Shift the marker if it's after the deleted range
if (marker.line > event.index) {
marker.line -= event.amount;
}
}));
marker.register(marker.onDispose(() => this._removeMarker(marker)));
return marker;
}
private _removeMarker(marker: Marker): void {
// TODO: This could probably be optimized by relying on sort order and trimming the array using .length
this.markers.splice(this.markers.indexOf(marker), 1);
}
@@ -413,31 +601,6 @@ export class Buffer implements IBuffer {
}
}
export class Marker extends EventEmitter implements IMarker {
private static _nextId = 1;
private _id: number = Marker._nextId++;
public isDisposed: boolean = false;
public get id(): number { return this._id; }
constructor(
public line: number
) {
super();
}
public dispose(): void {
if (this.isDisposed) {
return;
}
this.isDisposed = true;
// Emit before super.dispose such that dispose listeners get a change to react
this.emit('dispose');
super.dispose();
}
}
/**
* Iterator to get unwrapped content strings from the buffer.
* The iterator returns at least the string data between the borders
@@ -488,8 +651,7 @@ export class BufferStringIterator implements IBufferStringIterator {
range.last = Math.min(range.last, this._buffer.lines.length);
let result = '';
for (let i = range.first; i <= range.last; ++i) {
// TODO: always apply trimRight after fixing #1685
result += this._buffer.translateBufferLineToString(i, (this._trimRight) ? i === range.last : false);
result += this._buffer.translateBufferLineToString(i, this._trimRight);
}
this._current = range.last + 1;
return {range: range, content: result};
-203
View File
@@ -1,203 +0,0 @@
/**
* Copyright (c) 2018 The xterm.js authors. All rights reserved.
* @license MIT
*/
import * as chai from 'chai';
import { BufferLine } from './BufferLine';
import { CharData, IBufferLine } from './Types';
import { NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE } from './Buffer';
class TestBufferLine extends BufferLine {
public toArray(): CharData[] {
const result = [];
for (let i = 0; i < this.length; ++i) {
result.push(this.get(i));
}
return result;
}
}
describe('BufferLine', function(): void {
it('ctor', function(): void {
let line: IBufferLine = new TestBufferLine(0);
chai.expect(line.length).equals(0);
chai.expect(line.isWrapped).equals(false);
line = new TestBufferLine(10);
chai.expect(line.length).equals(10);
chai.expect(line.get(0)).eql([0, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]);
chai.expect(line.isWrapped).equals(false);
line = new TestBufferLine(10, null, true);
chai.expect(line.length).equals(10);
chai.expect(line.get(0)).eql([0, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]);
chai.expect(line.isWrapped).equals(true);
line = new TestBufferLine(10, [123, 'a', 456, 'a'.charCodeAt(0)], true);
chai.expect(line.length).equals(10);
chai.expect(line.get(0)).eql([123, 'a', 456, 'a'.charCodeAt(0)]);
chai.expect(line.isWrapped).equals(true);
});
it('insertCells', function(): void {
const line = new TestBufferLine(3);
line.set(0, [1, 'a', 0, 'a'.charCodeAt(0)]);
line.set(1, [2, 'b', 0, 'b'.charCodeAt(0)]);
line.set(2, [3, 'c', 0, 'c'.charCodeAt(0)]);
line.insertCells(1, 3, [4, 'd', 0, 'd'.charCodeAt(0)]);
chai.expect(line.toArray()).eql([
[1, 'a', 0, 'a'.charCodeAt(0)],
[4, 'd', 0, 'd'.charCodeAt(0)],
[4, 'd', 0, 'd'.charCodeAt(0)]
]);
});
it('deleteCells', function(): void {
const line = new TestBufferLine(5);
line.set(0, [1, 'a', 0, 'a'.charCodeAt(0)]);
line.set(1, [2, 'b', 0, 'b'.charCodeAt(0)]);
line.set(2, [3, 'c', 0, 'c'.charCodeAt(0)]);
line.set(3, [4, 'd', 0, 'd'.charCodeAt(0)]);
line.set(4, [5, 'e', 0, 'e'.charCodeAt(0)]);
line.deleteCells(1, 2, [6, 'f', 0, 'f'.charCodeAt(0)]);
chai.expect(line.toArray()).eql([
[1, 'a', 0, 'a'.charCodeAt(0)],
[4, 'd', 0, 'd'.charCodeAt(0)],
[5, 'e', 0, 'e'.charCodeAt(0)],
[6, 'f', 0, 'f'.charCodeAt(0)],
[6, 'f', 0, 'f'.charCodeAt(0)]
]);
});
it('replaceCells', function(): void {
const line = new TestBufferLine(5);
line.set(0, [1, 'a', 0, 'a'.charCodeAt(0)]);
line.set(1, [2, 'b', 0, 'b'.charCodeAt(0)]);
line.set(2, [3, 'c', 0, 'c'.charCodeAt(0)]);
line.set(3, [4, 'd', 0, 'd'.charCodeAt(0)]);
line.set(4, [5, 'e', 0, 'e'.charCodeAt(0)]);
line.replaceCells(2, 4, [6, 'f', 0, 'f'.charCodeAt(0)]);
chai.expect(line.toArray()).eql([
[1, 'a', 0, 'a'.charCodeAt(0)],
[2, 'b', 0, 'b'.charCodeAt(0)],
[6, 'f', 0, 'f'.charCodeAt(0)],
[6, 'f', 0, 'f'.charCodeAt(0)],
[5, 'e', 0, 'e'.charCodeAt(0)]
]);
});
it('fill', function(): void {
const line = new TestBufferLine(5);
line.set(0, [1, 'a', 0, 'a'.charCodeAt(0)]);
line.set(1, [2, 'b', 0, 'b'.charCodeAt(0)]);
line.set(2, [3, 'c', 0, 'c'.charCodeAt(0)]);
line.set(3, [4, 'd', 0, 'd'.charCodeAt(0)]);
line.set(4, [5, 'e', 0, 'e'.charCodeAt(0)]);
line.fill([123, 'z', 0, 'z'.charCodeAt(0)]);
chai.expect(line.toArray()).eql([
[123, 'z', 0, 'z'.charCodeAt(0)],
[123, 'z', 0, 'z'.charCodeAt(0)],
[123, 'z', 0, 'z'.charCodeAt(0)],
[123, 'z', 0, 'z'.charCodeAt(0)],
[123, 'z', 0, 'z'.charCodeAt(0)]
]);
});
it('clone', function(): void {
const line = new TestBufferLine(5, null, true);
line.set(0, [1, 'a', 0, 'a'.charCodeAt(0)]);
line.set(1, [2, 'b', 0, 'b'.charCodeAt(0)]);
line.set(2, [3, 'c', 0, 'c'.charCodeAt(0)]);
line.set(3, [4, 'd', 0, 'd'.charCodeAt(0)]);
line.set(4, [5, 'e', 0, 'e'.charCodeAt(0)]);
const line2 = line.clone();
chai.expect(TestBufferLine.prototype.toArray.apply(line2)).eql(line.toArray());
chai.expect(line2.length).equals(line.length);
chai.expect(line2.isWrapped).equals(line.isWrapped);
});
it('copyFrom', function(): void {
const line = new TestBufferLine(5);
line.set(0, [1, 'a', 0, 'a'.charCodeAt(0)]);
line.set(1, [2, 'b', 0, 'b'.charCodeAt(0)]);
line.set(2, [3, 'c', 0, 'c'.charCodeAt(0)]);
line.set(3, [4, 'd', 0, 'd'.charCodeAt(0)]);
line.set(4, [5, 'e', 0, 'e'.charCodeAt(0)]);
const line2 = new TestBufferLine(5, [1, 'a', 0, 'a'.charCodeAt(0)], true);
line2.copyFrom(line);
chai.expect(line2.toArray()).eql(line.toArray());
chai.expect(line2.length).equals(line.length);
chai.expect(line2.isWrapped).equals(line.isWrapped);
});
it('should support combining chars', function(): void {
// CHAR_DATA_CODE_INDEX resembles current behavior in InputHandler.print
// --> set code to the last charCodeAt value of the string
// Note: needs to be fixed once the string pointer is in place
const line = new TestBufferLine(2, [1, 'e\u0301', 0, '\u0301'.charCodeAt(0)]);
chai.expect(line.toArray()).eql([[1, 'e\u0301', 0, '\u0301'.charCodeAt(0)], [1, 'e\u0301', 0, '\u0301'.charCodeAt(0)]]);
const line2 = new TestBufferLine(5, [1, 'a', 0, '\u0301'.charCodeAt(0)], true);
line2.copyFrom(line);
chai.expect(line2.toArray()).eql(line.toArray());
const line3 = line.clone();
chai.expect(TestBufferLine.prototype.toArray.apply(line3)).eql(line.toArray());
});
describe('resize', function(): void {
it('enlarge(false)', function(): void {
const line = new TestBufferLine(5, [1, 'a', 0, 'a'.charCodeAt(0)], false);
line.resize(10, [1, 'a', 0, 'a'.charCodeAt(0)]);
chai.expect(line.toArray()).eql(Array(10).fill([1, 'a', 0, 'a'.charCodeAt(0)]));
});
it('enlarge(true)', function(): void {
const line = new TestBufferLine(5, [1, 'a', 0, 'a'.charCodeAt(0)], false);
line.resize(10, [1, 'a', 0, 'a'.charCodeAt(0)], true);
chai.expect(line.toArray()).eql(Array(10).fill([1, 'a', 0, 'a'.charCodeAt(0)]));
});
it('shrink(true) - should apply new size', function(): void {
const line = new TestBufferLine(10, [1, 'a', 0, 'a'.charCodeAt(0)], false);
line.resize(5, [1, 'a', 0, 'a'.charCodeAt(0)], true);
chai.expect(line.toArray()).eql(Array(5).fill([1, 'a', 0, 'a'.charCodeAt(0)]));
});
it('shrink(false) - should not apply new size', function(): void {
const line = new TestBufferLine(10, [1, 'a', 0, 'a'.charCodeAt(0)], false);
line.resize(5, [1, 'a', 0, 'a'.charCodeAt(0)], false);
chai.expect(line.toArray()).eql(Array(10).fill([1, 'a', 0, 'a'.charCodeAt(0)]));
});
it('shrink(false) + shrink(false) - should not apply new size', function(): void {
const line = new TestBufferLine(20, [1, 'a', 0, 'a'.charCodeAt(0)], false);
line.resize(10, [1, 'a', 0, 'a'.charCodeAt(0)], false);
line.resize(5, [1, 'a', 0, 'a'.charCodeAt(0)], false);
chai.expect(line.toArray()).eql(Array(20).fill([1, 'a', 0, 'a'.charCodeAt(0)]));
});
it('shrink(false) + enlarge(false) to smaller than before', function(): void {
const line = new TestBufferLine(20, [1, 'a', 0, 'a'.charCodeAt(0)], false);
line.resize(10, [1, 'a', 0, 'a'.charCodeAt(0)], false);
line.resize(15, [1, 'a', 0, 'a'.charCodeAt(0)]);
chai.expect(line.toArray()).eql(Array(20).fill([1, 'a', 0, 'a'.charCodeAt(0)]));
});
it('shrink(false) + enlarge(false) to bigger than before', function(): void {
const line = new TestBufferLine(20, [1, 'a', 0, 'a'.charCodeAt(0)], false);
line.resize(10, [1, 'a', 0, 'a'.charCodeAt(0)], false);
line.resize(25, [1, 'a', 0, 'a'.charCodeAt(0)]);
chai.expect(line.toArray()).eql(Array(25).fill([1, 'a', 0, 'a'.charCodeAt(0)]));
});
it('shrink(false) + resize shrink=true should enforce shrinking', function(): void {
const line = new TestBufferLine(20, [1, 'a', 0, 'a'.charCodeAt(0)], false);
line.resize(10, [1, 'a', 0, 'a'.charCodeAt(0)], false);
line.resize(10, [1, 'a', 0, 'a'.charCodeAt(0)], true);
chai.expect(line.toArray()).eql(Array(10).fill([1, 'a', 0, 'a'.charCodeAt(0)]));
});
it('enlarge from 0 length', function(): void {
const line = new TestBufferLine(0, [1, 'a', 0, 'a'.charCodeAt(0)], false);
line.resize(10, [1, 'a', 0, 'a'.charCodeAt(0)], false);
chai.expect(line.toArray()).eql(Array(10).fill([1, 'a', 0, 'a'.charCodeAt(0)]));
});
it('shrink to 0 length', function(): void {
const line = new TestBufferLine(10, [1, 'a', 0, 'a'.charCodeAt(0)], false);
line.resize(0, [1, 'a', 0, 'a'.charCodeAt(0)], true);
chai.expect(line.toArray()).eql(Array(0).fill([1, 'a', 0, 'a'.charCodeAt(0)]));
});
it('shrink(false) to 0 and enlarge to different sizes', function(): void {
const line = new TestBufferLine(10, [1, 'a', 0, 'a'.charCodeAt(0)], false);
line.resize(0, [1, 'a', 0, 'a'.charCodeAt(0)], false);
chai.expect(line.toArray()).eql(Array(10).fill([1, 'a', 0, 'a'.charCodeAt(0)]));
line.resize(5, [1, 'a', 0, 'a'.charCodeAt(0)], false);
chai.expect(line.toArray()).eql(Array(10).fill([1, 'a', 0, 'a'.charCodeAt(0)]));
line.resize(7, [1, 'a', 0, 'a'.charCodeAt(0)], false);
chai.expect(line.toArray()).eql(Array(10).fill([1, 'a', 0, 'a'.charCodeAt(0)]));
line.resize(7, [1, 'a', 0, 'a'.charCodeAt(0)], true);
chai.expect(line.toArray()).eql(Array(7).fill([1, 'a', 0, 'a'.charCodeAt(0)]));
});
});
});
-272
View File
@@ -1,272 +0,0 @@
/**
* Copyright (c) 2018 The xterm.js authors. All rights reserved.
* @license MIT
*/
import { CharData, IBufferLine } from './Types';
import { NULL_CELL_CODE, NULL_CELL_WIDTH, NULL_CELL_CHAR } from './Buffer';
/**
* Class representing a terminal line.
*
* @deprecated to be removed with one of the next releases
*/
export class BufferLineJSArray implements IBufferLine {
protected _data: CharData[];
public isWrapped = false;
public length: number;
constructor(cols: number, fillCharData?: CharData, isWrapped?: boolean) {
this._data = [];
if (!fillCharData) {
fillCharData = [0, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE];
}
for (let i = 0; i < cols; i++) {
this._push(fillCharData); // Note: the ctor ch is not cloned (resembles old behavior)
}
if (isWrapped) {
this.isWrapped = true;
}
this.length = this._data.length;
}
private _pop(): CharData | undefined {
const data = this._data.pop();
this.length = this._data.length;
return data;
}
private _push(data: CharData): void {
this._data.push(data);
this.length = this._data.length;
}
private _splice(start: number, deleteCount: number, ...items: CharData[]): CharData[] {
const removed = this._data.splice(start, deleteCount, ...items);
this.length = this._data.length;
return removed;
}
public get(index: number): CharData {
return this._data[index];
}
public set(index: number, data: CharData): void {
this._data[index] = data;
}
/** insert n cells ch at pos, right cells are lost (stable length) */
public insertCells(pos: number, n: number, ch: CharData): void {
while (n--) {
this._splice(pos, 0, ch);
this._pop();
}
}
/** delete n cells at pos, right side is filled with fill (stable length) */
public deleteCells(pos: number, n: number, fillCharData: CharData): void {
while (n--) {
this._splice(pos, 1);
this._push(fillCharData);
}
}
/** replace cells from pos to pos + n - 1 with fill */
public replaceCells(start: number, end: number, fillCharData: CharData): void {
while (start < end && start < this.length) {
this.set(start++, fillCharData); // Note: fill is not cloned (resembles old behavior)
}
}
/** resize line to cols filling new cells with fill */
public resize(cols: number, fillCharData: CharData, shrink: boolean = false): void {
while (this._data.length < cols) {
this._data.push(fillCharData);
}
if (shrink) {
while (this._data.length > cols) {
this._data.pop();
}
}
this.length = this._data.length;
}
public fill(fillCharData: CharData): void {
for (let i = 0; i < this.length; ++i) {
this.set(i, fillCharData);
}
}
public copyFrom(line: BufferLineJSArray): void {
this._data = line._data.slice(0);
this.length = line.length;
this.isWrapped = line.isWrapped;
}
public clone(): IBufferLine {
const newLine = new BufferLineJSArray(0);
newLine.copyFrom(this);
return newLine;
}
}
/** typed array slots taken by one cell */
const CELL_SIZE = 3;
/** cell member indices */
const enum Cell {
FLAGS = 0,
STRING = 1,
WIDTH = 2
}
/**
* Typed array based bufferline implementation.
*/
export class BufferLine implements IBufferLine {
protected _data: Uint32Array | null = null;
protected _combined: {[index: number]: string} = {};
public length: number;
constructor(cols: number, fillCharData?: CharData, public isWrapped: boolean = false) {
if (!fillCharData) {
fillCharData = [0, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE];
}
if (cols) {
this._data = new Uint32Array(cols * CELL_SIZE);
for (let i = 0; i < cols; ++i) {
this.set(i, fillCharData);
}
}
this.length = cols;
}
public get(index: number): CharData {
const stringData = this._data[index * CELL_SIZE + Cell.STRING];
return [
this._data[index * CELL_SIZE + Cell.FLAGS],
(stringData & 0x80000000)
? this._combined[index]
: (stringData) ? String.fromCharCode(stringData) : '',
this._data[index * CELL_SIZE + Cell.WIDTH],
(stringData & 0x80000000)
? this._combined[index].charCodeAt(this._combined[index].length - 1)
: stringData
];
}
public set(index: number, value: CharData): void {
this._data[index * CELL_SIZE + Cell.FLAGS] = value[0];
if (value[1].length > 1) {
this._combined[index] = value[1];
this._data[index * CELL_SIZE + Cell.STRING] = index | 0x80000000;
} else {
this._data[index * CELL_SIZE + Cell.STRING] = value[1].charCodeAt(0);
}
this._data[index * CELL_SIZE + Cell.WIDTH] = value[2];
}
public insertCells(pos: number, n: number, fillCharData: CharData): void {
pos %= this.length;
if (n < this.length - pos) {
for (let i = this.length - pos - n - 1; i >= 0; --i) {
this.set(pos + n + i, this.get(pos + i));
}
for (let i = 0; i < n; ++i) {
this.set(pos + i, fillCharData);
}
} else {
for (let i = pos; i < this.length; ++i) {
this.set(i, fillCharData);
}
}
}
public deleteCells(pos: number, n: number, fillCharData: CharData): void {
pos %= this.length;
if (n < this.length - pos) {
for (let i = 0; i < this.length - pos - n; ++i) {
this.set(pos + i, this.get(pos + n + i));
}
for (let i = this.length - n; i < this.length; ++i) {
this.set(i, fillCharData);
}
} else {
for (let i = pos; i < this.length; ++i) {
this.set(i, fillCharData);
}
}
}
public replaceCells(start: number, end: number, fillCharData: CharData): void {
while (start < end && start < this.length) {
this.set(start++, fillCharData);
}
}
public resize(cols: number, fillCharData: CharData, shrink: boolean = false): void {
if (cols === this.length || (!shrink && cols < this.length)) {
return;
}
if (cols > this.length) {
const data = new Uint32Array(cols * CELL_SIZE);
if (this.length) {
if (cols * CELL_SIZE < this._data.length) {
data.set(this._data.subarray(0, cols * CELL_SIZE));
} else {
data.set(this._data);
}
}
this._data = data;
for (let i = this.length; i < cols; ++i) {
this.set(i, fillCharData);
}
} else if (shrink) {
if (cols) {
const data = new Uint32Array(cols * CELL_SIZE);
data.set(this._data.subarray(0, cols * CELL_SIZE));
this._data = data;
} else {
this._data = null;
}
}
this.length = cols;
}
/** fill a line with fillCharData */
public fill(fillCharData: CharData): void {
this._combined = {};
for (let i = 0; i < this.length; ++i) {
this.set(i, fillCharData);
}
}
/** alter to a full copy of line */
public copyFrom(line: BufferLine): void {
if (this.length !== line.length) {
this._data = new Uint32Array(line._data);
} else {
// use high speed copy if lengths are equal
this._data.set(line._data);
}
this.length = line.length;
this._combined = {};
for (const el in line._combined) {
this._combined[el] = line._combined[el];
}
this.isWrapped = line.isWrapped;
}
/** create a new clone */
public clone(): IBufferLine {
const newLine = new BufferLine(0);
// creation of new typed array from another is actually pretty slow :(
// still faster than copying values one by one
newLine._data = new Uint32Array(this._data);
newLine.length = this.length;
for (const el in this._combined) {
newLine._combined[el] = this._combined[el];
}
newLine.isWrapped = this.isWrapped;
return newLine;
}
}
+3 -3
View File
@@ -7,7 +7,7 @@ import { assert } from 'chai';
import { ITerminal } from './Types';
import { BufferSet } from './BufferSet';
import { Buffer } from './Buffer';
import { MockTerminal } from './utils/TestUtils.test';
import { MockTerminal } from './TestUtils.test';
describe('BufferSet', () => {
let terminal: ITerminal;
@@ -15,8 +15,8 @@ describe('BufferSet', () => {
beforeEach(() => {
terminal = new MockTerminal();
terminal.cols = 80;
terminal.rows = 24;
(terminal as any).cols = 80;
(terminal as any).rows = 24;
terminal.options.scrollback = 1000;
bufferSet = new BufferSet(terminal);
});
+11 -7
View File
@@ -3,25 +3,29 @@
* @license MIT
*/
import { ITerminal, IBufferSet } from './Types';
import { ITerminal, IBufferSet, IBuffer } from './Types';
import { IAttributeData } from './core/Types';
import { Buffer } from './Buffer';
import { EventEmitter } from './common/EventEmitter';
import { EventEmitter2, IEvent } from './common/EventEmitter2';
/**
* The BufferSet represents the set of two buffers used by xterm terminals (normal and alt) and
* provides also utilities for working with them.
*/
export class BufferSet extends EventEmitter implements IBufferSet {
export class BufferSet implements IBufferSet {
private _normal: Buffer;
private _alt: Buffer;
private _activeBuffer: Buffer;
private _onBufferActivate = new EventEmitter2<{activeBuffer: IBuffer, inactiveBuffer: IBuffer}>();
public get onBufferActivate(): IEvent<{activeBuffer: IBuffer, inactiveBuffer: IBuffer}> { return this._onBufferActivate.event; }
/**
* Create a new BufferSet for the given terminal.
* @param _terminal - The terminal the BufferSet will belong to
*/
constructor(private _terminal: ITerminal) {
super();
this._normal = new Buffer(this._terminal, true);
this._normal.fillViewportRows();
@@ -68,7 +72,7 @@ export class BufferSet extends EventEmitter implements IBufferSet {
// when activated.
this._alt.clear();
this._activeBuffer = this._normal;
this.emit('activate', {
this._onBufferActivate.fire({
activeBuffer: this._normal,
inactiveBuffer: this._alt
});
@@ -77,7 +81,7 @@ export class BufferSet extends EventEmitter implements IBufferSet {
/**
* Sets the alt Buffer of the BufferSet as its currently active Buffer
*/
public activateAltBuffer(fillAttr?: number): void {
public activateAltBuffer(fillAttr?: IAttributeData): void {
if (this._activeBuffer === this._alt) {
return;
}
@@ -87,7 +91,7 @@ export class BufferSet extends EventEmitter implements IBufferSet {
this._alt.x = this._normal.x;
this._alt.y = this._normal.y;
this._activeBuffer = this._alt;
this.emit('activate', {
this._onBufferActivate.fire({
activeBuffer: this._alt,
inactiveBuffer: this._normal
});
@@ -4,7 +4,7 @@
*/
import jsdom = require('jsdom');
import { ICharMeasure } from '../Types';
import { ICharMeasure } from './Types';
import { assert } from 'chai';
import { CharMeasure } from './CharMeasure';
+11 -8
View File
@@ -3,23 +3,25 @@
* @license MIT
*/
import { ICharMeasure, ITerminalOptions } from '../Types';
import { EventEmitter } from '../common/EventEmitter';
import { ICharMeasure, ITerminalOptions } from './Types';
import { EventEmitter2, IEvent } from './common/EventEmitter2';
/**
* Utility class that measures the size of a character. Measurements are done in
* the DOM rather than with a canvas context because support for extracting the
* height of characters is patchy across browsers.
*/
export class CharMeasure extends EventEmitter implements ICharMeasure {
export class CharMeasure implements ICharMeasure {
private _document: Document;
private _parentElement: HTMLElement;
private _measureElement: HTMLElement;
private _width: number;
private _height: number;
private _onCharSizeChanged = new EventEmitter2<void>();
public get onCharSizeChanged(): IEvent<void> { return this._onCharSizeChanged.event; }
constructor(document: Document, parentElement: HTMLElement) {
super();
this._document = document;
this._parentElement = parentElement;
this._measureElement = this._document.createElement('span');
@@ -38,7 +40,7 @@ export class CharMeasure extends EventEmitter implements ICharMeasure {
}
public measure(options: ITerminalOptions): void {
this._measureElement.style.fontFamily = options.fontFamily;
this._measureElement.style.fontFamily = options.fontFamily;
this._measureElement.style.fontSize = `${options.fontSize}px`;
const geometry = this._measureElement.getBoundingClientRect();
// The element is likely currently display:none, we should retain the
@@ -46,10 +48,11 @@ export class CharMeasure extends EventEmitter implements ICharMeasure {
if (geometry.width === 0 || geometry.height === 0) {
return;
}
if (this._width !== geometry.width || this._height !== geometry.height) {
const adjustedHeight = Math.ceil(geometry.height);
if (this._width !== geometry.width || this._height !== adjustedHeight) {
this._width = geometry.width;
this._height = Math.ceil(geometry.height);
this.emit('charsizechanged');
this._height = adjustedHeight;
this._onCharSizeChanged.fire();
}
}
}
+3 -3
View File
@@ -3,11 +3,11 @@
* @license MIT
*/
import { TestTerminal } from './utils/TestUtils.test';
import { TestTerminal } from './TestUtils.test';
import { assert } from 'chai';
import { getStringCellWidth, wcwidth } from './CharWidth';
import { IBuffer } from './Types';
import { CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CHAR_INDEX } from './Buffer';
import { CellData, CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CHAR_INDEX } from './core/buffer/BufferLine';
describe('getStringCellWidth', function(): void {
@@ -22,7 +22,7 @@ describe('getStringCellWidth', function(): void {
for (let i = start; i < end; ++i) {
const line = buffer.lines.get(i);
for (let j = 0; j < line.length; ++j) { // TODO: change to trimBorder with multiline
const ch = line.get(j);
const ch = line.loadCell(j, new CellData()).getAsCharData();
result += ch[CHAR_DATA_WIDTH_INDEX];
// return on sentinel
if (ch[CHAR_DATA_CHAR_INDEX] === sentinel) {
+26 -20
View File
@@ -3,7 +3,7 @@
* @license MIT
*/
import { ITerminal, ISelectionManager } from '../Types';
import { ITerminal, ISelectionManager } from './Types';
interface IWindow extends Window {
clipboardData?: {
@@ -85,26 +85,32 @@ export function pasteHandler(ev: ClipboardEvent, term: ITerminal): void {
* @param ev The original right click event to be handled.
* @param textarea The terminal's textarea.
*/
export function moveTextAreaUnderMouseCursor(ev: MouseEvent, textarea: HTMLTextAreaElement): void {
// Bring textarea at the cursor position
textarea.style.position = 'fixed';
textarea.style.width = '20px';
textarea.style.height = '20px';
textarea.style.left = (ev.clientX - 10) + 'px';
textarea.style.top = (ev.clientY - 10) + 'px';
textarea.style.zIndex = '1000';
export function moveTextAreaUnderMouseCursor(ev: MouseEvent, term: ITerminal): void {
textarea.focus();
// Calculate textarea position relative to the screen element
const pos = term.screenElement.getBoundingClientRect();
const left = ev.clientX - pos.left - 10;
const top = ev.clientY - pos.top - 10;
// Bring textarea at the cursor position
term.textarea.style.position = 'absolute';
term.textarea.style.width = '20px';
term.textarea.style.height = '20px';
term.textarea.style.left = `${left}px`;
term.textarea.style.top = `${top}px`;
term.textarea.style.zIndex = '1000';
term.textarea.focus();
// Reset the terminal textarea's styling
// Timeout needs to be long enough for click event to be handled.
setTimeout(() => {
textarea.style.position = null;
textarea.style.width = null;
textarea.style.height = null;
textarea.style.left = null;
textarea.style.top = null;
textarea.style.zIndex = null;
term.textarea.style.position = null;
term.textarea.style.width = null;
term.textarea.style.height = null;
term.textarea.style.left = null;
term.textarea.style.top = null;
term.textarea.style.zIndex = null;
}, 200);
}
@@ -115,14 +121,14 @@ export function moveTextAreaUnderMouseCursor(ev: MouseEvent, textarea: HTMLTextA
* @param selectionManager The terminal's selection manager.
* @param shouldSelectWord If true and there is no selection the current word will be selected
*/
export function rightClickHandler(ev: MouseEvent, textarea: HTMLTextAreaElement, selectionManager: ISelectionManager, shouldSelectWord: boolean): void {
moveTextAreaUnderMouseCursor(ev, textarea);
export function rightClickHandler(ev: MouseEvent, term: ITerminal, selectionManager: ISelectionManager, shouldSelectWord: boolean): void {
moveTextAreaUnderMouseCursor(ev, term);
if (shouldSelectWord && !selectionManager.isClickInSelection(ev)) {
selectionManager.selectWordAtCursor(ev);
}
// Get textarea ready to copy from the context menu
textarea.value = selectionManager.selectionText;
textarea.select();
term.textarea.value = selectionManager.selectionText;
term.textarea.select();
}
+8 -6
View File
@@ -111,17 +111,17 @@ export class CompositionHelper {
/**
* Finalizes the composition, resuming regular input actions. This is called when a composition
* is ending.
* @param waitForPropogation Whether to wait for events to propogate before sending
* @param waitForPropagation Whether to wait for events to propagate before sending
* the input. This should be false if a non-composition keystroke is entered before the
* compositionend event is triggered, such as enter, so that the composition is send before
* compositionend event is triggered, such as enter, so that the composition is sent before
* the command is executed.
*/
private _finalizeComposition(waitForPropogation: boolean): void {
private _finalizeComposition(waitForPropagation: boolean): void {
this._compositionView.classList.remove('active');
this._isComposing = false;
this._clearTextareaPosition();
if (!waitForPropogation) {
if (!waitForPropagation) {
// Cancel any delayed composition send requests and send the input immediately.
this._isSendingComposition = false;
const input = this._textarea.value.substring(this._compositionPosition.start, this._compositionPosition.end);
@@ -136,8 +136,8 @@ export class CompositionHelper {
// Since composition* events happen before the changes take place in the textarea on most
// browsers, use a setTimeout with 0ms time to allow the native compositionend event to
// complete. This ensures the correct character is retrieved, this solution was used
// because:
// complete. This ensures the correct character is retrieved.
// This solution was used because:
// - The compositionend event's data property is unreliable, at least on Chromium
// - The last compositionupdate event's data property does not always accurately describe
// the character, a counter example being Korean where an ending consonsant can move to
@@ -203,6 +203,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._terminal.options.fontFamily;
this._compositionView.style.fontSize = this._terminal.options.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();
File diff suppressed because it is too large Load Diff
+95 -31
View File
@@ -4,7 +4,16 @@
*/
import { ParserState, ParserAction, IParsingState, IDcsHandler, IEscapeSequenceParser } from './Types';
import { IDisposable } from 'xterm';
import { Disposable } from './common/Lifecycle';
import { utf32ToString } from './core/input/TextDecoder';
interface IHandlerCollection<T> {
[key: string]: T[];
}
type CsiHandler = (params: number[], collect: string) => boolean | void;
type OscHandler = (data: string) => boolean | void;
/**
* Returns an array filled with numbers between the low and high parameters (right exclusive).
@@ -67,7 +76,8 @@ const PRINTABLES = r(0x20, 0x7f);
const EXECUTABLES = r(0x00, 0x18);
EXECUTABLES.push(0x19);
EXECUTABLES.push.apply(EXECUTABLES, r(0x1c, 0x20));
const DEFAULT_TRANSITION = ParserAction.ERROR << 4 | ParserState.GROUND;
// Pseudo-character placeholder for printable non-ascii characters.
const NON_ASCII_PRINTABLE = 0xA0;
/**
* VT500 compatible transition table.
@@ -79,10 +89,10 @@ export const VT500_TRANSITION_TABLE = (function (): TransitionTable {
const states: number[] = r(ParserState.GROUND, ParserState.DCS_PASSTHROUGH + 1);
let state: any;
// table with default transition [any] --> DEFAULT_TRANSITION
// table with default transition
for (state in states) {
// NOTE: table lookup is capped at 0xa0 in parse to keep the table small
for (let code = 0; code < 160; ++code) {
for (let code = 0; code <= NON_ASCII_PRINTABLE; ++code) {
table.add(code, state, ParserAction.ERROR, ParserState.GROUND);
}
}
@@ -125,6 +135,7 @@ export const VT500_TRANSITION_TABLE = (function (): TransitionTable {
table.addMany(PRINTABLES, ParserState.SOS_PM_APC_STRING, ParserAction.IGNORE, ParserState.SOS_PM_APC_STRING);
table.addMany(EXECUTABLES, ParserState.SOS_PM_APC_STRING, ParserAction.IGNORE, ParserState.SOS_PM_APC_STRING);
table.add(0x9c, ParserState.SOS_PM_APC_STRING, ParserAction.IGNORE, ParserState.GROUND);
table.add(0x7f, ParserState.SOS_PM_APC_STRING, ParserAction.IGNORE, ParserState.SOS_PM_APC_STRING);
// csi entries
table.add(0x5b, ParserState.ESCAPE, ParserAction.CLEAR, ParserState.CSI_ENTRY);
table.addMany(r(0x40, 0x7f), ParserState.CSI_ENTRY, ParserAction.CSI_DISPATCH, ParserState.GROUND);
@@ -184,6 +195,7 @@ export const VT500_TRANSITION_TABLE = (function (): TransitionTable {
table.addMany(PRINTABLES, ParserState.DCS_PASSTHROUGH, ParserAction.DCS_PUT, ParserState.DCS_PASSTHROUGH);
table.add(0x7f, ParserState.DCS_PASSTHROUGH, ParserAction.IGNORE, ParserState.DCS_PASSTHROUGH);
table.addMany([0x1b, 0x9c], ParserState.DCS_PASSTHROUGH, ParserAction.DCS_UNHOOK, ParserState.GROUND);
table.add(NON_ASCII_PRINTABLE, ParserState.OSC_STRING, ParserAction.OSC_PUT, ParserState.OSC_STRING);
return table;
})();
@@ -192,7 +204,7 @@ export const VT500_TRANSITION_TABLE = (function (): TransitionTable {
*/
class DcsDummy implements IDcsHandler {
hook(collect: string, params: number[], flag: number): void { }
put(data: string, start: number, end: number): void { }
put(data: Uint32Array, start: number, end: number): void { }
unhook(): void { }
}
@@ -218,17 +230,17 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
protected _collect: string;
// handler lookup containers
protected _printHandler: (data: string, start: number, end: number) => void;
protected _printHandler: (data: Uint32Array, start: number, end: number) => void;
protected _executeHandlers: any;
protected _csiHandlers: any;
protected _csiHandlers: IHandlerCollection<CsiHandler>;
protected _escHandlers: any;
protected _oscHandlers: any;
protected _oscHandlers: IHandlerCollection<OscHandler>;
protected _dcsHandlers: any;
protected _activeDcsHandler: IDcsHandler | null;
protected _errorHandler: (state: IParsingState) => IParsingState;
// fallback handlers
protected _printHandlerFb: (data: string, start: number, end: number) => void;
protected _printHandlerFb: (data: Uint32Array, start: number, end: number) => void;
protected _executeHandlerFb: (code: number) => void;
protected _csiHandlerFb: (collect: string, params: number[], flag: number) => void;
protected _escHandlerFb: (collect: string, flag: number) => void;
@@ -276,15 +288,15 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
this._errorHandlerFb = null;
this._printHandler = null;
this._executeHandlers = null;
this._csiHandlers = null;
this._escHandlers = null;
this._csiHandlers = null;
this._oscHandlers = null;
this._dcsHandlers = null;
this._activeDcsHandler = null;
this._errorHandler = null;
}
setPrintHandler(callback: (data: string, start: number, end: number) => void): void {
setPrintHandler(callback: (data: Uint32Array, start: number, end: number) => void): void {
this._printHandler = callback;
}
clearPrintHandler(): void {
@@ -301,8 +313,24 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
this._executeHandlerFb = callback;
}
addCsiHandler(flag: string, callback: CsiHandler): IDisposable {
const index = flag.charCodeAt(0);
if (this._csiHandlers[index] === undefined) {
this._csiHandlers[index] = [];
}
const handlerList = this._csiHandlers[index];
handlerList.push(callback);
return {
dispose: () => {
const handlerIndex = handlerList.indexOf(callback);
if (handlerIndex !== -1) {
handlerList.splice(handlerIndex, 1);
}
}
};
}
setCsiHandler(flag: string, callback: (params: number[], collect: string) => void): void {
this._csiHandlers[flag.charCodeAt(0)] = callback;
this._csiHandlers[flag.charCodeAt(0)] = [callback];
}
clearCsiHandler(flag: string): void {
if (this._csiHandlers[flag.charCodeAt(0)]) delete this._csiHandlers[flag.charCodeAt(0)];
@@ -321,8 +349,23 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
this._escHandlerFb = callback;
}
addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable {
if (this._oscHandlers[ident] === undefined) {
this._oscHandlers[ident] = [];
}
const handlerList = this._oscHandlers[ident];
handlerList.push(callback);
return {
dispose: () => {
const handlerIndex = handlerList.indexOf(callback);
if (handlerIndex !== -1) {
handlerList.splice(handlerIndex, 1);
}
}
};
}
setOscHandler(ident: number, callback: (data: string) => void): void {
this._oscHandlers[ident] = callback;
this._oscHandlers[ident] = [callback];
}
clearOscHandler(ident: number): void {
if (this._oscHandlers[ident]) delete this._oscHandlers[ident];
@@ -356,7 +399,7 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
this._activeDcsHandler = null;
}
parse(data: string): void {
parse(data: Uint32Array, length: number): void {
let code = 0;
let transition = 0;
let error = false;
@@ -371,15 +414,14 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
let callback: Function | null = null;
// process input string
const l = data.length;
for (let i = 0; i < l; ++i) {
code = data.charCodeAt(i);
for (let i = 0; i < length; ++i) {
code = data[i];
// shortcut for most chars (print action)
if (currentState === ParserState.GROUND && code > 0x1f && code < 0x80) {
print = (~print) ? print : i;
do i++;
while (i < l && data.charCodeAt(i) > 0x1f && data.charCodeAt(i) < 0x80);
while (i < length && data[i] > 0x1f && data[i] < 0x80);
i--;
continue;
}
@@ -391,7 +433,7 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
}
// normal transition & action lookup
transition = (code < 0xa0) ? (table[currentState << 8 | code]) : DEFAULT_TRANSITION;
transition = table[currentState << 8 | (code < 0xa0 ? code : NON_ASCII_PRINTABLE)];
switch (transition >> 4) {
case ParserAction.PRINT:
print = (~print) ? print : i;
@@ -423,10 +465,6 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
case ParserState.GROUND:
print = (~print) ? print : i;
break;
case ParserState.OSC_STRING:
osc += String.fromCharCode(code);
transition |= ParserState.OSC_STRING;
break;
case ParserState.CSI_IGNORE:
transition |= ParserState.CSI_IGNORE;
break;
@@ -463,9 +501,18 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
}
break;
case ParserAction.CSI_DISPATCH:
callback = this._csiHandlers[code];
if (callback) callback(params, collect);
else this._csiHandlerFb(collect, params, code);
// Trigger CSI Handler
const handlers = this._csiHandlers[code];
let j = handlers ? handlers.length - 1 : -1;
for (; j >= 0; j--) {
// undefined or true means success and to stop bubbling
if (handlers[j](params, collect) !== false) {
break;
}
}
if (j < 0) {
this._csiHandlerFb(collect, params, code);
}
break;
case ParserAction.PARAM:
if (code === 0x3b) params.push(0);
@@ -517,7 +564,15 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
osc = '';
break;
case ParserAction.OSC_PUT:
osc += data.charAt(i);
for (let j = i + 1; ; j++) {
if (j >= length
|| (code = data[j]) < 0x20
|| (code > 0x7f && code <= 0x9f)) {
osc += utf32ToString(data, i, j);
i = j - 1;
break;
}
}
break;
case ParserAction.OSC_END:
if (osc && code !== 0x18 && code !== 0x1a) {
@@ -532,9 +587,18 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
// or with an explicit NaN OSC handler
const identifier = parseInt(osc.substring(0, idx));
const content = osc.substring(idx + 1);
callback = this._oscHandlers[identifier];
if (callback) callback(content);
else this._oscHandlerFb(identifier, content);
// Trigger OSC Handler
const handlers = this._oscHandlers[identifier];
let j = handlers ? handlers.length - 1 : -1;
for (; j >= 0; j--) {
// undefined or true means success and to stop bubbling
if (handlers[j](content) !== false) {
break;
}
}
if (j < 0) {
this._oscHandlerFb(identifier, content);
}
}
}
if (code === 0x1b) transition |= ParserState.ESCAPE;
@@ -549,9 +613,9 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
// push leftover pushable buffers to terminal
if (currentState === ParserState.GROUND && ~print) {
this._printHandler(data, print, data.length);
this._printHandler(data, print, length);
} else if (currentState === ParserState.DCS_PASSTHROUGH && ~dcs && dcsHandler) {
dcsHandler.put(data, dcs, data.length);
dcsHandler.put(data, dcs, length);
}
// save non pushable buffers
+251
View File
@@ -0,0 +1,251 @@
/**
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
* @license MIT
*/
import * as puppeteer from 'puppeteer';
import { assert } from 'chai';
import { ITerminalOptions } from './Types';
const APP = 'http://127.0.0.1:3000/test';
let browser: puppeteer.Browser;
let page: puppeteer.Page;
const width = 800;
const height = 600;
describe('InputHandler Integration Tests', function(): void {
this.timeout(20000);
before(async function(): Promise<any> {
browser = await puppeteer.launch({
headless: process.argv.indexOf('--headless') !== -1,
slowMo: 80,
args: [`--window-size=${width},${height}`]
});
page = (await browser.pages())[0];
await page.setViewport({ width, height });
await page.goto(APP);
await openTerminal();
});
after(() => {
browser.close();
});
describe('CSI', () => {
beforeEach(async () => {
await page.evaluate(`window.term.reset()`);
});
it('ICH: Insert Ps (Blank) Character(s) (default = 1) - CSI Ps @', async function(): Promise<any> {
await page.evaluate(`
// Default
window.term.write('foo\\x1b[3D\\x1b[@\\n\\r')
// Explicit
window.term.write('bar\\x1b[3D\\x1b[4@')
`);
assert.deepEqual(await getLinesAsArray(2), [' foo', ' bar']);
});
it('CUU: Cursor Up Ps Times (default = 1) - CSI Ps A', async function(): Promise<any> {
await page.evaluate(`
// Default
window.term.write('\\n\\n\\n\\n\x1b[Aa')
// Explicit
window.term.write('\x1b[2Ab')
`);
assert.deepEqual(await getLinesAsArray(4), ['', ' b', '', 'a']);
});
it('CUD: Cursor Down Ps Times (default = 1) - CSI Ps B', async function(): Promise<any> {
await page.evaluate(`
// Default
window.term.write('\x1b[Ba')
// Explicit
window.term.write('\x1b[2Bb')
`);
assert.deepEqual(await getLinesAsArray(4), ['', 'a', '', ' b']);
});
it('CUF: Cursor Forward Ps Times (default = 1) - CSI Ps C', async function(): Promise<any> {
await page.evaluate(`
// Default
window.term.write('\x1b[Ca')
// Explicit
window.term.write('\x1b[2Cb')
`);
assert.deepEqual(await getLinesAsArray(1), [' a b']);
});
it('CUB: Cursor Backward Ps Times (default = 1) - CSI Ps D', async function(): Promise<any> {
await page.evaluate(`
// Default
window.term.write('foo\x1b[Da')
// Explicit
window.term.write('\x1b[2Db')
`);
assert.deepEqual(await getLinesAsArray(1), ['fba']);
});
it('CNL: Cursor Next Line Ps Times (default = 1) - CSI Ps E', async function(): Promise<any> {
await page.evaluate(`
// Default
window.term.write('\x1b[Ea')
// Explicit
window.term.write('\x1b[2Eb')
`);
assert.deepEqual(await getLinesAsArray(4), ['', 'a', '', 'b']);
});
it('CPL: Cursor Preceding Line Ps Times (default = 1) - CSI Ps F', async function(): Promise<any> {
await page.evaluate(`
// Default
window.term.write('\\n\\n\\n\\n\x1b[Fa')
// Explicit
window.term.write('\x1b[2Fb')
`);
assert.deepEqual(await getLinesAsArray(5), ['', 'b', '', 'a', '']);
});
it('CHA: Cursor Character Absolute [column] (default = [row,1]) - CSI Ps G', async function(): Promise<any> {
await page.evaluate(`
// Default
window.term.write('foo\x1b[Ga')
// Explicit
window.term.write('\x1b[10Gb')
`);
assert.deepEqual(await getLinesAsArray(1), ['aoo b']);
});
it('CUP: Cursor Position [row;column] (default = [1,1]) - CSI Ps ; Ps H', async function(): Promise<any> {
await page.evaluate(`
// Default
window.term.write('foo\x1b[Ha')
// Explicit
window.term.write('\x1b[3;3Hb')
`);
assert.deepEqual(await getLinesAsArray(3), ['aoo', '', ' b']);
});
it('CHT: Cursor Forward Tabulation Ps tab stops (default = 1) - CSI Ps I', async function(): Promise<any> {
await page.evaluate(`
// Default
window.term.write('\x1b[Ia')
// Explicit
window.term.write('\\n\\r\x1b[2Ib')
`);
assert.deepEqual(await getLinesAsArray(2), [' a', ' b']);
});
it('ED: Erase in Display, VT100 - CSI Ps J', async function(): Promise<any> {
const fixture = 'abc\\n\\rdef\\n\\rghi\x1b[2;2H';
await page.evaluate(`
// Default: Erase Below
window.term.resize(5, 5);
window.term.write('${fixture}\x1b[J')
`);
assert.deepEqual(await getLinesAsArray(3), ['abc', 'd', '']);
await page.evaluate(`
// 0: Erase Below
window.term.reset()
window.term.write('${fixture}\x1b[0J')
`);
assert.deepEqual(await getLinesAsArray(3), ['abc', 'd', '']);
await page.evaluate(`
// 1: Erase Above
window.term.reset()
window.term.write('${fixture}\x1b[1J')
`);
assert.deepEqual(await getLinesAsArray(3), ['', ' f', 'ghi']);
await page.evaluate(`
// 2: Erase Saved Lines (scrollback)
window.term.reset()
window.term.write('1\\n2\\n3\\n4\\n5${fixture}\x1b[3J')
`);
assert.equal(await page.evaluate(`window.term.buffer.length`), 5);
assert.deepEqual(await getLinesAsArray(5), [' 4', ' 5', 'abc', 'def', 'ghi']);
});
it('DECSED: Erase in Display, VT220 - CSI ? Ps J', async function(): Promise<any> {
const fixture = 'abc\\n\\rdef\\n\\rghi\x1b[2;2H';
await page.evaluate(`
// Default: Erase Below
window.term.resize(5, 5);
window.term.write('${fixture}\x1b[?J')
`);
assert.deepEqual(await getLinesAsArray(3), ['abc', 'd', '']);
await page.evaluate(`
// 0: Erase Below
window.term.reset()
window.term.write('${fixture}\x1b[?0J')
`);
assert.deepEqual(await getLinesAsArray(3), ['abc', 'd', '']);
await page.evaluate(`
// 1: Erase Above
window.term.reset()
window.term.write('${fixture}\x1b[?1J')
`);
assert.deepEqual(await getLinesAsArray(3), ['', ' f', 'ghi']);
await page.evaluate(`
// 2: Erase Saved Lines (scrollback)
window.term.reset()
window.term.write('1\\n2\\n3\\n4\\n5${fixture}\x1b[?3J')
`);
assert.equal(await page.evaluate(`window.term.buffer.length`), 5);
assert.deepEqual(await getLinesAsArray(5), [' 4', ' 5', 'abc', 'def', 'ghi']);
});
describe('DSR: Device Status Report', () => {
it('Status Report - CSI 5 n', async function(): Promise<any> {
await page.evaluate(`
window.term.onData(e => window.result = e);
window.term.write('\\x1b[5n');
`);
assert.equal(await page.evaluate(`window.result`), '\x1b[0n');
});
it('Report Cursor Position (CPR) - CSI 6 n', async function(): Promise<any> {
await page.evaluate(`window.term.write('\\n\\nfoo')`);
assert.deepEqual(await page.evaluate(`
[window.term.buffer.cursorY, window.term.buffer.cursorX]
`), [2, 3]);
await page.evaluate(`
window.term.onData(e => window.result = e);
window.term.write('\\x1b[6n');
`);
assert.equal(await page.evaluate(`window.result`), '\x1b[3;4R');
});
it('Report Cursor Position (DECXCPR) - CSI ? 6 n', async function(): Promise<any> {
await page.evaluate(`window.term.write('\\n\\nfoo')`);
assert.deepEqual(await page.evaluate(`
[window.term.buffer.cursorY, window.term.buffer.cursorX]
`), [2, 3]);
await page.evaluate(`
window.term.onData(e => window.result = e);
window.term.write('\\x1b[?6n');
`);
assert.equal(await page.evaluate(`window.result`), '\x1b[?3;4R');
});
});
});
});
async function openTerminal(options: ITerminalOptions = {}): Promise<void> {
await page.evaluate(`window.term = new Terminal(${JSON.stringify(options)})`);
await page.evaluate(`window.term.open(document.querySelector('#terminal-container'))`);
if (options.rendererType === 'dom') {
await page.waitForSelector('.xterm-rows');
} else {
await page.waitForSelector('.xterm-text-layer');
}
}
async function getLinesAsArray(count: number, start: number = 0): Promise<string[]> {
let text = '';
for (let i = start; i < start + count; i++) {
text += `window.term.buffer.getLine(${i}).translateToString(true),`;
}
return await page.evaluate(`[${text}]`);
}
+298 -324
View File
@@ -5,152 +5,32 @@
import { assert, expect } from 'chai';
import { InputHandler } from './InputHandler';
import { MockInputHandlingTerminal } from './utils/TestUtils.test';
import { NULL_CELL_CHAR, NULL_CELL_CODE, NULL_CELL_WIDTH, CHAR_DATA_CHAR_INDEX, CHAR_DATA_ATTR_INDEX, DEFAULT_ATTR } from './Buffer';
import { MockInputHandlingTerminal, TestTerminal } from './TestUtils.test';
import { Terminal } from './Terminal';
import { IBufferLine } from './Types';
// TODO: This and the sections related to this object in associated tests can be
// removed safely after InputHandler refactors are finished
class OldInputHandler extends InputHandler {
public eraseInLine(params: number[]): void {
switch (params[0]) {
case 0:
this.eraseRight(this._terminal.buffer.x, this._terminal.buffer.y);
break;
case 1:
this.eraseLeft(this._terminal.buffer.x, this._terminal.buffer.y);
break;
case 2:
this.eraseLine(this._terminal.buffer.y);
break;
}
}
public eraseInDisplay(params: number[]): void {
let j;
switch (params[0]) {
case 0:
this.eraseRight(this._terminal.buffer.x, this._terminal.buffer.y);
j = this._terminal.buffer.y + 1;
for (; j < this._terminal.rows; j++) {
this.eraseLine(j);
}
break;
case 1:
this.eraseLeft(this._terminal.buffer.x, this._terminal.buffer.y);
j = this._terminal.buffer.y;
while (j--) {
this.eraseLine(j);
}
break;
case 2:
j = this._terminal.rows;
while (j--) this.eraseLine(j);
break;
case 3:
// Clear scrollback (everything not in viewport)
const scrollBackSize = this._terminal.buffer.lines.length - this._terminal.rows;
if (scrollBackSize > 0) {
this._terminal.buffer.lines.trimStart(scrollBackSize);
this._terminal.buffer.ybase = Math.max(this._terminal.buffer.ybase - scrollBackSize, 0);
this._terminal.buffer.ydisp = Math.max(this._terminal.buffer.ydisp - scrollBackSize, 0);
// Force a scroll event to refresh viewport
this._terminal.emit('scroll', 0);
}
break;
}
}
/**
* Erase in the identified line everything from "x" to the end of the line (right).
* @param x The column from which to start erasing to the end of the line.
* @param y The line in which to operate.
*/
public eraseRight(x: number, y: number): void {
const line = this._terminal.buffer.lines.get(this._terminal.buffer.ybase + y);
if (!line) {
return;
}
line.replaceCells(x, this._terminal.cols, [this._terminal.eraseAttr(), NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]);
this._terminal.updateRange(y);
}
/**
* Erase in the identified line everything from "x" to the start of the line (left).
* @param x The column from which to start erasing to the start of the line.
* @param y The line in which to operate.
*/
public eraseLeft(x: number, y: number): void {
const line = this._terminal.buffer.lines.get(this._terminal.buffer.ybase + y);
if (!line) {
return;
}
line.replaceCells(0, x + 1, [this._terminal.eraseAttr(), NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]);
this._terminal.updateRange(y);
}
/**
* Erase all content in the given line
* @param y The line to erase all of its contents.
*/
public eraseLine(y: number): void {
this.eraseRight(0, y);
}
public insertChars(params: number[]): void {
let param = params[0];
if (param < 1) param = 1;
// make buffer local for faster access
const buffer = this._terminal.buffer;
const row = buffer.y + buffer.ybase;
let j = buffer.x;
while (param-- && j < this._terminal.cols) {
buffer.lines.get(row).insertCells(j++, 1, [this._terminal.eraseAttr(), NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]);
}
}
public deleteChars(params: number[]): void {
let param: number = params[0];
if (param < 1) {
param = 1;
}
// make buffer local for faster access
const buffer = this._terminal.buffer;
const row = buffer.y + buffer.ybase;
while (param--) {
buffer.lines.get(row).deleteCells(buffer.x, 1, [this._terminal.eraseAttr(), NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]);
}
this._terminal.updateRange(buffer.y);
}
}
import { IBufferLine } from './core/Types';
import { CellData, Attributes, AttributeData, DEFAULT_ATTR_DATA } from './core/buffer/BufferLine';
describe('InputHandler', () => {
describe('save and restore cursor', () => {
const terminal = new MockInputHandlingTerminal();
terminal.buffer.x = 1;
terminal.buffer.y = 2;
terminal.curAttr = 3;
terminal.curAttrData.fg = 3;
const inputHandler = new InputHandler(terminal);
// Save cursor position
inputHandler.saveCursor([]);
assert.equal(terminal.buffer.x, 1);
assert.equal(terminal.buffer.y, 2);
assert.equal(terminal.curAttr, 3);
assert.equal(terminal.curAttrData.fg, 3);
// Change cursor position
terminal.buffer.x = 10;
terminal.buffer.y = 20;
terminal.curAttr = 30;
terminal.curAttrData.fg = 30;
// Restore cursor position
inputHandler.restoreCursor([]);
assert.equal(terminal.buffer.x, 1);
assert.equal(terminal.buffer.y, 2);
assert.equal(terminal.curAttr, 3);
assert.equal(terminal.curAttrData.fg, 3);
});
describe('setCursorStyle', () => {
it('should call Terminal.setOption with correct params', () => {
@@ -208,302 +88,258 @@ describe('InputHandler', () => {
});
});
describe('regression tests', function(): void {
function lineContent(line: IBufferLine): string {
let content = '';
for (let i = 0; i < line.length; ++i) content += line.get(i)[CHAR_DATA_CHAR_INDEX];
return content;
}
function termContent(term: Terminal): string[] {
function termContent(term: Terminal, trim: boolean): string[] {
const result = [];
for (let i = 0; i < term.rows; ++i) result.push(lineContent(term.buffer.lines.get(i)));
for (let i = 0; i < term.rows; ++i) result.push(term.buffer.lines.get(i).translateToString(trim));
return result;
}
it('insertChars', function(): void {
const term = new Terminal();
const inputHandler = new InputHandler(term);
const oldInputHandler = new OldInputHandler(term);
// insert some data in first and second line
inputHandler.parse(Array(term.cols - 9).join('a'));
inputHandler.parse('1234567890');
inputHandler.parse(Array(term.cols - 9).join('a'));
inputHandler.parse('1234567890');
const line1: IBufferLine = term.buffer.lines.get(0); // line for old variant
const line2: IBufferLine = term.buffer.lines.get(1); // line for new variant
expect(lineContent(line1)).equals(Array(term.cols - 9).join('a') + '1234567890');
expect(lineContent(line2)).equals(Array(term.cols - 9).join('a') + '1234567890');
const line1: IBufferLine = term.buffer.lines.get(0);
expect(line1.translateToString(false)).equals(Array(term.cols - 9).join('a') + '1234567890');
// insert one char from params = [0]
term.buffer.y = 0;
term.buffer.x = 70;
oldInputHandler.insertChars([0]);
expect(lineContent(line1)).equals(Array(term.cols - 9).join('a') + ' 123456789');
term.buffer.y = 1;
term.buffer.x = 70;
inputHandler.insertChars([0]);
expect(lineContent(line2)).equals(Array(term.cols - 9).join('a') + ' 123456789');
expect(lineContent(line2)).equals(lineContent(line1));
expect(line1.translateToString(false)).equals(Array(term.cols - 9).join('a') + ' 123456789');
// insert one char from params = [1]
term.buffer.y = 0;
term.buffer.x = 70;
oldInputHandler.insertChars([1]);
expect(lineContent(line1)).equals(Array(term.cols - 9).join('a') + ' 12345678');
term.buffer.y = 1;
term.buffer.x = 70;
inputHandler.insertChars([1]);
expect(lineContent(line2)).equals(Array(term.cols - 9).join('a') + ' 12345678');
expect(lineContent(line2)).equals(lineContent(line1));
expect(line1.translateToString(false)).equals(Array(term.cols - 9).join('a') + ' 12345678');
// insert two chars from params = [2]
term.buffer.y = 0;
term.buffer.x = 70;
oldInputHandler.insertChars([2]);
expect(lineContent(line1)).equals(Array(term.cols - 9).join('a') + ' 123456');
term.buffer.y = 1;
term.buffer.x = 70;
inputHandler.insertChars([2]);
expect(lineContent(line2)).equals(Array(term.cols - 9).join('a') + ' 123456');
expect(lineContent(line2)).equals(lineContent(line1));
expect(line1.translateToString(false)).equals(Array(term.cols - 9).join('a') + ' 123456');
// insert 10 chars from params = [10]
term.buffer.y = 0;
term.buffer.x = 70;
oldInputHandler.insertChars([10]);
expect(lineContent(line1)).equals(Array(term.cols - 9).join('a') + ' ');
term.buffer.y = 1;
term.buffer.x = 70;
inputHandler.insertChars([10]);
expect(lineContent(line2)).equals(Array(term.cols - 9).join('a') + ' ');
expect(lineContent(line2)).equals(lineContent(line1));
expect(line1.translateToString(false)).equals(Array(term.cols - 9).join('a') + ' ');
expect(line1.translateToString(true)).equals(Array(term.cols - 9).join('a'));
});
it('deleteChars', function(): void {
const term = new Terminal();
const inputHandler = new InputHandler(term);
const oldInputHandler = new OldInputHandler(term);
// insert some data in first and second line
inputHandler.parse(Array(term.cols - 9).join('a'));
inputHandler.parse('1234567890');
inputHandler.parse(Array(term.cols - 9).join('a'));
inputHandler.parse('1234567890');
const line1: IBufferLine = term.buffer.lines.get(0); // line for old variant
const line2: IBufferLine = term.buffer.lines.get(1); // line for new variant
expect(lineContent(line1)).equals(Array(term.cols - 9).join('a') + '1234567890');
expect(lineContent(line2)).equals(Array(term.cols - 9).join('a') + '1234567890');
const line1: IBufferLine = term.buffer.lines.get(0);
expect(line1.translateToString(false)).equals(Array(term.cols - 9).join('a') + '1234567890');
// delete one char from params = [0]
term.buffer.y = 0;
term.buffer.x = 70;
oldInputHandler.deleteChars([0]);
expect(lineContent(line1)).equals(Array(term.cols - 9).join('a') + '234567890 ');
term.buffer.y = 1;
term.buffer.x = 70;
inputHandler.deleteChars([0]);
expect(lineContent(line2)).equals(Array(term.cols - 9).join('a') + '234567890 ');
expect(lineContent(line2)).equals(lineContent(line1));
expect(line1.translateToString(false)).equals(Array(term.cols - 9).join('a') + '234567890 ');
expect(line1.translateToString(true)).equals(Array(term.cols - 9).join('a') + '234567890');
// insert one char from params = [1]
term.buffer.y = 0;
term.buffer.x = 70;
oldInputHandler.deleteChars([1]);
expect(lineContent(line1)).equals(Array(term.cols - 9).join('a') + '34567890 ');
term.buffer.y = 1;
term.buffer.x = 70;
inputHandler.deleteChars([1]);
expect(lineContent(line2)).equals(Array(term.cols - 9).join('a') + '34567890 ');
expect(lineContent(line2)).equals(lineContent(line1));
expect(line1.translateToString(false)).equals(Array(term.cols - 9).join('a') + '34567890 ');
expect(line1.translateToString(true)).equals(Array(term.cols - 9).join('a') + '34567890');
// insert two chars from params = [2]
term.buffer.y = 0;
term.buffer.x = 70;
oldInputHandler.deleteChars([2]);
expect(lineContent(line1)).equals(Array(term.cols - 9).join('a') + '567890 ');
term.buffer.y = 1;
term.buffer.x = 70;
inputHandler.deleteChars([2]);
expect(lineContent(line2)).equals(Array(term.cols - 9).join('a') + '567890 ');
expect(lineContent(line2)).equals(lineContent(line1));
expect(line1.translateToString(false)).equals(Array(term.cols - 9).join('a') + '567890 ');
expect(line1.translateToString(true)).equals(Array(term.cols - 9).join('a') + '567890');
// insert 10 chars from params = [10]
term.buffer.y = 0;
term.buffer.x = 70;
oldInputHandler.deleteChars([10]);
expect(lineContent(line1)).equals(Array(term.cols - 9).join('a') + ' ');
term.buffer.y = 1;
term.buffer.x = 70;
inputHandler.deleteChars([10]);
expect(lineContent(line2)).equals(Array(term.cols - 9).join('a') + ' ');
expect(lineContent(line2)).equals(lineContent(line1));
expect(line1.translateToString(false)).equals(Array(term.cols - 9).join('a') + ' ');
expect(line1.translateToString(true)).equals(Array(term.cols - 9).join('a'));
});
it('eraseInLine', function(): void {
const term = new Terminal();
const inputHandler = new InputHandler(term);
const oldInputHandler = new OldInputHandler(term);
// fill 6 lines to test 3 different states
inputHandler.parse(Array(term.cols + 1).join('a'));
inputHandler.parse(Array(term.cols + 1).join('a'));
inputHandler.parse(Array(term.cols + 1).join('a'));
inputHandler.parse(Array(term.cols + 1).join('a'));
inputHandler.parse(Array(term.cols + 1).join('a'));
inputHandler.parse(Array(term.cols + 1).join('a'));
// params[0] - right erase
term.buffer.y = 0;
term.buffer.x = 70;
oldInputHandler.eraseInLine([0]);
expect(lineContent(term.buffer.lines.get(0))).equals(Array(71).join('a') + ' ');
inputHandler.eraseInLine([0]);
expect(term.buffer.lines.get(0).translateToString(false)).equals(Array(71).join('a') + ' ');
// params[1] - left erase
term.buffer.y = 1;
term.buffer.x = 70;
inputHandler.eraseInLine([0]);
expect(lineContent(term.buffer.lines.get(1))).equals(Array(71).join('a') + ' ');
inputHandler.eraseInLine([1]);
expect(term.buffer.lines.get(1).translateToString(false)).equals(Array(71).join(' ') + ' aaaaaaaaa');
// params[1] - left erase
term.buffer.y = 2;
term.buffer.x = 70;
oldInputHandler.eraseInLine([1]);
expect(lineContent(term.buffer.lines.get(2))).equals(Array(71).join(' ') + ' aaaaaaaaa');
term.buffer.y = 3;
term.buffer.x = 70;
inputHandler.eraseInLine([1]);
expect(lineContent(term.buffer.lines.get(3))).equals(Array(71).join(' ') + ' aaaaaaaaa');
// params[1] - left erase
term.buffer.y = 4;
term.buffer.x = 70;
oldInputHandler.eraseInLine([2]);
expect(lineContent(term.buffer.lines.get(4))).equals(Array(term.cols + 1).join(' '));
term.buffer.y = 5;
term.buffer.x = 70;
inputHandler.eraseInLine([2]);
expect(lineContent(term.buffer.lines.get(5))).equals(Array(term.cols + 1).join(' '));
expect(term.buffer.lines.get(2).translateToString(false)).equals(Array(term.cols + 1).join(' '));
});
it('eraseInDisplay', function(): void {
const termOld = new Terminal();
const inputHandlerOld = new OldInputHandler(termOld);
const termNew = new Terminal();
const inputHandlerNew = new InputHandler(termNew);
const term = new Terminal({cols: 80, rows: 7});
const inputHandler = new InputHandler(term);
// fill display with a's
for (let i = 0; i < termOld.rows; ++i) inputHandlerOld.parse(Array(termOld.cols + 1).join('a'));
for (let i = 0; i < termNew.rows; ++i) inputHandlerNew.parse(Array(termOld.cols + 1).join('a'));
const data = [];
for (let i = 0; i < termOld.rows; ++i) data.push(Array(termOld.cols + 1).join('a'));
expect(termContent(termOld)).eql(data);
expect(termContent(termOld)).eql(termContent(termNew));
for (let i = 0; i < term.rows; ++i) inputHandler.parse(Array(term.cols + 1).join('a'));
// params [0] - right and below erase
termOld.buffer.y = 5;
termOld.buffer.x = 40;
inputHandlerOld.eraseInDisplay([0]);
termNew.buffer.y = 5;
termNew.buffer.x = 40;
inputHandlerNew.eraseInDisplay([0]);
expect(termContent(termNew)).eql(termContent(termOld));
term.buffer.y = 5;
term.buffer.x = 40;
inputHandler.eraseInDisplay([0]);
expect(termContent(term, false)).eql([
Array(term.cols + 1).join('a'),
Array(term.cols + 1).join('a'),
Array(term.cols + 1).join('a'),
Array(term.cols + 1).join('a'),
Array(term.cols + 1).join('a'),
Array(40 + 1).join('a') + Array(term.cols - 40 + 1).join(' '),
Array(term.cols + 1).join(' ')
]);
expect(termContent(term, true)).eql([
Array(term.cols + 1).join('a'),
Array(term.cols + 1).join('a'),
Array(term.cols + 1).join('a'),
Array(term.cols + 1).join('a'),
Array(term.cols + 1).join('a'),
Array(40 + 1).join('a'),
''
]);
// reset
termOld.buffer.y = 0;
termOld.buffer.x = 0;
termNew.buffer.y = 0;
termNew.buffer.x = 0;
for (let i = 0; i < termOld.rows; ++i) inputHandlerOld.parse(Array(termOld.cols + 1).join('a'));
for (let i = 0; i < termNew.rows; ++i) inputHandlerNew.parse(Array(termOld.cols + 1).join('a'));
term.buffer.y = 0;
term.buffer.x = 0;
for (let i = 0; i < term.rows; ++i) inputHandler.parse(Array(term.cols + 1).join('a'));
// params [1] - left and above
termOld.buffer.y = 5;
termOld.buffer.x = 40;
inputHandlerOld.eraseInDisplay([1]);
termNew.buffer.y = 5;
termNew.buffer.x = 40;
inputHandlerNew.eraseInDisplay([1]);
expect(termContent(termNew)).eql(termContent(termOld));
term.buffer.y = 5;
term.buffer.x = 40;
inputHandler.eraseInDisplay([1]);
expect(termContent(term, false)).eql([
Array(term.cols + 1).join(' '),
Array(term.cols + 1).join(' '),
Array(term.cols + 1).join(' '),
Array(term.cols + 1).join(' '),
Array(term.cols + 1).join(' '),
Array(41 + 1).join(' ') + Array(term.cols - 41 + 1).join('a'),
Array(term.cols + 1).join('a')
]);
expect(termContent(term, true)).eql([
'',
'',
'',
'',
'',
Array(41 + 1).join(' ') + Array(term.cols - 41 + 1).join('a'),
Array(term.cols + 1).join('a')
]);
// reset
termOld.buffer.y = 0;
termOld.buffer.x = 0;
termNew.buffer.y = 0;
termNew.buffer.x = 0;
for (let i = 0; i < termOld.rows; ++i) inputHandlerOld.parse(Array(termOld.cols + 1).join('a'));
for (let i = 0; i < termNew.rows; ++i) inputHandlerNew.parse(Array(termOld.cols + 1).join('a'));
term.buffer.y = 0;
term.buffer.x = 0;
for (let i = 0; i < term.rows; ++i) inputHandler.parse(Array(term.cols + 1).join('a'));
// params [2] - whole screen
termOld.buffer.y = 5;
termOld.buffer.x = 40;
inputHandlerOld.eraseInDisplay([2]);
termNew.buffer.y = 5;
termNew.buffer.x = 40;
inputHandlerNew.eraseInDisplay([2]);
expect(termContent(termNew)).eql(termContent(termOld));
term.buffer.y = 5;
term.buffer.x = 40;
inputHandler.eraseInDisplay([2]);
expect(termContent(term, false)).eql([
Array(term.cols + 1).join(' '),
Array(term.cols + 1).join(' '),
Array(term.cols + 1).join(' '),
Array(term.cols + 1).join(' '),
Array(term.cols + 1).join(' '),
Array(term.cols + 1).join(' '),
Array(term.cols + 1).join(' ')
]);
expect(termContent(term, true)).eql([
'',
'',
'',
'',
'',
'',
''
]);
// reset and add a wrapped line
termNew.buffer.y = 0;
termNew.buffer.x = 0;
inputHandlerNew.parse(Array(termNew.cols + 1).join('a')); // line 0
inputHandlerNew.parse(Array(termNew.cols + 10).join('a')); // line 1 and 2
for (let i = 3; i < termOld.rows; ++i) inputHandlerNew.parse(Array(termNew.cols + 1).join('a'));
term.buffer.y = 0;
term.buffer.x = 0;
inputHandler.parse(Array(term.cols + 1).join('a')); // line 0
inputHandler.parse(Array(term.cols + 10).join('a')); // line 1 and 2
for (let i = 3; i < term.rows; ++i) inputHandler.parse(Array(term.cols + 1).join('a'));
// params[1] left and above with wrap
// confirm precondition that line 2 is wrapped
expect(termNew.buffer.lines.get(2).isWrapped).true;
termNew.buffer.y = 2;
termNew.buffer.x = 40;
inputHandlerNew.eraseInDisplay([1]);
expect(termNew.buffer.lines.get(2).isWrapped).false;
expect(term.buffer.lines.get(2).isWrapped).true;
term.buffer.y = 2;
term.buffer.x = 40;
inputHandler.eraseInDisplay([1]);
expect(term.buffer.lines.get(2).isWrapped).false;
// reset and add a wrapped line
termNew.buffer.y = 0;
termNew.buffer.x = 0;
inputHandlerNew.parse(Array(termNew.cols + 1).join('a')); // line 0
inputHandlerNew.parse(Array(termNew.cols + 10).join('a')); // line 1 and 2
for (let i = 3; i < termOld.rows; ++i) inputHandlerNew.parse(Array(termNew.cols + 1).join('a'));
term.buffer.y = 0;
term.buffer.x = 0;
inputHandler.parse(Array(term.cols + 1).join('a')); // line 0
inputHandler.parse(Array(term.cols + 10).join('a')); // line 1 and 2
for (let i = 3; i < term.rows; ++i) inputHandler.parse(Array(term.cols + 1).join('a'));
// params[1] left and above with wrap
// confirm precondition that line 2 is wrapped
expect(termNew.buffer.lines.get(2).isWrapped).true;
termNew.buffer.y = 1;
termNew.buffer.x = 90; // Cursor is beyond last column
inputHandlerNew.eraseInDisplay([1]);
expect(termNew.buffer.lines.get(2).isWrapped).false;
expect(term.buffer.lines.get(2).isWrapped).true;
term.buffer.y = 1;
term.buffer.x = 90; // Cursor is beyond last column
inputHandler.eraseInDisplay([1]);
expect(term.buffer.lines.get(2).isWrapped).false;
});
});
it('convertEol setting', function(): void {
// not converting
let s = '';
const termNotConverting = new Terminal({cols: 15, rows: 10});
(termNotConverting as any)._inputHandler.parse('Hello\nWorld');
for (let i = 0; i < termNotConverting.cols; ++i) {
s += termNotConverting.buffer.lines.get(0).get(i)[CHAR_DATA_CHAR_INDEX];
}
expect(s).equals('Hello ');
s = '';
for (let i = 0; i < termNotConverting.cols; ++i) {
s += termNotConverting.buffer.lines.get(1).get(i)[CHAR_DATA_CHAR_INDEX];
}
expect(s).equals(' World ');
expect(termNotConverting.buffer.lines.get(0).translateToString(false)).equals('Hello ');
expect(termNotConverting.buffer.lines.get(1).translateToString(false)).equals(' World ');
expect(termNotConverting.buffer.lines.get(0).translateToString(true)).equals('Hello');
expect(termNotConverting.buffer.lines.get(1).translateToString(true)).equals(' World');
// converting
s = '';
const termConverting = new Terminal({cols: 15, rows: 10, convertEol: true});
(termConverting as any)._inputHandler.parse('Hello\nWorld');
for (let i = 0; i < termConverting.cols; ++i) {
s += termConverting.buffer.lines.get(0).get(i)[CHAR_DATA_CHAR_INDEX];
}
expect(s).equals('Hello ');
s = '';
for (let i = 0; i < termConverting.cols; ++i) {
s += termConverting.buffer.lines.get(1).get(i)[CHAR_DATA_CHAR_INDEX];
}
expect(s).equals('World ');
expect(termConverting.buffer.lines.get(0).translateToString(false)).equals('Hello ');
expect(termConverting.buffer.lines.get(1).translateToString(false)).equals('World ');
expect(termConverting.buffer.lines.get(0).translateToString(true)).equals('Hello');
expect(termConverting.buffer.lines.get(1).translateToString(true)).equals('World');
});
describe('print', () => {
it('should not cause an infinite loop (regression test)', () => {
const term = new Terminal();
const inputHandler = new InputHandler(term);
inputHandler.print(String.fromCharCode(0x200B), 0, 1);
const container = new Uint32Array(10);
container[0] = 0x200B;
inputHandler.print(container, 0, 1);
});
});
@@ -511,60 +347,198 @@ describe('InputHandler', () => {
let term: Terminal;
let handler: InputHandler;
function lineContent(line: IBufferLine): string {
let content = '';
for (let i = 0; i < line.length; ++i) content += line.get(i)[CHAR_DATA_CHAR_INDEX];
return content;
}
beforeEach(() => {
term = new Terminal();
handler = new InputHandler(term);
});
it('should handle DECSET/DECRST 47 (alt screen buffer)', () => {
handler.parse('\x1b[?47h\r\n\x1b[31mJUNK\x1b[?47lTEST');
expect(lineContent(term.buffer.lines.get(0))).to.equal(Array(term.cols + 1).join(' '));
expect(lineContent(term.buffer.lines.get(1))).to.equal(' TEST' + Array(term.cols - 7).join(' '));
expect(term.buffer.translateBufferLineToString(0, true)).to.equal('');
expect(term.buffer.translateBufferLineToString(1, true)).to.equal(' TEST');
// Text color of 'TEST' should be red
expect((term.buffer.lines.get(1).get(4)[CHAR_DATA_ATTR_INDEX] >> 9) & 0x1ff).to.equal(1);
expect((term.buffer.lines.get(1).loadCell(4, new CellData()).getFgColor())).to.equal(1);
});
it('should handle DECSET/DECRST 1047 (alt screen buffer)', () => {
handler.parse('\x1b[?1047h\r\n\x1b[31mJUNK\x1b[?1047lTEST');
expect(lineContent(term.buffer.lines.get(0))).to.equal(Array(term.cols + 1).join(' '));
expect(lineContent(term.buffer.lines.get(1))).to.equal(' TEST' + Array(term.cols - 7).join(' '));
expect(term.buffer.translateBufferLineToString(0, true)).to.equal('');
expect(term.buffer.translateBufferLineToString(1, true)).to.equal(' TEST');
// Text color of 'TEST' should be red
expect((term.buffer.lines.get(1).get(4)[CHAR_DATA_ATTR_INDEX] >> 9) & 0x1ff).to.equal(1);
expect((term.buffer.lines.get(1).loadCell(4, new CellData()).getFgColor())).to.equal(1);
});
it('should handle DECSET/DECRST 1048 (alt screen cursor)', () => {
handler.parse('\x1b[?1048h\r\n\x1b[31mJUNK\x1b[?1048lTEST');
expect(lineContent(term.buffer.lines.get(0))).to.equal('TEST' + Array(term.cols - 3).join(' '));
expect(lineContent(term.buffer.lines.get(1))).to.equal('JUNK' + Array(term.cols - 3).join(' '));
expect(term.buffer.translateBufferLineToString(0, true)).to.equal('TEST');
expect(term.buffer.translateBufferLineToString(1, true)).to.equal('JUNK');
// Text color of 'TEST' should be default
expect(term.buffer.lines.get(0).get(0)[CHAR_DATA_ATTR_INDEX]).to.equal(DEFAULT_ATTR);
expect(term.buffer.lines.get(0).loadCell(0, new CellData()).fg).to.equal(DEFAULT_ATTR_DATA.fg);
// Text color of 'JUNK' should be red
expect((term.buffer.lines.get(1).get(0)[CHAR_DATA_ATTR_INDEX] >> 9) & 0x1ff).to.equal(1);
expect((term.buffer.lines.get(1).loadCell(0, new CellData()).getFgColor())).to.equal(1);
});
it('should handle DECSET/DECRST 1049 (alt screen buffer+cursor)', () => {
handler.parse('\x1b[?1049h\r\n\x1b[31mJUNK\x1b[?1049lTEST');
expect(lineContent(term.buffer.lines.get(0))).to.equal('TEST' + Array(term.cols - 3).join(' '));
expect(lineContent(term.buffer.lines.get(1))).to.equal(Array(term.cols + 1).join(' '));
expect(term.buffer.translateBufferLineToString(0, true)).to.equal('TEST');
expect(term.buffer.translateBufferLineToString(1, true)).to.equal('');
// Text color of 'TEST' should be default
expect(term.buffer.lines.get(0).get(0)[CHAR_DATA_ATTR_INDEX]).to.equal(DEFAULT_ATTR);
expect(term.buffer.lines.get(0).loadCell(0, new CellData()).fg).to.equal(DEFAULT_ATTR_DATA.fg);
});
it('should handle DECSET/DECRST 1049 - maintains saved cursor for alt buffer', () => {
handler.parse('\x1b[?1049h\r\n\x1b[31m\x1b[s\x1b[?1049lTEST');
expect(lineContent(term.buffer.lines.get(0))).to.equal('TEST' + Array(term.cols - 3).join(' '));
expect(term.buffer.translateBufferLineToString(0, true)).to.equal('TEST');
// Text color of 'TEST' should be default
expect(term.buffer.lines.get(0).get(0)[CHAR_DATA_ATTR_INDEX]).to.equal(DEFAULT_ATTR);
expect(term.buffer.lines.get(0).loadCell(0, new CellData()).fg).to.equal(DEFAULT_ATTR_DATA.fg);
handler.parse('\x1b[?1049h\x1b[uTEST');
expect(lineContent(term.buffer.lines.get(1))).to.equal('TEST' + Array(term.cols - 3).join(' '));
expect(term.buffer.translateBufferLineToString(1, true)).to.equal('TEST');
// Text color of 'TEST' should be red
expect((term.buffer.lines.get(1).get(0)[CHAR_DATA_ATTR_INDEX] >> 9) & 0x1ff).to.equal(1);
expect((term.buffer.lines.get(1).loadCell(0, new CellData()).getFgColor())).to.equal(1);
});
it('should handle DECSET/DECRST 1049 - clears alt buffer with erase attributes', () => {
handler.parse('\x1b[42m\x1b[?1049h');
// Buffer should be filled with green background
expect(term.buffer.lines.get(20).get(10)[CHAR_DATA_ATTR_INDEX] & 0x1ff).to.equal(2);
expect(term.buffer.lines.get(20).loadCell(10, new CellData()).getBgColor()).to.equal(2);
});
});
describe('text attributes', () => {
let term: TestTerminal;
beforeEach(() => {
term = new TestTerminal();
});
it('bold', () => {
term.writeSync('\x1b[1m');
assert.equal(!!term.curAttrData.isBold(), true);
term.writeSync('\x1b[22m');
assert.equal(!!term.curAttrData.isBold(), false);
});
it('dim', () => {
term.writeSync('\x1b[2m');
assert.equal(!!term.curAttrData.isDim(), true);
term.writeSync('\x1b[22m');
assert.equal(!!term.curAttrData.isDim(), false);
});
it('italic', () => {
term.writeSync('\x1b[3m');
assert.equal(!!term.curAttrData.isItalic(), true);
term.writeSync('\x1b[23m');
assert.equal(!!term.curAttrData.isItalic(), false);
});
it('underline', () => {
term.writeSync('\x1b[4m');
assert.equal(!!term.curAttrData.isUnderline(), true);
term.writeSync('\x1b[24m');
assert.equal(!!term.curAttrData.isUnderline(), false);
});
it('blink', () => {
term.writeSync('\x1b[5m');
assert.equal(!!term.curAttrData.isBlink(), true);
term.writeSync('\x1b[25m');
assert.equal(!!term.curAttrData.isBlink(), false);
});
it('inverse', () => {
term.writeSync('\x1b[7m');
assert.equal(!!term.curAttrData.isInverse(), true);
term.writeSync('\x1b[27m');
assert.equal(!!term.curAttrData.isInverse(), false);
});
it('invisible', () => {
term.writeSync('\x1b[8m');
assert.equal(!!term.curAttrData.isInvisible(), true);
term.writeSync('\x1b[28m');
assert.equal(!!term.curAttrData.isInvisible(), false);
});
it('colormode palette 16', () => {
assert.equal(term.curAttrData.getFgColorMode(), 0); // DEFAULT
assert.equal(term.curAttrData.getBgColorMode(), 0); // DEFAULT
// lower 8 colors
for (let i = 0; i < 8; ++i) {
term.writeSync(`\x1b[${i + 30};${i + 40}m`);
assert.equal(term.curAttrData.getFgColorMode(), Attributes.CM_P16);
assert.equal(term.curAttrData.getFgColor(), i);
assert.equal(term.curAttrData.getBgColorMode(), Attributes.CM_P16);
assert.equal(term.curAttrData.getBgColor(), i);
}
// reset to DEFAULT
term.writeSync(`\x1b[39;49m`);
assert.equal(term.curAttrData.getFgColorMode(), 0);
assert.equal(term.curAttrData.getBgColorMode(), 0);
});
it('colormode palette 256', () => {
assert.equal(term.curAttrData.getFgColorMode(), 0); // DEFAULT
assert.equal(term.curAttrData.getBgColorMode(), 0); // DEFAULT
// lower 8 colors
for (let i = 0; i < 256; ++i) {
term.writeSync(`\x1b[38;5;${i};48;5;${i}m`);
assert.equal(term.curAttrData.getFgColorMode(), Attributes.CM_P256);
assert.equal(term.curAttrData.getFgColor(), i);
assert.equal(term.curAttrData.getBgColorMode(), Attributes.CM_P256);
assert.equal(term.curAttrData.getBgColor(), i);
}
// reset to DEFAULT
term.writeSync(`\x1b[39;49m`);
assert.equal(term.curAttrData.getFgColorMode(), 0);
assert.equal(term.curAttrData.getFgColor(), -1);
assert.equal(term.curAttrData.getBgColorMode(), 0);
assert.equal(term.curAttrData.getBgColor(), -1);
});
it('colormode RGB', () => {
assert.equal(term.curAttrData.getFgColorMode(), 0); // DEFAULT
assert.equal(term.curAttrData.getBgColorMode(), 0); // DEFAULT
term.writeSync(`\x1b[38;2;1;2;3;48;2;4;5;6m`);
assert.equal(term.curAttrData.getFgColorMode(), Attributes.CM_RGB);
assert.equal(term.curAttrData.getFgColor(), 1 << 16 | 2 << 8 | 3);
assert.deepEqual(AttributeData.toColorRGB(term.curAttrData.getFgColor()), [1, 2, 3]);
assert.equal(term.curAttrData.getBgColorMode(), Attributes.CM_RGB);
assert.deepEqual(AttributeData.toColorRGB(term.curAttrData.getBgColor()), [4, 5, 6]);
// reset to DEFAULT
term.writeSync(`\x1b[39;49m`);
assert.equal(term.curAttrData.getFgColorMode(), 0);
assert.equal(term.curAttrData.getFgColor(), -1);
assert.equal(term.curAttrData.getBgColorMode(), 0);
assert.equal(term.curAttrData.getBgColor(), -1);
});
it('colormode transition RGB to 256', () => {
// enter RGB for FG and BG
term.writeSync(`\x1b[38;2;1;2;3;48;2;4;5;6m`);
// enter 256 for FG and BG
term.writeSync(`\x1b[38;5;255;48;5;255m`);
assert.equal(term.curAttrData.getFgColorMode(), Attributes.CM_P256);
assert.equal(term.curAttrData.getFgColor(), 255);
assert.equal(term.curAttrData.getBgColorMode(), Attributes.CM_P256);
assert.equal(term.curAttrData.getBgColor(), 255);
});
it('colormode transition RGB to 16', () => {
// enter RGB for FG and BG
term.writeSync(`\x1b[38;2;1;2;3;48;2;4;5;6m`);
// enter 16 for FG and BG
term.writeSync(`\x1b[37;47m`);
assert.equal(term.curAttrData.getFgColorMode(), Attributes.CM_P16);
assert.equal(term.curAttrData.getFgColor(), 7);
assert.equal(term.curAttrData.getBgColorMode(), Attributes.CM_P16);
assert.equal(term.curAttrData.getBgColor(), 7);
});
it('colormode transition 16 to 256', () => {
// enter 16 for FG and BG
term.writeSync(`\x1b[37;47m`);
// enter 256 for FG and BG
term.writeSync(`\x1b[38;5;255;48;5;255m`);
assert.equal(term.curAttrData.getFgColorMode(), Attributes.CM_P256);
assert.equal(term.curAttrData.getFgColor(), 255);
assert.equal(term.curAttrData.getBgColorMode(), Attributes.CM_P256);
assert.equal(term.curAttrData.getBgColor(), 255);
});
it('colormode transition 256 to 16', () => {
// enter 256 for FG and BG
term.writeSync(`\x1b[38;5;255;48;5;255m`);
// enter 16 for FG and BG
term.writeSync(`\x1b[37;47m`);
assert.equal(term.curAttrData.getFgColorMode(), Attributes.CM_P16);
assert.equal(term.curAttrData.getFgColor(), 7);
assert.equal(term.curAttrData.getBgColorMode(), Attributes.CM_P16);
assert.equal(term.curAttrData.getBgColor(), 7);
});
it('should zero missing RGB values', () => {
term.writeSync(`\x1b[38;2;1;2;3m`);
term.writeSync(`\x1b[38;2;5m`);
assert.deepEqual(AttributeData.toColorRGB(term.curAttrData.getFgColor()), [5, 0, 0]);
});
});
});
+205 -189
View File
@@ -4,15 +4,17 @@
* @license MIT
*/
import { IInputHandler, IDcsHandler, IEscapeSequenceParser, IBuffer, IInputHandlingTerminal } from './Types';
import { IInputHandler, IDcsHandler, IEscapeSequenceParser, IInputHandlingTerminal } from './Types';
import { C0, C1 } from './common/data/EscapeSequences';
import { CHARSETS, DEFAULT_CHARSET } from './core/data/Charsets';
import { CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CODE_INDEX, DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE } from './Buffer';
import { FLAGS } from './renderer/Types';
import { wcwidth } from './CharWidth';
import { EscapeSequenceParser } from './EscapeSequenceParser';
import { ICharset } from './core/Types';
import { IDisposable } from 'xterm';
import { Disposable } from './common/Lifecycle';
import { concat } from './common/TypedArrayUtils';
import { StringToUtf32, stringFromCodePoint, utf32ToString, Utf8ToUtf32 } from './core/input/TextDecoder';
import { CellData, Attributes, FgFlags, BgFlags, AttributeData, NULL_CELL_WIDTH, NULL_CELL_CODE, DEFAULT_ATTR_DATA } from './core/buffer/BufferLine';
import { EventEmitter2, IEvent } from './common/EventEmitter2';
/**
* Map collect to glevel. Used in `selectCharset`.
@@ -24,26 +26,6 @@ const GLEVEL: {[key: string]: number} = {'(': 0, ')': 1, '*': 2, '+': 3, '-': 1,
* DCS subparser implementations
*/
/**
* DCS + q Pt ST (xterm)
* Request Terminfo String
* not supported
*/
class RequestTerminfo implements IDcsHandler {
private _data: string;
constructor(private _terminal: any) { }
hook(collect: string, params: number[], flag: number): void {
this._data = '';
}
put(data: string, start: number, end: number): void {
this._data += data.substring(start, end);
}
unhook(): void {
// invalid: DCS 0 + r Pt ST
this._terminal.handler(`${C0.ESC}P0+r${this._data}${C0.ESC}\\`);
}
}
/**
* DCS $ q Pt ST
* DECRQSS (https://vt100.net/docs/vt510-rm/DECRQSS.html)
@@ -51,21 +33,22 @@ class RequestTerminfo implements IDcsHandler {
* Response: DECRPSS (https://vt100.net/docs/vt510-rm/DECRPSS.html)
*/
class DECRQSS implements IDcsHandler {
private _data: string;
private _data: Uint32Array = new Uint32Array(0);
constructor(private _terminal: any) { }
hook(collect: string, params: number[], flag: number): void {
// reset data
this._data = '';
this._data = new Uint32Array(0);
}
put(data: string, start: number, end: number): void {
this._data += data.substring(start, end);
put(data: Uint32Array, start: number, end: number): void {
this._data = concat(this._data, data.subarray(start, end));
}
unhook(): void {
switch (this._data) {
const data = utf32ToString(this._data);
this._data = new Uint32Array(0);
switch (data) {
// valid: DCS 1 $ r Pt ST (xterm)
case '"q': // DECSCA
return this._terminal.handler(`${C0.ESC}P1$r0"q${C0.ESC}\\`);
@@ -85,8 +68,8 @@ class DECRQSS implements IDcsHandler {
return this._terminal.handler(`${C0.ESC}P1$r${style} q${C0.ESC}\\`);
default:
// invalid: DCS 0 $ r Pt ST (xterm)
this._terminal.error('Unknown DCS $q %s', this._data);
this._terminal.handler(`${C0.ESC}P0$r${this._data}${C0.ESC}\\`);
this._terminal.error('Unknown DCS $q %s', data);
this._terminal.handler(`${C0.ESC}P0$r${C0.ESC}\\`);
}
}
}
@@ -97,11 +80,17 @@ class DECRQSS implements IDcsHandler {
* not supported
*/
/**
* DCS + p Pt ST (xterm)
* Set Terminfo Data
* not supported
*/
/**
* DCS + q Pt ST (xterm)
* Request Terminfo String
* not implemented
*/
/**
* DCS + p Pt ST (xterm)
* Set Terminfo Data
* not supported
*/
@@ -113,7 +102,19 @@ class DECRQSS implements IDcsHandler {
* each function's header comment.
*/
export class InputHandler extends Disposable implements IInputHandler {
private _surrogateFirst: string;
private _parseBuffer: Uint32Array = new Uint32Array(4096);
private _stringDecoder: StringToUtf32 = new StringToUtf32();
private _utf8Decoder: Utf8ToUtf32 = new Utf8ToUtf32();
private _workCell: CellData = new CellData();
private _onCursorMove = new EventEmitter2<void>();
public get onCursorMove(): IEvent<void> { return this._onCursorMove.event; }
private _onData = new EventEmitter2<string>();
public get onData(): IEvent<string> { return this._onData.event; }
private _onLineFeed = new EventEmitter2<void>();
public get onLineFeed(): IEvent<void> { return this._onLineFeed.event; }
private _onScroll = new EventEmitter2<number>();
public get onScroll(): IEvent<number> { return this._onScroll.event; }
constructor(
protected _terminal: IInputHandlingTerminal,
@@ -123,8 +124,6 @@ export class InputHandler extends Disposable implements IInputHandler {
this.register(this._parser);
this._surrogateFirst = '';
/**
* custom fallback handlers
*/
@@ -287,7 +286,6 @@ export class InputHandler extends Disposable implements IInputHandler {
* DCS handler
*/
this._parser.setDcsHandler('$q', new DECRQSS(this._terminal));
this._parser.setDcsHandler('+q', new RequestTerminfo(this._terminal));
}
public dispose(): void {
@@ -310,13 +308,36 @@ export class InputHandler extends Disposable implements IInputHandler {
this._terminal.log('data: ' + data);
}
// apply leftover surrogate high from last write
if (this._surrogateFirst) {
data = this._surrogateFirst + data;
this._surrogateFirst = '';
if (this._parseBuffer.length < data.length) {
this._parseBuffer = new Uint32Array(data.length);
}
this._parser.parse(this._parseBuffer, this._stringDecoder.decode(data, this._parseBuffer));
buffer = this._terminal.buffer;
if (buffer.x !== cursorStartX || buffer.y !== cursorStartY) {
this._onCursorMove.fire();
}
}
public parseUtf8(data: Uint8Array): void {
// Ensure the terminal is not disposed
if (!this._terminal) {
return;
}
this._parser.parse(data);
let buffer = this._terminal.buffer;
const cursorStartX = buffer.x;
const cursorStartY = buffer.y;
// TODO: Consolidate debug/logging #1560
if ((<any>this._terminal).debug) {
this._terminal.log('data: ' + data);
}
if (this._parseBuffer.length < data.length) {
this._parseBuffer = new Uint32Array(data.length);
}
this._parser.parse(this._parseBuffer, this._utf8Decoder.decode(data, this._parseBuffer));
buffer = this._terminal.buffer;
if (buffer.x !== cursorStartX || buffer.y !== cursorStartY) {
@@ -324,59 +345,38 @@ export class InputHandler extends Disposable implements IInputHandler {
}
}
public print(data: string, start: number, end: number): void {
let char: string;
public print(data: Uint32Array, start: number, end: number): void {
let code: number;
let chWidth: number;
const buffer: IBuffer = this._terminal.buffer;
const charset: ICharset = this._terminal.charset;
const screenReaderMode: boolean = this._terminal.options.screenReaderMode;
const cols: number = this._terminal.cols;
const wraparoundMode: boolean = this._terminal.wraparoundMode;
const insertMode: boolean = this._terminal.insertMode;
const curAttr: number = this._terminal.curAttr;
const buffer = this._terminal.buffer;
const charset = this._terminal.charset;
const screenReaderMode = this._terminal.options.screenReaderMode;
const cols = this._terminal.cols;
const wraparoundMode = this._terminal.wraparoundMode;
const insertMode = this._terminal.insertMode;
const curAttr = this._terminal.curAttrData;
let bufferRow = buffer.lines.get(buffer.y + buffer.ybase);
this._terminal.updateRange(buffer.y);
for (let stringPosition = start; stringPosition < end; ++stringPosition) {
char = data.charAt(stringPosition);
code = data.charCodeAt(stringPosition);
// surrogate pair handling
if (0xD800 <= code && code <= 0xDBFF) {
if (++stringPosition >= end) {
// end of input:
// handle pairs as true UTF-16 and wait for the second part
// since we expect the input comming from a stream there is
// a small chance that the surrogate pair got split
// therefore we dont process the first char here, instead
// it gets added as first char to the next processed chunk
this._surrogateFirst = char;
continue;
}
const second = data.charCodeAt(stringPosition);
// if the second part is in surrogate pair range create the high codepoint
// otherwise fall back to UCS-2 behavior (handle codepoints independently)
if (0xDC00 <= second && second <= 0xDFFF) {
code = (code - 0xD800) * 0x400 + second - 0xDC00 + 0x10000;
char += data.charAt(stringPosition);
} else {
stringPosition--;
}
}
for (let pos = start; pos < end; ++pos) {
code = data[pos];
// calculate print space
// expensive call, therefore we save width in line buffer
chWidth = wcwidth(code);
// get charset replacement character
if (charset) {
char = charset[char] || char;
code = char.charCodeAt(0);
// charset is only defined for ASCII, therefore we only
// search for an replacement char if code < 127
if (code < 127 && charset) {
const ch = charset[String.fromCharCode(code)];
if (ch) {
code = ch.charCodeAt(0);
}
}
if (screenReaderMode) {
this._terminal.emit('a11y.char', char);
this._terminal.emit('a11y.char', stringFromCodePoint(code));
}
// insert combining char at last cursor position
@@ -385,23 +385,13 @@ export class InputHandler extends Disposable implements IInputHandler {
// since they always follow a cell consuming char
// therefore we can test for buffer.x to avoid overflow left
if (!chWidth && buffer.x) {
const chMinusOne = bufferRow.get(buffer.x - 1);
if (chMinusOne) {
if (!chMinusOne[CHAR_DATA_WIDTH_INDEX]) {
// found empty cell after fullwidth, need to go 2 cells back
// it is save to step 2 cells back here
// since an empty cell is only set by fullwidth chars
const chMinusTwo = bufferRow.get(buffer.x - 2);
if (chMinusTwo) {
chMinusTwo[CHAR_DATA_CHAR_INDEX] += char;
chMinusTwo[CHAR_DATA_CODE_INDEX] = code;
bufferRow.set(buffer.x - 2, chMinusTwo); // must be set explicitly now
}
} else {
chMinusOne[CHAR_DATA_CHAR_INDEX] += char;
chMinusOne[CHAR_DATA_CODE_INDEX] = code;
bufferRow.set(buffer.x - 1, chMinusOne); // must be set explicitly now
}
if (!bufferRow.getWidth(buffer.x - 1)) {
// found empty cell after fullwidth, need to go 2 cells back
// it is save to step 2 cells back here
// since an empty cell is only set by fullwidth chars
bufferRow.addCodepointToCell(buffer.x - 2, code);
} else {
bufferRow.addCodepointToCell(buffer.x - 1, code);
}
continue;
}
@@ -440,31 +430,45 @@ export class InputHandler extends Disposable implements IInputHandler {
// insert mode: move characters to right
if (insertMode) {
// right shift cells according to the width
bufferRow.insertCells(buffer.x, chWidth, [curAttr, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]);
bufferRow.insertCells(buffer.x, chWidth, buffer.getNullCell(curAttr));
// test last cell - since the last cell has only room for
// a halfwidth char any fullwidth shifted there is lost
// and will be set to eraseChar
const lastCell = bufferRow.get(cols - 1);
if (lastCell[CHAR_DATA_WIDTH_INDEX] === 2) {
bufferRow.set(cols - 1, [curAttr, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]);
// and will be set to empty cell
if (bufferRow.getWidth(cols - 1) === 2) {
bufferRow.setCellFromCodePoint(cols - 1, NULL_CELL_CODE, NULL_CELL_WIDTH, curAttr.fg, curAttr.bg);
}
}
// write current char to buffer and advance cursor
bufferRow.set(buffer.x++, [curAttr, char, chWidth, code]);
bufferRow.setCellFromCodePoint(buffer.x++, code, chWidth, curAttr.fg, curAttr.bg);
// fullwidth char - also set next cell to placeholder stub and advance cursor
// for graphemes bigger than fullwidth we can simply loop to zero
// we already made sure above, that buffer.x + chWidth will not overflow right
if (chWidth > 0) {
while (--chWidth) {
bufferRow.set(buffer.x++, [curAttr, '', 0, undefined]);
// other than a regular empty cell a cell following a wide char has no width
bufferRow.setCellFromCodePoint(buffer.x++, 0, 0, curAttr.fg, curAttr.bg);
}
}
}
this._terminal.updateRange(buffer.y);
}
/**
* Forward addCsiHandler from parser.
*/
public addCsiHandler(flag: string, callback: (params: number[], collect: string) => boolean): IDisposable {
return this._parser.addCsiHandler(flag, callback);
}
/**
* Forward addOscHandler from parser.
*/
public addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable {
return this._parser.addOscHandler(ident, callback);
}
/**
* BEL
* Bell (Ctrl-G).
@@ -493,12 +497,8 @@ export class InputHandler extends Disposable implements IInputHandler {
if (buffer.x >= this._terminal.cols) {
buffer.x--;
}
/**
* This event is emitted whenever the terminal outputs a LF or NL.
*
* @event linefeed
*/
this._terminal.emit('linefeed');
this._onLineFeed.fire();
}
/**
@@ -557,7 +557,7 @@ export class InputHandler extends Disposable implements IInputHandler {
this._terminal.buffer.lines.get(this._terminal.buffer.y + this._terminal.buffer.ybase).insertCells(
this._terminal.buffer.x,
params[0] || 1,
[this._terminal.eraseAttr(), NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]
this._terminal.buffer.getNullCell(this._terminal.eraseAttrData())
);
this._terminal.updateRange(this._terminal.buffer.y);
}
@@ -731,7 +731,7 @@ export class InputHandler extends Disposable implements IInputHandler {
line.replaceCells(
start,
end,
[this._terminal.eraseAttr(), NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]
this._terminal.buffer.getNullCell(this._terminal.eraseAttrData())
);
if (clearWrap) {
line.isWrapped = false;
@@ -801,7 +801,7 @@ export class InputHandler extends Disposable implements IInputHandler {
this._terminal.buffer.ybase = Math.max(this._terminal.buffer.ybase - scrollBackSize, 0);
this._terminal.buffer.ydisp = Math.max(this._terminal.buffer.ydisp - scrollBackSize, 0);
// Force a scroll event to refresh viewport
this._terminal.emit('scroll', 0);
this._onScroll.fire(0);
}
break;
}
@@ -854,7 +854,7 @@ export class InputHandler extends Disposable implements IInputHandler {
// test: echo -e '\e[44m\e[1L\e[0m'
// blankLine(true) - xterm/linux behavior
buffer.lines.splice(scrollBottomAbsolute - 1, 1);
buffer.lines.splice(row, 0, buffer.getBlankLine(this._terminal.eraseAttr()));
buffer.lines.splice(row, 0, buffer.getBlankLine(this._terminal.eraseAttrData()));
}
// this.maxRange();
@@ -884,7 +884,7 @@ export class InputHandler extends Disposable implements IInputHandler {
// test: echo -e '\e[44m\e[1M\e[0m'
// blankLine(true) - xterm/linux behavior
buffer.lines.splice(row, 1);
buffer.lines.splice(j, 0, buffer.getBlankLine(this._terminal.eraseAttr()));
buffer.lines.splice(j, 0, buffer.getBlankLine(this._terminal.eraseAttrData()));
}
// this.maxRange();
@@ -900,7 +900,7 @@ export class InputHandler extends Disposable implements IInputHandler {
this._terminal.buffer.lines.get(this._terminal.buffer.y + this._terminal.buffer.ybase).deleteCells(
this._terminal.buffer.x,
params[0] || 1,
[this._terminal.eraseAttr(), NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]
this._terminal.buffer.getNullCell(this._terminal.eraseAttrData())
);
this._terminal.updateRange(this._terminal.buffer.y);
}
@@ -916,7 +916,7 @@ export class InputHandler extends Disposable implements IInputHandler {
while (param--) {
buffer.lines.splice(buffer.ybase + buffer.scrollTop, 1);
buffer.lines.splice(buffer.ybase + buffer.scrollBottom, 0, buffer.getBlankLine(DEFAULT_ATTR));
buffer.lines.splice(buffer.ybase + buffer.scrollBottom, 0, buffer.getBlankLine(DEFAULT_ATTR_DATA));
}
// this.maxRange();
this._terminal.updateRange(buffer.scrollTop);
@@ -935,7 +935,7 @@ export class InputHandler extends Disposable implements IInputHandler {
while (param--) {
buffer.lines.splice(buffer.ybase + buffer.scrollBottom, 1);
buffer.lines.splice(buffer.ybase + buffer.scrollBottom, 0, buffer.getBlankLine(DEFAULT_ATTR));
buffer.lines.splice(buffer.ybase + buffer.scrollTop, 0, buffer.getBlankLine(DEFAULT_ATTR_DATA));
}
// this.maxRange();
this._terminal.updateRange(buffer.scrollTop);
@@ -951,7 +951,7 @@ export class InputHandler extends Disposable implements IInputHandler {
this._terminal.buffer.lines.get(this._terminal.buffer.y + this._terminal.buffer.ybase).replaceCells(
this._terminal.buffer.x,
this._terminal.buffer.x + (params[0] || 1),
[this._terminal.eraseAttr(), NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]
this._terminal.buffer.getNullCell(this._terminal.eraseAttrData())
);
}
@@ -1007,9 +1007,10 @@ export class InputHandler extends Disposable implements IInputHandler {
// make buffer local for faster access
const buffer = this._terminal.buffer;
const line = buffer.lines.get(buffer.ybase + buffer.y);
line.loadCell(buffer.x - 1, this._workCell);
line.replaceCells(buffer.x,
buffer.x + (params[0] || 1),
line.get(buffer.x - 1) || [DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]
(this._workCell.content !== undefined) ? this._workCell : buffer.getNullCell(DEFAULT_ATTR_DATA)
);
// FIXME: no updateRange here?
}
@@ -1304,8 +1305,12 @@ export class InputHandler extends Disposable implements IInputHandler {
this._terminal.vt200Mouse = params[0] === 1000;
this._terminal.normalMouse = params[0] > 1000;
this._terminal.mouseEvents = true;
this._terminal.element.classList.add('enable-mouse-events');
this._terminal.selectionManager.disable();
if (this._terminal.element) {
this._terminal.element.classList.add('enable-mouse-events');
}
if (this._terminal.selectionManager) {
this._terminal.selectionManager.disable();
}
this._terminal.log('Binding to mouse events.');
break;
case 1004: // send focusin/focusout events
@@ -1343,7 +1348,7 @@ export class InputHandler extends Disposable implements IInputHandler {
// FALL-THROUGH
case 47: // alt screen buffer
case 1047: // alt screen buffer
this._terminal.buffers.activateAltBuffer(this._terminal.eraseAttr());
this._terminal.buffers.activateAltBuffer(this._terminal.eraseAttrData());
this._terminal.refresh(0, this._terminal.rows - 1);
if (this._terminal.viewport) {
this._terminal.viewport.syncScrollArea();
@@ -1492,8 +1497,12 @@ export class InputHandler extends Disposable implements IInputHandler {
this._terminal.vt200Mouse = false;
this._terminal.normalMouse = false;
this._terminal.mouseEvents = false;
this._terminal.element.classList.remove('enable-mouse-events');
this._terminal.selectionManager.enable();
if (this._terminal.element) {
this._terminal.element.classList.remove('enable-mouse-events');
}
if (this._terminal.selectionManager) {
this._terminal.selectionManager.enable();
}
break;
case 1004: // send focusin/focusout events
this._terminal.sendFocus = false;
@@ -1603,127 +1612,124 @@ export class InputHandler extends Disposable implements IInputHandler {
public charAttributes(params: number[]): void {
// Optimize a single SGR0.
if (params.length === 1 && params[0] === 0) {
this._terminal.curAttr = DEFAULT_ATTR;
this._terminal.curAttrData.fg = DEFAULT_ATTR_DATA.fg;
this._terminal.curAttrData.bg = DEFAULT_ATTR_DATA.bg;
return;
}
const l = params.length;
let flags = this._terminal.curAttr >> 18;
let fg = (this._terminal.curAttr >> 9) & 0x1ff;
let bg = this._terminal.curAttr & 0x1ff;
let p;
const attr = this._terminal.curAttrData;
for (let i = 0; i < l; i++) {
p = params[i];
if (p >= 30 && p <= 37) {
// fg color 8
fg = p - 30;
attr.fg &= ~(Attributes.CM_MASK | Attributes.PCOLOR_MASK);
attr.fg |= Attributes.CM_P16 | (p - 30);
} else if (p >= 40 && p <= 47) {
// bg color 8
bg = p - 40;
attr.bg &= ~(Attributes.CM_MASK | Attributes.PCOLOR_MASK);
attr.bg |= Attributes.CM_P16 | (p - 40);
} else if (p >= 90 && p <= 97) {
// fg color 16
p += 8;
fg = p - 90;
attr.fg &= ~(Attributes.CM_MASK | Attributes.PCOLOR_MASK);
attr.fg |= Attributes.CM_P16 | (p - 90) | 8;
} else if (p >= 100 && p <= 107) {
// bg color 16
p += 8;
bg = p - 100;
attr.bg &= ~(Attributes.CM_MASK | Attributes.PCOLOR_MASK);
attr.bg |= Attributes.CM_P16 | (p - 100) | 8;
} else if (p === 0) {
// default
flags = DEFAULT_ATTR >> 18;
fg = (DEFAULT_ATTR >> 9) & 0x1ff;
bg = DEFAULT_ATTR & 0x1ff;
// flags = 0;
// fg = 0x1ff;
// bg = 0x1ff;
attr.fg = DEFAULT_ATTR_DATA.fg;
attr.bg = DEFAULT_ATTR_DATA.bg;
} else if (p === 1) {
// bold text
flags |= FLAGS.BOLD;
attr.fg |= FgFlags.BOLD;
} else if (p === 3) {
// italic text
flags |= FLAGS.ITALIC;
attr.bg |= BgFlags.ITALIC;
} else if (p === 4) {
// underlined text
flags |= FLAGS.UNDERLINE;
attr.fg |= FgFlags.UNDERLINE;
} else if (p === 5) {
// blink
flags |= FLAGS.BLINK;
attr.fg |= FgFlags.BLINK;
} else if (p === 7) {
// inverse and positive
// test with: echo -e '\e[31m\e[42mhello\e[7mworld\e[27mhi\e[m'
flags |= FLAGS.INVERSE;
attr.fg |= FgFlags.INVERSE;
} else if (p === 8) {
// invisible
flags |= FLAGS.INVISIBLE;
attr.fg |= FgFlags.INVISIBLE;
} else if (p === 2) {
// dimmed text
flags |= FLAGS.DIM;
attr.bg |= BgFlags.DIM;
} else if (p === 22) {
// not bold nor faint
flags &= ~FLAGS.BOLD;
flags &= ~FLAGS.DIM;
attr.fg &= ~FgFlags.BOLD;
attr.bg &= ~BgFlags.DIM;
} else if (p === 23) {
// not italic
flags &= ~FLAGS.ITALIC;
attr.bg &= ~BgFlags.ITALIC;
} else if (p === 24) {
// not underlined
flags &= ~FLAGS.UNDERLINE;
attr.fg &= ~FgFlags.UNDERLINE;
} else if (p === 25) {
// not blink
flags &= ~FLAGS.BLINK;
attr.fg &= ~FgFlags.BLINK;
} else if (p === 27) {
// not inverse
flags &= ~FLAGS.INVERSE;
attr.fg &= ~FgFlags.INVERSE;
} else if (p === 28) {
// not invisible
flags &= ~FLAGS.INVISIBLE;
attr.fg &= ~FgFlags.INVISIBLE;
} else if (p === 39) {
// reset fg
fg = (DEFAULT_ATTR >> 9) & 0x1ff;
attr.fg &= ~(Attributes.CM_MASK | Attributes.RGB_MASK);
attr.fg |= DEFAULT_ATTR_DATA.fg & (Attributes.PCOLOR_MASK | Attributes.RGB_MASK);
} else if (p === 49) {
// reset bg
bg = DEFAULT_ATTR & 0x1ff;
attr.bg &= ~(Attributes.CM_MASK | Attributes.RGB_MASK);
attr.bg |= DEFAULT_ATTR_DATA.bg & (Attributes.PCOLOR_MASK | Attributes.RGB_MASK);
} else if (p === 38) {
// fg color 256
// fg color 256 and RGB
if (params[i + 1] === 2) {
i += 2;
fg = this._terminal.matchColor(
params[i] & 0xff,
params[i + 1] & 0xff,
params[i + 2] & 0xff);
if (fg === -1) fg = 0x1ff;
attr.fg |= Attributes.CM_RGB;
attr.fg &= ~Attributes.RGB_MASK;
attr.fg |= AttributeData.fromColorRGB([params[i], params[i + 1], params[i + 2]]);
i += 2;
} else if (params[i + 1] === 5) {
i += 2;
p = params[i] & 0xff;
fg = p;
attr.fg &= ~(Attributes.CM_MASK | Attributes.PCOLOR_MASK);
attr.fg |= Attributes.CM_P256 | p;
}
} else if (p === 48) {
// bg color 256
// bg color 256 and RGB
if (params[i + 1] === 2) {
i += 2;
bg = this._terminal.matchColor(
params[i] & 0xff,
params[i + 1] & 0xff,
params[i + 2] & 0xff);
if (bg === -1) bg = 0x1ff;
attr.bg |= Attributes.CM_RGB;
attr.bg &= ~Attributes.RGB_MASK;
attr.bg |= AttributeData.fromColorRGB([params[i], params[i + 1], params[i + 2]]);
i += 2;
} else if (params[i + 1] === 5) {
i += 2;
p = params[i] & 0xff;
bg = p;
attr.bg &= ~(Attributes.CM_MASK | Attributes.PCOLOR_MASK);
attr.bg |= Attributes.CM_P256 | p;
}
} else if (p === 100) {
// reset fg/bg
fg = (DEFAULT_ATTR >> 9) & 0x1ff;
bg = DEFAULT_ATTR & 0x1ff;
attr.fg &= ~(Attributes.CM_MASK | Attributes.RGB_MASK);
attr.fg |= DEFAULT_ATTR_DATA.fg & (Attributes.PCOLOR_MASK | Attributes.RGB_MASK);
attr.bg &= ~(Attributes.CM_MASK | Attributes.RGB_MASK);
attr.bg |= DEFAULT_ATTR_DATA.bg & (Attributes.PCOLOR_MASK | Attributes.RGB_MASK);
} else {
this._terminal.error('Unknown SGR attribute: %d.', p);
}
}
this._terminal.curAttr = (flags << 18) | (fg << 9) | bg;
}
/**
@@ -1754,13 +1760,13 @@ export class InputHandler extends Disposable implements IInputHandler {
switch (params[0]) {
case 5:
// status report
this._terminal.emit('data', `${C0.ESC}[0n`);
this._onData.fire(`${C0.ESC}[0n`);
break;
case 6:
// cursor position
const y = this._terminal.buffer.y + 1;
const x = this._terminal.buffer.x + 1;
this._terminal.emit('data', `${C0.ESC}[${y};${x}R`);
this._onData.fire(`${C0.ESC}[${y};${x}R`);
break;
}
} else if (collect === '?') {
@@ -1771,7 +1777,7 @@ export class InputHandler extends Disposable implements IInputHandler {
// cursor position
const y = this._terminal.buffer.y + 1;
const x = this._terminal.buffer.x + 1;
this._terminal.emit('data', `${C0.ESC}[?${y};${x}R`);
this._onData.fire(`${C0.ESC}[?${y};${x}R`);
break;
case 15:
// no printer
@@ -1810,7 +1816,7 @@ export class InputHandler extends Disposable implements IInputHandler {
this._terminal.applicationCursor = false;
this._terminal.buffer.scrollTop = 0;
this._terminal.buffer.scrollBottom = this._terminal.rows - 1;
this._terminal.curAttr = DEFAULT_ATTR;
this._terminal.curAttrData = DEFAULT_ATTR_DATA;
this._terminal.buffer.x = this._terminal.buffer.y = 0; // ?
this._terminal.charset = null;
this._terminal.glevel = 0; // ??
@@ -1857,7 +1863,9 @@ export class InputHandler extends Disposable implements IInputHandler {
* CSI ? Pm r
*/
public setScrollRegion(params: number[], collect?: string): void {
if (collect) return;
if (collect) {
return;
}
this._terminal.buffer.scrollTop = (params[0] || 1) - 1;
this._terminal.buffer.scrollBottom = (params[1] && params[1] <= this._terminal.rows ? params[1] : this._terminal.rows) - 1;
this._terminal.buffer.x = 0;
@@ -1873,7 +1881,8 @@ export class InputHandler extends Disposable implements IInputHandler {
public saveCursor(params: number[]): void {
this._terminal.buffer.savedX = this._terminal.buffer.x;
this._terminal.buffer.savedY = this._terminal.buffer.y;
this._terminal.buffer.savedCurAttr = this._terminal.curAttr;
this._terminal.buffer.savedCurAttrData.fg = this._terminal.curAttrData.fg;
this._terminal.buffer.savedCurAttrData.bg = this._terminal.curAttrData.bg;
}
@@ -1885,7 +1894,8 @@ export class InputHandler extends Disposable implements IInputHandler {
public restoreCursor(params: number[]): void {
this._terminal.buffer.x = this._terminal.buffer.savedX || 0;
this._terminal.buffer.y = this._terminal.buffer.savedY || 0;
this._terminal.curAttr = this._terminal.buffer.savedCurAttr || DEFAULT_ATTR;
this._terminal.curAttrData.fg = this._terminal.buffer.savedCurAttrData.fg;
this._terminal.curAttrData.bg = this._terminal.buffer.savedCurAttrData.bg;
}
@@ -1963,9 +1973,15 @@ export class InputHandler extends Disposable implements IInputHandler {
* Designate G3 Character Set (VT300). C = A -> ISO Latin-1 Supplemental. - Supported?
*/
public selectCharset(collectAndFlag: string): void {
if (collectAndFlag.length !== 2) return this.selectDefaultCharset();
if (collectAndFlag[0] === '/') return; // TODO: Is this supported?
if (collectAndFlag.length !== 2) {
this.selectDefaultCharset();
return;
}
if (collectAndFlag[0] === '/') {
return; // TODO: Is this supported?
}
this._terminal.setgCharset(GLEVEL[collectAndFlag[0]], CHARSETS[collectAndFlag[1]] || DEFAULT_CHARSET);
return;
}
/**
+12 -12
View File
@@ -4,12 +4,12 @@
*/
import { assert } from 'chai';
import { IMouseZoneManager, IMouseZone } from './ui/Types';
import { ILinkMatcher, ITerminal, IBufferLine } from './Types';
import { IMouseZoneManager, IMouseZone, ILinkMatcher, ITerminal } from './Types';
import { IBufferLine } from './core/Types';
import { Linkifier } from './Linkifier';
import { MockBuffer, MockTerminal, TestTerminal } from './utils/TestUtils.test';
import { MockBuffer, MockTerminal, TestTerminal } from './TestUtils.test';
import { CircularList } from './common/CircularList';
import { BufferLine } from './BufferLine';
import { BufferLine, CellData } from './core/buffer/BufferLine';
class TestLinkifier extends Linkifier {
constructor(terminal: ITerminal) {
@@ -41,8 +41,8 @@ describe('Linkifier', () => {
beforeEach(() => {
terminal = new MockTerminal();
terminal.cols = 100;
terminal.rows = 10;
(terminal as any).cols = 100;
(terminal as any).rows = 10;
terminal.buffer = new MockBuffer();
(<MockBuffer>terminal.buffer).setLines(new CircularList<IBufferLine>(20));
terminal.buffer.ydisp = 0;
@@ -53,7 +53,7 @@ describe('Linkifier', () => {
function stringToRow(text: string): IBufferLine {
const result = new BufferLine(text.length);
for (let i = 0; i < text.length; i++) {
result.set(i, [0, text.charAt(i), 1, text.charCodeAt(i)]);
result.setCell(i, CellData.fromCharData([0, text.charAt(i), 1, text.charCodeAt(i)]));
}
return result;
}
@@ -65,7 +65,7 @@ describe('Linkifier', () => {
function assertLinkifiesRow(rowText: string, linkMatcherRegex: RegExp, links: {x: number, length: number}[], done: MochaDone): void {
addRow(rowText);
linkifier.registerLinkMatcher(linkMatcherRegex, () => {});
terminal.rows = terminal.buffer.lines.length - 1;
(terminal as any).rows = terminal.buffer.lines.length - 1;
linkifier.linkifyRows();
// Allow linkify to happen
setTimeout(() => {
@@ -142,19 +142,19 @@ describe('Linkifier', () => {
});
describe('multi-line links', () => {
it('should match links that start on line 1/2 of a wrapped line and end on the last character of line 1/2', done => {
terminal.cols = 4;
(terminal as any).cols = 4;
assertLinkifiesMultiLineLink('12345', /1234/, [{x1: 0, x2: 4, y1: 0, y2: 0}], done);
});
it('should match links that start on line 1/2 of a wrapped line and wrap to line 2/2', done => {
terminal.cols = 4;
(terminal as any).cols = 4;
assertLinkifiesMultiLineLink('12345', /12345/, [{x1: 0, x2: 1, y1: 0, y2: 1}], done);
});
it('should match links that start and end on line 2/2 of a wrapped line', done => {
terminal.cols = 4;
(terminal as any).cols = 4;
assertLinkifiesMultiLineLink('12345678', /5678/, [{x1: 0, x2: 4, y1: 1, y2: 1}], done);
});
it('should match links that start on line 2/3 of a wrapped line and wrap to line 3/3', done => {
terminal.cols = 4;
(terminal as any).cols = 4;
assertLinkifiesMultiLineLink('123456789', /56789/, [{x1: 0, x2: 1, y1: 1, y2: 2}], done);
});
});
+26 -15
View File
@@ -3,17 +3,15 @@
* @license MIT
*/
import { IMouseZoneManager } from './ui/Types';
import { ILinkHoverEvent, ILinkMatcher, LinkMatcherHandler, LinkHoverEventTypes, ILinkMatcherOptions, ILinkifier, ITerminal, IBufferStringIteratorResult } from './Types';
import { MouseZone } from './ui/MouseZoneManager';
import { EventEmitter } from './common/EventEmitter';
import { CHAR_DATA_ATTR_INDEX } from './Buffer';
import { ILinkifierEvent, ILinkMatcher, LinkMatcherHandler, ILinkMatcherOptions, ILinkifier, ITerminal, IBufferStringIteratorResult, IMouseZoneManager } from './Types';
import { MouseZone } from './MouseZoneManager';
import { getStringCellWidth } from './CharWidth';
import { EventEmitter2, IEvent } from './common/EventEmitter2';
/**
* The Linkifier applies links to rows shortly after they have been refreshed.
*/
export class Linkifier extends EventEmitter implements ILinkifier {
export class Linkifier implements ILinkifier {
/**
* The time to wait after a row is changed before it is linkified. This prevents
* the costly operation of searching every row multiple times, potentially a
@@ -35,10 +33,16 @@ export class Linkifier extends EventEmitter implements ILinkifier {
private _nextLinkMatcherId = 0;
private _rowsToLinkify: { start: number, end: number };
private _onLinkHover = new EventEmitter2<ILinkifierEvent>();
public get onLinkHover(): IEvent<ILinkifierEvent> { return this._onLinkHover.event; }
private _onLinkLeave = new EventEmitter2<ILinkifierEvent>();
public get onLinkLeave(): IEvent<ILinkifierEvent> { return this._onLinkLeave.event; }
private _onLinkTooltip = new EventEmitter2<ILinkifierEvent>();
public get onLinkTooltip(): IEvent<ILinkifierEvent> { return this._onLinkTooltip.event; }
constructor(
protected _terminal: ITerminal
) {
super();
this._rowsToLinkify = {
start: null,
end: null
@@ -219,15 +223,22 @@ export class Linkifier extends EventEmitter implements ILinkifier {
// also correct regex and string search offsets for the next loop run
stringIndex = text.indexOf(uri, stringIndex + 1);
rex.lastIndex = stringIndex + uri.length;
if (stringIndex < 0) {
// invalid stringIndex (should not have happened)
break;
}
// get the buffer index as [absolute row, col] for the match
const bufferIndex = this._terminal.buffer.stringIndexToBufferIndex(rowIndex, stringIndex);
if (bufferIndex[0] < 0) {
// invalid bufferIndex (should not have happened)
break;
}
const line = this._terminal.buffer.lines.get(bufferIndex[0]);
const char = line.get(bufferIndex[1]);
const attr = line.getFg(bufferIndex[1]);
let fg: number | undefined;
if (char) {
const attr: number = char[CHAR_DATA_ATTR_INDEX];
if (attr) {
fg = (attr >> 9) & 0x1ff;
}
@@ -277,18 +288,18 @@ export class Linkifier extends EventEmitter implements ILinkifier {
}
window.open(uri, '_blank');
},
e => {
this.emit(LinkHoverEventTypes.HOVER, this._createLinkHoverEvent(x1, y1, x2, y2, fg));
() => {
this._onLinkHover.fire(this._createLinkHoverEvent(x1, y1, x2, y2, fg));
this._terminal.element.classList.add('xterm-cursor-pointer');
},
e => {
this.emit(LinkHoverEventTypes.TOOLTIP, this._createLinkHoverEvent(x1, y1, x2, y2, fg));
this._onLinkTooltip.fire(this._createLinkHoverEvent(x1, y1, x2, y2, fg));
if (matcher.hoverTooltipCallback) {
matcher.hoverTooltipCallback(e, uri);
}
},
() => {
this.emit(LinkHoverEventTypes.LEAVE, this._createLinkHoverEvent(x1, y1, x2, y2, fg));
this._onLinkLeave.fire(this._createLinkHoverEvent(x1, y1, x2, y2, fg));
this._terminal.element.classList.remove('xterm-cursor-pointer');
if (matcher.hoverLeaveCallback) {
matcher.hoverLeaveCallback();
@@ -303,7 +314,7 @@ export class Linkifier extends EventEmitter implements ILinkifier {
));
}
private _createLinkHoverEvent(x1: number, y1: number, x2: number, y2: number, fg: number): ILinkHoverEvent {
private _createLinkHoverEvent(x1: number, y1: number, x2: number, y2: number, fg: number): ILinkifierEvent {
return { x1, y1, x2, y2, cols: this._terminal.cols, fg };
}
}
@@ -31,40 +31,34 @@ describe('MouseHelper.getCoords', () => {
actualCellWidth: CHAR_WIDTH,
actualCellHeight: CHAR_HEIGHT
};
mouseHelper = new MouseHelper(renderer);
mouseHelper = new MouseHelper(renderer as any);
});
describe('when charMeasure is not initialized', () => {
it('should return null', () => {
charMeasure = new MockCharMeasure();
assert.equal(mouseHelper.getCoords({ pageX: 0, pageY: 0 }, document.createElement('div'), charMeasure, 10, 10), null);
});
});
describe('when pageX/pageY are not supported', () => {
it('should return null', () => {
assert.equal(mouseHelper.getCoords({ pageX: undefined, pageY: undefined }, document.createElement('div'), charMeasure, 10, 10), null);
assert.equal(mouseHelper.getCoords({ clientX: 0, clientY: 0 }, document.createElement('div'), charMeasure, 10, 10), null);
});
});
it('should return the cell that was clicked', () => {
let coords: [number, number];
coords = mouseHelper.getCoords({ pageX: CHAR_WIDTH / 2, pageY: CHAR_HEIGHT / 2 }, document.createElement('div'), charMeasure, 10, 10);
coords = mouseHelper.getCoords({ clientX: CHAR_WIDTH / 2, clientY: CHAR_HEIGHT / 2 }, document.createElement('div'), charMeasure, 10, 10);
assert.deepEqual(coords, [1, 1]);
coords = mouseHelper.getCoords({ pageX: CHAR_WIDTH, pageY: CHAR_HEIGHT }, document.createElement('div'), charMeasure, 10, 10);
coords = mouseHelper.getCoords({ clientX: CHAR_WIDTH, clientY: CHAR_HEIGHT }, document.createElement('div'), charMeasure, 10, 10);
assert.deepEqual(coords, [1, 1]);
coords = mouseHelper.getCoords({ pageX: CHAR_WIDTH, pageY: CHAR_HEIGHT + 1 }, document.createElement('div'), charMeasure, 10, 10);
coords = mouseHelper.getCoords({ clientX: CHAR_WIDTH, clientY: CHAR_HEIGHT + 1 }, document.createElement('div'), charMeasure, 10, 10);
assert.deepEqual(coords, [1, 2]);
coords = mouseHelper.getCoords({ pageX: CHAR_WIDTH + 1, pageY: CHAR_HEIGHT }, document.createElement('div'), charMeasure, 10, 10);
coords = mouseHelper.getCoords({ clientX: CHAR_WIDTH + 1, clientY: CHAR_HEIGHT }, document.createElement('div'), charMeasure, 10, 10);
assert.deepEqual(coords, [2, 1]);
});
it('should ensure the coordinates are returned within the terminal bounds', () => {
let coords: [number, number];
coords = mouseHelper.getCoords({ pageX: -1, pageY: -1 }, document.createElement('div'), charMeasure, 10, 10);
coords = mouseHelper.getCoords({ clientX: -1, clientY: -1 }, document.createElement('div'), charMeasure, 10, 10);
assert.deepEqual(coords, [1, 1]);
// Event are double the cols/rows
coords = mouseHelper.getCoords({ pageX: CHAR_WIDTH * 20, pageY: CHAR_HEIGHT * 20 }, document.createElement('div'), charMeasure, 10, 10);
coords = mouseHelper.getCoords({ clientX: CHAR_WIDTH * 20, clientY: CHAR_HEIGHT * 20 }, document.createElement('div'), charMeasure, 10, 10);
assert.deepEqual(coords, [10, 10], 'coordinates should never come back as larger than the terminal');
});
});
+12 -34
View File
@@ -3,40 +3,18 @@
* @license MIT
*/
import { ICharMeasure } from '../Types';
import { IRenderer } from '../renderer/Types';
import { ICharMeasure, IMouseHelper } from './Types';
import { RenderCoordinator } from './renderer/RenderCoordinator';
export class MouseHelper {
constructor(private _renderer: IRenderer) {}
public setRenderer(renderer: IRenderer): void {
this._renderer = renderer;
export class MouseHelper implements IMouseHelper {
constructor(
private _renderCoordinator: RenderCoordinator
) {
}
public static getCoordsRelativeToElement(event: {pageX: number, pageY: number}, element: HTMLElement): [number, number] {
// Ignore browsers that don't support MouseEvent.pageX
if (event.pageX === null || event.pageX === undefined) {
return null;
}
const originalElement = element;
let x = event.pageX;
let y = event.pageY;
// Converts the coordinates from being relative to the document to being
// relative to the terminal.
while (element) {
x -= element.offsetLeft;
y -= element.offsetTop;
element = <HTMLElement>element.offsetParent;
}
element = originalElement;
while (element && element !== element.ownerDocument.body) {
x += element.scrollLeft;
y += element.scrollTop;
element = <HTMLElement>element.parentElement;
}
return [x, y];
public static getCoordsRelativeToElement(event: {clientX: number, clientY: number}, element: HTMLElement): [number, number] {
const rect = element.getBoundingClientRect();
return [event.clientX - rect.left, event.clientY - rect.top];
}
/**
@@ -52,7 +30,7 @@ export class MouseHelper {
* apply an offset to the x value such that the left half of the cell will
* select that cell and the right half will select the next cell.
*/
public getCoords(event: {pageX: number, pageY: number}, element: HTMLElement, charMeasure: ICharMeasure, colCount: number, rowCount: number, isSelection?: boolean): [number, number] {
public getCoords(event: {clientX: number, clientY: number}, element: HTMLElement, charMeasure: ICharMeasure, colCount: number, rowCount: number, isSelection?: boolean): [number, number] {
// Coordinates cannot be measured if charMeasure has not been initialized
if (!charMeasure.width || !charMeasure.height) {
return null;
@@ -63,8 +41,8 @@ export class MouseHelper {
return null;
}
coords[0] = Math.ceil((coords[0] + (isSelection ? this._renderer.dimensions.actualCellWidth / 2 : 0)) / this._renderer.dimensions.actualCellWidth);
coords[1] = Math.ceil(coords[1] / this._renderer.dimensions.actualCellHeight);
coords[0] = Math.ceil((coords[0] + (isSelection ? this._renderCoordinator.dimensions.actualCellWidth / 2 : 0)) / this._renderCoordinator.dimensions.actualCellWidth);
coords[1] = Math.ceil(coords[1] / this._renderCoordinator.dimensions.actualCellHeight);
// Ensure coordinates are within the terminal viewport. Note that selections
// need an addition point of precision to cover the end point (as characters
@@ -3,10 +3,9 @@
* @license MIT
*/
import { ITerminal } from '../Types';
import { IMouseZoneManager, IMouseZone } from './Types';
import { Disposable } from '../common/Lifecycle';
import { addDisposableDomListener } from './Lifecycle';
import { ITerminal, IMouseZoneManager, IMouseZone } from './Types';
import { Disposable } from './common/Lifecycle';
import { addDisposableDomListener } from './ui/Lifecycle';
const HOVER_DURATION = 500;
@@ -23,11 +22,13 @@ export class MouseZoneManager extends Disposable implements IMouseZoneManager {
private _areZonesActive: boolean = false;
private _mouseMoveListener: (e: MouseEvent) => any;
private _mouseLeaveListener: (e: MouseEvent) => any;
private _clickListener: (e: MouseEvent) => any;
private _tooltipTimeout: number = null;
private _currentZone: IMouseZone = null;
private _lastHoverCoords: [number, number] = [null, null];
private _initialSelectionLength: number;
constructor(
private _terminal: ITerminal
@@ -38,6 +39,7 @@ export class MouseZoneManager extends Disposable implements IMouseZoneManager {
// These events are expensive, only listen to it when mouse zones are active
this._mouseMoveListener = e => this._onMouseMove(e);
this._mouseLeaveListener = e => this._onMouseLeave(e);
this._clickListener = e => this._onClick(e);
}
@@ -89,6 +91,7 @@ export class MouseZoneManager extends Disposable implements IMouseZoneManager {
if (!this._areZonesActive) {
this._areZonesActive = true;
this._terminal.element.addEventListener('mousemove', this._mouseMoveListener);
this._terminal.element.addEventListener('mouseleave', this._mouseLeaveListener);
this._terminal.element.addEventListener('click', this._clickListener);
}
}
@@ -97,6 +100,7 @@ export class MouseZoneManager extends Disposable implements IMouseZoneManager {
if (this._areZonesActive) {
this._areZonesActive = false;
this._terminal.element.removeEventListener('mousemove', this._mouseMoveListener);
this._terminal.element.removeEventListener('mouseleave', this._mouseLeaveListener);
this._terminal.element.removeEventListener('click', this._clickListener);
}
}
@@ -153,6 +157,10 @@ export class MouseZoneManager extends Disposable implements IMouseZoneManager {
}
private _onMouseDown(e: MouseEvent): void {
// Store current terminal selection length, to check if we're performing
// a selection operation
this._initialSelectionLength = this._terminal.getSelection().length;
// Ignore the event if there are no zones active
if (!this._areZonesActive) {
return;
@@ -169,10 +177,25 @@ export class MouseZoneManager extends Disposable implements IMouseZoneManager {
}
}
private _onMouseLeave(e: MouseEvent): void {
// Fire the hover end callback and cancel any existing timer if the mouse
// leaves the terminal element
if (this._currentZone) {
this._currentZone.leaveCallback();
this._currentZone = null;
if (this._tooltipTimeout) {
clearTimeout(this._tooltipTimeout);
}
}
}
private _onClick(e: MouseEvent): void {
// Find the active zone and click it if found
// Find the active zone and click it if found and no selection was
// being performed
const zone = this._findZoneEventAt(e);
if (zone) {
const currentSelectionLength = this._terminal.getSelection().length;
if (zone && currentSelectionLength === this._initialSelectionLength) {
zone.clickCallback(e);
e.preventDefault();
e.stopImmediatePropagation();
+10 -9
View File
@@ -4,13 +4,14 @@
*/
import { assert } from 'chai';
import { CharMeasure } from './ui/CharMeasure';
import { CharMeasure } from './CharMeasure';
import { SelectionManager, SelectionMode } from './SelectionManager';
import { SelectionModel } from './SelectionModel';
import { BufferSet } from './BufferSet';
import { ITerminal, IBuffer, IBufferLine } from './Types';
import { MockTerminal } from './utils/TestUtils.test';
import { BufferLine } from './BufferLine';
import { ITerminal, IBuffer } from './Types';
import { IBufferLine } from './core/Types';
import { MockTerminal } from './TestUtils.test';
import { BufferLine, CellData } from './core/buffer/BufferLine';
class TestMockTerminal extends MockTerminal {
emit(event: string, data: any): void {}
@@ -45,8 +46,8 @@ describe('SelectionManager', () => {
beforeEach(() => {
terminal = new TestMockTerminal();
terminal.cols = 80;
terminal.rows = 2;
(terminal as any).cols = 80;
(terminal as any).rows = 2;
terminal.options.scrollback = 100;
terminal.buffers = new BufferSet(terminal);
terminal.buffer = terminal.buffers.active;
@@ -57,14 +58,14 @@ describe('SelectionManager', () => {
function stringToRow(text: string): IBufferLine {
const result = new BufferLine(text.length);
for (let i = 0; i < text.length; i++) {
result.set(i, [0, text.charAt(i), 1, text.charCodeAt(i)]);
result.setCell(i, CellData.fromCharData([0, text.charAt(i), 1, text.charCodeAt(i)]));
}
return result;
}
function stringArrayToRow(chars: string[]): IBufferLine {
const line = new BufferLine(chars.length);
chars.map((c, idx) => line.set(idx, [0, c, 1, c.charCodeAt(0)]));
chars.map((c, idx) => line.setCell(idx, CellData.fromCharData([0, c, 1, c.charCodeAt(0)])));
return line;
}
@@ -119,7 +120,7 @@ describe('SelectionManager', () => {
[null, 'o', 1, 'o'.charCodeAt(0)]
];
const line = new BufferLine(data.length);
for (let i = 0; i < data.length; ++i) line.set(i, data[i]);
for (let i = 0; i < data.length; ++i) line.setCell(i, CellData.fromCharData(data[i]));
buffer.lines.set(0, line);
// Ensure wide characters take up 2 columns
selectionManager.selectWordAt([0, 0]);
+70 -60
View File
@@ -3,15 +3,16 @@
* @license MIT
*/
import { ITerminal, ISelectionManager, IBuffer, CharData, IBufferLine } from './Types';
import { XtermListener } from './common/Types';
import { MouseHelper } from './utils/MouseHelper';
import * as Browser from './core/Platform';
import { CharMeasure } from './ui/CharMeasure';
import { EventEmitter } from './common/EventEmitter';
import { ITerminal, ISelectionManager, IBuffer, ISelectionRedrawRequestEvent } from './Types';
import { IBufferLine } from './core/Types';
import { MouseHelper } from './MouseHelper';
import * as Browser from './common/Platform';
import { CharMeasure } from './CharMeasure';
import { SelectionModel } from './SelectionModel';
import { CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CHAR_INDEX, CHAR_DATA_CODE_INDEX } from './Buffer';
import { AltClickHandler } from './handlers/AltClickHandler';
import { CellData } from './core/buffer/BufferLine';
import { IDisposable } from 'xterm';
import { EventEmitter2, IEvent } from './common/EventEmitter2';
/**
* The number of pixels the mouse needs to be above or below the viewport in
@@ -67,10 +68,10 @@ export const enum SelectionMode {
* SelectionModel, SelectionManager handles with all logic associated with
* dealing with the selection, including handling mouse interaction, wide
* characters and fetching the actual text within the selection. Rendering is
* not handled by the SelectionManager but a 'refresh' event is fired when the
* selection is ready to be redrawn.
* not handled by the SelectionManager but the onRedrawRequest event is fired
* when the selection is ready to be redrawn (on an animation frame).
*/
export class SelectionManager extends EventEmitter implements ISelectionManager {
export class SelectionManager implements ISelectionManager {
protected _model: SelectionModel;
/**
@@ -102,15 +103,22 @@ export class SelectionManager extends EventEmitter implements ISelectionManager
private _mouseMoveListener: EventListener;
private _mouseUpListener: EventListener;
private _trimListener: XtermListener;
private _trimListener: IDisposable;
private _workCell: CellData = new CellData();
private _mouseDownTimeStamp: number;
private _onLinuxMouseSelection = new EventEmitter2<string>();
public get onLinuxMouseSelection(): IEvent<string> { return this._onLinuxMouseSelection.event; }
private _onRedrawRequest = new EventEmitter2<ISelectionRedrawRequestEvent>();
public get onRedrawRequest(): IEvent<ISelectionRedrawRequestEvent> { return this._onRedrawRequest.event; }
private _onSelectionChange = new EventEmitter2<void>();
public get onSelectionChange(): IEvent<void> { return this._onSelectionChange.event; }
constructor(
private _terminal: ITerminal,
private _charMeasure: CharMeasure
) {
super();
this._initListeners();
this.enable();
@@ -119,7 +127,6 @@ export class SelectionManager extends EventEmitter implements ISelectionManager
}
public dispose(): void {
super.dispose();
this._removeMouseDownListeners();
}
@@ -133,14 +140,13 @@ export class SelectionManager extends EventEmitter implements ISelectionManager
private _initListeners(): void {
this._mouseMoveListener = event => this._onMouseMove(<MouseEvent>event);
this._mouseUpListener = event => this._onMouseUp(<MouseEvent>event);
this._trimListener = (amount: number) => this._onTrim(amount);
this.initBuffersListeners();
}
public initBuffersListeners(): void {
this._terminal.buffer.lines.on('trim', this._trimListener);
this._terminal.buffers.on('activate', e => this._onBufferActivate(e));
this._trimListener = this._terminal.buffer.lines.onTrim(amount => this._onTrim(amount));
this._terminal.buffers.onBufferActivate(e => this._onBufferActivate(e));
}
/**
@@ -198,7 +204,7 @@ export class SelectionManager extends EventEmitter implements ISelectionManager
}
} else {
// Get first row
const startRowEndCol = start[1] === end[1] ? end[0] : null;
const startRowEndCol = start[1] === end[1] ? end[0] : undefined;
result.push(this._buffer.translateBufferLineToString(start[1], true, start[0], startRowEndCol));
// Get middle rows
@@ -240,14 +246,15 @@ export class SelectionManager extends EventEmitter implements ISelectionManager
this._model.clearSelection();
this._removeMouseDownListeners();
this.refresh();
this._onSelectionChange.fire();
}
/**
* Queues a refresh, redrawing the selection on the next opportunity.
* @param isNewSelection Whether the selection should be registered as a new
* @param isLinuxMouseSelection Whether the selection should be registered as a new
* selection on Linux.
*/
public refresh(isNewSelection?: boolean): void {
public refresh(isLinuxMouseSelection?: boolean): void {
// Queue the refresh for the renderer
if (!this._refreshAnimationFrame) {
this._refreshAnimationFrame = window.requestAnimationFrame(() => this._refresh());
@@ -255,10 +262,10 @@ export class SelectionManager extends EventEmitter implements ISelectionManager
// If the platform is Linux and the refresh call comes from a mouse event,
// we need to update the selection for middle click to paste selection.
if (Browser.isLinux && isNewSelection) {
if (Browser.isLinux && isLinuxMouseSelection) {
const selectionText = this.selectionText;
if (selectionText.length) {
this.emit('newselection', this.selectionText);
this._onLinuxMouseSelection.fire(this.selectionText);
}
}
}
@@ -269,7 +276,7 @@ export class SelectionManager extends EventEmitter implements ISelectionManager
*/
private _refresh(): void {
this._refreshAnimationFrame = null;
this.emit('refresh', {
this._onRedrawRequest.fire({
start: this._model.finalSelectionStart,
end: this._model.finalSelectionEnd,
columnSelectMode: this._activeSelectionMode === SelectionMode.COLUMN
@@ -318,7 +325,7 @@ export class SelectionManager extends EventEmitter implements ISelectionManager
public selectAll(): void {
this._model.isSelectAllActive = true;
this.refresh();
this._terminal.emit('selection');
this._onSelectionChange.fire();
}
public selectLines(start: number, end: number): void {
@@ -328,7 +335,7 @@ export class SelectionManager extends EventEmitter implements ISelectionManager
this._model.selectionStart = [0, start];
this._model.selectionEnd = [this._terminal.cols, end];
this.refresh();
this._terminal.emit('selection');
this._onSelectionChange.fire();
}
/**
@@ -506,8 +513,7 @@ export class SelectionManager extends EventEmitter implements ISelectionManager
// If the mouse is over the second half of a wide character, adjust the
// selection to cover the whole character
const char = line.get(this._model.selectionStart[0]);
if (char[CHAR_DATA_WIDTH_INDEX] === 0) {
if (line.hasWidth(this._model.selectionStart[0]) === 0) {
this._model.selectionStart[0]++;
}
}
@@ -552,7 +558,7 @@ export class SelectionManager extends EventEmitter implements ISelectionManager
*/
private _onMouseMove(event: MouseEvent): void {
// If the mousemove listener is active it means that a selection is
// currently being made, we should stop propogation to prevent mouse events
// currently being made, we should stop propagation to prevent mouse events
// to be sent to the pty.
event.stopImmediatePropagation();
@@ -596,8 +602,7 @@ export class SelectionManager extends EventEmitter implements ISelectionManager
// selection. Note that selections at the very end of the line will never
// have a character.
if (this._model.selectionEnd[1] < this._buffer.lines.length) {
const char = this._buffer.lines.get(this._model.selectionEnd[1]).get(this._model.selectionEnd[0]);
if (char && char[CHAR_DATA_WIDTH_INDEX] === 0) {
if (this._buffer.lines.get(this._model.selectionEnd[1]).hasWidth(this._model.selectionEnd[0]) === 0) {
this._model.selectionEnd[0]++;
}
}
@@ -648,7 +653,7 @@ export class SelectionManager extends EventEmitter implements ISelectionManager
if (this.selectionText.length <= 1 && timeElapsed < ALT_CLICK_MOVE_CURSOR_TIME) {
(new AltClickHandler(event, this._terminal)).move();
} else if (this.hasSelection) {
this._terminal.emit('selection');
this._onSelectionChange.fire();
}
}
@@ -658,8 +663,10 @@ export class SelectionManager extends EventEmitter implements ISelectionManager
// reverseIndex) and delete in a splice is only ever used when the same
// number of elements was just added. Given this is could actually be
// beneficial to leave the selection as is for these cases.
e.inactiveBuffer.lines.off('trim', this._trimListener);
e.activeBuffer.lines.on('trim', this._trimListener);
if (this._trimListener) {
this._trimListener.dispose();
}
this._trimListener = e.activeBuffer.lines.onTrim(amount => this._onTrim(amount));
}
/**
@@ -670,16 +677,16 @@ export class SelectionManager extends EventEmitter implements ISelectionManager
private _convertViewportColToCharacterIndex(bufferLine: IBufferLine, coords: [number, number]): number {
let charIndex = coords[0];
for (let i = 0; coords[0] >= i; i++) {
const char = bufferLine.get(i);
if (char[CHAR_DATA_WIDTH_INDEX] === 0) {
const length = bufferLine.loadCell(i, this._workCell).getChars().length;
if (this._workCell.getWidth() === 0) {
// Wide characters aren't included in the line string so decrement the
// index so the index is back on the wide character.
charIndex--;
} else if (char[CHAR_DATA_CHAR_INDEX].length > 1 && coords[0] !== i) {
} else if (length > 1 && coords[0] !== i) {
// Emojis take up multiple characters, so adjust accordingly. For these
// we don't want ot include the character at the column as we're
// returning the start index in the string, not the end index.
charIndex += char[CHAR_DATA_CHAR_INDEX].length - 1;
charIndex += length - 1;
}
}
return charIndex;
@@ -739,48 +746,51 @@ export class SelectionManager extends EventEmitter implements ISelectionManager
// Consider the initial position, skip it and increment the wide char
// variable
if (bufferLine.get(startCol)[CHAR_DATA_WIDTH_INDEX] === 0) {
if (bufferLine.getWidth(startCol) === 0) {
leftWideCharCount++;
startCol--;
}
if (bufferLine.get(endCol)[CHAR_DATA_WIDTH_INDEX] === 2) {
if (bufferLine.getWidth(endCol) === 2) {
rightWideCharCount++;
endCol++;
}
// Adjust the end index for characters whose length are > 1 (emojis)
if (bufferLine.get(endCol)[CHAR_DATA_CHAR_INDEX].length > 1) {
rightLongCharOffset += bufferLine.get(endCol)[CHAR_DATA_CHAR_INDEX].length - 1;
endIndex += bufferLine.get(endCol)[CHAR_DATA_CHAR_INDEX].length - 1;
const length = bufferLine.getString(endCol).length;
if (length > 1) {
rightLongCharOffset += length - 1;
endIndex += length - 1;
}
// Expand the string in both directions until a space is hit
while (startCol > 0 && startIndex > 0 && !this._isCharWordSeparator(bufferLine.get(startCol - 1))) {
const char = bufferLine.get(startCol - 1);
if (char[CHAR_DATA_WIDTH_INDEX] === 0) {
while (startCol > 0 && startIndex > 0 && !this._isCharWordSeparator(bufferLine.loadCell(startCol - 1, this._workCell))) {
bufferLine.loadCell(startCol - 1, this._workCell);
const length = this._workCell.getChars().length;
if (this._workCell.getWidth() === 0) {
// If the next character is a wide char, record it and skip the column
leftWideCharCount++;
startCol--;
} else if (char[CHAR_DATA_CHAR_INDEX].length > 1) {
} else if (length > 1) {
// If the next character's string is longer than 1 char (eg. emoji),
// adjust the index
leftLongCharOffset += char[CHAR_DATA_CHAR_INDEX].length - 1;
startIndex -= char[CHAR_DATA_CHAR_INDEX].length - 1;
leftLongCharOffset += length - 1;
startIndex -= length - 1;
}
startIndex--;
startCol--;
}
while (endCol < bufferLine.length && endIndex + 1 < line.length && !this._isCharWordSeparator(bufferLine.get(endCol + 1))) {
const char = bufferLine.get(endCol + 1);
if (char[CHAR_DATA_WIDTH_INDEX] === 2) {
while (endCol < bufferLine.length && endIndex + 1 < line.length && !this._isCharWordSeparator(bufferLine.loadCell(endCol + 1, this._workCell))) {
bufferLine.loadCell(endCol + 1, this._workCell);
const length = this._workCell.getChars().length;
if (this._workCell.getWidth() === 2) {
// If the next character is a wide char, record it and skip the column
rightWideCharCount++;
endCol++;
} else if (char[CHAR_DATA_CHAR_INDEX].length > 1) {
} else if (length > 1) {
// If the next character's string is longer than 1 char (eg. emoji),
// adjust the index
rightLongCharOffset += char[CHAR_DATA_CHAR_INDEX].length - 1;
endIndex += char[CHAR_DATA_CHAR_INDEX].length - 1;
rightLongCharOffset += length - 1;
endIndex += length - 1;
}
endIndex++;
endCol++;
@@ -814,9 +824,9 @@ export class SelectionManager extends EventEmitter implements ISelectionManager
// Recurse upwards if the line is wrapped and the word wraps to the above line
if (followWrappedLinesAbove) {
if (start === 0 && bufferLine.get(0)[CHAR_DATA_CODE_INDEX] !== 32 /*' '*/) {
if (start === 0 && bufferLine.getCodePoint(0) !== 32 /*' '*/) {
const previousBufferLine = this._buffer.lines.get(coords[1] - 1);
if (previousBufferLine && bufferLine.isWrapped && previousBufferLine.get(this._terminal.cols - 1)[CHAR_DATA_CODE_INDEX] !== 32 /*' '*/) {
if (previousBufferLine && bufferLine.isWrapped && previousBufferLine.getCodePoint(this._terminal.cols - 1) !== 32 /*' '*/) {
const previousLineWordPosition = this._getWordAt([this._terminal.cols - 1, coords[1] - 1], false, true, false);
if (previousLineWordPosition) {
const offset = this._terminal.cols - previousLineWordPosition.start;
@@ -829,9 +839,9 @@ export class SelectionManager extends EventEmitter implements ISelectionManager
// Recurse downwards if the line is wrapped and the word wraps to the next line
if (followWrappedLinesBelow) {
if (start + length === this._terminal.cols && bufferLine.get(this._terminal.cols - 1)[CHAR_DATA_CODE_INDEX] !== 32 /*' '*/) {
if (start + length === this._terminal.cols && bufferLine.getCodePoint(this._terminal.cols - 1) !== 32 /*' '*/) {
const nextBufferLine = this._buffer.lines.get(coords[1] + 1);
if (nextBufferLine && nextBufferLine.isWrapped && nextBufferLine.get(0)[CHAR_DATA_CODE_INDEX] !== 32 /*' '*/) {
if (nextBufferLine && nextBufferLine.isWrapped && nextBufferLine.getCodePoint(0) !== 32 /*' '*/) {
const nextLineWordPosition = this._getWordAt([0, coords[1] + 1], false, false, true);
if (nextLineWordPosition) {
length += nextLineWordPosition.length;
@@ -894,13 +904,13 @@ export class SelectionManager extends EventEmitter implements ISelectionManager
* word logic.
* @param char The character to check.
*/
private _isCharWordSeparator(charData: CharData): boolean {
private _isCharWordSeparator(cell: CellData): boolean {
// Zero width characters are never separators as they are always to the
// right of wide characters
if (charData[CHAR_DATA_WIDTH_INDEX] === 0) {
if (cell.getWidth() === 0) {
return false;
}
return WORD_SEPARATORS.indexOf(charData[CHAR_DATA_CHAR_INDEX]) >= 0;
return WORD_SEPARATORS.indexOf(cell.getChars()) >= 0;
}
/**
+3 -3
View File
@@ -7,7 +7,7 @@ import { assert } from 'chai';
import { ITerminal } from './Types';
import { SelectionModel } from './SelectionModel';
import { BufferSet } from './BufferSet';
import { MockTerminal } from './utils/TestUtils.test';
import { MockTerminal } from './TestUtils.test';
class TestSelectionModel extends SelectionModel {
constructor(
@@ -23,8 +23,8 @@ describe('SelectionManager', () => {
beforeEach(() => {
terminal = new MockTerminal();
terminal.cols = 80;
terminal.rows = 2;
(terminal as any).cols = 80;
(terminal as any).rows = 2;
terminal.options.scrollback = 10;
terminal.buffers = new BufferSet(terminal);
terminal.buffer = terminal.buffers.active;
+6 -5
View File
@@ -13,8 +13,8 @@ import * as path from 'path';
import * as pty from 'node-pty';
import { assert } from 'chai';
import { Terminal } from './Terminal';
import { CHAR_DATA_CHAR_INDEX } from './Buffer';
import { IViewport } from './Types';
import { CellData, WHITESPACE_CELL_CHAR } from './core/buffer/BufferLine';
class TestTerminal extends Terminal {
innerWrite(): void { this._innerWrite(); }
@@ -67,7 +67,7 @@ function terminalToString(term: Terminal): string {
for (let line = term.buffer.ybase; line < term.buffer.ybase + term.rows; line++) {
lineText = '';
for (let cell = 0; cell < term.cols; ++cell) {
lineText += term.buffer.lines.get(line).get(cell)[CHAR_DATA_CHAR_INDEX];
lineText += term.buffer.lines.get(line).loadCell(cell, new CellData()).getChars() || WHITESPACE_CELL_CHAR;
}
// rtrim empty cells as xterm does
lineText = lineText.replace(/\s+$/, '');
@@ -91,7 +91,8 @@ if (os.platform() !== 'win32') {
primitivePty = (<any>pty).native.open(cols, rows);
/** tests */
describe('xterm output comparison', () => {
describe('xterm output comparison', function(): void {
this.timeout(10000);
let xterm: TestTerminal;
beforeEach(() => {
@@ -113,8 +114,8 @@ if (os.platform() !== 'win32') {
51, 52, 54, 55, 56, 57, 58, 59, 60, 61,
63, 68
];
// These are failing on macOS only
if (os.platform() === 'darwin') {
// These are failing on macOS only
skip.push(3, 7, 11, 67);
}
for (let i = 0; i < files.length; i++) {
@@ -122,9 +123,9 @@ if (os.platform() !== 'win32') {
continue;
}
((filename: string) => {
const inFile = fs.readFileSync(filename, 'utf8');
it(filename.split('/').slice(-1)[0], done => {
ptyReset(() => {
const inFile = fs.readFileSync(filename, 'utf8');
ptyWriteRead(inFile, fromPty => {
// uncomment this to get log from terminal
// console.log = function(){};
+328 -208
View File
@@ -5,8 +5,8 @@
import { assert, expect } from 'chai';
import { Terminal } from './Terminal';
import { MockViewport, MockCompositionHelper, MockRenderer } from './utils/TestUtils.test';
import { CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX, DEFAULT_ATTR } from './Buffer';
import { MockViewport, MockCompositionHelper, MockRenderer } from './TestUtils.test';
import { CellData, DEFAULT_ATTR_DATA } from './core/buffer/BufferLine';
const INIT_COLS = 80;
const INIT_ROWS = 24;
@@ -16,7 +16,7 @@ class TestTerminal extends Terminal {
public keyPress(ev: any): boolean { return this._keyPress(ev); }
}
describe('term.js addons', () => {
describe('Terminal', () => {
let term: TestTerminal;
const termOptions = {
cols: INIT_COLS,
@@ -67,6 +67,81 @@ describe('term.js addons', () => {
});
});
describe('events', () => {
it('should fire the onData evnet', (done) => {
term.onData(() => done());
term.handler('fake');
});
it('should fire the onCursorMove event', (done) => {
term.on('cursormove', () => done());
term.write('foo');
});
it('should fire the onLineFeed event', (done) => {
term.on('linefeed', () => done());
term.write('\n');
});
it('should fire a scroll event when scrollback is created', (done) => {
term.on('scroll', () => done());
term.write('\n'.repeat(INIT_ROWS));
});
it('should fire a scroll event when scrollback is cleared', (done) => {
term.write('\n'.repeat(INIT_ROWS));
term.on('scroll', () => done());
term.clear();
});
it('should fire a key event after a keypress DOM event', (done) => {
term.onKey(e => {
assert.equal(typeof e.key, 'string');
expect(e.domEvent).to.be.an.instanceof(Object);
done();
});
const evKeyPress = <KeyboardEvent>{
preventDefault: () => { },
stopPropagation: () => { },
type: 'keypress',
keyCode: 13
};
term.keyPress(evKeyPress);
});
it('should fire a key event after a keydown DOM event', (done) => {
term.onKey(e => {
assert.equal(typeof e.key, 'string');
expect(e.domEvent).to.be.an.instanceof(Object);
done();
});
const evKeyDown = <KeyboardEvent>{
preventDefault: () => { },
stopPropagation: () => { },
type: 'keydown',
keyCode: 13
};
term.keyDown(evKeyDown);
});
it('should fire the onResize event', (done) => {
term.onResize(e => {
expect(e).to.have.keys(['cols', 'rows']);
assert.equal(typeof e.cols, 'number');
assert.equal(typeof e.rows, 'number');
done();
});
term.resize(1, 1);
});
it('should fire the onScroll event', (done) => {
term.onScroll(e => {
assert.equal(typeof e, 'number');
done();
});
term.scroll();
});
it('should fire the onTitleChange event', (done) => {
term.onTitleChange(e => {
assert.equal(e, 'title');
done();
});
term.handleTitle('title');
});
});
describe('on', () => {
beforeEach(() => {
term.on('key', () => { });
@@ -84,6 +159,40 @@ describe('term.js addons', () => {
});
});
describe('cursormove', () => {
it('should emit a cursormove event', (done) => {
term.on('cursormove', () => {
done();
});
term.write('foo');
});
});
describe('linefeed', () => {
it('should emit a linefeed event', (done) => {
term.on('linefeed', () => {
done();
});
term.write('\n');
});
});
describe('scroll', () => {
it('should emit a scroll event when scrollback is created', (done) => {
term.on('scroll', () => {
done();
});
term.write('\n'.repeat(INIT_ROWS));
});
it('should emit a scroll event when scrollback is cleared', (done) => {
term.write('\n'.repeat(INIT_ROWS));
term.on('scroll', () => {
done();
});
term.clear();
});
});
describe(`keypress (including 'key' event)`, () => {
it('should receive a string and event object', (done) => {
let steps = 0;
@@ -227,7 +336,7 @@ describe('term.js addons', () => {
});
describe('setOption', () => {
it('should set the option correctly', () => {
it('should set option correctly', () => {
term.setOption('cursorBlink', true);
assert.equal(term.options.cursorBlink, true);
term.setOption('cursorBlink', false);
@@ -259,7 +368,7 @@ describe('term.js addons', () => {
assert.equal(term.buffer.lines.length, term.rows);
assert.deepEqual(term.buffer.lines.get(0), promptLine);
for (let i = 1; i < term.rows; i++) {
assert.deepEqual(term.buffer.lines.get(i), term.buffer.getBlankLine(DEFAULT_ATTR));
assert.deepEqual(term.buffer.lines.get(i), term.buffer.getBlankLine(DEFAULT_ATTR_DATA));
}
});
it('should clear a buffer larger than rows', () => {
@@ -276,7 +385,7 @@ describe('term.js addons', () => {
assert.equal(term.buffer.lines.length, term.rows);
assert.deepEqual(term.buffer.lines.get(0), promptLine);
for (let i = 1; i < term.rows; i++) {
assert.deepEqual(term.buffer.lines.get(i), term.buffer.getBlankLine(DEFAULT_ATTR));
assert.deepEqual(term.buffer.lines.get(i), term.buffer.getBlankLine(DEFAULT_ATTR_DATA));
}
});
it('should not break the prompt when cleared twice', () => {
@@ -289,7 +398,7 @@ describe('term.js addons', () => {
assert.equal(term.buffer.lines.length, term.rows);
assert.deepEqual(term.buffer.lines.get(0), promptLine);
for (let i = 1; i < term.rows; i++) {
assert.deepEqual(term.buffer.lines.get(i), term.buffer.getBlankLine(DEFAULT_ATTR));
assert.deepEqual(term.buffer.lines.get(i), term.buffer.getBlankLine(DEFAULT_ATTR_DATA));
}
});
});
@@ -455,62 +564,62 @@ describe('term.js addons', () => {
describe('scroll() function', () => {
describe('when scrollback > 0', () => {
it('should create a new line and scroll', () => {
term.buffer.lines.get(0).set(0, [0, 'a', 0, 'a'.charCodeAt(0)]);
term.buffer.lines.get(INIT_ROWS - 1).set(0, [0, 'b', 0, 'b'.charCodeAt(0)]);
term.buffer.lines.get(0).setCell(0, CellData.fromCharData([0, 'a', 0, 'a'.charCodeAt(0)]));
term.buffer.lines.get(INIT_ROWS - 1).setCell(0, CellData.fromCharData([0, 'b', 0, 'b'.charCodeAt(0)]));
term.buffer.y = INIT_ROWS - 1; // Move cursor to last line
term.scroll();
assert.equal(term.buffer.lines.length, INIT_ROWS + 1);
assert.equal(term.buffer.lines.get(0).get(0)[CHAR_DATA_CHAR_INDEX], 'a');
assert.equal(term.buffer.lines.get(INIT_ROWS - 1).get(0)[CHAR_DATA_CHAR_INDEX], 'b');
assert.equal(term.buffer.lines.get(INIT_ROWS).get(0)[CHAR_DATA_CHAR_INDEX], ' ');
assert.equal(term.buffer.lines.get(0).loadCell(0, new CellData()).getChars(), 'a');
assert.equal(term.buffer.lines.get(INIT_ROWS - 1).loadCell(0, new CellData()).getChars(), 'b');
assert.equal(term.buffer.lines.get(INIT_ROWS).loadCell(0, new CellData()).getChars(), '');
});
it('should properly scroll inside a scroll region (scrollTop set)', () => {
term.buffer.lines.get(0).set(0, [0, 'a', 0, 'a'.charCodeAt(0)]);
term.buffer.lines.get(1).set(0, [0, 'b', 0, 'b'.charCodeAt(0)]);
term.buffer.lines.get(2).set(0, [0, 'c', 0, 'c'.charCodeAt(0)]);
term.buffer.lines.get(0).setCell(0, CellData.fromCharData([0, 'a', 0, 'a'.charCodeAt(0)]));
term.buffer.lines.get(1).setCell(0, CellData.fromCharData([0, 'b', 0, 'b'.charCodeAt(0)]));
term.buffer.lines.get(2).setCell(0, CellData.fromCharData([0, 'c', 0, 'c'.charCodeAt(0)]));
term.buffer.y = INIT_ROWS - 1; // Move cursor to last line
term.buffer.scrollTop = 1;
term.scroll();
assert.equal(term.buffer.lines.length, INIT_ROWS);
assert.equal(term.buffer.lines.get(0).get(0)[CHAR_DATA_CHAR_INDEX], 'a');
assert.equal(term.buffer.lines.get(1).get(0)[CHAR_DATA_CHAR_INDEX], 'c');
assert.equal(term.buffer.lines.get(0).loadCell(0, new CellData()).getChars(), 'a');
assert.equal(term.buffer.lines.get(1).loadCell(0, new CellData()).getChars(), 'c');
});
it('should properly scroll inside a scroll region (scrollBottom set)', () => {
term.buffer.lines.get(0).set(0, [0, 'a', 0, 'a'.charCodeAt(0)]);
term.buffer.lines.get(1).set(0, [0, 'b', 0, 'b'.charCodeAt(0)]);
term.buffer.lines.get(2).set(0, [0, 'c', 0, 'c'.charCodeAt(0)]);
term.buffer.lines.get(3).set(0, [0, 'd', 0, 'd'.charCodeAt(0)]);
term.buffer.lines.get(4).set(0, [0, 'e', 0, 'e'.charCodeAt(0)]);
term.buffer.lines.get(0).setCell(0, CellData.fromCharData([0, 'a', 0, 'a'.charCodeAt(0)]));
term.buffer.lines.get(1).setCell(0, CellData.fromCharData([0, 'b', 0, 'b'.charCodeAt(0)]));
term.buffer.lines.get(2).setCell(0, CellData.fromCharData([0, 'c', 0, 'c'.charCodeAt(0)]));
term.buffer.lines.get(3).setCell(0, CellData.fromCharData([0, 'd', 0, 'd'.charCodeAt(0)]));
term.buffer.lines.get(4).setCell(0, CellData.fromCharData([0, 'e', 0, 'e'.charCodeAt(0)]));
term.buffer.y = 3;
term.buffer.scrollBottom = 3;
term.scroll();
assert.equal(term.buffer.lines.length, INIT_ROWS + 1);
assert.equal(term.buffer.lines.get(0).get(0)[CHAR_DATA_CHAR_INDEX], 'a', '\'a\' should be pushed to the scrollback');
assert.equal(term.buffer.lines.get(1).get(0)[CHAR_DATA_CHAR_INDEX], 'b');
assert.equal(term.buffer.lines.get(2).get(0)[CHAR_DATA_CHAR_INDEX], 'c');
assert.equal(term.buffer.lines.get(3).get(0)[CHAR_DATA_CHAR_INDEX], 'd');
assert.equal(term.buffer.lines.get(4).get(0)[CHAR_DATA_CHAR_INDEX], ' ', 'a blank line should be added at scrollBottom\'s index');
assert.equal(term.buffer.lines.get(5).get(0)[CHAR_DATA_CHAR_INDEX], 'e');
assert.equal(term.buffer.lines.get(0).loadCell(0, new CellData()).getChars(), 'a', '\'a\' should be pushed to the scrollback');
assert.equal(term.buffer.lines.get(1).loadCell(0, new CellData()).getChars(), 'b');
assert.equal(term.buffer.lines.get(2).loadCell(0, new CellData()).getChars(), 'c');
assert.equal(term.buffer.lines.get(3).loadCell(0, new CellData()).getChars(), 'd');
assert.equal(term.buffer.lines.get(4).loadCell(0, new CellData()).getChars(), '', 'a blank line should be added at scrollBottom\'s index');
assert.equal(term.buffer.lines.get(5).loadCell(0, new CellData()).getChars(), 'e');
});
it('should properly scroll inside a scroll region (scrollTop and scrollBottom set)', () => {
term.buffer.lines.get(0).set(0, [0, 'a', 0, 'a'.charCodeAt(0)]);
term.buffer.lines.get(1).set(0, [0, 'b', 0, 'b'.charCodeAt(0)]);
term.buffer.lines.get(2).set(0, [0, 'c', 0, 'c'.charCodeAt(0)]);
term.buffer.lines.get(3).set(0, [0, 'd', 0, 'd'.charCodeAt(0)]);
term.buffer.lines.get(4).set(0, [0, 'e', 0, 'e'.charCodeAt(0)]);
term.buffer.lines.get(0).setCell(0, CellData.fromCharData([0, 'a', 0, 'a'.charCodeAt(0)]));
term.buffer.lines.get(1).setCell(0, CellData.fromCharData([0, 'b', 0, 'b'.charCodeAt(0)]));
term.buffer.lines.get(2).setCell(0, CellData.fromCharData([0, 'c', 0, 'c'.charCodeAt(0)]));
term.buffer.lines.get(3).setCell(0, CellData.fromCharData([0, 'd', 0, 'd'.charCodeAt(0)]));
term.buffer.lines.get(4).setCell(0, CellData.fromCharData([0, 'e', 0, 'e'.charCodeAt(0)]));
term.buffer.y = INIT_ROWS - 1; // Move cursor to last line
term.buffer.scrollTop = 1;
term.buffer.scrollBottom = 3;
term.scroll();
assert.equal(term.buffer.lines.length, INIT_ROWS);
assert.equal(term.buffer.lines.get(0).get(0)[CHAR_DATA_CHAR_INDEX], 'a');
assert.equal(term.buffer.lines.get(1).get(0)[CHAR_DATA_CHAR_INDEX], 'c', '\'b\' should be removed from the buffer');
assert.equal(term.buffer.lines.get(2).get(0)[CHAR_DATA_CHAR_INDEX], 'd');
assert.equal(term.buffer.lines.get(3).get(0)[CHAR_DATA_CHAR_INDEX], ' ', 'a blank line should be added at scrollBottom\'s index');
assert.equal(term.buffer.lines.get(4).get(0)[CHAR_DATA_CHAR_INDEX], 'e');
assert.equal(term.buffer.lines.get(0).loadCell(0, new CellData()).getChars(), 'a');
assert.equal(term.buffer.lines.get(1).loadCell(0, new CellData()).getChars(), 'c', '\'b\' should be removed from the buffer');
assert.equal(term.buffer.lines.get(2).loadCell(0, new CellData()).getChars(), 'd');
assert.equal(term.buffer.lines.get(3).loadCell(0, new CellData()).getChars(), '', 'a blank line should be added at scrollBottom\'s index');
assert.equal(term.buffer.lines.get(4).loadCell(0, new CellData()).getChars(), 'e');
});
});
@@ -521,65 +630,65 @@ describe('term.js addons', () => {
});
it('should create a new line and shift everything up', () => {
term.buffer.lines.get(0).set(0, [0, 'a', 0, 'a'.charCodeAt(0)]);
term.buffer.lines.get(1).set(0, [0, 'b', 0, 'b'.charCodeAt(0)]);
term.buffer.lines.get(INIT_ROWS - 1).set(0, [0, 'c', 0, 'c'.charCodeAt(0)]);
term.buffer.lines.get(0).setCell(0, CellData.fromCharData([0, 'a', 0, 'a'.charCodeAt(0)]));
term.buffer.lines.get(1).setCell(0, CellData.fromCharData([0, 'b', 0, 'b'.charCodeAt(0)]));
term.buffer.lines.get(INIT_ROWS - 1).setCell(0, CellData.fromCharData([0, 'c', 0, 'c'.charCodeAt(0)]));
term.buffer.y = INIT_ROWS - 1; // Move cursor to last line
assert.equal(term.buffer.lines.length, INIT_ROWS);
term.scroll();
assert.equal(term.buffer.lines.length, INIT_ROWS);
// 'a' gets pushed out of buffer
assert.equal(term.buffer.lines.get(0).get(0)[CHAR_DATA_CHAR_INDEX], 'b');
assert.equal(term.buffer.lines.get(1).get(0)[CHAR_DATA_CHAR_INDEX], ' ');
assert.equal(term.buffer.lines.get(INIT_ROWS - 2).get(0)[CHAR_DATA_CHAR_INDEX], 'c');
assert.equal(term.buffer.lines.get(INIT_ROWS - 1).get(0)[CHAR_DATA_CHAR_INDEX], ' ');
assert.equal(term.buffer.lines.get(0).loadCell(0, new CellData()).getChars(), 'b');
assert.equal(term.buffer.lines.get(1).loadCell(0, new CellData()).getChars(), '');
assert.equal(term.buffer.lines.get(INIT_ROWS - 2).loadCell(0, new CellData()).getChars(), 'c');
assert.equal(term.buffer.lines.get(INIT_ROWS - 1).loadCell(0, new CellData()).getChars(), '');
});
it('should properly scroll inside a scroll region (scrollTop set)', () => {
term.buffer.lines.get(0).set(0, [0, 'a', 0, 'a'.charCodeAt(0)]);
term.buffer.lines.get(1).set(0, [0, 'b', 0, 'b'.charCodeAt(0)]);
term.buffer.lines.get(2).set(0, [0, 'c', 0, 'c'.charCodeAt(0)]);
term.buffer.lines.get(0).setCell(0, CellData.fromCharData([0, 'a', 0, 'a'.charCodeAt(0)]));
term.buffer.lines.get(1).setCell(0, CellData.fromCharData([0, 'b', 0, 'b'.charCodeAt(0)]));
term.buffer.lines.get(2).setCell(0, CellData.fromCharData([0, 'c', 0, 'c'.charCodeAt(0)]));
term.buffer.y = INIT_ROWS - 1; // Move cursor to last line
term.buffer.scrollTop = 1;
term.scroll();
assert.equal(term.buffer.lines.length, INIT_ROWS);
assert.equal(term.buffer.lines.get(0).get(0)[CHAR_DATA_CHAR_INDEX], 'a');
assert.equal(term.buffer.lines.get(1).get(0)[CHAR_DATA_CHAR_INDEX], 'c');
assert.equal(term.buffer.lines.get(0).loadCell(0, new CellData()).getChars(), 'a');
assert.equal(term.buffer.lines.get(1).loadCell(0, new CellData()).getChars(), 'c');
});
it('should properly scroll inside a scroll region (scrollBottom set)', () => {
term.buffer.lines.get(0).set(0, [0, 'a', 0, 'a'.charCodeAt(0)]);
term.buffer.lines.get(1).set(0, [0, 'b', 0, 'b'.charCodeAt(0)]);
term.buffer.lines.get(2).set(0, [0, 'c', 0, 'c'.charCodeAt(0)]);
term.buffer.lines.get(3).set(0, [0, 'd', 0, 'd'.charCodeAt(0)]);
term.buffer.lines.get(4).set(0, [0, 'e', 0, 'e'.charCodeAt(0)]);
term.buffer.lines.get(0).setCell(0, CellData.fromCharData([0, 'a', 0, 'a'.charCodeAt(0)]));
term.buffer.lines.get(1).setCell(0, CellData.fromCharData([0, 'b', 0, 'b'.charCodeAt(0)]));
term.buffer.lines.get(2).setCell(0, CellData.fromCharData([0, 'c', 0, 'c'.charCodeAt(0)]));
term.buffer.lines.get(3).setCell(0, CellData.fromCharData([0, 'd', 0, 'd'.charCodeAt(0)]));
term.buffer.lines.get(4).setCell(0, CellData.fromCharData([0, 'e', 0, 'e'.charCodeAt(0)]));
term.buffer.y = 3;
term.buffer.scrollBottom = 3;
term.scroll();
assert.equal(term.buffer.lines.length, INIT_ROWS);
assert.equal(term.buffer.lines.get(0).get(0)[CHAR_DATA_CHAR_INDEX], 'b');
assert.equal(term.buffer.lines.get(1).get(0)[CHAR_DATA_CHAR_INDEX], 'c');
assert.equal(term.buffer.lines.get(2).get(0)[CHAR_DATA_CHAR_INDEX], 'd');
assert.equal(term.buffer.lines.get(3).get(0)[CHAR_DATA_CHAR_INDEX], ' ', 'a blank line should be added at scrollBottom\'s index');
assert.equal(term.buffer.lines.get(4).get(0)[CHAR_DATA_CHAR_INDEX], 'e');
assert.equal(term.buffer.lines.get(0).loadCell(0, new CellData()).getChars(), 'b');
assert.equal(term.buffer.lines.get(1).loadCell(0, new CellData()).getChars(), 'c');
assert.equal(term.buffer.lines.get(2).loadCell(0, new CellData()).getChars(), 'd');
assert.equal(term.buffer.lines.get(3).loadCell(0, new CellData()).getChars(), '', 'a blank line should be added at scrollBottom\'s index');
assert.equal(term.buffer.lines.get(4).loadCell(0, new CellData()).getChars(), 'e');
});
it('should properly scroll inside a scroll region (scrollTop and scrollBottom set)', () => {
term.buffer.lines.get(0).set(0, [0, 'a', 0, 'a'.charCodeAt(0)]);
term.buffer.lines.get(1).set(0, [0, 'b', 0, 'b'.charCodeAt(0)]);
term.buffer.lines.get(2).set(0, [0, 'c', 0, 'c'.charCodeAt(0)]);
term.buffer.lines.get(3).set(0, [0, 'd', 0, 'd'.charCodeAt(0)]);
term.buffer.lines.get(4).set(0, [0, 'e', 0, 'e'.charCodeAt(0)]);
term.buffer.lines.get(0).setCell(0, CellData.fromCharData([0, 'a', 0, 'a'.charCodeAt(0)]));
term.buffer.lines.get(1).setCell(0, CellData.fromCharData([0, 'b', 0, 'b'.charCodeAt(0)]));
term.buffer.lines.get(2).setCell(0, CellData.fromCharData([0, 'c', 0, 'c'.charCodeAt(0)]));
term.buffer.lines.get(3).setCell(0, CellData.fromCharData([0, 'd', 0, 'd'.charCodeAt(0)]));
term.buffer.lines.get(4).setCell(0, CellData.fromCharData([0, 'e', 0, 'e'.charCodeAt(0)]));
term.buffer.y = INIT_ROWS - 1; // Move cursor to last line
term.buffer.scrollTop = 1;
term.buffer.scrollBottom = 3;
term.scroll();
assert.equal(term.buffer.lines.length, INIT_ROWS);
assert.equal(term.buffer.lines.get(0).get(0)[CHAR_DATA_CHAR_INDEX], 'a');
assert.equal(term.buffer.lines.get(1).get(0)[CHAR_DATA_CHAR_INDEX], 'c', '\'b\' should be removed from the buffer');
assert.equal(term.buffer.lines.get(2).get(0)[CHAR_DATA_CHAR_INDEX], 'd');
assert.equal(term.buffer.lines.get(3).get(0)[CHAR_DATA_CHAR_INDEX], ' ', 'a blank line should be added at scrollBottom\'s index');
assert.equal(term.buffer.lines.get(4).get(0)[CHAR_DATA_CHAR_INDEX], 'e');
assert.equal(term.buffer.lines.get(0).loadCell(0, new CellData()).getChars(), 'a');
assert.equal(term.buffer.lines.get(1).loadCell(0, new CellData()).getChars(), 'c', '\'b\' should be removed from the buffer');
assert.equal(term.buffer.lines.get(2).loadCell(0, new CellData()).getChars(), 'd');
assert.equal(term.buffer.lines.get(3).loadCell(0, new CellData()).getChars(), '', 'a blank line should be added at scrollBottom\'s index');
assert.equal(term.buffer.lines.get(4).loadCell(0, new CellData()).getChars(), 'e');
});
});
});
@@ -770,116 +879,126 @@ describe('term.js addons', () => {
it('2 characters per cell', function (): void {
this.timeout(10000); // This is needed because istanbul patches code and slows it down
const high = String.fromCharCode(0xD800);
const cell = new CellData();
for (let i = 0xDC00; i <= 0xDCFF; ++i) {
term.write(high + String.fromCharCode(i));
const tchar = term.buffer.lines.get(0).get(0);
expect(tchar[CHAR_DATA_CHAR_INDEX]).eql(high + String.fromCharCode(i));
expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(2);
expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(1);
expect(term.buffer.lines.get(0).get(1)[CHAR_DATA_CHAR_INDEX]).eql(' ');
const tchar = term.buffer.lines.get(0).loadCell(0, cell);
expect(tchar.getChars()).eql(high + String.fromCharCode(i));
expect(tchar.getChars().length).eql(2);
expect(tchar.getWidth()).eql(1);
expect(term.buffer.lines.get(0).loadCell(1, cell).getChars()).eql('');
term.reset();
}
});
it('2 characters at last cell', () => {
const high = String.fromCharCode(0xD800);
const cell = new CellData();
for (let i = 0xDC00; i <= 0xDCFF; ++i) {
term.buffer.x = term.cols - 1;
term.write(high + String.fromCharCode(i));
expect(term.buffer.lines.get(0).get(term.buffer.x - 1)[CHAR_DATA_CHAR_INDEX]).eql(high + String.fromCharCode(i));
expect(term.buffer.lines.get(0).get(term.buffer.x - 1)[CHAR_DATA_CHAR_INDEX].length).eql(2);
expect(term.buffer.lines.get(1).get(0)[CHAR_DATA_CHAR_INDEX]).eql(' ');
expect(term.buffer.lines.get(0).loadCell(term.buffer.x - 1, cell).getChars()).eql(high + String.fromCharCode(i));
expect(term.buffer.lines.get(0).loadCell(term.buffer.x - 1, cell).getChars().length).eql(2);
expect(term.buffer.lines.get(1).loadCell(0, cell).getChars()).eql('');
term.reset();
}
});
it('2 characters per cell over line end with autowrap', () => {
const high = String.fromCharCode(0xD800);
const cell = new CellData();
for (let i = 0xDC00; i <= 0xDCFF; ++i) {
term.buffer.x = term.cols - 1;
term.wraparoundMode = true;
term.write('a' + high + String.fromCharCode(i));
expect(term.buffer.lines.get(0).get(term.cols - 1)[CHAR_DATA_CHAR_INDEX]).eql('a');
expect(term.buffer.lines.get(1).get(0)[CHAR_DATA_CHAR_INDEX]).eql(high + String.fromCharCode(i));
expect(term.buffer.lines.get(1).get(0)[CHAR_DATA_CHAR_INDEX].length).eql(2);
expect(term.buffer.lines.get(1).get(1)[CHAR_DATA_CHAR_INDEX]).eql(' ');
expect(term.buffer.lines.get(0).loadCell(term.cols - 1, cell).getChars()).eql('a');
expect(term.buffer.lines.get(1).loadCell(0, cell).getChars()).eql(high + String.fromCharCode(i));
expect(term.buffer.lines.get(1).loadCell(0, cell).getChars().length).eql(2);
expect(term.buffer.lines.get(1).loadCell(1, cell).getChars()).eql('');
term.reset();
}
});
it('2 characters per cell over line end without autowrap', () => {
const high = String.fromCharCode(0xD800);
const cell = new CellData();
for (let i = 0xDC00; i <= 0xDCFF; ++i) {
term.buffer.x = term.cols - 1;
term.wraparoundMode = false;
term.write('a' + high + String.fromCharCode(i));
// auto wraparound mode should cut off the rest of the line
expect(term.buffer.lines.get(0).get(term.cols - 1)[CHAR_DATA_CHAR_INDEX]).eql('a');
expect(term.buffer.lines.get(0).get(term.cols - 1)[CHAR_DATA_CHAR_INDEX].length).eql(1);
expect(term.buffer.lines.get(1).get(1)[CHAR_DATA_CHAR_INDEX]).eql(' ');
expect(term.buffer.lines.get(0).loadCell(term.cols - 1, cell).getChars()).eql('a');
expect(term.buffer.lines.get(0).loadCell(term.cols - 1, cell).getChars().length).eql(1);
expect(term.buffer.lines.get(1).loadCell(1, cell).getChars()).eql('');
term.reset();
}
});
it('splitted surrogates', () => {
const high = String.fromCharCode(0xD800);
const cell = new CellData();
for (let i = 0xDC00; i <= 0xDCFF; ++i) {
term.write(high);
term.write(String.fromCharCode(i));
const tchar = term.buffer.lines.get(0).get(0);
expect(tchar[CHAR_DATA_CHAR_INDEX]).eql(high + String.fromCharCode(i));
expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(2);
expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(1);
expect(term.buffer.lines.get(0).get(1)[CHAR_DATA_CHAR_INDEX]).eql(' ');
const tchar = term.buffer.lines.get(0).loadCell(0, cell);
expect(tchar.getChars()).eql(high + String.fromCharCode(i));
expect(tchar.getChars().length).eql(2);
expect(tchar.getWidth()).eql(1);
expect(term.buffer.lines.get(0).loadCell(1, cell).getChars()).eql('');
term.reset();
}
});
});
describe('unicode - combining characters', () => {
const cell = new CellData();
it('café', () => {
term.write('cafe\u0301');
expect(term.buffer.lines.get(0).get(3)[CHAR_DATA_CHAR_INDEX]).eql('e\u0301');
expect(term.buffer.lines.get(0).get(3)[CHAR_DATA_CHAR_INDEX].length).eql(2);
expect(term.buffer.lines.get(0).get(3)[CHAR_DATA_WIDTH_INDEX]).eql(1);
term.buffer.lines.get(0).loadCell(3, cell);
expect(cell.getChars()).eql('e\u0301');
expect(cell.getChars().length).eql(2);
expect(cell.getWidth()).eql(1);
});
it('café - end of line', () => {
term.buffer.x = term.cols - 1 - 3;
term.write('cafe\u0301');
expect(term.buffer.lines.get(0).get(term.cols - 1)[CHAR_DATA_CHAR_INDEX]).eql('e\u0301');
expect(term.buffer.lines.get(0).get(term.cols - 1)[CHAR_DATA_CHAR_INDEX].length).eql(2);
expect(term.buffer.lines.get(0).get(term.cols - 1)[CHAR_DATA_WIDTH_INDEX]).eql(1);
expect(term.buffer.lines.get(0).get(1)[CHAR_DATA_CHAR_INDEX]).eql(' ');
expect(term.buffer.lines.get(0).get(1)[CHAR_DATA_CHAR_INDEX].length).eql(1);
expect(term.buffer.lines.get(0).get(1)[CHAR_DATA_WIDTH_INDEX]).eql(1);
term.buffer.lines.get(0).loadCell(term.cols - 1, cell);
expect(cell.getChars()).eql('e\u0301');
expect(cell.getChars().length).eql(2);
expect(cell.getWidth()).eql(1);
term.buffer.lines.get(0).loadCell(1, cell);
expect(cell.getChars()).eql('');
expect(cell.getChars().length).eql(0);
expect(cell.getWidth()).eql(1);
});
it('multiple combined é', () => {
term.wraparoundMode = true;
term.write(Array(100).join('e\u0301'));
for (let i = 0; i < term.cols; ++i) {
const tchar = term.buffer.lines.get(0).get(i);
expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('e\u0301');
expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(2);
expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(1);
term.buffer.lines.get(0).loadCell(i, cell);
expect(cell.getChars()).eql('e\u0301');
expect(cell.getChars().length).eql(2);
expect(cell.getWidth()).eql(1);
}
const tchar = term.buffer.lines.get(1).get(0);
expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('e\u0301');
expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(2);
expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(1);
term.buffer.lines.get(1).loadCell(0, cell);
expect(cell.getChars()).eql('e\u0301');
expect(cell.getChars().length).eql(2);
expect(cell.getWidth()).eql(1);
});
it('multiple surrogate with combined', () => {
term.wraparoundMode = true;
term.write(Array(100).join('\uD800\uDC00\u0301'));
for (let i = 0; i < term.cols; ++i) {
const tchar = term.buffer.lines.get(0).get(i);
expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('\uD800\uDC00\u0301');
expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(3);
expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(1);
term.buffer.lines.get(0).loadCell(i, cell);
expect(cell.getChars()).eql('\uD800\uDC00\u0301');
expect(cell.getChars().length).eql(3);
expect(cell.getWidth()).eql(1);
}
const tchar = term.buffer.lines.get(1).get(0);
expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('\uD800\uDC00\u0301');
expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(3);
expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(1);
term.buffer.lines.get(1).loadCell(0, cell);
expect(cell.getChars()).eql('\uD800\uDC00\u0301');
expect(cell.getChars().length).eql(3);
expect(cell.getWidth()).eql(1);
});
});
describe('unicode - fullwidth characters', () => {
const cell = new CellData();
it('cursor movement even', () => {
expect(term.buffer.x).eql(0);
term.write('¥');
@@ -895,140 +1014,141 @@ describe('term.js addons', () => {
term.wraparoundMode = true;
term.write(Array(50).join('¥'));
for (let i = 0; i < term.cols; ++i) {
const tchar = term.buffer.lines.get(0).get(i);
term.buffer.lines.get(0).loadCell(i, cell);
if (i % 2) {
expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('');
expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(0);
expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(0);
expect(cell.getChars()).eql('');
expect(cell.getChars().length).eql(0);
expect(cell.getWidth()).eql(0);
} else {
expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('¥');
expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(1);
expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(2);
expect(cell.getChars()).eql('¥');
expect(cell.getChars().length).eql(1);
expect(cell.getWidth()).eql(2);
}
}
const tchar = term.buffer.lines.get(1).get(0);
expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('¥');
expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(1);
expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(2);
term.buffer.lines.get(1).loadCell(0, cell);
expect(cell.getChars()).eql('¥');
expect(cell.getChars().length).eql(1);
expect(cell.getWidth()).eql(2);
});
it('line of ¥ odd', () => {
term.wraparoundMode = true;
term.buffer.x = 1;
term.write(Array(50).join('¥'));
for (let i = 1; i < term.cols - 1; ++i) {
const tchar = term.buffer.lines.get(0).get(i);
term.buffer.lines.get(0).loadCell(i, cell);
if (!(i % 2)) {
expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('');
expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(0);
expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(0);
expect(cell.getChars()).eql('');
expect(cell.getChars().length).eql(0);
expect(cell.getWidth()).eql(0);
} else {
expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('¥');
expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(1);
expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(2);
expect(cell.getChars()).eql('¥');
expect(cell.getChars().length).eql(1);
expect(cell.getWidth()).eql(2);
}
}
let tchar = term.buffer.lines.get(0).get(term.cols - 1);
expect(tchar[CHAR_DATA_CHAR_INDEX]).eql(' ');
expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(1);
expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(1);
tchar = term.buffer.lines.get(1).get(0);
expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('¥');
expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(1);
expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(2);
term.buffer.lines.get(0).loadCell(term.cols - 1, cell);
expect(cell.getChars()).eql('');
expect(cell.getChars().length).eql(0);
expect(cell.getWidth()).eql(1);
term.buffer.lines.get(1).loadCell(0, cell);
expect(cell.getChars()).eql('¥');
expect(cell.getChars().length).eql(1);
expect(cell.getWidth()).eql(2);
});
it('line of ¥ with combining odd', () => {
term.wraparoundMode = true;
term.buffer.x = 1;
term.write(Array(50).join('¥\u0301'));
for (let i = 1; i < term.cols - 1; ++i) {
const tchar = term.buffer.lines.get(0).get(i);
term.buffer.lines.get(0).loadCell(i, cell);
if (!(i % 2)) {
expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('');
expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(0);
expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(0);
expect(cell.getChars()).eql('');
expect(cell.getChars().length).eql(0);
expect(cell.getWidth()).eql(0);
} else {
expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('¥\u0301');
expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(2);
expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(2);
expect(cell.getChars()).eql('¥\u0301');
expect(cell.getChars().length).eql(2);
expect(cell.getWidth()).eql(2);
}
}
let tchar = term.buffer.lines.get(0).get(term.cols - 1);
expect(tchar[CHAR_DATA_CHAR_INDEX]).eql(' ');
expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(1);
expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(1);
tchar = term.buffer.lines.get(1).get(0);
expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('¥\u0301');
expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(2);
expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(2);
term.buffer.lines.get(0).loadCell(term.cols - 1, cell);
expect(cell.getChars()).eql('');
expect(cell.getChars().length).eql(0);
expect(cell.getWidth()).eql(1);
term.buffer.lines.get(1).loadCell(0, cell);
expect(cell.getChars()).eql('¥\u0301');
expect(cell.getChars().length).eql(2);
expect(cell.getWidth()).eql(2);
});
it('line of ¥ with combining even', () => {
term.wraparoundMode = true;
term.write(Array(50).join('¥\u0301'));
for (let i = 0; i < term.cols; ++i) {
const tchar = term.buffer.lines.get(0).get(i);
term.buffer.lines.get(0).loadCell(i, cell);
if (i % 2) {
expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('');
expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(0);
expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(0);
expect(cell.getChars()).eql('');
expect(cell.getChars().length).eql(0);
expect(cell.getWidth()).eql(0);
} else {
expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('¥\u0301');
expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(2);
expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(2);
expect(cell.getChars()).eql('¥\u0301');
expect(cell.getChars().length).eql(2);
expect(cell.getWidth()).eql(2);
}
}
const tchar = term.buffer.lines.get(1).get(0);
expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('¥\u0301');
expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(2);
expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(2);
term.buffer.lines.get(1).loadCell(0, cell);
expect(cell.getChars()).eql('¥\u0301');
expect(cell.getChars().length).eql(2);
expect(cell.getWidth()).eql(2);
});
it('line of surrogate fullwidth with combining odd', () => {
term.wraparoundMode = true;
term.buffer.x = 1;
term.write(Array(50).join('\ud843\ude6d\u0301'));
for (let i = 1; i < term.cols - 1; ++i) {
const tchar = term.buffer.lines.get(0).get(i);
term.buffer.lines.get(0).loadCell(i, cell);
if (!(i % 2)) {
expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('');
expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(0);
expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(0);
expect(cell.getChars()).eql('');
expect(cell.getChars().length).eql(0);
expect(cell.getWidth()).eql(0);
} else {
expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('\ud843\ude6d\u0301');
expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(3);
expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(2);
expect(cell.getChars()).eql('\ud843\ude6d\u0301');
expect(cell.getChars().length).eql(3);
expect(cell.getWidth()).eql(2);
}
}
let tchar = term.buffer.lines.get(0).get(term.cols - 1);
expect(tchar[CHAR_DATA_CHAR_INDEX]).eql(' ');
expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(1);
expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(1);
tchar = term.buffer.lines.get(1).get(0);
expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('\ud843\ude6d\u0301');
expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(3);
expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(2);
term.buffer.lines.get(0).loadCell(term.cols - 1, cell);
expect(cell.getChars()).eql('');
expect(cell.getChars().length).eql(0);
expect(cell.getWidth()).eql(1);
term.buffer.lines.get(1).loadCell(0, cell);
expect(cell.getChars()).eql('\ud843\ude6d\u0301');
expect(cell.getChars().length).eql(3);
expect(cell.getWidth()).eql(2);
});
it('line of surrogate fullwidth with combining even', () => {
term.wraparoundMode = true;
term.write(Array(50).join('\ud843\ude6d\u0301'));
for (let i = 0; i < term.cols; ++i) {
const tchar = term.buffer.lines.get(0).get(i);
term.buffer.lines.get(0).loadCell(i, cell);
if (i % 2) {
expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('');
expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(0);
expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(0);
expect(cell.getChars()).eql('');
expect(cell.getChars().length).eql(0);
expect(cell.getWidth()).eql(0);
} else {
expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('\ud843\ude6d\u0301');
expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(3);
expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(2);
expect(cell.getChars()).eql('\ud843\ude6d\u0301');
expect(cell.getChars().length).eql(3);
expect(cell.getWidth()).eql(2);
}
}
const tchar = term.buffer.lines.get(1).get(0);
expect(tchar[CHAR_DATA_CHAR_INDEX]).eql('\ud843\ude6d\u0301');
expect(tchar[CHAR_DATA_CHAR_INDEX].length).eql(3);
expect(tchar[CHAR_DATA_WIDTH_INDEX]).eql(2);
term.buffer.lines.get(1).loadCell(0, cell);
expect(cell.getChars()).eql('\ud843\ude6d\u0301');
expect(cell.getChars().length).eql(3);
expect(cell.getWidth()).eql(2);
});
});
describe('insert mode', () => {
const cell = new CellData();
it('halfwidth - all', () => {
term.write(Array(9).join('0123456789').slice(-80));
term.buffer.x = 10;
@@ -1036,10 +1156,10 @@ describe('term.js addons', () => {
term.insertMode = true;
term.write('abcde');
expect(term.buffer.lines.get(0).length).eql(term.cols);
expect(term.buffer.lines.get(0).get(10)[CHAR_DATA_CHAR_INDEX]).eql('a');
expect(term.buffer.lines.get(0).get(14)[CHAR_DATA_CHAR_INDEX]).eql('e');
expect(term.buffer.lines.get(0).get(15)[CHAR_DATA_CHAR_INDEX]).eql('0');
expect(term.buffer.lines.get(0).get(79)[CHAR_DATA_CHAR_INDEX]).eql('4');
expect(term.buffer.lines.get(0).loadCell(10, cell).getChars()).eql('a');
expect(term.buffer.lines.get(0).loadCell(14, cell).getChars()).eql('e');
expect(term.buffer.lines.get(0).loadCell(15, cell).getChars()).eql('0');
expect(term.buffer.lines.get(0).loadCell(79, cell).getChars()).eql('4');
});
it('fullwidth - insert', () => {
term.write(Array(9).join('0123456789').slice(-80));
@@ -1048,11 +1168,11 @@ describe('term.js addons', () => {
term.insertMode = true;
term.write('¥¥¥');
expect(term.buffer.lines.get(0).length).eql(term.cols);
expect(term.buffer.lines.get(0).get(10)[CHAR_DATA_CHAR_INDEX]).eql('¥');
expect(term.buffer.lines.get(0).get(11)[CHAR_DATA_CHAR_INDEX]).eql('');
expect(term.buffer.lines.get(0).get(14)[CHAR_DATA_CHAR_INDEX]).eql('¥');
expect(term.buffer.lines.get(0).get(15)[CHAR_DATA_CHAR_INDEX]).eql('');
expect(term.buffer.lines.get(0).get(79)[CHAR_DATA_CHAR_INDEX]).eql('3');
expect(term.buffer.lines.get(0).loadCell(10, cell).getChars()).eql('¥');
expect(term.buffer.lines.get(0).loadCell(11, cell).getChars()).eql('');
expect(term.buffer.lines.get(0).loadCell(14, cell).getChars()).eql('¥');
expect(term.buffer.lines.get(0).loadCell(15, cell).getChars()).eql('');
expect(term.buffer.lines.get(0).loadCell(79, cell).getChars()).eql('3');
});
it('fullwidth - right border', () => {
term.write(Array(41).join('¥'));
@@ -1061,14 +1181,14 @@ describe('term.js addons', () => {
term.insertMode = true;
term.write('a');
expect(term.buffer.lines.get(0).length).eql(term.cols);
expect(term.buffer.lines.get(0).get(10)[CHAR_DATA_CHAR_INDEX]).eql('a');
expect(term.buffer.lines.get(0).get(11)[CHAR_DATA_CHAR_INDEX]).eql('¥');
expect(term.buffer.lines.get(0).get(79)[CHAR_DATA_CHAR_INDEX]).eql(' '); // fullwidth char got replaced
expect(term.buffer.lines.get(0).loadCell(10, cell).getChars()).eql('a');
expect(term.buffer.lines.get(0).loadCell(11, cell).getChars()).eql('¥');
expect(term.buffer.lines.get(0).loadCell(79, cell).getChars()).eql(''); // fullwidth char got replaced
term.write('b');
expect(term.buffer.lines.get(0).length).eql(term.cols);
expect(term.buffer.lines.get(0).get(11)[CHAR_DATA_CHAR_INDEX]).eql('b');
expect(term.buffer.lines.get(0).get(12)[CHAR_DATA_CHAR_INDEX]).eql('¥');
expect(term.buffer.lines.get(0).get(79)[CHAR_DATA_CHAR_INDEX]).eql(''); // empty cell after fullwidth
expect(term.buffer.lines.get(0).loadCell(11, cell).getChars()).eql('b');
expect(term.buffer.lines.get(0).loadCell(12, cell).getChars()).eql('¥');
expect(term.buffer.lines.get(0).loadCell(79, cell).getChars()).eql(''); // empty cell after fullwidth
});
});
});
+344 -227
View File
File diff suppressed because it is too large Load Diff
@@ -3,13 +3,16 @@
* @license MIT
*/
import { IColorSet, IRenderer, IRenderDimensions, IColorManager } from '../renderer/Types';
import { IInputHandlingTerminal, IViewport, ICompositionHelper, ITerminal, IBuffer, IBufferSet, IBrowser, ICharMeasure, ISelectionManager, ITerminalOptions, ILinkifier, IMouseHelper, ILinkMatcherOptions, CharacterJoinerHandler, IBufferLine, IBufferStringIterator } from '../Types';
import { ICircularList, XtermListener } from '../common/Types';
import { Buffer } from '../Buffer';
import * as Browser from '../core/Platform';
import { ITheme, IDisposable, IMarker } from 'xterm';
import { Terminal } from '../Terminal';
import { IRenderer, IRenderDimensions } from './renderer/Types';
import { IInputHandlingTerminal, IViewport, ICompositionHelper, ITerminal, IBuffer, IBufferSet, IBrowser, ICharMeasure, ISelectionManager, ITerminalOptions, ILinkifier, IMouseHelper, ILinkMatcherOptions, CharacterJoinerHandler, IBufferStringIterator } from './Types';
import { IBufferLine, ICellData, IAttributeData } from './core/Types';
import { ICircularList, XtermListener } from './common/Types';
import { Buffer } from './Buffer';
import * as Browser from './common/Platform';
import { IDisposable, IMarker, IEvent, ISelectionPosition } from 'xterm';
import { Terminal } from './Terminal';
import { AttributeData } from './core/buffer/BufferLine';
import { IColorManager, IColorSet } from './ui/Types';
export class TestTerminal extends Terminal {
writeSync(data: string): void {
@@ -19,6 +22,15 @@ export class TestTerminal extends Terminal {
}
export class MockTerminal implements ITerminal {
onCursorMove: IEvent<void>;
onLineFeed: IEvent<void>;
onSelectionChange: IEvent<void>;
onData: IEvent<string>;
onTitleChange: IEvent<string>;
onScroll: IEvent<number>;
onKey: IEvent<{ key: string; domEvent: KeyboardEvent; }>;
onRender: IEvent<{ start: number; end: number; }>;
onResize: IEvent<{ cols: number; rows: number; }>;
markers: IMarker[];
addMarker(cursorYOffset: number): IMarker {
throw new Error('Method not implemented.');
@@ -54,6 +66,12 @@ export class MockTerminal implements ITerminal {
attachCustomKeyEventHandler(customKeyEventHandler: (event: KeyboardEvent) => boolean): void {
throw new Error('Method not implemented.');
}
addCsiHandler(flag: string, callback: (params: number[], collect: string) => boolean): IDisposable {
throw new Error('Method not implemented.');
}
addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable {
throw new Error('Method not implemented.');
}
registerLinkMatcher(regex: RegExp, handler: (event: MouseEvent, uri: string) => boolean | void, options?: ILinkMatcherOptions): number {
throw new Error('Method not implemented.');
}
@@ -66,9 +84,15 @@ export class MockTerminal implements ITerminal {
getSelection(): string {
throw new Error('Method not implemented.');
}
getSelectionPosition(): ISelectionPosition | undefined {
throw new Error('Method not implemented.');
}
clearSelection(): void {
throw new Error('Method not implemented.');
}
select(column: number, row: number, length: number): void {
throw new Error('Method not implemented.');
}
selectAll(): void {
throw new Error('Method not implemented.');
}
@@ -93,6 +117,9 @@ export class MockTerminal implements ITerminal {
write(data: string): void {
throw new Error('Method not implemented.');
}
writeUtf8(data: Uint8Array): void {
throw new Error('Method not implemented.');
}
bracketedPasteMode: boolean;
mouseHelper: IMouseHelper;
renderer: IRenderer;
@@ -159,6 +186,7 @@ export class MockTerminal implements ITerminal {
}
export class MockCharMeasure implements ICharMeasure {
onCharSizeChanged: IEvent<void>;
width: number;
height: number;
measure(options: ITerminalOptions): void {
@@ -181,7 +209,7 @@ export class MockInputHandlingTerminal implements IInputHandlingTerminal {
insertMode: boolean;
wraparoundMode: boolean;
bracketedPasteMode: boolean;
curAttr: number;
curAttrData = new AttributeData();
savedCols: number;
x10Mouse: boolean;
vt200Mouse: boolean;
@@ -216,7 +244,7 @@ export class MockInputHandlingTerminal implements IInputHandlingTerminal {
setgLevel(g: number): void {
throw new Error('Method not implemented.');
}
eraseAttr(): number {
eraseAttrData(): IAttributeData {
throw new Error('Method not implemented.');
}
eraseRight(x: number, y: number): void {
@@ -303,7 +331,7 @@ export class MockBuffer implements IBuffer {
scrollTop: number;
savedY: number;
savedX: number;
savedCurAttr: number;
savedCurAttrData = new AttributeData();
translateBufferLineToString(lineIndex: number, trimRight: boolean, startCol?: number, endCol?: number): string {
return Buffer.prototype.translateBufferLineToString.apply(this, arguments);
}
@@ -319,7 +347,7 @@ export class MockBuffer implements IBuffer {
setLines(lines: ICircularList<IBufferLine>): void {
this.lines = lines;
}
getBlankLine(attr: number, isWrapped?: boolean): IBufferLine {
getBlankLine(attr: IAttributeData, isWrapped?: boolean): IBufferLine {
return Buffer.prototype.getBlankLine.apply(this, arguments);
}
stringIndexToBufferIndex(lineIndex: number, stringIndex: number): number[] {
@@ -328,9 +356,17 @@ export class MockBuffer implements IBuffer {
iterator(trimRight: boolean, startIndex?: number, endIndex?: number): IBufferStringIterator {
return Buffer.prototype.iterator.apply(this, arguments);
}
getNullCell(attr?: IAttributeData): ICellData {
throw new Error('Method not implemented.');
}
getWhitespaceCell(attr?: IAttributeData): ICellData {
throw new Error('Method not implemented.');
}
}
export class MockRenderer implements IRenderer {
onCanvasResize: IEvent<{ width: number; height: number; }>;
onRender: IEvent<{ start: number; end: number; }>;
dispose(): void {
throw new Error('Method not implemented.');
}
@@ -348,7 +384,9 @@ export class MockRenderer implements IRenderer {
throw new Error('Method not implemented.');
}
dimensions: IRenderDimensions;
setTheme(theme: ITheme): IColorSet { return <IColorSet>{}; }
setColors(colors: IColorSet): void {
throw new Error('Method not implemented.');
}
onResize(cols: number, rows: number): void {}
onCharSizeChanged(): void {}
onBlur(): void {}
@@ -356,9 +394,9 @@ export class MockRenderer implements IRenderer {
onSelectionChanged(start: [number, number], end: [number, number]): void {}
onCursorMove(): void {}
onOptionsChanged(): void {}
onWindowResize(devicePixelRatio: number): void {}
onDevicePixelRatioChange(): void {}
clear(): void {}
refreshRows(start: number, end: number): void {}
renderRows(start: number, end: number): void {}
registerCharacterJoiner(handler: CharacterJoinerHandler): number { return 0; }
deregisterCharacterJoiner(): boolean { return true; }
}
@@ -368,7 +406,7 @@ export class MockViewport implements IViewport {
throw new Error('Method not implemented.');
}
scrollBarWidth: number = 0;
onThemeChanged(colors: IColorSet): void {
onThemeChange(colors: IColorSet): void {
throw new Error('Method not implemented.');
}
onWheel(ev: WheelEvent): void {
+117 -54
View File
@@ -3,15 +3,14 @@
* @license MIT
*/
import { Terminal as PublicTerminal, ITerminalOptions as IPublicTerminalOptions, IEventEmitter, IDisposable } from 'xterm';
import { IColorSet, IRenderer } from './renderer/Types';
import { IMouseZoneManager } from './ui/Types';
import { ICharset } from './core/Types';
import { ITerminalOptions as IPublicTerminalOptions, IEventEmitter, IDisposable, IMarker, ISelectionPosition } from 'xterm';
import { ICharset, IAttributeData, ICellData, IBufferLine, CharData } from './core/Types';
import { ICircularList } from './common/Types';
import { IEvent } from './common/EventEmitter2';
import { IColorSet } from './ui/Types';
export type CustomKeyEventHandler = (event: KeyboardEvent) => boolean;
export type CharData = [number, string, number, number];
export type LineData = CharData[];
export type LinkMatcherHandler = (event: MouseEvent, uri: string) => void;
@@ -22,12 +21,6 @@ export type CharacterJoinerHandler = (text: string) => [number, number][];
// BufferIndex denotes a position in the buffer: [rowIndex, colIndex]
export type BufferIndex = [number, number];
export const enum LinkHoverEventTypes {
HOVER = 'linkhover',
TOOLTIP = 'linktooltip',
LEAVE = 'linkleave'
}
/**
* This interface encapsulates everything needed from the Terminal by the
* InputHandler. This cleanly separates the large amount of methods needed by
@@ -48,7 +41,7 @@ export interface IInputHandlingTerminal extends IEventEmitter {
insertMode: boolean;
wraparoundMode: boolean;
bracketedPasteMode: boolean;
curAttr: number;
curAttrData: IAttributeData;
savedCols: number;
x10Mouse: boolean;
vt200Mouse: boolean;
@@ -70,7 +63,7 @@ export interface IInputHandlingTerminal extends IEventEmitter {
updateRange(y: number): void;
scroll(isWrapped?: boolean): void;
setgLevel(g: number): void;
eraseAttr(): number;
eraseAttrData(): IAttributeData;
is(term: string): boolean;
setgCharset(g: number, charset: ICharset): void;
resize(x: number, y: number): void;
@@ -78,7 +71,6 @@ export interface IInputHandlingTerminal extends IEventEmitter {
reset(): void;
showCursor(): void;
refresh(start: number, end: number): void;
matchColor(r1: number, g1: number, b1: number): number;
error(text: string, data?: any): void;
setOption(key: string, value: any): void;
tabSet(): void;
@@ -95,7 +87,7 @@ export interface IViewport extends IDisposable {
onWheel(ev: WheelEvent): void;
onTouchStart(ev: TouchEvent): void;
onTouchMove(ev: TouchEvent): void;
onThemeChanged(colors: IColorSet): void;
onThemeChange(colors: IColorSet): void;
}
export interface ICompositionHelper {
@@ -111,7 +103,8 @@ export interface ICompositionHelper {
*/
export interface IInputHandler {
parse(data: string): void;
print(data: string, start: number, end: number): void;
parseUtf8(data: Uint8Array): void;
print(data: Uint32Array, start: number, end: number): void;
/** C0 BEL */ bell(): void;
/** C0 LF */ lineFeed(): void;
@@ -194,7 +187,7 @@ export interface ILinkMatcher {
willLinkActivate?: (event: MouseEvent, uri: string) => boolean;
}
export interface ILinkHoverEvent {
export interface ILinkifierEvent {
x1: number;
y1: number;
x2: number;
@@ -203,11 +196,10 @@ export interface ILinkHoverEvent {
fg: number;
}
export interface ITerminal extends PublicTerminal, IElementAccessor, IBufferAccessor, ILinkifierAccessor {
export interface ITerminal extends IPublicTerminal, IElementAccessor, IBufferAccessor, ILinkifierAccessor {
screenElement: HTMLElement;
selectionManager: ISelectionManager;
charMeasure: ICharMeasure;
renderer: IRenderer;
browser: IBrowser;
writeBuffer: string[];
cursorHidden: boolean;
@@ -221,10 +213,6 @@ export interface ITerminal extends PublicTerminal, IElementAccessor, IBufferAcce
bracketedPasteMode: boolean;
applicationCursor: boolean;
/**
* Emit the 'data' event and populate the given data.
* @param data The data to populate in the event.
*/
handler(data: string): void;
scrollLines(disp: number, suppressScrollEvent?: boolean): void;
cancel(ev: Event, force?: boolean): boolean | void;
@@ -232,12 +220,64 @@ export interface ITerminal extends PublicTerminal, IElementAccessor, IBufferAcce
showCursor(): void;
}
// Portions of the public API that are required by the internal Terminal
export interface IPublicTerminal extends IDisposable, IEventEmitter {
textarea: HTMLTextAreaElement;
rows: number;
cols: number;
buffer: IBuffer;
markers: IMarker[];
onCursorMove: IEvent<void>;
onData: IEvent<string>;
onKey: IEvent<{ key: string, domEvent: KeyboardEvent }>;
onLineFeed: IEvent<void>;
onScroll: IEvent<number>;
onSelectionChange: IEvent<void>;
onRender: IEvent<{ start: number, end: number }>;
onResize: IEvent<{ cols: number, rows: number }>;
onTitleChange: IEvent<string>;
blur(): void;
focus(): void;
resize(columns: number, rows: number): void;
writeln(data: string): void;
open(parent: HTMLElement): void;
attachCustomKeyEventHandler(customKeyEventHandler: (event: KeyboardEvent) => boolean): void;
addCsiHandler(flag: string, callback: (params: number[], collect: string) => boolean): IDisposable;
addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable;
registerLinkMatcher(regex: RegExp, handler: (event: MouseEvent, uri: string) => void, options?: ILinkMatcherOptions): number;
deregisterLinkMatcher(matcherId: number): void;
registerCharacterJoiner(handler: (text: string) => [number, number][]): number;
deregisterCharacterJoiner(joinerId: number): void;
addMarker(cursorYOffset: number): IMarker;
hasSelection(): boolean;
getSelection(): string;
getSelectionPosition(): ISelectionPosition | undefined;
clearSelection(): void;
select(column: number, row: number, length: number): void;
selectAll(): void;
selectLines(start: number, end: number): void;
dispose(): void;
destroy(): void;
scrollLines(amount: number): void;
scrollPages(pageCount: number): void;
scrollToTop(): void;
scrollToBottom(): void;
scrollToLine(line: number): void;
clear(): void;
write(data: string): void;
writeUtf8(data: Uint8Array): void;
getOption(key: string): any;
setOption(key: string, value: any): void;
refresh(start: number, end: number): void;
reset(): void;
}
export interface IBufferAccessor {
buffer: IBuffer;
}
export interface IElementAccessor {
element: HTMLElement;
readonly element: HTMLElement;
}
export interface ILinkifierAccessor {
@@ -245,13 +285,16 @@ export interface ILinkifierAccessor {
}
export interface IMouseHelper {
getCoords(event: { pageX: number, pageY: number }, element: HTMLElement, charMeasure: ICharMeasure, colCount: number, rowCount: number, isSelection?: boolean): [number, number];
getCoords(event: { clientX: number, clientY: number }, element: HTMLElement, charMeasure: ICharMeasure, colCount: number, rowCount: number, isSelection?: boolean): [number, number];
getRawByteCoords(event: MouseEvent, element: HTMLElement, charMeasure: ICharMeasure, colCount: number, rowCount: number): { x: number, y: number };
}
export interface ICharMeasure {
width: number;
height: number;
onCharSizeChanged: IEvent<void>;
measure(options: ITerminalOptions): void;
}
@@ -289,24 +332,28 @@ export interface IBuffer {
hasScrollback: boolean;
savedY: number;
savedX: number;
savedCurAttr: number;
savedCurAttrData: IAttributeData;
isCursorInViewport: boolean;
translateBufferLineToString(lineIndex: number, trimRight: boolean, startCol?: number, endCol?: number): string;
getWrappedRangeForLine(y: number): { first: number, last: number };
nextStop(x?: number): number;
prevStop(x?: number): number;
getBlankLine(attr: number, isWrapped?: boolean): IBufferLine;
getBlankLine(attr: IAttributeData, isWrapped?: boolean): IBufferLine;
stringIndexToBufferIndex(lineIndex: number, stringIndex: number): number[];
iterator(trimRight: boolean, startIndex?: number, endIndex?: number, startOverscan?: number, endOverscan?: number): IBufferStringIterator;
getNullCell(attr?: IAttributeData): ICellData;
getWhitespaceCell(attr?: IAttributeData): ICellData;
}
export interface IBufferSet extends IEventEmitter {
export interface IBufferSet {
alt: IBuffer;
normal: IBuffer;
active: IBuffer;
onBufferActivate: IEvent<{ activeBuffer: IBuffer, inactiveBuffer: IBuffer }>;
activateNormalBuffer(): void;
activateAltBuffer(fillAttr?: number): void;
activateAltBuffer(fillAttr?: IAttributeData): void;
}
export interface ISelectionManager {
@@ -321,7 +368,17 @@ export interface ISelectionManager {
selectWordAtCursor(event: MouseEvent): void;
}
export interface ILinkifier extends IEventEmitter {
export interface ISelectionRedrawRequestEvent {
start: [number, number];
end: [number, number];
columnSelectMode: boolean;
}
export interface ILinkifier {
onLinkHover: IEvent<ILinkifierEvent>;
onLinkLeave: IEvent<ILinkifierEvent>;
onLinkTooltip: IEvent<ILinkifierEvent>;
attachToDom(mouseZoneManager: IMouseZoneManager): void;
linkifyRows(start: number, end: number): void;
registerLinkMatcher(regex: RegExp, handler: LinkMatcherHandler, options?: ILinkMatcherOptions): number;
@@ -452,18 +509,26 @@ export interface IParsingState {
* DCS handler signature for EscapeSequenceParser.
* EscapeSequenceParser handles DCS commands via separate
* subparsers that get hook/unhooked and can handle
* arbitrary amount of print data.
* arbitrary amount of data.
*
* On entering a DSC sequence `hook` is called by
* `EscapeSequenceParser`. Use it to initialize or reset
* states needed to handle the current DCS sequence.
* Note: A DCS parser is only instantiated once, therefore
* you cannot rely on the ctor to reinitialize state.
*
* EscapeSequenceParser will call `put` several times if the
* parsed string got splitted, therefore you might have to collect
* `data` until `unhook` is called. `unhook` marks the end
* of the current DCS sequence.
* parsed data got split, therefore you might have to collect
* `data` until `unhook` is called.
* Note: `data` is borrowed, if you cannot process the data
* in chunks you have to copy it, doing otherwise will lead to
* data losses or corruption.
*
* `unhook` marks the end of the current DCS sequence.
*/
export interface IDcsHandler {
hook(collect: string, params: number[], flag: number): void;
put(data: string, start: number, end: number): void;
put(data: Uint32Array, start: number, end: number): void;
unhook(): void;
}
@@ -480,9 +545,9 @@ export interface IEscapeSequenceParser extends IDisposable {
* Parse string `data`.
* @param data The data to parse.
*/
parse(data: string): void;
parse(data: Uint32Array, length: number): void;
setPrintHandler(callback: (data: string, start: number, end: number) => void): void;
setPrintHandler(callback: (data: Uint32Array, start: number, end: number) => void): void;
clearPrintHandler(): void;
setExecuteHandler(flag: string, callback: () => void): void;
@@ -492,6 +557,8 @@ export interface IEscapeSequenceParser extends IDisposable {
setCsiHandler(flag: string, callback: (params: number[], collect: string) => void): void;
clearCsiHandler(flag: string): void;
setCsiHandlerFallback(callback: (collect: string, params: number[], flag: number) => void): void;
addCsiHandler(flag: string, callback: (params: number[], collect: string) => boolean): IDisposable;
addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable;
setEscHandler(collectAndFlag: string, callback: () => void): void;
clearEscHandler(collectAndFlag: string): void;
@@ -509,23 +576,19 @@ export interface IEscapeSequenceParser extends IDisposable {
clearErrorHandler(): void;
}
/**
* Interface for a line in the terminal buffer.
*/
export interface IBufferLine {
length: number;
isWrapped: boolean;
get(index: number): CharData;
set(index: number, value: CharData): void;
insertCells(pos: number, n: number, ch: CharData): void;
deleteCells(pos: number, n: number, fill: CharData): void;
replaceCells(start: number, end: number, fill: CharData): void;
resize(cols: number, fill: CharData, shrink?: boolean): void;
fill(fillCharData: CharData): void;
copyFrom(line: IBufferLine): void;
clone(): IBufferLine;
export interface IMouseZoneManager extends IDisposable {
add(zone: IMouseZone): void;
clearAll(start?: number, end?: number): void;
}
export interface IBufferLineConstructor {
new(cols: number, fillCharData?: CharData, isWrapped?: boolean): IBufferLine;
export interface IMouseZone {
x1: number;
x2: number;
y1: number;
y2: number;
clickCallback: (e: MouseEvent) => any;
hoverCallback: (e: MouseEvent) => any | undefined;
tooltipCallback: (e: MouseEvent) => any | undefined;
leaveCallback: () => any | undefined;
willLinkActivate: (e: MouseEvent) => boolean;
}
+14 -8
View File
@@ -3,11 +3,12 @@
* @license MIT
*/
import { IColorSet } from './renderer/Types';
import { ITerminal, IViewport } from './Types';
import { CharMeasure } from './ui/CharMeasure';
import { CharMeasure } from './CharMeasure';
import { Disposable } from './common/Lifecycle';
import { addDisposableDomListener } from './ui/Lifecycle';
import { IColorSet } from './ui/Types';
import { IRenderDimensions } from './renderer/Types';
const FALLBACK_SCROLL_BAR_WIDTH = 15;
@@ -43,7 +44,8 @@ export class Viewport extends Disposable implements IViewport {
private _terminal: ITerminal,
private _viewportElement: HTMLElement,
private _scrollArea: HTMLElement,
private _charMeasure: CharMeasure
private _charMeasure: CharMeasure,
private _dimensions: IRenderDimensions
) {
super();
@@ -57,7 +59,11 @@ export class Viewport extends Disposable implements IViewport {
setTimeout(() => this.syncScrollArea(), 0);
}
public onThemeChanged(colors: IColorSet): void {
public onDimensionsChance(dimensions: IRenderDimensions): void {
this._dimensions = dimensions;
}
public onThemeChange(colors: IColorSet): void {
this._viewportElement.style.backgroundColor = colors.background.css;
}
@@ -73,9 +79,9 @@ export class Viewport extends Disposable implements IViewport {
private _innerRefresh(): void {
if (this._charMeasure.height > 0) {
this._currentRowHeight = this._terminal.renderer.dimensions.scaledCellHeight / window.devicePixelRatio;
this._currentRowHeight = this._dimensions.scaledCellHeight / window.devicePixelRatio;
this._lastRecordedViewportHeight = this._viewportElement.offsetHeight;
const newBufferHeight = Math.round(this._currentRowHeight * this._lastRecordedBufferLength) + (this._lastRecordedViewportHeight - this._terminal.renderer.dimensions.canvasHeight);
const newBufferHeight = Math.round(this._currentRowHeight * this._lastRecordedBufferLength) + (this._lastRecordedViewportHeight - this._dimensions.canvasHeight);
if (this._lastRecordedBufferHeight !== newBufferHeight) {
this._lastRecordedBufferHeight = newBufferHeight;
this._scrollArea.style.height = this._lastRecordedBufferHeight + 'px';
@@ -106,7 +112,7 @@ export class Viewport extends Disposable implements IViewport {
}
// If viewport height changed
if (this._lastRecordedViewportHeight !== (<any>this._terminal).renderer.dimensions.canvasHeight) {
if (this._lastRecordedViewportHeight !== this._dimensions.canvasHeight) {
this._refresh();
return;
}
@@ -125,7 +131,7 @@ export class Viewport extends Disposable implements IViewport {
}
// If row height changed
if (this._terminal.renderer.dimensions.scaledCellHeight / window.devicePixelRatio !== this._currentRowHeight) {
if (this._dimensions.scaledCellHeight / window.devicePixelRatio !== this._currentRowHeight) {
this._refresh();
return;
}
+30
View File
@@ -0,0 +1,30 @@
/**
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
* @license MIT
*/
import { IDisposable } from 'xterm';
import { ITerminal } from './Types';
import { CHAR_DATA_CODE_INDEX, NULL_CELL_CODE, WHITESPACE_CELL_CODE } from './core/buffer/BufferLine';
export function applyWindowsMode(terminal: ITerminal): IDisposable {
// Winpty does not support wraparound mode which means that lines will never
// be marked as wrapped. This causes issues for things like copying a line
// retaining the wrapped new line characters or if consumers are listening
// in on the data stream.
//
// The workaround for this is to listen to every incoming line feed and mark
// the line as wrapped if the last character in the previous line is not a
// space. This is certainly not without its problems, but generally on
// Windows when text reaches the end of the terminal it's likely going to be
// wrapped.
return terminal.onLineFeed(() => {
const line = terminal.buffer.lines.get(terminal.buffer.ybase + terminal.buffer.y - 1);
const lastChar = line.get(terminal.cols - 1);
if (lastChar[CHAR_DATA_CODE_INDEX] !== NULL_CELL_CODE && lastChar[CHAR_DATA_CODE_INDEX] !== WHITESPACE_CELL_CODE) {
const nextLine = terminal.buffer.lines.get(terminal.buffer.ybase + terminal.buffer.y);
nextLine.isWrapped = true;
}
});
}
+1
View File
@@ -14,6 +14,7 @@ export interface IAttachAddonTerminal extends Terminal {
__socket?: WebSocket;
__attachSocketBuffer?: string;
__dataListener?: IDisposable;
__getMessage?(ev: MessageEvent): void;
__flushBuffer?(): void;
+4 -2
View File
@@ -90,7 +90,8 @@ export function attach(term: Terminal, socket: WebSocket, bidirectional: boolean
addonTerminal._core.register(addSocketListener(socket, 'message', addonTerminal.__getMessage));
if (bidirectional) {
addonTerminal._core.register(addonTerminal.addDisposableListener('data', addonTerminal.__sendData));
addonTerminal.__dataListener = addonTerminal.onData(addonTerminal.__sendData);
addonTerminal._core.register(addonTerminal.__dataListener);
}
addonTerminal._core.register(addSocketListener(socket, 'close', () => detach(addonTerminal, socket)));
@@ -119,7 +120,8 @@ function addSocketListener(socket: WebSocket, type: string, handler: (this: WebS
*/
export function detach(term: Terminal, socket: WebSocket): void {
const addonTerminal = <IAttachAddonTerminal>term;
addonTerminal.off('data', addonTerminal.__sendData);
addonTerminal.__dataListener.dispose();
addonTerminal.__dataListener = undefined;
socket = (typeof socket === 'undefined') ? addonTerminal.__socket : socket;
+1 -2
View File
@@ -10,8 +10,7 @@
"outDir": "../../../lib/addons/attach/",
"sourceMap": true,
"removeComments": true,
"declaration": true,
"preserveWatchOutput": true
"declaration": true
},
"include": [
"**/*.ts",
+3 -3
View File
@@ -39,8 +39,8 @@ export function proposeGeometry(term: Terminal): IGeometry {
const availableHeight = parentElementHeight - elementPaddingVer;
const availableWidth = parentElementWidth - elementPaddingHor - (<any>term)._core.viewport.scrollBarWidth;
const geometry = {
cols: Math.floor(availableWidth / (<any>term)._core.renderer.dimensions.actualCellWidth),
rows: Math.floor(availableHeight / (<any>term)._core.renderer.dimensions.actualCellHeight)
cols: Math.floor(availableWidth / (<any>term)._core._renderCoordinator.dimensions.actualCellWidth),
rows: Math.floor(availableHeight / (<any>term)._core._renderCoordinator.dimensions.actualCellHeight)
};
return geometry;
}
@@ -50,7 +50,7 @@ export function fit(term: Terminal): void {
if (geometry) {
// Force a full render
if (term.rows !== geometry.rows || term.cols !== geometry.cols) {
(<any>term)._core.renderer.clear();
(<any>term)._core._renderCoordinator.clear();
term.resize(geometry.cols, geometry.rows);
}
}
+3 -4
View File
@@ -11,13 +11,12 @@
"sourceMap": true,
"removeComments": true,
"declaration": true,
"preserveWatchOutput": true,
"types": [
"../../node_modules/@types/mocha",
"../.."
"../../node_modules/@types/mocha"
]
},
"include": [
"**/*.ts"
"**/*.ts",
"../../../typings/xterm.d.ts"
]
}
+1
View File
@@ -22,6 +22,7 @@ export function toggleFullScreen(term: Terminal, fullscreen: boolean): void {
fn = term.element.classList.add;
}
fn = fn.bind(term.element.classList);
fn('fullscreen');
}
+3 -4
View File
@@ -11,13 +11,12 @@
"sourceMap": true,
"removeComments": true,
"declaration": true,
"preserveWatchOutput": true,
"types": [
"../../node_modules/@types/mocha",
"../.."
"../../node_modules/@types/mocha"
]
},
"include": [
"**/*.ts"
"**/*.ts",
"../../../typings/xterm.d.ts"
]
}
+5 -7
View File
@@ -5,15 +5,8 @@
import { Terminal } from 'xterm';
// TODO: Don't rely on this private API
export interface ITerminalCore {
buffer: any;
selectionManager: any;
}
export interface ISearchAddonTerminal extends Terminal {
__searchHelper?: ISearchHelper;
_core: ITerminalCore;
}
export interface ISearchHelper {
@@ -25,6 +18,11 @@ export interface ISearchOptions {
regex?: boolean;
wholeWord?: boolean;
caseSensitive?: boolean;
/**
* Use this when you want the selection to expand if it still matches as the
* user types. Note that this only affects findNext.
*/
incremental?: boolean;
}
export interface ISearchResult {
+200 -75
View File
@@ -4,16 +4,27 @@
*/
import { ISearchHelper, ISearchAddonTerminal, ISearchOptions, ISearchResult } from './Interfaces';
const nonWordCharacters = ' ~!@#$%^&*()+`-=[]{}|\;:"\',./<>?';
import { IDisposable } from 'xterm';
const NON_WORD_CHARACTERS = ' ~!@#$%^&*()+`-=[]{}|\;:"\',./<>?';
const LINES_CACHE_TIME_TO_LIVE = 15 * 1000; // 15 secs
/**
* A class that knows how to search the terminal and how to display the results.
*/
export class SearchHelper implements ISearchHelper {
/**
* translateBufferLineToStringWithWrap is a fairly expensive call.
* We memoize the calls into an array that has a time based ttl.
* _linesCache is also invalidated when the terminal cursor moves.
*/
private _linesCache: string[] = null;
private _linesCacheTimeoutId = 0;
private _cursorMoveListener: IDisposable | undefined;
private _resizeListener: IDisposable | undefined;
constructor(private _terminal: ISearchAddonTerminal) {
// TODO: Search for multiple instances on 1 line
// TODO: Don't use the actual selection, instead use a "find selection" so multiple instances can be highlighted
// TODO: Highlight other instances in the viewport
this._destroyLinesCache = this._destroyLinesCache.bind(this);
}
/**
@@ -24,32 +35,60 @@ export class SearchHelper implements ISearchHelper {
* @return Whether a result was found.
*/
public findNext(term: string, searchOptions?: ISearchOptions): boolean {
const {incremental} = searchOptions;
let result: ISearchResult;
if (!term || term.length === 0) {
this._terminal.clearSelection();
return false;
}
let result: ISearchResult;
let startCol: number = 0;
let startRow = this._terminal.buffer.viewportY;
let startRow = this._terminal._core.buffer.ydisp;
if (this._terminal._core.selectionManager.selectionEnd) {
if (this._terminal.hasSelection()) {
// Start from the selection end if there is a selection
if (this._terminal.getSelection().length !== 0) {
startRow = this._terminal._core.selectionManager.selectionEnd[1];
}
// For incremental search, use existing row
const currentSelection = this._terminal.getSelectionPosition();
startRow = incremental ? currentSelection.startRow : currentSelection.endRow;
startCol = incremental ? currentSelection.startColumn : currentSelection.endColumn;
}
// Search from ydisp + 1 to end
for (let y = startRow + 1; y < this._terminal._core.buffer.ybase + this._terminal.rows; y++) {
result = this._findInLine(term, y, searchOptions);
if (result) {
break;
}
this._initLinesCache();
// A row that has isWrapped = false
let findingRow = startRow;
// index of beginning column that _findInLine need to scan.
let cumulativeCols = startCol;
// If startRow is wrapped row, scan for unwrapped row above.
// So we can start matching on wrapped line from long unwrapped line.
while (this._terminal.buffer.getLine(findingRow).isWrapped) {
findingRow--;
cumulativeCols += this._terminal.cols;
}
// Search from the top to the current ydisp
// Search startRow
result = this._findInLine(term, findingRow, cumulativeCols, searchOptions);
// Search from startRow + 1 to end
if (!result) {
for (let y = 0; y < startRow; y++) {
result = this._findInLine(term, y, searchOptions);
for (let y = startRow + 1; y < this._terminal.buffer.baseY + this._terminal.rows; y++) {
// If the current line is wrapped line, increase index of column to ignore the previous scan
// Otherwise, reset beginning column index to zero with set new unwrapped line index
result = this._findInLine(term, y, 0, searchOptions);
if (result) {
break;
}
}
}
// Search from the top to the startRow (search the whole startRow again in
// case startCol > 0)
if (!result) {
for (let y = 0; y < findingRow; y++) {
result = this._findInLine(term, y, 0, searchOptions);
if (result) {
break;
}
@@ -68,35 +107,67 @@ export class SearchHelper implements ISearchHelper {
* @return Whether a result was found.
*/
public findPrevious(term: string, searchOptions?: ISearchOptions): boolean {
let result: ISearchResult;
if (!term || term.length === 0) {
this._terminal.clearSelection();
return false;
}
let result: ISearchResult;
const isReverseSearch = true;
let startRow = this._terminal.buffer.viewportY + this._terminal.rows - 1;
let startCol = this._terminal.cols;
let startRow = this._terminal._core.buffer.ydisp;
if (this._terminal._core.selectionManager.selectionStart) {
// Start from the selection end if there is a selection
if (this._terminal.getSelection().length !== 0) {
startRow = this._terminal._core.selectionManager.selectionStart[1];
}
if (this._terminal.hasSelection()) {
// Start from the selection start if there is a selection
const currentSelection = this._terminal.getSelectionPosition();
startRow = currentSelection.startRow;
startCol = currentSelection.startColumn;
}
// Search from ydisp + 1 to end
for (let y = startRow - 1; y >= 0; y--) {
result = this._findInLine(term, y, searchOptions);
if (result) {
break;
}
}
this._initLinesCache();
// Search from the top to the current ydisp
// Search startRow
result = this._findInLine(term, startRow, startCol, searchOptions, isReverseSearch);
// Search from startRow - 1 to top
if (!result) {
for (let y = this._terminal._core.buffer.ybase + this._terminal.rows - 1; y > startRow; y--) {
result = this._findInLine(term, y, searchOptions);
// If the line is wrapped line, increase number of columns that is needed to be scanned
// Se we can scan on wrapped line from unwrapped line
let cumulativeCols = this._terminal.cols;
if (this._terminal.buffer.getLine(startRow).isWrapped) {
cumulativeCols += startCol;
}
for (let y = startRow - 1; y >= 0; y--) {
result = this._findInLine(term, y, cumulativeCols, searchOptions, isReverseSearch);
if (result) {
break;
}
// If the current line is wrapped line, increase scanning range,
// preparing for scanning on unwrapped line
if (this._terminal.buffer.getLine(y).isWrapped) {
cumulativeCols += this._terminal.cols;
} else {
cumulativeCols = this._terminal.cols;
}
}
}
// Search from the bottom to startRow (search the whole startRow again in
// case startCol > 0)
if (!result) {
const searchFrom = this._terminal.buffer.baseY + this._terminal.rows - 1;
let cumulativeCols = this._terminal.cols;
for (let y = searchFrom; y >= startRow; y--) {
result = this._findInLine(term, y, cumulativeCols, searchOptions, isReverseSearch);
if (result) {
break;
}
if (this._terminal.buffer.getLine(y).isWrapped) {
cumulativeCols += this._terminal.cols;
} else {
cumulativeCols = this._terminal.cols;
}
}
}
@@ -104,6 +175,36 @@ export class SearchHelper implements ISearchHelper {
return this._selectResult(result);
}
/**
* Sets up a line cache with a ttl
*/
private _initLinesCache(): void {
if (!this._linesCache) {
this._linesCache = new Array(this._terminal.buffer.length);
this._cursorMoveListener = this._terminal.onCursorMove(() => this._destroyLinesCache());
this._resizeListener = this._terminal.onResize(() => this._destroyLinesCache());
}
window.clearTimeout(this._linesCacheTimeoutId);
this._linesCacheTimeoutId = window.setTimeout(() => this._destroyLinesCache(), LINES_CACHE_TIME_TO_LIVE);
}
private _destroyLinesCache(): void {
this._linesCache = null;
if (this._cursorMoveListener) {
this._cursorMoveListener.dispose();
this._cursorMoveListener = undefined;
}
if (this._resizeListener) {
this._resizeListener.dispose();
this._resizeListener = undefined;
}
if (this._linesCacheTimeoutId) {
window.clearTimeout(this._linesCacheTimeoutId);
this._linesCacheTimeoutId = 0;
}
}
/**
* A found substring is a whole word if it doesn't have an alphanumeric character directly adjacent to it.
* @param searchIndex starting indext of the potential whole word substring
@@ -111,8 +212,8 @@ export class SearchHelper implements ISearchHelper {
* @param term the substring that starts at searchIndex
*/
private _isWholeWord(searchIndex: number, line: string, term: string): boolean {
return (((searchIndex === 0) || (nonWordCharacters.indexOf(line[searchIndex - 1]) !== -1)) &&
(((searchIndex + term.length) === line.length) || (nonWordCharacters.indexOf(line[searchIndex + term.length]) !== -1)));
return (((searchIndex === 0) || (NON_WORD_CHARACTERS.indexOf(line[searchIndex - 1]) !== -1)) &&
(((searchIndex + term.length) === line.length) || (NON_WORD_CHARACTERS.indexOf(line[searchIndex + term.length]) !== -1)));
}
/**
@@ -121,65 +222,89 @@ export class SearchHelper implements ISearchHelper {
* started on an earlier line then it is skipped since it will be properly searched when the terminal line that the
* text starts on is searched.
* @param term The search term.
* @param y The line to search.
* @param row The line to start the search from.
* @param col The column to start the search from.
* @param searchOptions Search options.
* @return The search result if it was found.
*/
protected _findInLine(term: string, y: number, searchOptions: ISearchOptions = {}): ISearchResult {
if (this._terminal._core.buffer.lines.get(y).isWrapped) {
protected _findInLine(term: string, row: number, col: number, searchOptions: ISearchOptions = {}, isReverseSearch: boolean = false): ISearchResult {
// Ignore wrapped lines, only consider on unwrapped line (first row of command string).
if (this._terminal.buffer.getLine(row).isWrapped) {
return;
}
const stringLine = this.translateBufferLineToStringWithWrap(y, true);
const searchStringLine = searchOptions.caseSensitive ? stringLine : stringLine.toLowerCase();
const searchTerm = searchOptions.caseSensitive ? term : term.toLowerCase();
let searchIndex = -1;
if (searchOptions.regex) {
const searchRegex = RegExp(searchTerm, 'g');
const foundTerm = searchRegex.exec(searchStringLine);
if (foundTerm && foundTerm[0].length > 0) {
searchIndex = searchRegex.lastIndex - foundTerm[0].length;
term = foundTerm[0];
let stringLine = this._linesCache ? this._linesCache[row] : void 0;
if (stringLine === void 0) {
stringLine = this.translateBufferLineToStringWithWrap(row, true);
if (this._linesCache) {
this._linesCache[row] = stringLine;
}
} else {
searchIndex = searchStringLine.indexOf(searchTerm);
}
if (searchIndex >= 0) {
// Adjust the row number and search index if needed since a "line" of text can span multiple rows
if (searchIndex >= this._terminal.cols) {
y += Math.floor(searchIndex / this._terminal.cols);
searchIndex = searchIndex % this._terminal.cols;
const searchTerm = searchOptions.caseSensitive ? term : term.toLowerCase();
const searchStringLine = searchOptions.caseSensitive ? stringLine : stringLine.toLowerCase();
let resultIndex = -1;
if (searchOptions.regex) {
const searchRegex = RegExp(searchTerm, 'g');
let foundTerm: RegExpExecArray;
if (isReverseSearch) {
// This loop will get the resultIndex of the _last_ regex match in the range 0..col
while (foundTerm = searchRegex.exec(searchStringLine.slice(0, col))) {
resultIndex = searchRegex.lastIndex - foundTerm[0].length;
term = foundTerm[0];
searchRegex.lastIndex -= (term.length - 1);
}
} else {
foundTerm = searchRegex.exec(searchStringLine.slice(col));
if (foundTerm && foundTerm[0].length > 0) {
resultIndex = col + (searchRegex.lastIndex - foundTerm[0].length);
term = foundTerm[0];
}
}
if (searchOptions.wholeWord && !this._isWholeWord(searchIndex, searchStringLine, term)) {
} else {
if (isReverseSearch) {
if (col - searchTerm.length >= 0) {
resultIndex = searchStringLine.lastIndexOf(searchTerm, col - searchTerm.length);
}
} else {
resultIndex = searchStringLine.indexOf(searchTerm, col);
}
}
if (resultIndex >= 0) {
// Adjust the row number and search index if needed since a "line" of text can span multiple rows
if (resultIndex >= this._terminal.cols) {
row += Math.floor(resultIndex / this._terminal.cols);
resultIndex = resultIndex % this._terminal.cols;
}
if (searchOptions.wholeWord && !this._isWholeWord(resultIndex, searchStringLine, term)) {
return;
}
const line = this._terminal._core.buffer.lines.get(y);
const line = this._terminal.buffer.getLine(row);
for (let i = 0; i < searchIndex; i++) {
const charData = line.get(i);
for (let i = 0; i < resultIndex; i++) {
const cell = line.getCell(i);
// Adjust the searchIndex to normalize emoji into single chars
const char = charData[1/*CHAR_DATA_CHAR_INDEX*/];
const char = cell.char;
if (char.length > 1) {
searchIndex -= char.length - 1;
resultIndex -= char.length - 1;
}
// Adjust the searchIndex for empty characters following wide unicode
// chars (eg. CJK)
const charWidth = charData[2/*CHAR_DATA_WIDTH_INDEX*/];
const charWidth = cell.width;
if (charWidth === 0) {
searchIndex++;
resultIndex++;
}
}
return {
term,
col: searchIndex,
row: y
col: resultIndex,
row
};
}
}
/**
* Translates a buffer line to a string, including subsequent lines if they are wraps.
* Wide characters will count as two columns in the resulting string. This
@@ -193,9 +318,9 @@ export class SearchHelper implements ISearchHelper {
let lineWrapsToNext: boolean;
do {
const nextLine = this._terminal._core.buffer.lines.get(lineIndex + 1);
const nextLine = this._terminal.buffer.getLine(lineIndex + 1);
lineWrapsToNext = nextLine ? nextLine.isWrapped : false;
lineString += this._terminal._core.buffer.translateBufferLineToString(lineIndex, !lineWrapsToNext && trimRight).substring(0, this._terminal.cols);
lineString += this._terminal.buffer.getLine(lineIndex).translateToString(!lineWrapsToNext && trimRight).substring(0, this._terminal.cols);
lineIndex++;
} while (lineWrapsToNext);
@@ -212,8 +337,8 @@ export class SearchHelper implements ISearchHelper {
this._terminal.clearSelection();
return false;
}
this._terminal._core.selectionManager.setSelection(result.col, result.row, result.term.length);
this._terminal.scrollLines(result.row - this._terminal._core.buffer.ydisp);
this._terminal.select(result.col, result.row, result.term.length);
this._terminal.scrollLines(result.row - this._terminal.buffer.viewportY);
return true;
}
}
+111 -4
View File
@@ -23,14 +23,62 @@ class MockTerminal {
get core(): any {
return this._core;
}
get buffer(): IBuffer {
// TODO: This is a hacky workaround until we use puppeteer for addon tests
const buffer = this._core.buffer;
return {
cursorY: buffer.y,
cursorX: buffer.x,
viewportY: buffer.ydisp,
baseY: buffer.ybase,
length: buffer.length,
getLine(y: number): IBufferLine {
return {
isWrapped: buffer.lines.get(y) ? buffer.lines.get(y).isWrapped : false,
getCell(x: number): IBufferCell {
return {
char: buffer.lines.get(y).get(x)[1/*CHAR_DATA_CHAR_INDEX*/],
width: buffer.lines.get(y).get(x)[2/*CHAR_DATA_WIDTH_INDEX*/]
};
},
translateToString(trimRight?: boolean, startColumn?: number, endColumn?: number): string {
return buffer.translateBufferLineToString(y, trimRight);
}
};
}
};
}
pushWriteData(): void {
this._core._innerWrite();
}
}
interface IBuffer {
readonly cursorY: number;
readonly cursorX: number;
readonly viewportY: number;
readonly baseY: number;
readonly length: number;
getLine(y: number): IBufferLine | undefined;
}
interface IBufferLine {
readonly isWrapped: boolean;
getCell(x: number): IBufferCell;
translateToString(trimRight?: boolean, startColumn?: number, endColumn?: number): string;
}
interface IBufferCell {
readonly char: string;
readonly width: number;
}
class TestSearchHelper extends SearchHelper {
public findInLine(term: string, y: number, searchOptions?: ISearchOptions): ISearchResult {
return this._findInLine(term, y, searchOptions);
public findInLine(term: string, rowNumber: number, searchOptions?: ISearchOptions): ISearchResult {
return this._findInLine(term, rowNumber, 0, searchOptions);
}
public findFromIndex(term: string, row: number, col: number, searchOptions?: ISearchOptions, isReverseSearch?: boolean): ISearchResult {
return this._findInLine(term, row, col, searchOptions, isReverseSearch);
}
}
@@ -118,8 +166,6 @@ describe('search addon', () => {
it('should not select empty lines', () => {
search.apply(<any>MockTerminal);
const term = new MockTerminal({cols: 20, rows: 3});
term.core.write(' ');
term.pushWriteData();
const line = term.searchHelper.findInLine('^.*$', 0, { regex: true });
expect(line).eql(undefined);
});
@@ -247,5 +293,66 @@ describe('search addon', () => {
expect(hello4).eql(undefined);
expect(hello5).eql(undefined);
});
it('should find multiple matches in line', function(): void {
search.apply(<any>MockTerminal);
const term = new MockTerminal({cols: 20, rows: 5});
term.core.write('helloooo helloooo\r\naaaAAaaAAA');
term.pushWriteData();
const searchOptions = {
regex: false,
wholeWord: false,
caseSensitive: false
};
const find0 = term.searchHelper.findFromIndex('hello', 0, 0, searchOptions);
const find1 = term.searchHelper.findFromIndex('hello', 0, find0.col + find0.term.length, searchOptions);
const find2 = term.searchHelper.findFromIndex('aaaa', 1, 0, searchOptions);
const find3 = term.searchHelper.findFromIndex('aaaa', 1, find2.col + find2.term.length, searchOptions);
const find4 = term.searchHelper.findFromIndex('aaaa', 1, find3.col + find3.term.length, searchOptions);
expect(find0).eql({col: 0, row: 0, term: 'hello'});
expect(find1).eql({col: 9, row: 0, term: 'hello'});
expect(find2).eql({col: 0, row: 1, term: 'aaaa'});
expect(find3).eql({col: 4, row: 1, term: 'aaaa'});
expect(find4).eql(undefined);
});
it('should find multiple matches in line - reverse search', function(): void {
search.apply(<any>MockTerminal);
const term = new MockTerminal({cols: 20, rows: 5});
term.core.write('it is what it is');
term.pushWriteData();
const searchOptions = {
regex: false,
wholeWord: false,
caseSensitive: false
};
const isReverseSearch = true;
const find0 = term.searchHelper.findFromIndex('is', 0, 16, searchOptions, isReverseSearch);
const find1 = term.searchHelper.findFromIndex('is', 0, find0.col, searchOptions, isReverseSearch);
const find2 = term.searchHelper.findFromIndex('it', 0, 16, searchOptions, isReverseSearch);
const find3 = term.searchHelper.findFromIndex('it', 0, find2.col, searchOptions, isReverseSearch);
expect(find0).eql({col: 14, row: 0, term: 'is'});
expect(find1).eql({col: 3, row: 0, term: 'is'});
expect(find2).eql({col: 11, row: 0, term: 'it'});
expect(find3).eql({col: 0, row: 0, term: 'it'});
});
it('should find multiple matches in line - reverse search with regex', function(): void {
search.apply(<any>MockTerminal);
const term = new MockTerminal({cols: 20, rows: 5});
term.core.write('zzzABCzzzzABCABC');
term.pushWriteData();
const searchOptions = {
regex: true,
wholeWord: false,
caseSensitive: true
};
const isReverseSearch = true;
const find0 = term.searchHelper.findFromIndex('[A-Z]{3}', 0, 16, searchOptions, isReverseSearch);
const find1 = term.searchHelper.findFromIndex('[A-Z]{3}', 0, find0.col, searchOptions, isReverseSearch);
const find2 = term.searchHelper.findFromIndex('[A-Z]{3}', 0, find1.col, searchOptions, isReverseSearch);
const find3 = term.searchHelper.findFromIndex('[A-Z]{3}', 0, find2.col, searchOptions, isReverseSearch);
expect(find0).eql({col: 13, row: 0, term: 'ABC'});
expect(find1).eql({col: 10, row: 0, term: 'ABC'});
expect(find2).eql({col: 3, row: 0, term: 'ABC'});
expect(find3).eql(undefined);
});
});
});
+4 -4
View File
@@ -3,6 +3,7 @@
"module": "commonjs",
"target": "es5",
"lib": [
"dom",
"es5"
],
"rootDir": ".",
@@ -10,13 +11,12 @@
"sourceMap": true,
"removeComments": true,
"declaration": true,
"preserveWatchOutput": true,
"types": [
"../../node_modules/@types/mocha",
"../.."
"../../node_modules/@types/mocha"
]
},
"include": [
"**/*.ts"
"**/*.ts",
"../../../typings/xterm.d.ts"
]
}
+6 -1
View File
@@ -5,11 +5,16 @@
* Implements the attach method, that attaches the terminal to a WebSocket stream.
*/
import { Terminal } from 'xterm';
import { Terminal, IDisposable } from 'xterm';
export interface ITerminadoAddonTerminal extends Terminal {
_core: {
register<T extends IDisposable>(d: T): void;
};
__socket?: WebSocket;
__attachSocketBuffer?: string;
__dataListener?: IDisposable;
__getMessage?(ev: MessageEvent): void;
__flushBuffer?(): void;
+4 -3
View File
@@ -59,9 +59,9 @@ export function terminadoAttach(term: Terminal, socket: WebSocket, bidirectional
socket.addEventListener('message', addonTerminal.__getMessage);
if (bidirectional) {
addonTerminal.on('data', addonTerminal.__sendData);
addonTerminal._core.register(addonTerminal.onData(addonTerminal.__sendData));
}
addonTerminal.on('resize', addonTerminal.__setSize);
addonTerminal._core.register(addonTerminal.onResize(addonTerminal.__setSize));
socket.addEventListener('close', () => terminadoDetach(addonTerminal, socket));
socket.addEventListener('error', () => terminadoDetach(addonTerminal, socket));
@@ -75,7 +75,8 @@ export function terminadoAttach(term: Terminal, socket: WebSocket, bidirectional
*/
export function terminadoDetach(term: Terminal, socket: WebSocket): void {
const addonTerminal = <ITerminadoAddonTerminal>term;
addonTerminal.off('data', addonTerminal.__sendData);
addonTerminal.__dataListener.dispose();
addonTerminal.__dataListener = undefined;
socket = (typeof socket === 'undefined') ? addonTerminal.__socket : socket;
+3 -4
View File
@@ -10,13 +10,12 @@
"sourceMap": true,
"removeComments": true,
"declaration": true,
"preserveWatchOutput": true,
"types": [
"../../node_modules/@types/mocha",
"../.."
"../../node_modules/@types/mocha"
]
},
"include": [
"**/*.ts"
"**/*.ts",
"../../../typings/xterm.d.ts"
]
}
+3 -4
View File
@@ -11,13 +11,12 @@
"sourceMap": true,
"removeComments": true,
"declaration": true,
"preserveWatchOutput": true,
"types": [
"../../node_modules/@types/mocha",
"../.."
"../../node_modules/@types/mocha"
]
},
"include": [
"**/*.ts"
"**/*.ts",
"../../../typings/xterm.d.ts"
]
}
+167 -21
View File
@@ -28,39 +28,185 @@ describe('webLinks addon', () => {
});
});
it('should allow ~ character in URI path', () => {
const term = new MockTerminal();
webLinks.webLinksInit(<any>term);
describe('should allow simple URI path', () => {
it('foo.com', () => {
const term = new MockTerminal();
webLinks.webLinksInit(<any>term);
const row = ' http://foo.com/a~b#c~d?e~f ';
const row = ' http://foo.com ';
const match = row.match(term.regex);
const uri = match[term.options.matchIndex];
const match = row.match(term.regex);
const uri = match[term.options.matchIndex];
assert.equal(uri, 'http://foo.com/a~b#c~d?e~f');
assert.equal(uri, 'http://foo.com');
});
it('bar.io', () => {
const term = new MockTerminal();
webLinks.webLinksInit(<any>term);
const row = ' http://bar.io ';
const match = row.match(term.regex);
const uri = match[term.options.matchIndex];
assert.equal(uri, 'http://bar.io');
});
});
it('should allow : character in URI path', () => {
const term = new MockTerminal();
webLinks.webLinksInit(<any>term);
describe('should allow ~ character in URI path', () => {
it('foo.com', () => {
const term = new MockTerminal();
webLinks.webLinksInit(<any>term);
const row = ' http://foo.com/colon:test ';
const row = ' http://foo.com/a~b#c~d?e~f ';
const match = row.match(term.regex);
const uri = match[term.options.matchIndex];
const match = row.match(term.regex);
const uri = match[term.options.matchIndex];
assert.equal(uri, 'http://foo.com/colon:test');
assert.equal(uri, 'http://foo.com/a~b#c~d?e~f');
});
it('bar.io', () => {
const term = new MockTerminal();
webLinks.webLinksInit(<any>term);
const row = ' http://bar.io/a~b#c~d?e~f ';
const match = row.match(term.regex);
const uri = match[term.options.matchIndex];
assert.equal(uri, 'http://bar.io/a~b#c~d?e~f');
});
});
it('should not allow : character at the end of a URI path', () => {
const term = new MockTerminal();
webLinks.webLinksInit(<any>term);
describe('should allow : character in URI path', () => {
it('foo.com', () => {
const term = new MockTerminal();
webLinks.webLinksInit(<any>term);
const row = ' http://foo.com/colon:test: ';
const row = ' http://foo.com/colon:test ';
const match = row.match(term.regex);
const uri = match[term.options.matchIndex];
const match = row.match(term.regex);
const uri = match[term.options.matchIndex];
assert.equal(uri, 'http://foo.com/colon:test');
assert.equal(uri, 'http://foo.com/colon:test');
});
it('bar.io', () => {
const term = new MockTerminal();
webLinks.webLinksInit(<any>term);
const row = ' http://bar.io/colon:test ';
const match = row.match(term.regex);
const uri = match[term.options.matchIndex];
assert.equal(uri, 'http://bar.io/colon:test');
});
});
describe('should not allow : character at the end of a URI path', () => {
it('foo.com', () => {
const term = new MockTerminal();
webLinks.webLinksInit(<any>term);
const row = ' http://foo.com/colon:test: ';
const match = row.match(term.regex);
const uri = match[term.options.matchIndex];
assert.equal(uri, 'http://foo.com/colon:test');
});
it('bar.io', () => {
const term = new MockTerminal();
webLinks.webLinksInit(<any>term);
const row = ' http://bar.io/colon:test: ';
const match = row.match(term.regex);
const uri = match[term.options.matchIndex];
assert.equal(uri, 'http://bar.io/colon:test');
});
});
describe('should not allow " character at the end of a URI enclosed with ""', () => {
it('foo.com', () => {
const term = new MockTerminal();
webLinks.webLinksInit(<any>term);
const row = '"http://foo.com/"';
const match = row.match(term.regex);
const uri = match[term.options.matchIndex];
assert.equal(uri, 'http://foo.com/');
});
it('bar.io', () => {
const term = new MockTerminal();
webLinks.webLinksInit(<any>term);
const row = '"http://bar.io/"';
const match = row.match(term.regex);
const uri = match[term.options.matchIndex];
assert.equal(uri, 'http://bar.io/');
});
});
describe('should not allow \' character at the end of a URI enclosed with \'\'', () => {
it('foo.com', () => {
const term = new MockTerminal();
webLinks.webLinksInit(<any>term);
const row = '\'http://foo.com/\'';
const match = row.match(term.regex);
const uri = match[term.options.matchIndex];
assert.equal(uri, 'http://foo.com/');
});
it('bar.io', () => {
const term = new MockTerminal();
webLinks.webLinksInit(<any>term);
const row = '\'http://bar.io/\'';
const match = row.match(term.regex);
const uri = match[term.options.matchIndex];
assert.equal(uri, 'http://bar.io/');
});
});
describe('should allow + character in URI path', () => {
it('foo.com', () => {
const term = new MockTerminal();
webLinks.webLinksInit(<any>term);
const row = 'http://foo.com/subpath/+/id';
const match = row.match(term.regex);
const uri = match[term.options.matchIndex];
assert.equal(uri, 'http://foo.com/subpath/+/id');
});
it('bar.io', () => {
const term = new MockTerminal();
webLinks.webLinksInit(<any>term);
const row = 'http://bar.io/subpath/+/id';
const match = row.match(term.regex);
const uri = match[term.options.matchIndex];
assert.equal(uri, 'http://bar.io/subpath/+/id');
});
});
});
+2 -1
View File
@@ -14,7 +14,8 @@ const ipClause = '((\\d{1,3}\\.){3}\\d{1,3})';
const localHostClause = '(localhost)';
const portClause = '(:\\d{1,5})';
const hostClause = '((' + domainBodyClause + '\\.' + tldClause + ')|' + ipClause + '|' + localHostClause + ')' + portClause + '?';
const pathClause = '(\\/[\\/\\w\\.\\-%~:]*)*([^:\\s])';
const pathCharacterSet = '(\\/[\\/\\w\\.\\-%~:+]*)*([^:"\'\\s])';
const pathClause = '(' + pathCharacterSet + ')?';
const queryStringHashFragmentCharacterSet = '[0-9\\w\\[\\]\\(\\)\\/\\?\\!#@$%&\'*+,:;~\\=\\.\\-]*';
const queryStringClause = '(\\?' + queryStringHashFragmentCharacterSet + ')?';
const hashFragmentClause = '(#' + queryStringHashFragmentCharacterSet + ')?';
-14
View File
@@ -1,14 +0,0 @@
/**
* Copyright (c) 2018 The xterm.js authors. All rights reserved.
* @license MIT
*/
import { Terminal } from 'xterm';
export interface ITerminalCore {
buffer: any;
}
export interface IWinptyCompatAddonTerminal extends Terminal {
_core: ITerminalCore;
}
-5
View File
@@ -1,5 +0,0 @@
{
"name": "xterm.winptycompat",
"main": "winptyCompat.js",
"private": true
}
-22
View File
@@ -1,22 +0,0 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "es5",
"lib": [
"es5"
],
"rootDir": ".",
"outDir": "../../../lib/addons/winptyCompat/",
"sourceMap": true,
"removeComments": true,
"declaration": true,
"preserveWatchOutput": true,
"types": [
"../../node_modules/@types/mocha",
"../.."
]
},
"include": [
"**/*.ts"
]
}
@@ -1,19 +0,0 @@
/**
* Copyright (c) 2017 The xterm.js authors. All rights reserved.
* @license MIT
*/
import { assert } from 'chai';
import * as winptyCompat from './winptyCompat';
class MockTerminal {}
describe('winptyCompat addon', () => {
describe('apply', () => {
it('should do register the `winptyCompatInit` method', () => {
winptyCompat.apply(<any>MockTerminal);
assert.equal(typeof (<any>MockTerminal).prototype.winptyCompatInit, 'function');
});
});
});
-46
View File
@@ -1,46 +0,0 @@
/**
* Copyright (c) 2017 The xterm.js authors. All rights reserved.
* @license MIT
*/
import { Terminal } from 'xterm';
import { IWinptyCompatAddonTerminal } from './Interfaces';
const CHAR_DATA_CODE_INDEX = 3;
const NULL_CELL_CODE = 32;
export function winptyCompatInit(terminal: Terminal): void {
const addonTerminal = <IWinptyCompatAddonTerminal>terminal;
// Don't do anything when the platform is not Windows
const isWindows = ['Windows', 'Win16', 'Win32', 'WinCE'].indexOf(navigator.platform) >= 0;
if (!isWindows) {
return;
}
// Winpty does not support wraparound mode which means that lines will never
// be marked as wrapped. This causes issues for things like copying a line
// retaining the wrapped new line characters or if consumers are listening
// in on the data stream.
//
// The workaround for this is to listen to every incoming line feed and mark
// the line as wrapped if the last character in the previous line is not a
// space. This is certainly not without its problems, but generally on
// Windows when text reaches the end of the terminal it's likely going to be
// wrapped.
addonTerminal.on('linefeed', () => {
const line = addonTerminal._core.buffer.lines.get(addonTerminal._core.buffer.ybase + addonTerminal._core.buffer.y - 1);
const lastChar = line.get(addonTerminal.cols - 1);
if (lastChar[CHAR_DATA_CODE_INDEX] !== NULL_CELL_CODE) {
const nextLine = addonTerminal._core.buffer.lines.get(addonTerminal._core.buffer.ybase + addonTerminal._core.buffer.y);
nextLine.isWrapped = true;
}
});
}
export function apply(terminalConstructor: typeof Terminal): void {
(<any>terminalConstructor.prototype).winptyCompatInit = function (): void {
winptyCompatInit(this);
};
}
+3 -4
View File
@@ -10,13 +10,12 @@
"sourceMap": true,
"removeComments": true,
"declaration": true,
"preserveWatchOutput": true,
"types": [
"../../node_modules/@types/mocha",
"../.."
"../../node_modules/@types/mocha"
]
},
"include": [
"**/*.ts"
"**/*.ts",
"../../../typings/xterm.d.ts"
]
}
+38 -24
View File
@@ -3,22 +3,38 @@
* @license MIT
*/
import { EventEmitter } from './EventEmitter';
import { ICircularList } from './Types';
import { EventEmitter2, IEvent } from './EventEmitter2';
export interface IInsertEvent {
index: number;
amount: number;
}
export interface IDeleteEvent {
index: number;
amount: number;
}
/**
* Represents a circular list; a list with a maximum size that wraps around when push is called,
* overriding values at the start of the list.
*/
export class CircularList<T> extends EventEmitter implements ICircularList<T> {
export class CircularList<T> implements ICircularList<T> {
protected _array: (T | undefined)[];
private _startIndex: number;
private _length: number;
public onDeleteEmitter = new EventEmitter2<IDeleteEvent>();
public get onDelete(): IEvent<IDeleteEvent> { return this.onDeleteEmitter.event; }
public onInsertEmitter = new EventEmitter2<IInsertEvent>();
public get onInsert(): IEvent<IInsertEvent> { return this.onInsertEmitter.event; }
public onTrimEmitter = new EventEmitter2<number>();
public get onTrim(): IEvent<number> { return this.onTrimEmitter.event; }
constructor(
private _maxLength: number
) {
super();
this._array = new Array<T>(this._maxLength);
this._startIndex = 0;
this._length = 0;
@@ -91,7 +107,7 @@ export class CircularList<T> extends EventEmitter implements ICircularList<T> {
this._array[this._getCyclicIndex(this._length)] = value;
if (this._length === this._maxLength) {
this._startIndex = ++this._startIndex % this._maxLength;
this.emit('trim', 1);
this.onTrimEmitter.fire(1);
} else {
this._length++;
}
@@ -107,7 +123,7 @@ export class CircularList<T> extends EventEmitter implements ICircularList<T> {
throw new Error('Can only recycle when the buffer is full');
}
this._startIndex = ++this._startIndex % this._maxLength;
this.emit('trim', 1);
this.onTrimEmitter.fire(1);
return this._array[this._getCyclicIndex(this._length - 1)]!;
}
@@ -144,24 +160,22 @@ export class CircularList<T> extends EventEmitter implements ICircularList<T> {
this._length -= deleteCount;
}
if (items && items.length) {
// Add items
for (let i = this._length - 1; i >= start; i--) {
this._array[this._getCyclicIndex(i + items.length)] = this._array[this._getCyclicIndex(i)];
}
for (let i = 0; i < items.length; i++) {
this._array[this._getCyclicIndex(start + i)] = items[i];
}
// Add items
for (let i = this._length - 1; i >= start; i--) {
this._array[this._getCyclicIndex(i + items.length)] = this._array[this._getCyclicIndex(i)];
}
for (let i = 0; i < items.length; i++) {
this._array[this._getCyclicIndex(start + i)] = items[i];
}
// Adjust length as needed
if (this._length + items.length > this._maxLength) {
const countToTrim = (this._length + items.length) - this._maxLength;
this._startIndex += countToTrim;
this._length = this._maxLength;
this.emit('trim', countToTrim);
} else {
this._length += items.length;
}
// Adjust length as needed
if (this._length + items.length > this._maxLength) {
const countToTrim = (this._length + items.length) - this._maxLength;
this._startIndex += countToTrim;
this._length = this._maxLength;
this.onTrimEmitter.fire(countToTrim);
} else {
this._length += items.length;
}
}
@@ -175,7 +189,7 @@ export class CircularList<T> extends EventEmitter implements ICircularList<T> {
}
this._startIndex += count;
this._length -= count;
this.emit('trim', count);
this.onTrimEmitter.fire(count);
}
public shiftElements(start: number, count: number, offset: number): void {
@@ -199,7 +213,7 @@ export class CircularList<T> extends EventEmitter implements ICircularList<T> {
while (this._length > this._maxLength) {
this._length--;
this._startIndex++;
this.emit('trim', 1);
this.onTrimEmitter.fire(1);
}
}
} else {
@@ -101,7 +101,7 @@ describe('clone', () => {
test.a.b.c.d.e.f = 'bar';
// The values at a greater depth then 5 should not be cloned
assert.equal(cloned.a.b.c.d.e.f, 'bar');
assert.equal((cloned as any).a.b.c.d.e.f, 'bar');
});
it('should allow an optional maximum depth to be set', () => {
@@ -118,7 +118,7 @@ describe('clone', () => {
test.a.b.c = 'bar';
// The values at a greater depth then 2 should not be cloned
assert.equal(cloned.a.b.c, 'bar');
assert.equal((cloned as any).a.b.c, 'bar');
});
it('should not throw when cloning a recursive reference', () => {
+2 -2
View File
@@ -6,7 +6,7 @@
/*
* A simple utility for cloning values
*/
export const clone = <T>(val: T, depth: number = 5): T => {
export function clone<T>(val: T, depth: number = 5): T | null {
if (typeof val !== 'object') {
return val;
}
@@ -25,4 +25,4 @@ export const clone = <T>(val: T, depth: number = 5): T => {
}
return clonedObject as T;
};
}
+15 -3
View File
@@ -3,8 +3,7 @@
* @license MIT
*/
import { XtermListener } from './Types';
import { IEventEmitter, IDisposable } from 'xterm';
import { IDisposable, IEventEmitter, XtermListener } from './Types';
import { Disposable } from './Lifecycle';
export class EventEmitter extends Disposable implements IEventEmitter, IDisposable {
@@ -23,7 +22,7 @@ export class EventEmitter extends Disposable implements IEventEmitter, IDisposab
}
/**
* Adds a disposabe listener to the EventEmitter, returning the disposable.
* Adds a disposable listener to the EventEmitter, returning the disposable.
* @param type The event type.
* @param handler The handler for the listener.
*/
@@ -75,6 +74,19 @@ export class EventEmitter extends Disposable implements IEventEmitter, IDisposab
}
}
public emitMayRemoveListeners(type: string, ...args: any[]): void {
if (!this._events[type]) {
return;
}
const obj = this._events[type];
let length = obj.length;
for (let i = 0; i < obj.length; i++) {
obj[i].apply(this, args);
i -= length - obj.length;
length = obj.length;
}
}
public listeners(type: string): XtermListener[] {
return this._events[type] || [];
}
+31
View File
@@ -0,0 +1,31 @@
/**
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
* @license MIT
*/
import { assert } from 'chai';
import { EventEmitter2 } from './EventEmitter2';
describe('EventEmitter2', () => {
it('should fire listeners multiple times', () => {
const order: string[] = [];
const emitter = new EventEmitter2<number>();
emitter.event(data => order.push(data + 'a'));
emitter.event(data => order.push(data + 'b'));
emitter.fire(1);
emitter.fire(2);
assert.deepEqual(order, [ '1a', '1b', '2a', '2b' ]);
});
it('should not fire listeners once disposed', () => {
const order: string[] = [];
const emitter = new EventEmitter2<number>();
emitter.event(data => order.push(data + 'a'));
const disposeB = emitter.event(data => order.push(data + 'b'));
emitter.event(data => order.push(data + 'c'));
emitter.fire(1);
disposeB.dispose();
emitter.fire(2);
assert.deepEqual(order, [ '1a', '1b', '1c', '2a', '2c' ]);
});
});
+49
View File
@@ -0,0 +1,49 @@
/**
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
* @license MIT
*/
import { IDisposable } from './Types';
interface IListener<T> {
(e: T): void;
}
export interface IEvent<T> {
(listener: (e: T) => any): IDisposable;
}
export class EventEmitter2<T> {
private _listeners: IListener<T>[] = [];
private _event?: IEvent<T>;
public get event(): IEvent<T> {
if (!this._event) {
this._event = (listener: (e: T) => any) => {
this._listeners.push(listener);
const disposable = {
dispose: () => {
for (let i = 0; i < this._listeners.length; i++) {
if (this._listeners[i] === listener) {
this._listeners.splice(i, 1);
return;
}
}
}
};
return disposable;
};
}
return this._event;
}
public fire(data: T): void {
const queue: IListener<T>[] = [];
for (let i = 0; i < this._listeners.length; i++) {
queue.push(this._listeners[i]);
}
for (let i = 0; i < queue.length; i++) {
queue[i].call(undefined, data);
}
}
}
+1 -1
View File
@@ -3,7 +3,7 @@
* @license MIT
*/
import { IDisposable } from 'xterm';
import { IDisposable } from './Types';
/**
* A base class that can be extended to provide convenience methods for managing the lifecycle of an
@@ -3,6 +3,16 @@
* @license MIT
*/
interface INavigator {
userAgent: string;
language: string;
platform: string;
}
// We're declaring a navigator global here as we expect it in all runtimes (node and browser), but
// we want this module to live in common.
declare const navigator: INavigator;
const isNode = (typeof navigator === 'undefined') ? true : false;
const userAgent = (isNode) ? 'node' : navigator.userAgent;
const platform = (isNode) ? 'node' : navigator.platform;
+16 -8
View File
@@ -3,21 +3,20 @@
* @license MIT
*/
import { assert } from 'chai';
import { fillFallback } from './TypedArrayUtils';
import { fillFallback, concat } from './TypedArrayUtils';
type TypedArray = Uint8Array | Uint16Array | Uint32Array | Uint8ClampedArray
| Int8Array | Int16Array | Int32Array
| Float32Array | Float64Array;
describe('polyfill conformance tests', function(): void {
function deepEquals(a: TypedArray, b: TypedArray): void {
assert.equal(a.length, b.length);
for (let i = 0; i < a.length; ++i) {
assert.equal(a[i], b[i]);
}
function deepEquals(a: TypedArray, b: TypedArray): void {
assert.equal(a.length, b.length);
for (let i = 0; i < a.length; ++i) {
assert.equal(a[i], b[i]);
}
}
describe('polyfill conformance tests', function(): void {
describe('TypedArray.fill', function(): void {
it('should work with all typed array types', function(): void {
const u81 = new Uint8Array(5);
@@ -87,3 +86,12 @@ describe('polyfill conformance tests', function(): void {
});
});
});
describe('typed array convenience functions', () => {
it('concat', () => {
const a = new Uint8Array([1, 2, 3, 4, 5]);
const b = new Uint8Array([6, 7, 8, 9, 0]);
const merged = concat(a, b);
deepEquals(merged, new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 0]));
});
});
+16 -5
View File
@@ -3,15 +3,15 @@
* @license MIT
*/
export type TypedArray = Uint8Array | Uint16Array | Uint32Array | Uint8ClampedArray
| Int8Array | Int16Array | Int32Array
| Float32Array | Float64Array;
/**
* polyfill for TypedArray.fill
* This is needed to support .fill in all safari versions and IE 11.
*/
type TypedArray = Uint8Array | Uint16Array | Uint32Array | Uint8ClampedArray
| Int8Array | Int16Array | Int32Array
| Float32Array | Float64Array;
export function fill<T extends TypedArray>(array: T, value: number, start?: number, end?: number): T {
// all modern engines that support .fill
if (array.fill) {
@@ -39,3 +39,14 @@ export function fillFallback<T extends TypedArray>(array: T, value: number, star
}
return array;
}
/**
* Concat two typed arrays `a` and `b`.
* Returns a new typed array.
*/
export function concat<T extends TypedArray>(a: T, b: T): T {
const result = new (a.constructor as any)(a.length + b.length);
result.set(a);
result.set(b, a.length);
return result;
}
+23 -2
View File
@@ -3,7 +3,21 @@
* @license MIT
*/
import { IEventEmitter } from 'xterm';
import { IEvent, EventEmitter2 } from './EventEmitter2';
import { IDeleteEvent, IInsertEvent } from './CircularList';
export const DEFAULT_COLOR = 256;
export interface IDisposable {
dispose(): void;
}
export interface IEventEmitter {
on(type: string, listener: (...args: any[]) => void): void;
off(type: string, listener: (...args: any[]) => void): void;
emit(type: string, data?: any): void;
addDisposableListener(type: string, handler: (...args: any[]) => void): IDisposable;
}
export type XtermListener = (...args: any[]) => void;
@@ -21,11 +35,18 @@ export interface IKeyboardEvent {
type: string;
}
export interface ICircularList<T> extends IEventEmitter {
export interface ICircularList<T> {
length: number;
maxLength: number;
isFull: boolean;
onDeleteEmitter: EventEmitter2<IDeleteEvent>;
onDelete: IEvent<IDeleteEvent>;
onInsertEmitter: EventEmitter2<IInsertEvent>;
onInsert: IEvent<IInsertEvent>;
onTrimEmitter: EventEmitter2<number>;
onTrim: IEvent<number>;
get(index: number): T | undefined;
set(index: number, value: T): void;
push(value: T): void;
+4 -13
View File
@@ -1,19 +1,10 @@
{
"extends": "../tsconfig-library-base",
"compilerOptions": {
"target": "es5",
"lib": [
"es5"
],
"rootDir": ".",
"noEmit": true,
"strict": true,
"pretty": true,
"outDir": "../../lib",
"types": [
"../../node_modules/@types/mocha",
"../../"
"../../node_modules/@types/mocha"
]
},
"include": [
"./**/*"
]
"include": [ "./**/*" ]
}
+87
View File
@@ -3,6 +3,8 @@
* @license MIT
*/
import { IDisposable } from '../common/Types';
export const enum KeyboardResultType {
SEND_KEY,
SELECT_ALL,
@@ -19,3 +21,88 @@ export interface IKeyboardResult {
export interface ICharset {
[key: string]: string;
}
export type CharData = [number, string, number, number];
export type IColorRGB = [number, number, number];
/** Attribute data */
export interface IAttributeData {
fg: number;
bg: number;
clone(): IAttributeData;
// flags
isInverse(): number;
isBold(): number;
isUnderline(): number;
isBlink(): number;
isInvisible(): number;
isItalic(): number;
isDim(): number;
// color modes
getFgColorMode(): number;
getBgColorMode(): number;
isFgRGB(): boolean;
isBgRGB(): boolean;
isFgPalette(): boolean;
isBgPalette(): boolean;
isFgDefault(): boolean;
isBgDefault(): boolean;
// colors
getFgColor(): number;
getBgColor(): number;
}
/** Cell data */
export interface ICellData extends IAttributeData {
content: number;
combinedData: string;
isCombined(): number;
getWidth(): number;
getChars(): string;
getCode(): number;
setFromCharData(value: CharData): void;
getAsCharData(): CharData;
}
/**
* Interface for a line in the terminal buffer.
*/
export interface IBufferLine {
length: number;
isWrapped: boolean;
get(index: number): CharData;
set(index: number, value: CharData): void;
loadCell(index: number, cell: ICellData): ICellData;
setCell(index: number, cell: ICellData): void;
setCellFromCodePoint(index: number, codePoint: number, width: number, fg: number, bg: number): void;
addCodepointToCell(index: number, codePoint: number): void;
insertCells(pos: number, n: number, ch: ICellData): void;
deleteCells(pos: number, n: number, fill: ICellData): void;
replaceCells(start: number, end: number, fill: ICellData): void;
resize(cols: number, fill: ICellData): void;
fill(fillCellData: ICellData): void;
copyFrom(line: IBufferLine): void;
clone(): IBufferLine;
getTrimmedLength(): number;
translateToString(trimRight?: boolean, startCol?: number, endCol?: number): string;
/* direct access to cell attrs */
getWidth(index: number): number;
hasWidth(index: number): number;
getFg(index: number): number;
getBg(index: number): number;
hasContent(index: number): number;
getCodePoint(index: number): number;
isCombined(index: number): number;
getString(index: number): string;
}
export interface IMarker extends IDisposable {
readonly id: number;
readonly isDisposed: boolean;
readonly line: number;
}
+367
View File
@@ -0,0 +1,367 @@
/**
* Copyright (c) 2018 The xterm.js authors. All rights reserved.
* @license MIT
*/
import * as chai from 'chai';
import { BufferLine, CellData, Content, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE, DEFAULT_ATTR } from './BufferLine';
import { CharData, IBufferLine } from '../Types';
class TestBufferLine extends BufferLine {
public get combined(): {[index: number]: string} {
return this._combined;
}
public toArray(): CharData[] {
const result = [];
for (let i = 0; i < this.length; ++i) {
result.push(this.loadCell(i, new CellData()).getAsCharData());
}
return result;
}
}
describe('CellData', () => {
it('CharData <--> CellData equality', () => {
const cell = new CellData();
// ASCII
cell.setFromCharData([123, 'a', 1, 'a'.charCodeAt(0)]);
chai.assert.deepEqual(cell.getAsCharData(), [123, 'a', 1, 'a'.charCodeAt(0)]);
chai.assert.equal(cell.isCombined(), 0);
// combining
cell.setFromCharData([123, 'e\u0301', 1, '\u0301'.charCodeAt(0)]);
chai.assert.deepEqual(cell.getAsCharData(), [123, 'e\u0301', 1, '\u0301'.charCodeAt(0)]);
chai.assert.equal(cell.isCombined(), Content.IS_COMBINED_MASK);
// surrogate
cell.setFromCharData([123, '𝄞', 1, 0x1D11E]);
chai.assert.deepEqual(cell.getAsCharData(), [123, '𝄞', 1, 0x1D11E]);
chai.assert.equal(cell.isCombined(), 0);
// surrogate + combining
cell.setFromCharData([123, '𓂀\u0301', 1, '𓂀\u0301'.charCodeAt(2)]);
chai.assert.deepEqual(cell.getAsCharData(), [123, '𓂀\u0301', 1, '𓂀\u0301'.charCodeAt(2)]);
chai.assert.equal(cell.isCombined(), Content.IS_COMBINED_MASK);
// wide char
cell.setFromCharData([123, '1', 2, '1'.charCodeAt(0)]);
chai.assert.deepEqual(cell.getAsCharData(), [123, '1', 2, '1'.charCodeAt(0)]);
chai.assert.equal(cell.isCombined(), 0);
});
});
describe('BufferLine', function(): void {
it('ctor', function(): void {
let line: IBufferLine = new TestBufferLine(0);
chai.expect(line.length).equals(0);
chai.expect(line.isWrapped).equals(false);
line = new TestBufferLine(10);
chai.expect(line.length).equals(10);
chai.expect(line.loadCell(0, new CellData()).getAsCharData()).eql([0, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]);
chai.expect(line.isWrapped).equals(false);
line = new TestBufferLine(10, undefined, true);
chai.expect(line.length).equals(10);
chai.expect(line.loadCell(0, new CellData()).getAsCharData()).eql([0, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]);
chai.expect(line.isWrapped).equals(true);
line = new TestBufferLine(10, CellData.fromCharData([123, 'a', 456, 'a'.charCodeAt(0)]), true);
chai.expect(line.length).equals(10);
chai.expect(line.loadCell(0, new CellData()).getAsCharData()).eql([123, 'a', 456, 'a'.charCodeAt(0)]);
chai.expect(line.isWrapped).equals(true);
});
it('insertCells', function(): void {
const line = new TestBufferLine(3);
line.setCell(0, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]));
line.setCell(1, CellData.fromCharData([2, 'b', 0, 'b'.charCodeAt(0)]));
line.setCell(2, CellData.fromCharData([3, 'c', 0, 'c'.charCodeAt(0)]));
line.insertCells(1, 3, CellData.fromCharData([4, 'd', 0, 'd'.charCodeAt(0)]));
chai.expect(line.toArray()).eql([
[1, 'a', 0, 'a'.charCodeAt(0)],
[4, 'd', 0, 'd'.charCodeAt(0)],
[4, 'd', 0, 'd'.charCodeAt(0)]
]);
});
it('deleteCells', function(): void {
const line = new TestBufferLine(5);
line.setCell(0, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]));
line.setCell(1, CellData.fromCharData([2, 'b', 0, 'b'.charCodeAt(0)]));
line.setCell(2, CellData.fromCharData([3, 'c', 0, 'c'.charCodeAt(0)]));
line.setCell(3, CellData.fromCharData([4, 'd', 0, 'd'.charCodeAt(0)]));
line.setCell(4, CellData.fromCharData([5, 'e', 0, 'e'.charCodeAt(0)]));
line.deleteCells(1, 2, CellData.fromCharData([6, 'f', 0, 'f'.charCodeAt(0)]));
chai.expect(line.toArray()).eql([
[1, 'a', 0, 'a'.charCodeAt(0)],
[4, 'd', 0, 'd'.charCodeAt(0)],
[5, 'e', 0, 'e'.charCodeAt(0)],
[6, 'f', 0, 'f'.charCodeAt(0)],
[6, 'f', 0, 'f'.charCodeAt(0)]
]);
});
it('replaceCells', function(): void {
const line = new TestBufferLine(5);
line.setCell(0, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]));
line.setCell(1, CellData.fromCharData([2, 'b', 0, 'b'.charCodeAt(0)]));
line.setCell(2, CellData.fromCharData([3, 'c', 0, 'c'.charCodeAt(0)]));
line.setCell(3, CellData.fromCharData([4, 'd', 0, 'd'.charCodeAt(0)]));
line.setCell(4, CellData.fromCharData([5, 'e', 0, 'e'.charCodeAt(0)]));
line.replaceCells(2, 4, CellData.fromCharData([6, 'f', 0, 'f'.charCodeAt(0)]));
chai.expect(line.toArray()).eql([
[1, 'a', 0, 'a'.charCodeAt(0)],
[2, 'b', 0, 'b'.charCodeAt(0)],
[6, 'f', 0, 'f'.charCodeAt(0)],
[6, 'f', 0, 'f'.charCodeAt(0)],
[5, 'e', 0, 'e'.charCodeAt(0)]
]);
});
it('fill', function(): void {
const line = new TestBufferLine(5);
line.setCell(0, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]));
line.setCell(1, CellData.fromCharData([2, 'b', 0, 'b'.charCodeAt(0)]));
line.setCell(2, CellData.fromCharData([3, 'c', 0, 'c'.charCodeAt(0)]));
line.setCell(3, CellData.fromCharData([4, 'd', 0, 'd'.charCodeAt(0)]));
line.setCell(4, CellData.fromCharData([5, 'e', 0, 'e'.charCodeAt(0)]));
line.fill(CellData.fromCharData([123, 'z', 0, 'z'.charCodeAt(0)]));
chai.expect(line.toArray()).eql([
[123, 'z', 0, 'z'.charCodeAt(0)],
[123, 'z', 0, 'z'.charCodeAt(0)],
[123, 'z', 0, 'z'.charCodeAt(0)],
[123, 'z', 0, 'z'.charCodeAt(0)],
[123, 'z', 0, 'z'.charCodeAt(0)]
]);
});
it('clone', function(): void {
const line = new TestBufferLine(5, undefined, true);
line.setCell(0, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]));
line.setCell(1, CellData.fromCharData([2, 'b', 0, 'b'.charCodeAt(0)]));
line.setCell(2, CellData.fromCharData([3, 'c', 0, 'c'.charCodeAt(0)]));
line.setCell(3, CellData.fromCharData([4, 'd', 0, 'd'.charCodeAt(0)]));
line.setCell(4, CellData.fromCharData([5, 'e', 0, 'e'.charCodeAt(0)]));
const line2 = line.clone();
chai.expect(TestBufferLine.prototype.toArray.apply(line2)).eql(line.toArray());
chai.expect(line2.length).equals(line.length);
chai.expect(line2.isWrapped).equals(line.isWrapped);
});
it('copyFrom', function(): void {
const line = new TestBufferLine(5);
line.setCell(0, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]));
line.setCell(1, CellData.fromCharData([2, 'b', 0, 'b'.charCodeAt(0)]));
line.setCell(2, CellData.fromCharData([3, 'c', 0, 'c'.charCodeAt(0)]));
line.setCell(3, CellData.fromCharData([4, 'd', 0, 'd'.charCodeAt(0)]));
line.setCell(4, CellData.fromCharData([5, 'e', 0, 'e'.charCodeAt(0)]));
const line2 = new TestBufferLine(5, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]), true);
line2.copyFrom(line);
chai.expect(line2.toArray()).eql(line.toArray());
chai.expect(line2.length).equals(line.length);
chai.expect(line2.isWrapped).equals(line.isWrapped);
});
it('should support combining chars', function(): void {
// CHAR_DATA_CODE_INDEX resembles current behavior in InputHandler.print
// --> set code to the last charCodeAt value of the string
// Note: needs to be fixed once the string pointer is in place
const line = new TestBufferLine(2, CellData.fromCharData([1, 'e\u0301', 0, '\u0301'.charCodeAt(0)]));
chai.expect(line.toArray()).eql([[1, 'e\u0301', 0, '\u0301'.charCodeAt(0)], [1, 'e\u0301', 0, '\u0301'.charCodeAt(0)]]);
const line2 = new TestBufferLine(5, CellData.fromCharData([1, 'a', 0, '\u0301'.charCodeAt(0)]), true);
line2.copyFrom(line);
chai.expect(line2.toArray()).eql(line.toArray());
const line3 = line.clone();
chai.expect(TestBufferLine.prototype.toArray.apply(line3)).eql(line.toArray());
});
describe('resize', function(): void {
it('enlarge(false)', function(): void {
const line = new TestBufferLine(5, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]), false);
line.resize(10, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]));
chai.expect(line.toArray()).eql((Array(10) as any).fill([1, 'a', 0, 'a'.charCodeAt(0)]));
});
it('enlarge(true)', function(): void {
const line = new TestBufferLine(5, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]), false);
line.resize(10, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]));
chai.expect(line.toArray()).eql((Array(10) as any).fill([1, 'a', 0, 'a'.charCodeAt(0)]));
});
it('shrink(true) - should apply new size', function(): void {
const line = new TestBufferLine(10, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]), false);
line.resize(5, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]));
chai.expect(line.toArray()).eql((Array(5) as any).fill([1, 'a', 0, 'a'.charCodeAt(0)]));
});
it('shrink to 0 length', function(): void {
const line = new TestBufferLine(10, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]), false);
line.resize(0, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]));
chai.expect(line.toArray()).eql((Array(0) as any).fill([1, 'a', 0, 'a'.charCodeAt(0)]));
});
it('should remove combining data on replaced cells after shrinking then enlarging', () => {
const line = new TestBufferLine(10, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]), false);
line.set(2, [ 0, '😁', 1, '😁'.charCodeAt(0) ]);
line.set(9, [ 0, '😁', 1, '😁'.charCodeAt(0) ]);
chai.expect(line.translateToString()).eql('aa😁aaaaaa😁');
chai.expect(Object.keys(line.combined).length).eql(2);
line.resize(5, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]));
chai.expect(line.translateToString()).eql('aa😁aa');
line.resize(10, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]));
chai.expect(line.translateToString()).eql('aa😁aaaaaaa');
chai.expect(Object.keys(line.combined).length).eql(1);
});
});
describe('getTrimLength', function(): void {
it('empty line', function(): void {
const line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]), false);
chai.expect(line.getTrimmedLength()).equal(0);
});
it('ASCII', function(): void {
const line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]), false);
line.setCell(0, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)]));
line.setCell(2, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)]));
chai.expect(line.getTrimmedLength()).equal(3);
});
it('surrogate', function(): void {
const line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]), false);
line.setCell(0, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)]));
line.setCell(2, CellData.fromCharData([1, '𝄞', 1, '𝄞'.charCodeAt(0)]));
chai.expect(line.getTrimmedLength()).equal(3);
});
it('combining', function(): void {
const line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]), false);
line.setCell(0, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)]));
line.setCell(2, CellData.fromCharData([1, 'e\u0301', 1, '\u0301'.charCodeAt(0)]));
chai.expect(line.getTrimmedLength()).equal(3);
});
it('fullwidth', function(): void {
const line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]), false);
line.setCell(0, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)]));
line.setCell(2, CellData.fromCharData([1, '1', 2, '1'.charCodeAt(0)]));
line.setCell(3, CellData.fromCharData([0, '', 0, 0]));
chai.expect(line.getTrimmedLength()).equal(4); // also counts null cell after fullwidth
});
});
describe('translateToString with and w\'o trimming', function(): void {
it('empty line', function(): void {
const line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]), false);
chai.expect(line.translateToString(false)).equal(' ');
chai.expect(line.translateToString(true)).equal('');
});
it('ASCII', function(): void {
const line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]), false);
line.setCell(0, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)]));
line.setCell(2, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)]));
line.setCell(4, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)]));
line.setCell(5, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)]));
chai.expect(line.translateToString(false)).equal('a a aa ');
chai.expect(line.translateToString(true)).equal('a a aa');
chai.expect(line.translateToString(false, 0, 5)).equal('a a a');
chai.expect(line.translateToString(false, 0, 4)).equal('a a ');
chai.expect(line.translateToString(false, 0, 3)).equal('a a');
chai.expect(line.translateToString(true, 0, 5)).equal('a a a');
chai.expect(line.translateToString(true, 0, 4)).equal('a a ');
chai.expect(line.translateToString(true, 0, 3)).equal('a a');
});
it('surrogate', function(): void {
const line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]), false);
line.setCell(0, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)]));
line.setCell(2, CellData.fromCharData([1, '𝄞', 1, '𝄞'.charCodeAt(0)]));
line.setCell(4, CellData.fromCharData([1, '𝄞', 1, '𝄞'.charCodeAt(0)]));
line.setCell(5, CellData.fromCharData([1, '𝄞', 1, '𝄞'.charCodeAt(0)]));
chai.expect(line.translateToString(false)).equal('a 𝄞 𝄞𝄞 ');
chai.expect(line.translateToString(true)).equal('a 𝄞 𝄞𝄞');
chai.expect(line.translateToString(false, 0, 5)).equal('a 𝄞 𝄞');
chai.expect(line.translateToString(false, 0, 4)).equal('a 𝄞 ');
chai.expect(line.translateToString(false, 0, 3)).equal('a 𝄞');
chai.expect(line.translateToString(true, 0, 5)).equal('a 𝄞 𝄞');
chai.expect(line.translateToString(true, 0, 4)).equal('a 𝄞 ');
chai.expect(line.translateToString(true, 0, 3)).equal('a 𝄞');
});
it('combining', function(): void {
const line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]), false);
line.setCell(0, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)]));
line.setCell(2, CellData.fromCharData([1, 'e\u0301', 1, '\u0301'.charCodeAt(0)]));
line.setCell(4, CellData.fromCharData([1, 'e\u0301', 1, '\u0301'.charCodeAt(0)]));
line.setCell(5, CellData.fromCharData([1, 'e\u0301', 1, '\u0301'.charCodeAt(0)]));
chai.expect(line.translateToString(false)).equal('a e\u0301 e\u0301e\u0301 ');
chai.expect(line.translateToString(true)).equal('a e\u0301 e\u0301e\u0301');
chai.expect(line.translateToString(false, 0, 5)).equal('a e\u0301 e\u0301');
chai.expect(line.translateToString(false, 0, 4)).equal('a e\u0301 ');
chai.expect(line.translateToString(false, 0, 3)).equal('a e\u0301');
chai.expect(line.translateToString(true, 0, 5)).equal('a e\u0301 e\u0301');
chai.expect(line.translateToString(true, 0, 4)).equal('a e\u0301 ');
chai.expect(line.translateToString(true, 0, 3)).equal('a e\u0301');
});
it('fullwidth', function(): void {
const line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]), false);
line.setCell(0, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)]));
line.setCell(2, CellData.fromCharData([1, '1', 2, '1'.charCodeAt(0)]));
line.setCell(3, CellData.fromCharData([0, '', 0, 0]));
line.setCell(5, CellData.fromCharData([1, '1', 2, '1'.charCodeAt(0)]));
line.setCell(6, CellData.fromCharData([0, '', 0, 0]));
line.setCell(7, CellData.fromCharData([1, '1', 2, '1'.charCodeAt(0)]));
line.setCell(8, CellData.fromCharData([0, '', 0, 0]));
chai.expect(line.translateToString(false)).equal('a 1 11 ');
chai.expect(line.translateToString(true)).equal('a 1 11');
chai.expect(line.translateToString(false, 0, 7)).equal('a 1 1');
chai.expect(line.translateToString(false, 0, 6)).equal('a 1 1');
chai.expect(line.translateToString(false, 0, 5)).equal('a 1 ');
chai.expect(line.translateToString(false, 0, 4)).equal('a 1');
chai.expect(line.translateToString(false, 0, 3)).equal('a 1');
chai.expect(line.translateToString(false, 0, 2)).equal('a ');
chai.expect(line.translateToString(true, 0, 7)).equal('a 1 1');
chai.expect(line.translateToString(true, 0, 6)).equal('a 1 1');
chai.expect(line.translateToString(true, 0, 5)).equal('a 1 ');
chai.expect(line.translateToString(true, 0, 4)).equal('a 1');
chai.expect(line.translateToString(true, 0, 3)).equal('a 1');
chai.expect(line.translateToString(true, 0, 2)).equal('a ');
});
it('space at end', function(): void {
const line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]), false);
line.setCell(0, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)]));
line.setCell(2, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)]));
line.setCell(4, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)]));
line.setCell(5, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)]));
line.setCell(6, CellData.fromCharData([1, ' ', 1, ' '.charCodeAt(0)]));
chai.expect(line.translateToString(false)).equal('a a aa ');
chai.expect(line.translateToString(true)).equal('a a aa ');
});
it('should always return some sane value', function(): void {
// sanity check - broken line with invalid out of bound null width cells
// this can atm happen with deleting/inserting chars in inputhandler by "breaking"
// fullwidth pairs --> needs to be fixed after settling BufferLine impl
const line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, 0, NULL_CELL_CODE]), false);
chai.expect(line.translateToString(false)).equal(' ');
chai.expect(line.translateToString(true)).equal('');
});
it('should work with endCol=0', () => {
const line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, 0, NULL_CELL_CODE]), false);
line.setCell(0, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)]));
chai.expect(line.translateToString(true, 0, 0)).equal('');
});
});
describe('addCharToCell', () => {
it('should set width to 1 for empty cell', () => {
const line = new TestBufferLine(3, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]), false);
line.addCodepointToCell(0, '\u0301'.charCodeAt(0));
const cell = line.loadCell(0, new CellData());
// chars contains single combining char
// width is set to 1
chai.assert.deepEqual(cell.getAsCharData(), [DEFAULT_ATTR, '\u0301', 1, 0x0301]);
// do not account a single combining char as combined
chai.assert.equal(cell.isCombined(), 0);
});
it('should add char to combining string in cell', () => {
const line = new TestBufferLine(3, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]), false);
const cell = line .loadCell(0, new CellData());
cell.setFromCharData([123, 'e\u0301', 1, 'e\u0301'.charCodeAt(1)]);
line.setCell(0, cell);
line.addCodepointToCell(0, '\u0301'.charCodeAt(0));
line.loadCell(0, cell);
// chars contains 3 chars
// width is set to 1
chai.assert.deepEqual(cell.getAsCharData(), [123, 'e\u0301\u0301', 1, 0x0301]);
// do not account a single combining char as combined
chai.assert.equal(cell.isCombined(), Content.IS_COMBINED_MASK);
});
it('should create combining string on taken cell', () => {
const line = new TestBufferLine(3, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]), false);
const cell = line .loadCell(0, new CellData());
cell.setFromCharData([123, 'e', 1, 'e'.charCodeAt(1)]);
line.setCell(0, cell);
line.addCodepointToCell(0, '\u0301'.charCodeAt(0));
line.loadCell(0, cell);
// chars contains 2 chars
// width is set to 1
chai.assert.deepEqual(cell.getAsCharData(), [123, 'e\u0301', 1, 0x0301]);
// do not account a single combining char as combined
chai.assert.equal(cell.isCombined(), Content.IS_COMBINED_MASK);
});
});
});

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