Compare commits

...
209 Commits
Author SHA1 Message Date
Daniel Imms bd0d267d97 Merge pull request #2605 from Tyriar/r43
v4.3.0
2019-12-05 12:40:34 -08:00
Daniel Imms c9babb9477 v4.3.0 2019-12-05 12:34:36 -08:00
Daniel Imms 17cb610ef1 Merge pull request #2602 from Tyriar/fix_dom
Fix minimumContrastRatio on dom/truecolor
2019-12-04 10:59:01 -08:00
Daniel Imms 543b3d6c9f Fix minimumContrastRatio on dom/truecolor
Fixes #2593
2019-12-04 10:54:17 -08:00
Daniel Imms 8b5c48fe87 Merge pull request #2560 from ivanwonder/reverse-transparency
force alpha to 1 when using background color as inverted foreground color
2019-11-25 15:10:59 -08:00
Daniel Imms 1ff1809ab7 Support opaque inverse background in webgl, add test 2019-11-25 15:06:28 -08:00
Daniel Imms ef05a6a053 Draw transparent background in WebGL renderer
Part of #2252
2019-11-25 14:33:12 -08:00
Daniel Imms bceddfbc28 Cover inverse non-black uncached case in canvas renderer 2019-11-25 14:22:03 -08:00
Daniel Imms 4cd8b22a59 Use toRgba helper 2019-11-25 11:58:26 -08:00
Daniel Imms 1a5c3f9364 Properly set non-black transparency backgrounds
Before it was always set to 0 as the resolved color was pulled from
an ImageData
2019-11-25 11:57:52 -08:00
Daniel Imms cbb8dc7780 Merge remote-tracking branch 'ups/master' into pr/ivanwonder/2560 2019-11-25 11:14:03 -08:00
Daniel Imms 3b1479a02b Merge pull request #2585 from Tyriar/unused
Remove unused imports/functions
2019-11-25 09:24:20 -08:00
Daniel Imms afe6cb6b86 Remove unused imports/functions 2019-11-25 09:12:39 -08:00
Daniel Imms 2173a5399d Merge pull request #2583 from xtermjs/Tyriar-patch-1
Include ` in word separators
2019-11-25 07:50:10 -08:00
Daniel Imms 25f2e66222 Include ` in word separators
See microsoft/vscode#84483
2019-11-25 07:36:06 -08:00
Daniel Imms 397d053e06 Merge pull request #2582 from Tyriar/2581_cursor
Mark before and after cursor rows dirty when changed
2019-11-24 11:22:47 -08:00
Daniel Imms 217bc24235 Mark before and after cursor rows dirty when changed
The DOM renderer renders the cursor as part of the content, not on a
separate layer, so it needs to be marked dirty to draw.

Fixes #2581
2019-11-24 11:15:28 -08:00
Daniel Imms 4d2afce5f2 Merge pull request #2580 from Tyriar/puppeteer_release
Skip puppeteer download during release job
2019-11-24 09:47:18 -08:00
Daniel Imms 2da5a7a26b Skip puppeteer download during release job 2019-11-24 09:42:33 -08:00
Daniel Imms fffbec7294 Merge pull request #2579 from Tyriar/publish_fix
Fix publish script for dot separated beta
2019-11-24 09:35:44 -08:00
Daniel Imms c60b822f4b Fix publish script for dot separated beta 2019-11-24 09:34:29 -08:00
Daniel Imms 2882ae76ef Merge pull request #2578 from Tyriar/85048
Don't throw when rendering before atlas is set
2019-11-24 09:26:13 -08:00
Daniel Imms 53fb1ba54d Don't throw when rendering before atlas is set
This fixes a problem when loading the webgl addon before the terminal
is attached.

See microsoft/vscode#85048
2019-11-24 09:22:07 -08:00
Daniel Imms 40d8b606a3 Merge pull request #2577 from Tyriar/beta_dot
Add a . before beta and beta version
2019-11-22 19:38:47 -08:00
Daniel Imms 2f1f2f8785 Add a . before beta and beta version
Fixes #2576
2019-11-22 19:37:55 -08:00
Daniel Imms 1300b7a6b7 Merge pull request #2575 from Tyriar/webgl_exception
Prevent atlas missing exception when opened before attach
2019-11-22 16:06:30 -08:00
Daniel Imms 0bd0f3a364 Prevent atlas missing exception when opened before attach
See microsoft/vscode#85048
2019-11-22 16:00:25 -08:00
Daniel Imms c0da45113f Merge pull request #2574 from Tyriar/question
Direct question issues to Stack Overflow
2019-11-22 09:39:03 -08:00
Daniel Imms 93487d4fb3 Direct question issues to Stack Overflow 2019-11-22 09:34:06 -08:00
Daniel Imms 70babeacb6 Merge pull request #2569 from Tyriar/layers6
Move WindowsMode to common/
2019-11-15 10:27:55 -08:00
Daniel Imms 12e9dce6c7 Merge pull request #2563 from Tyriar/322_minimum_contrast
Implement minimum contrast ratio
2019-11-15 10:24:35 -08:00
Daniel Imms 943928c4d1 Move WindowsMode to common/
Part of #1507
2019-11-14 22:26:26 -08:00
Daniel Imms 5aa3157672 Return inverse colors correctly
This probably wasn't causing a bug since the opposite values were
being returned, it was wrong and made the code confusing though.
2019-11-14 22:12:36 -08:00
Daniel Imms e5f4e2ff8f Support min contrast ratio in canvas renderer too: 2019-11-14 22:07:50 -08:00
Daniel Imms c95337a742 Remove unneeded params from webgl funcs 2019-11-14 21:57:15 -08:00
Daniel Imms 023fab9095 Remove unused import 2019-11-14 20:34:01 -08:00
Daniel Imms cc9f649e3a Merge branch 'master' into 322_minimum_contrast 2019-11-14 20:14:17 -08:00
Daniel Imms be9700878b Have inverse bold respect drawBoldTextInBrightColors 2019-11-14 20:02:40 -08:00
Daniel Imms 24dc87af2c Only enter updateBackgounds condition if cell changed
We want to check when the background changed or when the foreground
changed (and either the old or new value was inverse)
2019-11-14 19:46:24 -08:00
jerch c99a096d52 Merge pull request #2566 from jerch/binary_event
onBinary event
2019-11-14 22:39:32 +01:00
Jörg Breitbart 57fe84508c apply empty report rule to all encodings; comments added 2019-11-14 22:29:46 +01:00
Jörg Breitbart f1f103850c fix tests; fix attach addon 2019-11-14 00:48:16 +01:00
Jörg Breitbart 00d626b6cc add onBinary to interfaces 2019-11-13 23:57:51 +01:00
Jörg Breitbart b780abaf3b add onBinary on CoreService, use for DEFAULT mouse reports 2019-11-13 23:46:01 +01:00
Daniel Imms 7652cd8af3 Use hash notation over rgb() for dom colors 2019-11-13 06:59:50 -08:00
Daniel Imms 579d74ee33 jsdoc rgbRelativeLuminance2 2019-11-13 06:53:29 -08:00
Daniel Imms 2c959dfb2e Keep a working AttributeData in WebglCharAtlas 2019-11-13 06:52:17 -08:00
Daniel Imms 20b0a95bb0 Add tests for ensureContrastRatioRgba 2019-11-12 11:05:58 -08:00
Daniel Imms db090c70de Add unit tests for ColorContrastCache 2019-11-12 10:21:23 -08:00
Daniel Imms d1f7d2ef6a Tweak readme to call out min contrast ratio 2019-11-12 10:10:55 -08:00
Daniel Imms 56d12fe846 Fix color manager unit test 2019-11-12 10:09:32 -08:00
Daniel Imms b0c3606207 Add example values to .d.ts 2019-11-12 10:03:38 -08:00
Daniel Imms 032185f8b0 Link webgl directly to browser/Color 2019-11-12 10:00:40 -08:00
Daniel Imms b3f439af09 Remove unused imports 2019-11-12 09:55:09 -08:00
Daniel Imms 647cff53db Lint 2019-11-12 09:53:42 -08:00
Daniel Imms 3b0042fd46 Remove log 2019-11-12 09:46:07 -08:00
Daniel Imms 8a0122f482 Add contrast caching to webgl 2019-11-12 09:42:55 -08:00
Daniel Imms faaa07be68 Add contrast caching for DOM renderer 2019-11-12 09:33:50 -08:00
Daniel Imms c2474626f0 Clear setting as part of run 2019-11-12 09:07:08 -08:00
Daniel Imms f5774fdc70 Merge branch 'master' into 322_minimum_contrast 2019-11-12 08:53:25 -08:00
Daniel Imms ca5cee7e86 Fix contrast tests 2019-11-12 08:53:06 -08:00
Daniel Imms 5ee9bcf2b5 Remove .only 2019-11-11 23:23:33 -08:00
Daniel Imms 6626f9628d Use ceil in both 2019-11-11 23:14:27 -08:00
Daniel Imms 2fc1791dde Fix floor/ceil in luminance methods 2019-11-11 23:11:36 -08:00
Daniel Imms 414eea2a2c Merge pull request #2562 from AndrewSverdrup/change-codevolve-to-next-tech
Update Codevolve to Next Tech
2019-11-11 22:56:35 -08:00
Daniel Imms 9b139a744b Add test for min contrast on white bg 2019-11-11 22:55:08 -08:00
Daniel Imms 531409c2c7 Support minimum contrast ratio in webgl 2019-11-11 22:47:26 -08:00
Daniel Imms 2283ea45a6 Refresh rows after ratio changes 2019-11-11 22:18:39 -08:00
Daniel Imms db775afd70 Support drawBoldTextInBrightColors in webgl again
This broke as part of this PR
2019-11-11 21:35:51 -08:00
Daniel Imms b1bcb899e0 Fix inverse in webgl, add tests 2019-11-11 21:29:02 -08:00
Daniel Imms a6dcb21f1c Fix tests 2019-11-11 19:30:36 -08:00
Daniel Imms 9cd66e72bb Add tests for relative luminance 2019-11-11 19:14:35 -08:00
Daniel Imms e46ff9ec85 Add minimumContrastRatio to DOM renderer
Part of #322
2019-11-11 19:03:12 -08:00
ivanwonder fd868044fd add a helper to make color opaque 2019-11-12 10:06:51 +08:00
Andrew Sverdrup dfc1664d6e Update Codevolve to Next Tech
We changed our name several months back.
2019-11-11 13:23:59 -08:00
Daniel Imms e911656306 Merge pull request #2561 from xtermjs/Tyriar-patch-1
Prefer non-relative imports
2019-11-11 10:54:15 -08:00
Daniel Imms 25f9487239 Prefer non-relative imports
Fixes some auto imports in VS Code, see microsoft/TypeScript#32144
2019-11-11 10:24:54 -08:00
Daniel Imms 339d4d6a73 Merge pull request #2559 from xtermjs/Tyriar-patch-1
Generate coverage report for pipelines
2019-11-10 21:43:41 -08:00
Daniel Imms 94afd1fa38 Generate coverage report for pipelines 2019-11-10 21:38:43 -08:00
ivanwonder 5bc80fdf67 force alpha to 1 when using background color as inverted foreground color. 2019-11-11 11:01:32 +08:00
Daniel Imms 03fe1677b6 Merge pull request #2558 from Tyriar/force_build
Whitespace to force a webgl release
2019-11-10 12:07:01 -08:00
Daniel Imms 6b7e29ea32 Whitespace to force a webgl release 2019-11-10 12:06:20 -08:00
Daniel Imms 9eeb5aac32 Merge pull request #2557 from Tyriar/fix_release
Correct import in Color.ts
2019-11-10 11:49:12 -08:00
Daniel Imms 450a626218 Correct import in Color.ts 2019-11-10 11:48:32 -08:00
Daniel Imms a8ff27ceb9 Merge pull request #2552 from Tyriar/webgl_compat_attr
True color support in WebGL renderer
2019-11-10 11:37:26 -08:00
Daniel Imms e89d79b879 Remove unused code 2019-11-10 11:33:41 -08:00
Daniel Imms 734bd8c96a Factor out toRgba function and test 2019-11-10 11:30:48 -08:00
Daniel Imms 2da77dd5f8 Use opaque selection in webgl, fix selection default rgba 2019-11-10 11:23:34 -08:00
Daniel Imms 51c77568a8 Add tests for Color functions 2019-11-10 11:17:36 -08:00
Daniel Imms 53b0149764 Add selectionOpaque and factor out color into new file 2019-11-10 11:11:26 -08:00
Daniel Imms 2b2bd91e2e Add background true color tests 2019-11-10 10:41:30 -08:00
Daniel Imms 350ca8d5fb Organize tests 2019-11-10 10:39:56 -08:00
Daniel Imms 753ad30dbc Add true color fg tests for webgl 2019-11-10 10:38:23 -08:00
Daniel Imms 8bf837795f Add tests for webgl colors 16-255 2019-11-10 10:29:50 -08:00
Daniel 01628362f5 Support selection color 2019-11-10 01:19:27 -08:00
Daniel 811307562b Use actual default color for rectangle renderer 2019-11-10 00:59:11 -08:00
Daniel Imms 98fe63051d Merge branch 'master' into webgl_compat_attr 2019-11-10 00:53:42 -08:00
Daniel Imms adc631d192 Merge pull request #2553 from Tyriar/retry
Add retries to onRender test
2019-11-10 00:53:36 -08:00
Daniel 2cde011397 Add retries to onRender test
I've seen this flaky locally and in CI
2019-11-10 00:49:10 -08:00
Daniel b56e29a35b Import clean up 2019-11-10 00:39:46 -08:00
Daniel 149cc76803 Avoid extra object creation for foreground fill 2019-11-10 00:30:58 -08:00
Daniel b13e9066bd Use a switch for color mode conditions 2019-11-10 00:29:50 -08:00
Daniel a2da8f9fbb Fix true color glyphs not having background cleared 2019-11-10 00:23:08 -08:00
Daniel 3773b22439 Support true color... mostly 2019-11-09 23:41:00 -08:00
Daniel 39f0e17edf Consolidate constants, fix rectangle rendering 2019-11-09 23:05:39 -08:00
Daniel 2b5d229aab Reduce size of model to 3 by removing attr 2019-11-09 22:56:42 -08:00
Daniel a2bd0e8eaa Remove attr compat from webgl 2019-11-09 22:50:22 -08:00
Daniel 9dd2ccb1ad Fix selection color to set correct mode 2019-11-09 22:48:28 -08:00
Daniel e15476704c Adopt new [bg][fg] map in char cache 2019-11-09 22:43:25 -08:00
Daniel 8f0f8bf827 Remove cache reliance on flags 2019-11-09 22:31:10 -08:00
Daniel e72ee3c58d Move webgl drawing over from compat
Still need to tackle glyph key
2019-11-09 22:28:59 -08:00
Daniel 8a6e0fca07 Move parts over to CellData 2019-11-09 21:15:07 -08:00
Daniel Imms 87d2015793 Merge pull request #2551 from Tyriar/puppeteer_slowmo
Make integration tests very fast by removing slowMo
2019-11-09 19:14:31 -08:00
Daniel Imms 40b3766abc Fix import that got lost in merge 2019-11-09 19:05:20 -08:00
Daniel Imms 0c69b68b4c Merge branch 'master' into puppeteer_slowmo 2019-11-09 19:02:55 -08:00
Daniel Imms 641b5f867b Merge pull request #2548 from Tyriar/2254_webgl_dispose
Implement webgl renderer dispose
2019-11-09 19:01:52 -08:00
Daniel Imms 057d0a16f2 Ensure link hover exists before hovering 2019-11-09 18:54:24 -08:00
Daniel Imms 669622108c Wait for all elements to be rendered in web links 2019-11-09 18:48:09 -08:00
Daniel Imms 971e69f505 Reduce flakiness 2019-11-09 18:43:25 -08:00
Daniel Imms b0640b7705 Remove slowMo param everywhere 2019-11-09 18:28:50 -08:00
Daniel Imms 7ff6e4f493 Fix flaky terminal api tests 2019-11-09 18:28:24 -08:00
Daniel Imms 34e703e472 Fix most of Terminal.api timing issues 2019-11-09 17:29:32 -08:00
Daniel Imms 17677568f9 Fix timing issues in mouse tracking tests 2019-11-09 14:16:57 -08:00
Daniel Imms 2aaa997d39 Fix timing issues in input handler 2019-11-09 14:15:16 -08:00
Daniel Imms 74f04ae92e Fix timing issues in char width test 2019-11-09 14:12:46 -08:00
Daniel Imms b0c0f8701e Fix timing issues in webglrenderer 2019-11-09 14:08:37 -08:00
Daniel Imms d3bf013ceb Fix timing issues in web links addon tests 2019-11-09 13:57:42 -08:00
Daniel Imms 840e56cf1e Fix Attach tests 2019-11-09 11:30:45 -08:00
Daniel Imms b8acdd6e67 Remove slowmo from puppeteer 2019-11-09 11:15:15 -08:00
Daniel Imms 60bda1bbd3 Fix tests hanging 2019-11-09 11:04:55 -08:00
Daniel Imms e4b50d505c Merge pull request #2550 from Tyriar/2549_refresh_options
Refresh rows on options change for all renderers
2019-11-09 10:37:28 -08:00
Daniel Imms 22f4342194 Move options row refresh into render service 2019-11-09 10:07:24 -08:00
Daniel Imms 8adbed84b0 webgl: Refresh rows on options change
Fixes #2549
2019-11-09 09:34:40 -08:00
Daniel Imms d09ac41f75 After tests wait for browser close 2019-11-09 09:29:04 -08:00
Daniel Imms 2aec4e6d29 Add a test for removing canvases 2019-11-09 09:27:30 -08:00
Daniel Imms bcd4c766d6 Implement webgl renderer dispose
Fixes #2254
2019-11-09 09:09:59 -08:00
Daniel Imms b68a4d27ab Merge pull request #2547 from Tyriar/layering5
Move remaining renderer parts into browser
2019-11-07 21:42:53 -08:00
Daniel Imms 939dcca5bb Merge branch 'master' into layering5 2019-11-07 21:22:06 -08:00
Daniel Imms 3bf149bbc5 Fix webgl cursor 2019-11-07 18:46:56 -08:00
Daniel Imms 3f7238d9dc Move Renderer to browser 2019-11-07 18:45:13 -08:00
Daniel Imms 873de2b127 Move cursor render layer to browser 2019-11-07 18:39:46 -08:00
Daniel Imms c204ab00e0 Move cursorHidden into core service 2019-11-07 18:22:18 -08:00
Daniel Imms 9d7935f8bd Move cursorState into core service 2019-11-07 18:21:06 -08:00
Daniel Imms 1e1038b2c7 Remove renderer circular dep on renderservice
Also fix super.dispose not being called correctly on RenderService
2019-11-07 18:09:40 -08:00
Daniel Imms 509d0e7019 Remove some ITerminal usage from Renderer 2019-11-07 17:44:03 -08:00
Daniel Imms 82e18d7aaa Move DomRenderer to browser, fix strict errors 2019-11-07 17:41:29 -08:00
Daniel Imms 7c39217392 Break DomRenderer dependency on ITerminal
Part of #1507
2019-11-07 17:35:22 -08:00
Daniel Imms dd4eb7bdec Merge pull request #2532 from glneto/2444
#2444: Fix wrapped lines search
2019-11-07 16:49:24 -08:00
Daniel Imms e84469d140 Merge pull request #2546 from Tyriar/polish_pipelines
Reduce duplicate in pipeline, cache in release job
2019-11-07 16:48:29 -08:00
Daniel Imms 1d63be0506 Reduce duplicate in pipeline, cache in release job 2019-11-07 13:52:39 -08:00
Daniel Imms fce7a67c8e Remove type jsdoc 2019-11-07 13:39:42 -08:00
Daniel Imms 94ce037eb8 Add integration tests for regression in #2444 2019-11-07 13:00:43 -08:00
Daniel Imms aad32e9943 Merge branch 'master' into 2444 2019-11-07 12:20:33 -08:00
Daniel Imms 16409da517 Merge pull request #2541 from Tyriar/2539
Skip puppeteer download on unit test runs, fix windows tests, polish pipeline
2019-11-06 11:52:45 -08:00
Daniel Imms 0878294964 Skip Terminal2 tests on Windows 2019-11-06 11:24:34 -08:00
Daniel Imms 31afe5b305 test-coverage -> test-unit-coverage 2019-11-06 11:23:42 -08:00
Daniel Imms fe2f1ea4df Get Windows unit tests running again
Fixes #2543
2019-11-06 11:20:52 -08:00
Daniel Imms 48b20e3200 Don't run through nyc on mac and Windows 2019-11-06 11:08:47 -08:00
Daniel Imms 030a8bda80 Set coverage lines threshold to 60% 2019-11-06 11:08:39 -08:00
Daniel Imms d079775690 Merge coverage check into regular unit test check
Fixes #2542
2019-11-06 11:04:37 -08:00
Daniel Imms d4dbdac73f Don't cache integration test modules 2019-11-06 10:53:42 -08:00
Daniel Imms fc0568074f Cache node modules with puppeteer 2019-11-06 10:37:00 -08:00
Daniel Imms 0630baaf02 Don't cache on integration tests 2019-11-05 12:50:37 -08:00
Daniel Imms c974b273d4 Install puppeteer explicitly 2019-11-05 12:41:03 -08:00
Daniel Imms d2164584d5 Fix error in yml 2019-11-05 11:56:44 -08:00
Daniel Imms 2960d5df53 New cache key 2019-11-05 11:55:06 -08:00
Daniel Imms 5411f60f44 Try cache node_modules 2019-11-05 11:54:32 -08:00
Daniel Imms 86264d4763 Specify yarn cache folder 2019-11-05 11:42:35 -08:00
Daniel Imms 70500e31ac Add yarn cache caching 2019-11-05 11:36:54 -08:00
Daniel Imms 3dca46619c Skip puppeteer download on unit test runs
Fixes #2539
2019-11-05 11:33:39 -08:00
Daniel Imms 7f598a3675 Merge pull request #2540 from Tyriar/ts37
Update TS to 3.7 and adopt optional chaining
2019-11-05 11:22:11 -08:00
Daniel Imms 843d5ff53c Adopt optional chaining 2019-11-05 11:09:47 -08:00
Daniel Imms 267fbc7f5e typescript@3.7 and fix errors 2019-11-05 10:54:02 -08:00
Daniel Imms d259cd7332 Merge pull request #2536 from Tyriar/parse_rows
Only refresh rows that changed during parsing
2019-11-04 11:31:50 -08:00
Daniel Imms 1afffba1d8 Only refresh rows that changed during parsing
Fixes #2534
2019-11-04 11:00:19 -08:00
Geraldo Neto 9358718b5f 2444: Fix no-else-return lint 2019-11-02 19:33:21 -03:00
Geraldo Neto 8667237525 Merge branch '2444' of https://github.com/glneto/xterm.js into 2444 2019-11-02 19:22:09 -03:00
Geraldo Neto 9d50d106e3 2444: Return after calculating startCol in reverse search 2019-11-02 19:21:58 -03:00
Geraldo Neto d53215480a Merge branch 'master' into 2444 2019-11-02 19:14:01 -03:00
Geraldo Neto 1f4780a71a #2444: Fix wrapped lines search 2019-11-02 18:58:04 -03:00
Daniel Imms bc9649bfbc Merge pull request #2527 from xtermjs/Tyriar-patch-1
Change to tracking lines in code coverage
2019-10-31 12:23:07 -07:00
Daniel Imms fbb9ef9f7f Change to tracking lines in code coverage 2019-10-31 11:59:04 -07:00
Daniel Imms e8153d929d Merge pull request #2525 from Tyriar/az
Polish pipeline config and add coverage reporting
2019-10-30 21:18:06 -07:00
Daniel 08b10ba651 Add note about unit test coverage in contributing 2019-10-30 21:15:23 -07:00
Daniel 39c0fdb54a Remove report directory 2019-10-30 20:31:40 -07:00
Daniel a450bd52d7 Generate and publish coverage report to pipelines 2019-10-30 20:18:32 -07:00
Daniel 79f3a7343c Don't wrap for single line scripts 2019-10-30 20:07:41 -07:00
Daniel 6c2b34a5c8 Use YarnInstaller 2019-10-30 20:03:53 -07:00
Daniel Imms 80e44f4978 Merge pull request #2518 from Tyriar/2407_toggle_addons
Support check boxes in demo to toggle addons
2019-10-29 14:49:23 -07:00
Daniel Imms 5b555bacc0 Merge pull request #2521 from alberto/patch-1
Fix typos
2019-10-28 07:28:32 -07:00
alberto cb1cf3cd11 Fix grammar 2019-10-27 22:50:22 +01:00
alberto b880cd3ad3 Fix punctuation mark 2019-10-27 22:43:26 +01:00
alberto ffe2d66aff Remove duplicated punctuation 2019-10-27 22:38:55 +01:00
alberto fe2ac04b85 Fix grammar 2019-10-27 22:34:54 +01:00
alberto 8d0fdc8731 Fix grammar 2019-10-27 22:29:36 +01:00
alberto dca1e0b935 Fix another typo 2019-10-27 22:18:11 +01:00
alberto 56d3366831 Fix typo 2019-10-27 22:08:42 +01:00
Daniel Imms 719fa55dcf Merge pull request #2517 from revanthmahesh/dispose-eventemitters
Support disposing of EventEmitters
2019-10-26 21:52:50 -07:00
Daniel Imms f3bbcb35e4 Clean up 2019-10-26 21:52:11 -07:00
Revanth Mahesh d0e05eb795 Support disposing of EventEmitters 2019-10-27 10:11:43 +05:30
Daniel 2f18edbcac Remove old webgl enable button 2019-10-26 18:12:56 -07:00
Daniel 486fc714b3 Support checkboxes in demo to toggle addons
Fixes #2407
2019-10-26 18:12:20 -07:00
jerch edc09572b5 Merge pull request #2516 from jerch/addon_build
conditional `yarn` for addons
2019-10-26 17:32:08 +02:00
Jörg Breitbart 5b3a093f13 stick to prepare to avoid re-running yarn for addons everytime during build 2019-10-26 17:20:35 +02:00
Jörg Breitbart 03435a9f4d use prebuild instead of postinstall 2019-10-25 22:11:51 +02:00
jerchandDaniel Imms 04f828bc1a Update bin/install-addons.js
Co-Authored-By: Daniel Imms <tyriar@tyriar.com>
2019-10-25 21:46:49 +02:00
Jörg Breitbart d733ce3e63 add copyright note 2019-10-25 19:27:29 +02:00
Jörg Breitbart 6f8cbc56eb skip folder w'o package.json; restrict linter to src folders 2019-10-25 18:07:32 +02:00
Jörg Breitbart 25c18e8e07 conditional build for addons 2019-10-25 17:29:38 +02:00
86 changed files with 3738 additions and 1188 deletions
+5
View File
@@ -0,0 +1,5 @@
blank_issues_enabled: false
contact_links:
- name: Question
url: https://stackoverflow.com/questions/tagged/xtermjs
about: Please ask and answer questions here.
-8
View File
@@ -1,8 +0,0 @@
---
name: Question
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.
+1
View File
@@ -4,6 +4,7 @@ node_modules/
lib/
out/
out-test/
.nyc_output/
Makefile.gyp
*.Makefile
*.target.gyp.mk
+2 -1
View File
@@ -1,3 +1,4 @@
{
"typescript.preferences.quoteStyle": "single"
"typescript.preferences.quoteStyle": "single",
"typescript.preferences.importModuleSpecifier": "non-relative"
}
+4
View File
@@ -52,3 +52,7 @@ By contributing code to xterm.js you:
holder has explicitly granted the right to use it like this,
through a compatible open source license or through a direct
agreement with you.)
### Test coverage
One area that always needs attention is improving out unit test coverage, you can view the code coverage report on [Azure Pipelines](https://dev.azure.com/xtermjs/xterm.js/_build/latest?definitionId=3) by clicking the Code Coverage tab.
+2 -2
View File
@@ -10,7 +10,7 @@ Xterm.js is a front-end component written in TypeScript that lets applications b
- **Performant**: 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.
- **Accessible**: Screen reader and minimum contrast ratio support can be turned on
- **And much more**: Links, theming, addons, well documented API, etc.
## What xterm.js is not
@@ -110,7 +110,7 @@ Xterm.js is used in several world-class applications to provide great terminal e
- [**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.
- [**Next Tech**](https://next.tech "Next Tech"): 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.
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "xterm-addon-attach",
"version": "0.3.0",
"version": "0.4.0",
"author": {
"name": "The xterm.js authors",
"url": "https://xtermjs.org/"
@@ -20,7 +20,6 @@ describe('AttachAddon', () => {
this.timeout(20000);
browser = await puppeteer.launch({
headless: process.argv.indexOf('--headless') !== -1,
slowMo: 80,
args: [`--window-size=${width},${height}`, `--no-sandbox`]
});
page = (await browser.pages())[0];
@@ -43,7 +42,7 @@ describe('AttachAddon', () => {
const server = new WebSocket.Server({ port });
server.on('connection', socket => socket.send('foo'));
await page.evaluate(`window.term.loadAddon(new window.AttachAddon(new WebSocket('ws://localhost:${port}')))`);
assert.equal(await page.evaluate(`window.term.buffer.getLine(0).translateToString(true)`), 'foo');
await pollFor(page, `window.term.buffer.getLine(0).translateToString(true)`, 'foo');
server.close();
});
@@ -55,7 +54,7 @@ describe('AttachAddon', () => {
const data = new Uint8Array([102, 111, 111]);
server.on('connection', socket => socket.send(data));
await page.evaluate(`window.term.loadAddon(new window.AttachAddon(new WebSocket('ws://localhost:${port}')))`);
assert.equal(await page.evaluate(`window.term.buffer.getLine(0).translateToString(true)`), 'foo');
await pollFor(page, `window.term.buffer.getLine(0).translateToString(true)`, 'foo');
server.close();
});
});
@@ -69,3 +68,12 @@ async function openTerminal(options: ITerminalOptions = {}): Promise<void> {
await page.waitForSelector('.xterm-text-layer');
}
}
async function pollFor(page: puppeteer.Page, fn: string, val: any): Promise<void> {
const result = await page.evaluate(fn);
if (result !== val) {
return new Promise<void>(r => {
setTimeout(() => r(pollFor(page, fn, val)), 10);
});
}
}
@@ -33,6 +33,7 @@ export class AttachAddon implements ITerminalAddon {
if (this._bidirectional) {
this._disposables.push(terminal.onData(data => this._sendData(data)));
this._disposables.push(terminal.onBinary(data => this._sendBinary(data)));
}
this._disposables.push(addSocketListener(this._socket, 'close', () => this.dispose()));
@@ -51,6 +52,17 @@ export class AttachAddon implements ITerminalAddon {
}
this._socket.send(data);
}
private _sendBinary(data: string): void {
if (this._socket.readyState !== 1) {
return;
}
const buffer = new Uint8Array(data.length);
for (let i = 0; i < data.length; ++i) {
buffer[i] = data.charCodeAt(i) & 255;
}
this._socket.send(buffer);
}
}
function addSocketListener<K extends keyof WebSocketEventMap>(socket: WebSocket, type: K, handler: (this: WebSocket, ev: WebSocketEventMap[K]) => any): IDisposable {
@@ -19,7 +19,6 @@ describe('FitAddon', () => {
this.timeout(20000);
browser = await puppeteer.launch({
headless: process.argv.indexOf('--headless') !== -1,
slowMo: 80,
args: [`--window-size=${width},${height}`, `--no-sandbox`]
});
page = (await browser.pages())[0];
@@ -0,0 +1,70 @@
/usr/bin/cmake -E cmake_progress_report /home/yan/ssd/searchtrunk/tmp/release/CMakeFiles
make[3]: Entering directory `/home/yan/ssd/searchtrunk/tmp/release'
make[3]: Nothing to be done for `libsrc/wrap_curl/CMakeFiles/wrap_curl.dir/build'.
make[3]: Leaving directory `/home/yan/ssd/searchtrunk/tmp/release'
[ 0%] /usr/bin/cmake -E cmake_progress_report /home/yan/ssd/searchtrunk/tmp/release/CMakeFiles
[ 0%] [ 0%] /usr/bin/cmake -E cmake_progress_report /home/yan/ssd/searchtrunk/tmp/release/CMakeFiles
Built target config_utils
[ 0%] make[3]: Entering directory `/home/yan/ssd/searchtrunk/tmp/release'
/usr/bin/cmake -E cmake_progress_report /home/yan/ssd/searchtrunk/tmp/release/CMakeFiles
Built target omega
make[3]: Entering directory `/home/yan/ssd/searchtrunk/tmp/release'
cd /home/yan/ssd/searchtrunk/tmp/release && /usr/bin/cmake -E cmake_depends "Unix Makefiles" /home/yan/ssd/searchtrunk /home/yan/ssd/searchtrunk/projects/protogen/cpp /home/yan/ssd/searchtrunk/tmp/release /home/yan/ssd/searchtrunk/tmp/release/projects/protogen/cpp /home/yan/ssd/searchtrunk/tmp/release/projects/protogen/cpp/CMakeFiles/protogen.dir/DependInfo.cmake --color=
/usr/bin/cmake -E cmake_progress_report /home/yan/ssd/searchtrunk/tmp/release/CMakeFiles
Built target morpheus
make[3]: Leaving directory `/home/yan/ssd/searchtrunk/tmp/release'
make -f libsrc/base/CMakeFiles/base.dir/build.make libsrc/base/CMakeFiles/base.dir/build
make[3]: Entering directory `/home/yan/ssd/searchtrunk/tmp/release'
make[3]: Nothing to be done for `libsrc/coroutine/CMakeFiles/coroutine.dir/build'.
Built target wrap_curl
make[3]: Leaving directory `/home/yan/ssd/searchtrunk/tmp/release'
/usr/bin/cmake -E cmake_progress_report /home/yan/ssd/searchtrunk/tmp/release/CMakeFiles
/usr/bin/cmake -E cmake_progress_report /home/yan/ssd/searchtrunk/tmp/release/CMakeFiles
[ 0%] [ 0%] [ 0%] [ 0%] Building CXX object libsrc/image_crop/CMakeFiles/image_crop.dir/src/crop_context.cpp.o
cd /home/yan/ssd/searchtrunk/tmp/release/libsrc/image_crop && /opt/rh/devtoolset-7/root/usr/bin/c++ -DGOGO_USE_LIBEVENT2=1 -DGOGO_USE_OPENSSL_SNI=1 -DHAVE_BOOST -DHAVE_GLIBC -DHAVE_MALLOC_TRIM -DHAVE_PREAD -DHAVE_READAHEAD -D_FILE_OFFSET_BITS=64 -D_STAT_LEMM -pipe -Wall -Wextra -Werror=multichar -Wno-deprecated -Wno-unused-parameter -pthread -fPIC -Woverloaded-virtual -Wnon-virtual-dtor -Werror -std=gnu++11 -Wno-unknown-pragmas -DBOOST_FILESYSTEM_VERSION=3 -DBOOST_NO_CXX11_HDR_CODECVT -DMAGICKCORE_QUANTUM_DEPTH=8 -DMAGICKCORE_HDRI_ENABLE=0 -Wno-unused-local-typedefs -D_GLIBCXX_USE_CXX11_ABI=0 -std=gnu++14 -DCXXHASH128_EXIST -static-libstdc++ -Wno-implicit-fallthrough -Wno-stringop-overflow -O3 -DNDEBUG -DBOOST_UBLAS_NDEBUG -g0 -march=core2 -mssse3 -msse4.1 -I/home/yan/ssd/searchtrunk/tmp/release/__inc -I/home/yan/ssd/searchtrunk/libsrc/image_crop/src -I/home/yan/ssd/searchtrunk/include -isystem /home/yan/ssd/searchtrunk/tmp/release/contrib/boost_1_69_0/include -isystem /home/yan/ssd/searchtrunk/tmp/release/contrib/icu-49.1.2/include -isystem /home/yan/ssd/searchtrunk/tmp/release/contrib/protobuf-3.4.0/include -I/home/yan/ssd/searchtrunk/tmp/release -isystem /home/yan/ssd/searchtrunk/tmp/release/contrib/opencv-3.0.0m2/include -isystem /home/yan/ssd/searchtrunk/tmp/release/contrib/dlib-19.7p1/include -Wno-array-bounds -o CMakeFiles/image_crop.dir/src/crop_context.cpp.o -c /home/yan/ssd/searchtrunk/libsrc/image_crop/src/crop_context.cpp
Building CXX object libsrc/image_crop/CMakeFiles/image_crop.dir/src/detector.cpp.o
Building CXX object libsrc/image_crop/CMakeFiles/image_crop.dir/src/image_crop.cpp.o
cd /home/yan/ssd/searchtrunk/tmp/release/libsrc/image_crop && /opt/rh/devtoolset-7/root/usr/bin/c++ -DGOGO_USE_LIBEVENT2=1 -DGOGO_USE_OPENSSL_SNI=1 -DHAVE_BOOST -DHAVE_GLIBC -DHAVE_MALLOC_TRIM -DHAVE_PREAD -DHAVE_READAHEAD -D_FILE_OFFSET_BITS=64 -D_STAT_LEMM -pipe -Wall -Wextra -Werror=multichar -Wno-deprecated -Wno-unused-parameter -pthread -fPIC -Woverloaded-virtual -Wnon-virtual-dtor -Werror -std=gnu++11 -Wno-unknown-pragmas -DBOOST_FILESYSTEM_VERSION=3 -DBOOST_NO_CXX11_HDR_CODECVT -DMAGICKCORE_QUANTUM_DEPTH=8 -DMAGICKCORE_HDRI_ENABLE=0 -Wno-unused-local-typedefs -D_GLIBCXX_USE_CXX11_ABI=0 -std=gnu++14 -DCXXHASH128_EXIST -static-libstdc++ -Wno-implicit-fallthrough -Wno-stringop-overflow -O3 -DNDEBUG -DBOOST_UBLAS_NDEBUG -g0 -march=core2 -mssse3 -msse4.1 -I/home/yan/ssd/searchtrunk/tmp/release/__inc -I/home/yan/ssd/searchtrunk/libsrc/image_crop/src -I/home/yan/ssd/searchtrunk/include -isystem /home/yan/ssd/searchtrunk/tmp/release/contrib/boost_1_69_0/include -isystem /home/yan/ssd/searchtrunk/tmp/release/contrib/icu-49.1.2/include -isystem /home/yan/ssd/searchtrunk/tmp/release/contrib/protobuf-3.4.0/include -I/home/yan/ssd/searchtrunk/tmp/release -isystem /home/yan/ssd/searchtrunk/tmp/release/contrib/opencv-3.0.0m2/include -isystem /home/yan/ssd/searchtrunk/tmp/release/contrib/dlib-19.7p1/include -Wno-array-bounds -o CMakeFiles/image_crop.dir/src/detector.cpp.o -c /home/yan/ssd/searchtrunk/libsrc/image_crop/src/detector.cpp
Built target coroutine
cd /home/yan/ssd/searchtrunk/tmp/release/libsrc/image_crop && /opt/rh/devtoolset-7/root/usr/bin/c++ -DGOGO_USE_LIBEVENT2=1 -DGOGO_USE_OPENSSL_SNI=1 -DHAVE_BOOST -DHAVE_GLIBC -DHAVE_MALLOC_TRIM -DHAVE_PREAD -DHAVE_READAHEAD -D_FILE_OFFSET_BITS=64 -D_STAT_LEMM -pipe -Wall -Wextra -Werror=multichar -Wno-deprecated -Wno-unused-parameter -pthread -fPIC -Woverloaded-virtual -Wnon-virtual-dtor -Werror -std=gnu++11 -Wno-unknown-pragmas -DBOOST_FILESYSTEM_VERSION=3 -DBOOST_NO_CXX11_HDR_CODECVT -DMAGICKCORE_QUANTUM_DEPTH=8 -DMAGICKCORE_HDRI_ENABLE=0 -Wno-unused-local-typedefs -D_GLIBCXX_USE_CXX11_ABI=0 -std=gnu++14 -DCXXHASH128_EXIST -static-libstdc++ -Wno-implicit-fallthrough -Wno-stringop-overflow -O3 -DNDEBUG -DBOOST_UBLAS_NDEBUG -g0 -march=core2 -mssse3 -msse4.1 -I/home/yan/ssd/searchtrunk/tmp/release/__inc -I/home/yan/ssd/searchtrunk/libsrc/image_crop/src -I/home/yan/ssd/searchtrunk/include -isystem /home/yan/ssd/searchtrunk/tmp/release/contrib/boost_1_69_0/include -isystem /home/yan/ssd/searchtrunk/tmp/release/contrib/icu-49.1.2/include -isystem /home/yan/ssd/searchtrunk/tmp/release/contrib/protobuf-3.4.0/include -I/home/yan/ssd/searchtrunk/tmp/release -isystem /home/yan/ssd/searchtrunk/tmp/release/contrib/opencv-3.0.0m2/include -isystem /home/yan/ssd/searchtrunk/tmp/release/contrib/dlib-19.7p1/include -Wno-array-bounds -o CMakeFiles/image_crop.dir/src/image_crop.cpp.o -c /home/yan/ssd/searchtrunk/libsrc/image_crop/src/image_crop.cpp
make[3]: Entering directory `/home/yan/ssd/searchtrunk/tmp/release'
make[3]: Nothing to be done for `libsrc/base/CMakeFiles/base.dir/build'.
make[3]: Leaving directory `/home/yan/ssd/searchtrunk/tmp/release'
/usr/bin/cmake -E cmake_progress_report /home/yan/ssd/searchtrunk/tmp/release/CMakeFiles
make[3]: Leaving directory `/home/yan/ssd/searchtrunk/tmp/release'
make -f projects/protogen/cpp/CMakeFiles/protogen.dir/build.make projects/protogen/cpp/CMakeFiles/protogen.dir/build
[ 0%] Built target base
In file included from /home/yan/ssd/searchtrunk/include/image_crop/detector.hpp:3:0,
from /home/yan/ssd/searchtrunk/libsrc/image_crop/src/detector.cpp:2:
/home/yan/ssd/searchtrunk/include/image_crop/crop_context.hpp:6:10: fatal error: opencv2/dnn.hpp: No such file or directory
#include <opencv2/dnn.hpp>
^~~~~~~~~~~~~~~~~
compilation terminated.
make[3]: *** [libsrc/image_crop/CMakeFiles/image_crop.dir/src/detector.cpp.o] Error 1
make[3]: *** Waiting for unfinished jobs....
In file included from /home/yan/ssd/searchtrunk/include/image_crop/image_crop.hpp:3:0,
from /home/yan/ssd/searchtrunk/libsrc/image_crop/src/image_crop.cpp:1:
/home/yan/ssd/searchtrunk/include/image_crop/crop_context.hpp:6:10: fatal error: opencv2/dnn.hpp: No such file or directory
#include <opencv2/dnn.hpp>
^~~~~~~~~~~~~~~~~
compilation terminated.
make[3]: *** [libsrc/image_crop/CMakeFiles/image_crop.dir/src/image_crop.cpp.o] Error 1
In file included from /home/yan/ssd/searchtrunk/libsrc/image_crop/src/crop_context.cpp:1:0:
/home/yan/ssd/searchtrunk/include/image_crop/crop_context.hpp:6:10: fatal error: opencv2/dnn.hpp: No such file or directory
#include <opencv2/dnn.hpp>
^~~~~~~~~~~~~~~~~
compilation terminated.
make[3]: *** [libsrc/image_crop/CMakeFiles/image_crop.dir/src/crop_context.cpp.o] Error 1
make[3]: Leaving directory `/home/yan/ssd/searchtrunk/tmp/release'
make[2]: *** [libsrc/image_crop/CMakeFiles/image_crop.dir/all] Error 2
make[2]: *** Waiting for unfinished jobs....
make[3]: Entering directory `/home/yan/ssd/searchtrunk/tmp/release'
make[3]: Nothing to be done for `projects/protogen/cpp/CMakeFiles/protogen.dir/build'.
make[3]: Leaving directory `/home/yan/ssd/searchtrunk/tmp/release'
/usr/bin/cmake -E cmake_progress_report /home/yan/ssd/searchtrunk/tmp/release/CMakeFiles 63 64 65 66
[ 25%] Built target protogen
make[2]: Leaving directory `/home/yan/ssd/searchtrunk/tmp/release'
make[1]: *** [all] Error 2
make[1]: Leaving directory `/home/yan/ssd/searchtrunk/tmp/release/projects/projectX'
make: *** [install] Error 2
yan@yPC:~/ssd/searchtrunk/projects/projectX$
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "xterm-addon-search",
"version": "0.3.0",
"version": "0.4.0",
"author": {
"name": "The xterm.js authors",
"url": "https://xtermjs.org/"
@@ -6,6 +6,8 @@
import * as puppeteer from 'puppeteer';
import { assert } from 'chai';
import { ITerminalOptions } from 'xterm';
import { readFile } from 'fs';
import { resolve } from 'path';
const APP = 'http://127.0.0.1:3000/test';
@@ -20,7 +22,6 @@ describe('Search Tests', function (): void {
before(async function (): Promise<any> {
browser = await puppeteer.launch({
headless: process.argv.indexOf('--headless') !== -1,
slowMo: 80,
args: [`--window-size=${width},${height}`, `--no-sandbox`]
});
page = (await browser.pages())[0];
@@ -91,7 +92,7 @@ describe('Search Tests', function (): void {
selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`);
assert.deepEqual(line.substring(selectionPosition.startColumn, selectionPosition.endColumn), 'package.jsonc');
});
it ('Simple Regex', async () => {
it('Simple Regex', async () => {
await writeSync('abc123defABCD');
await page.evaluate(`window.search.findNext('[a-z]+', {regex: true})`);
assert.deepEqual(await page.evaluate(`window.term.getSelection()`), 'abc');
@@ -106,6 +107,86 @@ describe('Search Tests', function (): void {
assert.deepEqual(await page.evaluate(`window.search.findNext('abc')`), true);
assert.deepEqual(await page.evaluate(`window.term.getSelection()`), 'abc');
});
describe('Regression tests', () => {
describe('#2444 wrapped line content not being found', () => {
let fixture: string;
before(async () => {
const rawFixture = await new Promise<Buffer>(r => readFile(resolve(__dirname, '../fixtures/issue-2444'), (err, data) => r(data)));
fixture = rawFixture.toString()
.replace(/\n/g, '\\n\\r')
.replace(/'/g, '\\\'');
});
it('should find all occurrences using findNext', async () => {
await writeSync(fixture);
assert.deepEqual(await page.evaluate(`window.search.findNext('opencv')`), true);
let selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`);
assert.deepEqual(selectionPosition, { startColumn: 24, startRow: 53, endColumn: 30, endRow: 53 });
assert.deepEqual(await page.evaluate(`window.search.findNext('opencv')`), true);
selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`);
assert.deepEqual(selectionPosition, { startColumn: 24, startRow: 76, endColumn: 30, endRow: 76 });
assert.deepEqual(await page.evaluate(`window.search.findNext('opencv')`), true);
selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`);
assert.deepEqual(selectionPosition, { startColumn: 24, startRow: 96, endColumn: 30, endRow: 96 });
assert.deepEqual(await page.evaluate(`window.search.findNext('opencv')`), true);
selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`);
assert.deepEqual(selectionPosition, { startColumn: 1, startRow: 114, endColumn: 7, endRow: 114 });
assert.deepEqual(await page.evaluate(`window.search.findNext('opencv')`), true);
selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`);
assert.deepEqual(selectionPosition, { startColumn: 11, startRow: 115, endColumn: 17, endRow: 115 });
assert.deepEqual(await page.evaluate(`window.search.findNext('opencv')`), true);
selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`);
assert.deepEqual(selectionPosition, { startColumn: 1, startRow: 126, endColumn: 7, endRow: 126 });
assert.deepEqual(await page.evaluate(`window.search.findNext('opencv')`), true);
selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`);
assert.deepEqual(selectionPosition, { startColumn: 11, startRow: 127, endColumn: 17, endRow: 127 });
assert.deepEqual(await page.evaluate(`window.search.findNext('opencv')`), true);
selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`);
assert.deepEqual(selectionPosition, { startColumn: 1, startRow: 135, endColumn: 7, endRow: 135 });
assert.deepEqual(await page.evaluate(`window.search.findNext('opencv')`), true);
selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`);
assert.deepEqual(selectionPosition, { startColumn: 11, startRow: 136, endColumn: 17, endRow: 136 });
// Wrap around to first result
assert.deepEqual(await page.evaluate(`window.search.findNext('opencv')`), true);
selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`);
assert.deepEqual(selectionPosition, { startColumn: 24, startRow: 53, endColumn: 30, endRow: 53 });
});
it('should find all occurrences using findPrevious', async () => {
await writeSync(fixture);
assert.deepEqual(await page.evaluate(`window.search.findPrevious('opencv')`), true);
let selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`);
assert.deepEqual(selectionPosition, { startColumn: 11, startRow: 136, endColumn: 17, endRow: 136 });
assert.deepEqual(await page.evaluate(`window.search.findPrevious('opencv')`), true);
selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`);
assert.deepEqual(selectionPosition, { startColumn: 1, startRow: 135, endColumn: 7, endRow: 135 });
assert.deepEqual(await page.evaluate(`window.search.findPrevious('opencv')`), true);
selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`);
assert.deepEqual(selectionPosition, { startColumn: 11, startRow: 127, endColumn: 17, endRow: 127 });
assert.deepEqual(await page.evaluate(`window.search.findPrevious('opencv')`), true);
selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`);
assert.deepEqual(selectionPosition, { startColumn: 1, startRow: 126, endColumn: 7, endRow: 126 });
assert.deepEqual(await page.evaluate(`window.search.findPrevious('opencv')`), true);
selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`);
assert.deepEqual(selectionPosition, { startColumn: 11, startRow: 115, endColumn: 17, endRow: 115 });
assert.deepEqual(await page.evaluate(`window.search.findPrevious('opencv')`), true);
selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`);
assert.deepEqual(selectionPosition, { startColumn: 1, startRow: 114, endColumn: 7, endRow: 114 });
assert.deepEqual(await page.evaluate(`window.search.findPrevious('opencv')`), true);
selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`);
assert.deepEqual(selectionPosition, { startColumn: 24, startRow: 96, endColumn: 30, endRow: 96 });
assert.deepEqual(await page.evaluate(`window.search.findPrevious('opencv')`), true);
selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`);
assert.deepEqual(selectionPosition, { startColumn: 24, startRow: 76, endColumn: 30, endRow: 76 });
assert.deepEqual(await page.evaluate(`window.search.findPrevious('opencv')`), true);
selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`);
assert.deepEqual(selectionPosition, { startColumn: 24, startRow: 53, endColumn: 30, endRow: 53 });
// Wrap around to first result
assert.deepEqual(await page.evaluate(`window.search.findPrevious('opencv')`), true);
selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`);
assert.deepEqual(selectionPosition, { startColumn: 11, startRow: 136, endColumn: 17, endRow: 136 });
});
});
});
});
async function openTerminal(options: ITerminalOptions = {}): Promise<void> {
+43 -14
View File
@@ -12,6 +12,11 @@ export interface ISearchOptions {
incremental?: boolean;
}
export interface ISearchPosition {
startCol: number;
startRow: number;
}
export interface ISearchResult {
term: string;
col: number;
@@ -71,17 +76,23 @@ export class SearchAddon implements ITerminalAddon {
this._initLinesCache();
const searchPosition: ISearchPosition = {
startRow,
startCol
};
// Search startRow
let result = this._findInLine(term, startRow, startCol, searchOptions);
let result = this._findInLine(term, searchPosition, searchOptions);
// Search from startRow + 1 to end
if (!result) {
for (let y = startRow + 1; y < this._terminal.buffer.baseY + this._terminal.rows; y++) {
searchPosition.startRow = y;
searchPosition.startCol = 0;
// If the current line is wrapped line, increase index of column to ignore the previous scan
// Otherwise, reset beginning column index to zero with set new unwrapped line index
result = this._findInLine(term, y, 0, searchOptions);
result = this._findInLine(term, searchPosition, searchOptions);
if (result) {
break;
}
@@ -90,7 +101,9 @@ export class SearchAddon implements ITerminalAddon {
// If we hit the bottom and didn't search from the very top wrap back up
if (!result && startRow !== 0) {
for (let y = 0; y < startRow; y++) {
result = this._findInLine(term, y, 0, searchOptions);
searchPosition.startRow = y;
searchPosition.startCol = 0;
result = this._findInLine(term, searchPosition, searchOptions);
if (result) {
break;
}
@@ -135,21 +148,26 @@ export class SearchAddon implements ITerminalAddon {
}
this._initLinesCache();
const searchPosition: ISearchPosition = {
startRow,
startCol
};
if (incremental) {
result = this._findInLine(term, startRow, startCol, searchOptions, false);
result = this._findInLine(term, searchPosition, searchOptions, false);
if (!(result && result.row === startRow && result.col === startCol)) {
result = this._findInLine(term, startRow, startCol, searchOptions, true);
result = this._findInLine(term, searchPosition, searchOptions, true);
}
} else {
result = this._findInLine(term, startRow, startCol, searchOptions, isReverseSearch);
result = this._findInLine(term, searchPosition, searchOptions, isReverseSearch);
}
// Search from startRow - 1 to top
if (!result) {
startCol = this._terminal.cols;
searchPosition.startCol = Math.max(searchPosition.startCol, this._terminal.cols);
for (let y = startRow - 1; y >= 0; y--) {
result = this._findInLine(term, y, startCol, searchOptions, isReverseSearch);
searchPosition.startRow = y;
result = this._findInLine(term, searchPosition, searchOptions, isReverseSearch);
if (result) {
break;
}
@@ -158,7 +176,8 @@ export class SearchAddon implements ITerminalAddon {
// If we hit the top and didn't search from the very bottom wrap back down
if (!result && startRow !== (this._terminal.buffer.baseY + this._terminal.rows)) {
for (let y = (this._terminal.buffer.baseY + this._terminal.rows); y > startRow; y--) {
result = this._findInLine(term, y, startCol, searchOptions, isReverseSearch);
searchPosition.startRow = y;
result = this._findInLine(term, searchPosition, searchOptions, isReverseSearch);
if (result) {
break;
}
@@ -220,18 +239,28 @@ export class SearchAddon implements ITerminalAddon {
* 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 row The line to start the search from.
* @param col The column to start the search from.
* @param position The position to start the search.
* @param searchOptions Search options.
* @return The search result if it was found.
*/
protected _findInLine(term: string, row: number, col: number, searchOptions: ISearchOptions = {}, isReverseSearch: boolean = false): ISearchResult | undefined {
protected _findInLine(term: string, searchPosition: ISearchPosition, searchOptions: ISearchOptions = {}, isReverseSearch: boolean = false): ISearchResult | undefined {
const terminal = this._terminal!;
let row = searchPosition.startRow;
const col = searchPosition.startCol;
// Ignore wrapped lines, only consider on unwrapped line (first row of command string).
const firstLine = terminal.buffer.getLine(row);
if (firstLine && firstLine.isWrapped) {
return;
if (isReverseSearch) {
searchPosition.startCol += terminal.cols;
return;
}
// This will iterate until we find the line start.
// When we find it, we will search using the calculated start column.
searchPosition.startRow--;
searchPosition.startCol += terminal.cols;
return this._findInLine(term, searchPosition, searchOptions);
}
let stringLine = this._linesCache ? this._linesCache[row] : void 0;
if (stringLine === void 0) {
+2 -2
View File
@@ -17,7 +17,7 @@ declare module 'xterm-addon-search' {
/**
* Whether to search for a whole word, the result is only valid if it's
* suppounded in "non-word" characters such as `_`, `(`, `)` or space.
* surrounded in "non-word" characters such as `_`, `(`, `)` or space.
*/
wholeWord?: boolean;
@@ -27,7 +27,7 @@ declare module 'xterm-addon-search' {
caseSensitive?: boolean;
/**
* Whether to do an indcremental search, this will expand the selection if it
* Whether to do an incremental search, this will expand the selection if it
* still matches the term the user typed. Note that this only affects
* `findNext`, not `findPrevious`.
*/
@@ -19,7 +19,6 @@ describe('WebLinksAddon', () => {
this.timeout(10000);
browser = await puppeteer.launch({
headless: process.argv.indexOf('--headless') !== -1,
slowMo: 80,
args: [`--window-size=${width},${height}`, `--no-sandbox`]
});
page = (await browser.pages())[0];
@@ -54,22 +53,21 @@ describe('WebLinksAddon', () => {
async function testHostName(hostname: string): Promise<void> {
await openTerminal({ rendererType: 'dom' });
await page.evaluate(`window.term.loadAddon(new window.WebLinksAddon())`);
await page.evaluate(`
window.term.writeln(' http://${hostname} ');
window.term.writeln(' http://${hostname}/a~b#c~d?e~f ');
window.term.writeln(' http://${hostname}/colon:test ');
window.term.writeln(' http://${hostname}/colon:test: ');
window.term.writeln('"http://${hostname}/"');
window.term.writeln('\\'http://${hostname}/\\'');
window.term.writeln('http://${hostname}/subpath/+/id');
`);
assert.equal(await getLinkAtCell(3, 1), `http://${hostname}`);
assert.equal(await getLinkAtCell(3, 2), `http://${hostname}/a~b#c~d?e~f`);
assert.equal(await getLinkAtCell(3, 3), `http://${hostname}/colon:test`);
assert.equal(await getLinkAtCell(3, 4), `http://${hostname}/colon:test`);
assert.equal(await getLinkAtCell(2, 5), `http://${hostname}/`);
assert.equal(await getLinkAtCell(2, 6), `http://${hostname}/`);
assert.equal(await getLinkAtCell(1, 7), `http://${hostname}/subpath/+/id`);
const data = ` http://${hostname} \\r\\n` +
` http://${hostname}/a~b#c~d?e~f \\r\\n` +
` http://${hostname}/colon:test \\r\\n` +
` http://${hostname}/colon:test: \\r\\n` +
`"http://${hostname}/"\\r\\n` +
`\\'http://${hostname}/\\'\\r\\n` +
`http://${hostname}/subpath/+/id`;
await writeSync(page, data);
await pollForLinkAtCell(3, 1, `http://${hostname}`);
await pollForLinkAtCell(3, 2, `http://${hostname}/a~b#c~d?e~f`);
await pollForLinkAtCell(3, 3, `http://${hostname}/colon:test`);
await pollForLinkAtCell(3, 4, `http://${hostname}/colon:test`);
await pollForLinkAtCell(2, 5, `http://${hostname}/`);
await pollForLinkAtCell(2, 6, `http://${hostname}/`);
await pollForLinkAtCell(1, 7, `http://${hostname}/subpath/+/id`);
}
async function openTerminal(options: ITerminalOptions = {}): Promise<void> {
@@ -82,8 +80,30 @@ async function openTerminal(options: ITerminalOptions = {}): Promise<void> {
}
}
async function getLinkAtCell(col: number, row: number): Promise<string> {
async function pollForLinkAtCell(col: number, row: number, value: string): Promise<void> {
const rowSelector = `.xterm-rows > :nth-child(${row})`;
await page.hover(`${rowSelector} > :nth-child(${col})`);
return await page.evaluate(`Array.prototype.reduce.call(document.querySelectorAll('${rowSelector} > span[style]'), (a, b) => a + b.textContent, '');`);
// Ensure the hover element exists before trying to hover it
await pollFor(page, `!!document.querySelector('${rowSelector} > :nth-child(${col})')`, true);
await pollFor(page, `document.querySelectorAll('${rowSelector} > span[style]').length >= ${value.length}`, true, async () => page.hover(`${rowSelector} > :nth-child(${col})`));
assert.equal(await page.evaluate(`Array.prototype.reduce.call(document.querySelectorAll('${rowSelector} > span[style]'), (a, b) => a + b.textContent, '');`), value);
}
async function pollFor(page: puppeteer.Page, fn: string, val: any, preFn?: () => Promise<void>): Promise<void> {
if (preFn) {
await preFn();
}
const result = await page.evaluate(fn);
if (result !== val) {
return new Promise<void>(r => {
setTimeout(() => r(pollFor(page, fn, val, preFn)), 10);
});
}
}
async function writeSync(page: puppeteer.Page, data: string): Promise<void> {
await page.evaluate(`
window.ready = false;
window.term.write('${data}', () => window.ready = true);
`);
await pollFor(page, 'window.ready', true);
}
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "xterm-addon-webgl",
"version": "0.3.0",
"version": "0.4.0",
"author": {
"name": "The xterm.js authors",
"url": "https://xtermjs.org/"
@@ -1,28 +0,0 @@
/**
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
* @license MIT
*/
import { CellData } from 'common/buffer/CellData';
import { FLAGS } from './Constants';
import { IBufferLine } from 'common/Types';
export function getCompatAttr(bufferLine: IBufferLine, index: number): number {
// TODO: Need to move WebGL over to the new system and remove this block
const cell = new CellData();
bufferLine.loadCell(index, cell);
const oldBg = cell.getBgColor() === -1 ? 256 : cell.getBgColor();
const oldFg = cell.getFgColor() === -1 ? 256 : cell.getFgColor();
const oldAttr =
(cell.isBold() ? FLAGS.BOLD : 0) |
(cell.isUnderline() ? FLAGS.UNDERLINE : 0) |
(cell.isBlink() ? FLAGS.BLINK : 0) |
(cell.isInverse() ? FLAGS.INVERSE : 0) |
(cell.isDim() ? FLAGS.DIM : 0) |
(cell.isItalic() ? FLAGS.ITALIC : 0);
const attrCompat =
oldBg |
(oldFg << 9) |
(oldAttr << 18);
return attrCompat;
}
@@ -1,14 +0,0 @@
/**
* @license MIT
* Copyright (c) 2018 The xterm.js authors. All rights reserved.
*/
import { IColor } from 'browser/Types';
export function getLuminance(color: IColor): number {
// Coefficients taken from: https://www.w3.org/TR/AERT/#color-contrast
const r = color.rgba >> 24 & 0xff;
const g = color.rgba >> 16 & 0xff;
const b = color.rgba >> 8 & 0xff;
return (0.299 * r + 0.587 * g + 0.114 * b) / 255;
}
-15
View File
@@ -1,15 +0,0 @@
/**
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
* @license MIT
*/
// TODO: Should be removed after chardata workaround is fixed
export const enum FLAGS {
BOLD = 1,
UNDERLINE = 2,
BLINK = 4,
INVERSE = 8,
INVISIBLE = 16,
DIM = 32,
ITALIC = 64
}
+17 -27
View File
@@ -6,12 +6,10 @@
import { createProgram, PROJECTION_MATRIX, throwIfFalsy } from './WebglUtils';
import { WebglCharAtlas } from './atlas/WebglCharAtlas';
import { IWebGL2RenderingContext, IWebGLVertexArrayObject, IRenderModel, IRasterizedGlyph } from './Types';
import { INDICIES_PER_CELL } from './WebglRenderer';
import { COMBINED_CHAR_BIT_MASK } from './RenderModel';
import { COMBINED_CHAR_BIT_MASK, RENDER_MODEL_INDICIES_PER_CELL, RENDER_MODEL_FG_OFFSET } from './RenderModel';
import { fill } from 'common/TypedArrayUtils';
import { slice } from './TypedArray';
import { NULL_CELL_CODE, WHITESPACE_CELL_CODE } from 'common/buffer/Constants';
import { getLuminance } from './ColorUtils';
import { NULL_CELL_CODE, WHITESPACE_CELL_CODE, Attributes } from 'common/buffer/Constants';
import { Terminal, IBufferLine } from 'xterm';
import { IColorSet } from 'browser/Types';
import { IRenderDimensions } from 'browser/renderer/Types';
@@ -103,7 +101,6 @@ export class GlyphRenderer {
private _dimensions: IRenderDimensions
) {
const gl = this._gl;
const program = throwIfFalsy(createProgram(gl, vertexShaderSource, fragmentShaderSource));
this._program = program;
@@ -169,11 +166,11 @@ export class GlyphRenderer {
return this._atlas ? this._atlas.beginFrame() : true;
}
public updateCell(x: number, y: number, code: number, attr: number, bg: number, fg: number, chars: string): void {
this._updateCell(this._vertices.attributes, x, y, code, attr, bg, fg, chars);
public updateCell(x: number, y: number, code: number, bg: number, fg: number, chars: string): void {
this._updateCell(this._vertices.attributes, x, y, code, bg, fg, chars);
}
private _updateCell(array: Float32Array, x: number, y: number, code: number | undefined, attr: number, bg: number, fg: number, chars?: string): void {
private _updateCell(array: Float32Array, x: number, y: number, code: number | undefined, bg: number, fg: number, chars?: string): void {
const terminal = this._terminal;
const i = (y * terminal.cols + x) * INDICES_PER_CELL;
@@ -186,12 +183,12 @@ export class GlyphRenderer {
let rasterizedGlyph: IRasterizedGlyph;
if (!this._atlas) {
throw new Error('atlas must be set before updating cell');
return;
}
if (chars && chars.length > 1) {
rasterizedGlyph = this._atlas.getRasterizedGlyphCombinedChar(chars, attr, bg, fg);
rasterizedGlyph = this._atlas.getRasterizedGlyphCombinedChar(chars, bg, fg);
} else {
rasterizedGlyph = this._atlas.getRasterizedGlyph(code, attr, bg, fg);
rasterizedGlyph = this._atlas.getRasterizedGlyph(code, bg, fg);
}
// Fill empty if no glyph was found
@@ -220,58 +217,51 @@ export class GlyphRenderer {
this._vertices.selectionAttributes = slice(this._vertices.attributes, 0);
// TODO: Make fg and bg configurable, currently since the buffer doesn't
// support truecolor the char atlas cannot store it.
const lumi = getLuminance(this._colors.background);
const fg = lumi > 0.5 ? 7 : 0;
const bg = lumi > 0.5 ? 0 : 7;
const bg = (this._colors.selectionOpaque.rgba >>> 8) | Attributes.CM_RGB;
if (columnSelectMode) {
const startCol = model.selection.startCol;
const width = model.selection.endCol - startCol;
const height = model.selection.viewportCappedEndRow - model.selection.viewportCappedStartRow + 1;
for (let y = model.selection.viewportCappedStartRow; y < model.selection.viewportCappedStartRow + height; y++) {
this._updateSelectionRange(startCol, startCol + width, y, model, bg, fg);
this._updateSelectionRange(startCol, startCol + width, y, model, bg);
}
} else {
// Draw first row
const startCol = model.selection.viewportStartRow === model.selection.viewportCappedStartRow ? model.selection.startCol : 0;
const startRowEndCol = model.selection.viewportCappedStartRow === model.selection.viewportCappedEndRow ? model.selection.endCol : terminal.cols;
this._updateSelectionRange(startCol, startRowEndCol, model.selection.viewportCappedStartRow, model, bg, fg);
this._updateSelectionRange(startCol, startRowEndCol, model.selection.viewportCappedStartRow, model, bg);
// Draw middle rows
const middleRowsCount = Math.max(model.selection.viewportCappedEndRow - model.selection.viewportCappedStartRow - 1, 0);
for (let y = model.selection.viewportCappedStartRow + 1; y <= model.selection.viewportCappedStartRow + middleRowsCount; y++) {
this._updateSelectionRange(0, startRowEndCol, y, model, bg, fg);
this._updateSelectionRange(0, startRowEndCol, y, model, bg);
}
// Draw final row
if (model.selection.viewportCappedStartRow !== model.selection.viewportCappedEndRow) {
// Only draw viewportEndRow if it's not the same as viewportStartRow
const endCol = model.selection.viewportEndRow === model.selection.viewportCappedEndRow ? model.selection.endCol : terminal.cols;
this._updateSelectionRange(0, endCol, model.selection.viewportCappedEndRow, model, bg, fg);
this._updateSelectionRange(0, endCol, model.selection.viewportCappedEndRow, model, bg);
}
}
}
private _updateSelectionRange(startCol: number, endCol: number, y: number, model: IRenderModel, bg: number, fg: number): void {
private _updateSelectionRange(startCol: number, endCol: number, y: number, model: IRenderModel, bg: number): void {
const terminal = this._terminal;
const row = y + terminal.buffer.viewportY;
let line: IBufferLine | undefined;
for (let x = startCol; x < endCol; x++) {
const offset = (y * this._terminal.cols + x) * INDICIES_PER_CELL;
// Because the cache uses attr as a lookup key it needs to contain the selection colors as well
let attr = model.cells[offset + 1];
attr = attr & ~0x3ffff | bg << 9 | fg;
const offset = (y * this._terminal.cols + x) * RENDER_MODEL_INDICIES_PER_CELL;
const code = model.cells[offset];
if (code & COMBINED_CHAR_BIT_MASK) {
if (!line) {
line = terminal.buffer.getLine(row);
}
const chars = line!.getCell(x)!.char;
this._updateCell(this._vertices.selectionAttributes, x, y, model.cells[offset], attr, bg, fg, chars);
this._updateCell(this._vertices.selectionAttributes, x, y, model.cells[offset], bg, model.cells[offset + RENDER_MODEL_FG_OFFSET], chars);
} else {
this._updateCell(this._vertices.selectionAttributes, x, y, model.cells[offset], attr, bg, fg);
this._updateCell(this._vertices.selectionAttributes, x, y, model.cells[offset], bg, model.cells[offset + RENDER_MODEL_FG_OFFSET]);
}
}
}
@@ -6,12 +6,11 @@
import { createProgram, expandFloat32Array, PROJECTION_MATRIX, throwIfFalsy } from './WebglUtils';
import { IRenderModel, IWebGLVertexArrayObject, IWebGL2RenderingContext, ISelectionRenderModel } from './Types';
import { fill } from 'common/TypedArrayUtils';
import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants';
import { is256Color } from './atlas/CharAtlasUtils';
import { DEFAULT_COLOR } from 'common/buffer/Constants';
import { Attributes, FgFlags } from 'common/buffer/Constants';
import { Terminal } from 'xterm';
import { IColorSet, IColor } from 'browser/Types';
import { IRenderDimensions } from 'browser/renderer/Types';
import { RENDER_MODEL_BG_OFFSET, RENDER_MODEL_FG_OFFSET, RENDER_MODEL_INDICIES_PER_CELL } from './RenderModel';
const enum VertexAttribLocations {
POSITION = 0,
@@ -23,13 +22,13 @@ const enum VertexAttribLocations {
const vertexShaderSource = `#version 300 es
layout (location = ${VertexAttribLocations.POSITION}) in vec2 a_position;
layout (location = ${VertexAttribLocations.SIZE}) in vec2 a_size;
layout (location = ${VertexAttribLocations.COLOR}) in vec3 a_color;
layout (location = ${VertexAttribLocations.COLOR}) in vec4 a_color;
layout (location = ${VertexAttribLocations.UNIT_QUAD}) in vec2 a_unitquad;
uniform mat4 u_projection;
uniform vec2 u_resolution;
out vec3 v_color;
out vec4 v_color;
void main() {
vec2 zeroToOne = (a_position + (a_unitquad * a_size)) / u_resolution;
@@ -40,12 +39,12 @@ void main() {
const fragmentShaderSource = `#version 300 es
precision lowp float;
in vec3 v_color;
in vec4 v_color;
out vec4 outColor;
void main() {
outColor = vec4(v_color, 1);
outColor = v_color;
}`;
interface IVertices {
@@ -156,7 +155,7 @@ export class RectangleRenderer {
private _updateCachedColors(): void {
this._bgFloat = this._colorToFloat32Array(this._colors.background);
this._selectionFloat = this._colorToFloat32Array(this._colors.selection);
this._selectionFloat = this._colorToFloat32Array(this._colors.selectionOpaque);
}
private _updateViewportRectangle(): void {
@@ -247,47 +246,73 @@ export class RectangleRenderer {
for (let y = 0; y < terminal.rows; y++) {
let currentStartX = -1;
let currentBg = DEFAULT_COLOR;
let currentBg = 0;
let currentFg = 0;
let currentInverse = false;
for (let x = 0; x < terminal.cols; x++) {
const modelIndex = ((y * terminal.cols) + x) * 4;
const bg = model.cells[modelIndex + 2];
if (bg !== currentBg) {
const modelIndex = ((y * terminal.cols) + x) * RENDER_MODEL_INDICIES_PER_CELL;
const bg = model.cells[modelIndex + RENDER_MODEL_BG_OFFSET];
const fg = model.cells[modelIndex + RENDER_MODEL_FG_OFFSET];
const inverse = !!(fg & FgFlags.INVERSE);
if (bg !== currentBg || (fg !== currentFg && (currentInverse || inverse))) {
// A rectangle needs to be drawn if going from non-default to another color
if (currentBg !== DEFAULT_COLOR) {
if (currentBg !== 0 || (currentInverse && currentFg !== 0)) {
const offset = rectangleCount++ * INDICES_PER_RECTANGLE;
this._updateRectangle(vertices, offset, currentBg, currentStartX, x, y);
this._updateRectangle(vertices, offset, currentFg, currentBg, currentStartX, x, y);
}
currentStartX = x;
currentBg = bg;
currentFg = fg;
currentInverse = inverse;
}
}
// Finish rectangle if it's still going
if (currentBg !== DEFAULT_COLOR) {
if (currentBg !== 0 || (currentInverse && currentFg !== 0)) {
const offset = rectangleCount++ * INDICES_PER_RECTANGLE;
this._updateRectangle(vertices, offset, currentBg, currentStartX, terminal.cols, y);
this._updateRectangle(vertices, offset, currentFg, currentBg, currentStartX, terminal.cols, y);
}
}
vertices.count = rectangleCount;
}
private _updateRectangle(vertices: IVertices, offset: number, bg: number, startX: number, endX: number, y: number): void {
let color: IColor | null = null;
if (bg === INVERTED_DEFAULT_COLOR) {
color = this._colors.foreground;
} else if (is256Color(bg)) {
color = this._colors.ansi[bg];
private _updateRectangle(vertices: IVertices, offset: number, fg: number, bg: number, startX: number, endX: number, y: number): void {
let rgba: number | undefined;
if (fg & FgFlags.INVERSE) {
switch (fg & Attributes.CM_MASK) {
case Attributes.CM_P16:
case Attributes.CM_P256:
rgba = this._colors.ansi[fg & Attributes.PCOLOR_MASK].rgba;
break;
case Attributes.CM_RGB:
rgba = (fg & Attributes.RGB_MASK) << 8;
break;
case Attributes.CM_DEFAULT:
default:
rgba = this._colors.foreground.rgba;
}
} else {
// TODO: Add support for true color
color = this._colors.foreground;
switch (bg & Attributes.CM_MASK) {
case Attributes.CM_P16:
case Attributes.CM_P256:
rgba = this._colors.ansi[bg & Attributes.PCOLOR_MASK].rgba;
break;
case Attributes.CM_RGB:
rgba = (bg & Attributes.RGB_MASK) << 8;
break;
case Attributes.CM_DEFAULT:
default:
rgba = this._colors.background.rgba;
}
}
if (vertices.attributes.length < offset + 4) {
vertices.attributes = expandFloat32Array(vertices.attributes, this._terminal.rows * this._terminal.cols * INDICES_PER_RECTANGLE);
}
const x1 = startX * this._dimensions.scaledCellWidth;
const y1 = y * this._dimensions.scaledCellHeight;
const r = ((color.rgba >> 24) & 0xFF) / 255;
const g = ((color.rgba >> 16) & 0xFF) / 255;
const b = ((color.rgba >> 8 ) & 0xFF) / 255;
const r = ((rgba >> 24) & 0xFF) / 255;
const g = ((rgba >> 16) & 0xFF) / 255;
const b = ((rgba >> 8 ) & 0xFF) / 255;
this._addRectangle(vertices.attributes, offset, x1, y1, (endX - startX) * this._dimensions.scaledCellWidth, this._dimensions.scaledCellHeight, r, g, b, 1);
}
+3 -1
View File
@@ -6,7 +6,9 @@
import { IRenderModel, ISelectionRenderModel } from './Types';
import { fill } from 'common/TypedArrayUtils';
export const RENDER_MODEL_INDICIES_PER_CELL = 4;
export const RENDER_MODEL_INDICIES_PER_CELL = 3;
export const RENDER_MODEL_BG_OFFSET = 1;
export const RENDER_MODEL_FG_OFFSET = 2;
export const COMBINED_CHAR_BIT_MASK = 0x80000000;
+1 -1
View File
@@ -4,7 +4,7 @@
*/
export interface IRasterizedGlyphSet {
[flags: number]: IRasterizedGlyph;
[bg: number]: { [fg: number]: IRasterizedGlyph } | undefined;
}
/**
+10 -2
View File
@@ -9,20 +9,28 @@ import { IRenderService } from 'browser/services/Services';
import { IColorSet } from 'browser/Types';
export class WebglAddon implements ITerminalAddon {
private _terminal?: Terminal;
constructor(
private _preserveDrawingBuffer?: boolean
) {}
public activate(terminal: Terminal): void {
if (!terminal.element) {
throw new Error('Cannot activate WebglRendererAddon before Terminal.open');
throw new Error('Cannot activate WebglAddon before Terminal.open');
}
this._terminal = terminal;
const renderService: IRenderService = (<any>terminal)._core._renderService;
const colors: IColorSet = (<any>terminal)._core._colorManager.colors;
renderService.setRenderer(new WebglRenderer(terminal, colors, this._preserveDrawingBuffer));
}
public dispose(): void {
throw new Error('WebglRendererAddon.dispose Not yet implemented');
if (!this._terminal) {
throw new Error('Cannot dispose WebglAddon because it is activated');
}
const renderService: IRenderService = (<any>this._terminal)._core._renderService;
renderService.setRenderer((<any>this._terminal)._core._createRenderer());
renderService.onResize(this._terminal.cols, this._terminal.rows);
}
}
+691 -60
View File
@@ -4,9 +4,10 @@
*/
import * as puppeteer from 'puppeteer';
import { assert } from 'chai';
import { ITerminalOptions } from '../../../src/Types';
import { ITheme } from 'xterm';
import { assert } from 'chai';
import deepEqual = require('deep-equal');
const APP = 'http://127.0.0.1:3000/test';
@@ -16,31 +17,20 @@ const width = 800;
const height = 600;
describe('WebGL Renderer 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}`, `--no-sandbox`]
});
page = (await browser.pages())[0];
await page.setViewport({ width, height });
await page.goto(APP);
await openTerminal();
await page.evaluate(`window.term.loadAddon(new WebglAddon(true));`);
it('dispose removes renderer canvases', async () => {
await setupBrowser();
assert.equal(await page.evaluate(`document.querySelectorAll('.xterm canvas').length`), 3);
await page.evaluate(`addon.dispose()`);
assert.equal(await page.evaluate(`document.querySelectorAll('.xterm canvas').length`), 0);
await browser.close();
});
after(() => {
browser.close();
});
describe('colors', () => {
before(async () => setupBrowser());
after(async () => browser.close());
beforeEach(async () => page.evaluate(`window.term.reset()`));
beforeEach(async () => {
await page.evaluate(`window.term.reset()`);
});
describe('WebGL Renderer', () => {
it('foreground colors normal', async function(): Promise<any> {
it('foreground 0-15', async () => {
const theme: ITheme = {
black: '#010203',
red: '#040506',
@@ -53,17 +43,17 @@ describe('WebGL Renderer Integration Tests', function(): void {
};
await page.evaluate(`window.term.setOption('theme', ${JSON.stringify(theme)});`);
await writeSync(`\\x1b[30m█\\x1b[31m█\\x1b[32m█\\x1b[33m█\\x1b[34m█\\x1b[35m█\\x1b[36m█\\x1b[37m█`);
assert.deepEqual(await getCellColor(1, 1), [1, 2, 3, 255]);
assert.deepEqual(await getCellColor(2, 1), [4, 5, 6, 255]);
assert.deepEqual(await getCellColor(3, 1), [7, 8, 9, 255]);
assert.deepEqual(await getCellColor(4, 1), [10, 11, 12, 255]);
assert.deepEqual(await getCellColor(5, 1), [13, 14, 15, 255]);
assert.deepEqual(await getCellColor(6, 1), [16, 17, 18, 255]);
assert.deepEqual(await getCellColor(7, 1), [19, 20, 21, 255]);
assert.deepEqual(await getCellColor(8, 1), [22, 23, 24, 255]);
await pollFor(page, () => getCellColor(1, 1), [1, 2, 3, 255]);
await pollFor(page, () => getCellColor(2, 1), [4, 5, 6, 255]);
await pollFor(page, () => getCellColor(3, 1), [7, 8, 9, 255]);
await pollFor(page, () => getCellColor(4, 1), [10, 11, 12, 255]);
await pollFor(page, () => getCellColor(5, 1), [13, 14, 15, 255]);
await pollFor(page, () => getCellColor(6, 1), [16, 17, 18, 255]);
await pollFor(page, () => getCellColor(7, 1), [19, 20, 21, 255]);
await pollFor(page, () => getCellColor(8, 1), [22, 23, 24, 255]);
});
it('foreground colors bright', async function(): Promise<any> {
it('foreground 0-7 drawBoldTextInBrightColors', async () => {
const theme: ITheme = {
brightBlack: '#010203',
brightRed: '#040506',
@@ -74,19 +64,22 @@ describe('WebGL Renderer Integration Tests', function(): void {
brightCyan: '#131415',
brightWhite: '#161718'
};
await page.evaluate(`window.term.setOption('theme', ${JSON.stringify(theme)});`);
await writeSync(`\\x1b[90m█\\x1b[91m█\\x1b[92m█\\x1b[93m█\\x1b[94m█\\x1b[95m█\\x1b[96m█\\x1b[97m█`);
assert.deepEqual(await getCellColor(1, 1), [1, 2, 3, 255]);
assert.deepEqual(await getCellColor(2, 1), [4, 5, 6, 255]);
assert.deepEqual(await getCellColor(3, 1), [7, 8, 9, 255]);
assert.deepEqual(await getCellColor(4, 1), [10, 11, 12, 255]);
assert.deepEqual(await getCellColor(5, 1), [13, 14, 15, 255]);
assert.deepEqual(await getCellColor(6, 1), [16, 17, 18, 255]);
assert.deepEqual(await getCellColor(7, 1), [19, 20, 21, 255]);
assert.deepEqual(await getCellColor(8, 1), [22, 23, 24, 255]);
await page.evaluate(`
window.term.setOption('theme', ${JSON.stringify(theme)});
window.term.setOption('drawBoldTextInBrightColors', true);
`);
await writeSync(`\\x1b[1;30m█\\x1b[1;31m█\\x1b[1;32m█\\x1b[1;33m█\\x1b[1;34m█\\x1b[1;35m█\\x1b[1;36m█\\x1b[1;37m█`);
await pollFor(page, () => getCellColor(1, 1), [1, 2, 3, 255]);
await pollFor(page, () => getCellColor(2, 1), [4, 5, 6, 255]);
await pollFor(page, () => getCellColor(3, 1), [7, 8, 9, 255]);
await pollFor(page, () => getCellColor(4, 1), [10, 11, 12, 255]);
await pollFor(page, () => getCellColor(5, 1), [13, 14, 15, 255]);
await pollFor(page, () => getCellColor(6, 1), [16, 17, 18, 255]);
await pollFor(page, () => getCellColor(7, 1), [19, 20, 21, 255]);
await pollFor(page, () => getCellColor(8, 1), [22, 23, 24, 255]);
});
it('background colors normal', async function(): Promise<any> {
it('background 0-15', async () => {
const theme: ITheme = {
black: '#010203',
red: '#040506',
@@ -99,17 +92,86 @@ describe('WebGL Renderer Integration Tests', function(): void {
};
await page.evaluate(`window.term.setOption('theme', ${JSON.stringify(theme)});`);
await writeSync(`\\x1b[40m \\x1b[41m \\x1b[42m \\x1b[43m \\x1b[44m \\x1b[45m \\x1b[46m \\x1b[47m `);
assert.deepEqual(await getCellColor(1, 1), [1, 2, 3, 255]);
assert.deepEqual(await getCellColor(2, 1), [4, 5, 6, 255]);
assert.deepEqual(await getCellColor(3, 1), [7, 8, 9, 255]);
assert.deepEqual(await getCellColor(4, 1), [10, 11, 12, 255]);
assert.deepEqual(await getCellColor(5, 1), [13, 14, 15, 255]);
assert.deepEqual(await getCellColor(6, 1), [16, 17, 18, 255]);
assert.deepEqual(await getCellColor(7, 1), [19, 20, 21, 255]);
assert.deepEqual(await getCellColor(8, 1), [22, 23, 24, 255]);
await pollFor(page, () => getCellColor(1, 1), [1, 2, 3, 255]);
await pollFor(page, () => getCellColor(2, 1), [4, 5, 6, 255]);
await pollFor(page, () => getCellColor(3, 1), [7, 8, 9, 255]);
await pollFor(page, () => getCellColor(4, 1), [10, 11, 12, 255]);
await pollFor(page, () => getCellColor(5, 1), [13, 14, 15, 255]);
await pollFor(page, () => getCellColor(6, 1), [16, 17, 18, 255]);
await pollFor(page, () => getCellColor(7, 1), [19, 20, 21, 255]);
await pollFor(page, () => getCellColor(8, 1), [22, 23, 24, 255]);
});
it('background colors bright', async function(): Promise<any> {
it('foreground 0-15 inverse', async () => {
const theme: ITheme = {
black: '#010203',
red: '#040506',
green: '#070809',
yellow: '#0a0b0c',
blue: '#0d0e0f',
magenta: '#101112',
cyan: '#131415',
white: '#161718'
};
await page.evaluate(`window.term.setOption('theme', ${JSON.stringify(theme)});`);
await writeSync(`\\x1b[7;30m \\x1b[7;31m \\x1b[7;32m \\x1b[7;33m \\x1b[7;34m \\x1b[7;35m \\x1b[7;36m \\x1b[7;37m `);
await pollFor(page, () => getCellColor(1, 1), [1, 2, 3, 255]);
await pollFor(page, () => getCellColor(2, 1), [4, 5, 6, 255]);
await pollFor(page, () => getCellColor(3, 1), [7, 8, 9, 255]);
await pollFor(page, () => getCellColor(4, 1), [10, 11, 12, 255]);
await pollFor(page, () => getCellColor(5, 1), [13, 14, 15, 255]);
await pollFor(page, () => getCellColor(6, 1), [16, 17, 18, 255]);
await pollFor(page, () => getCellColor(7, 1), [19, 20, 21, 255]);
await pollFor(page, () => getCellColor(8, 1), [22, 23, 24, 255]);
});
it('background 0-15 inverse', async () => {
const theme: ITheme = {
black: '#010203',
red: '#040506',
green: '#070809',
yellow: '#0a0b0c',
blue: '#0d0e0f',
magenta: '#101112',
cyan: '#131415',
white: '#161718'
};
await page.evaluate(`window.term.setOption('theme', ${JSON.stringify(theme)});`);
await writeSync(`\\x1b[7;40m█\\x1b[7;41m█\\x1b[7;42m█\\x1b[7;43m█\\x1b[7;44m█\\x1b[7;45m█\\x1b[7;46m█\\x1b[7;47m█`);
await pollFor(page, () => getCellColor(1, 1), [1, 2, 3, 255]);
await pollFor(page, () => getCellColor(2, 1), [4, 5, 6, 255]);
await pollFor(page, () => getCellColor(3, 1), [7, 8, 9, 255]);
await pollFor(page, () => getCellColor(4, 1), [10, 11, 12, 255]);
await pollFor(page, () => getCellColor(5, 1), [13, 14, 15, 255]);
await pollFor(page, () => getCellColor(6, 1), [16, 17, 18, 255]);
await pollFor(page, () => getCellColor(7, 1), [19, 20, 21, 255]);
await pollFor(page, () => getCellColor(8, 1), [22, 23, 24, 255]);
});
it('foreground 0-15 bright', async () => {
const theme: ITheme = {
brightBlack: '#010203',
brightRed: '#040506',
brightGreen: '#070809',
brightYellow: '#0a0b0c',
brightBlue: '#0d0e0f',
brightMagenta: '#101112',
brightCyan: '#131415',
brightWhite: '#161718'
};
await page.evaluate(`window.term.setOption('theme', ${JSON.stringify(theme)});`);
await writeSync(`\\x1b[90m█\\x1b[91m█\\x1b[92m█\\x1b[93m█\\x1b[94m█\\x1b[95m█\\x1b[96m█\\x1b[97m█`);
await pollFor(page, () => getCellColor(1, 1), [1, 2, 3, 255]);
await pollFor(page, () => getCellColor(2, 1), [4, 5, 6, 255]);
await pollFor(page, () => getCellColor(3, 1), [7, 8, 9, 255]);
await pollFor(page, () => getCellColor(4, 1), [10, 11, 12, 255]);
await pollFor(page, () => getCellColor(5, 1), [13, 14, 15, 255]);
await pollFor(page, () => getCellColor(6, 1), [16, 17, 18, 255]);
await pollFor(page, () => getCellColor(7, 1), [19, 20, 21, 255]);
await pollFor(page, () => getCellColor(8, 1), [22, 23, 24, 255]);
});
it('background 0-15 bright', async () => {
const theme: ITheme = {
brightBlack: '#010203',
brightRed: '#040506',
@@ -122,14 +184,538 @@ describe('WebGL Renderer Integration Tests', function(): void {
};
await page.evaluate(`window.term.setOption('theme', ${JSON.stringify(theme)});`);
await writeSync(`\\x1b[100m \\x1b[101m \\x1b[102m \\x1b[103m \\x1b[104m \\x1b[105m \\x1b[106m \\x1b[107m `);
assert.deepEqual(await getCellColor(1, 1), [1, 2, 3, 255]);
assert.deepEqual(await getCellColor(2, 1), [4, 5, 6, 255]);
assert.deepEqual(await getCellColor(3, 1), [7, 8, 9, 255]);
assert.deepEqual(await getCellColor(4, 1), [10, 11, 12, 255]);
assert.deepEqual(await getCellColor(5, 1), [13, 14, 15, 255]);
assert.deepEqual(await getCellColor(6, 1), [16, 17, 18, 255]);
assert.deepEqual(await getCellColor(7, 1), [19, 20, 21, 255]);
assert.deepEqual(await getCellColor(8, 1), [22, 23, 24, 255]);
await pollFor(page, () => getCellColor(1, 1), [1, 2, 3, 255]);
await pollFor(page, () => getCellColor(2, 1), [4, 5, 6, 255]);
await pollFor(page, () => getCellColor(3, 1), [7, 8, 9, 255]);
await pollFor(page, () => getCellColor(4, 1), [10, 11, 12, 255]);
await pollFor(page, () => getCellColor(5, 1), [13, 14, 15, 255]);
await pollFor(page, () => getCellColor(6, 1), [16, 17, 18, 255]);
await pollFor(page, () => getCellColor(7, 1), [19, 20, 21, 255]);
await pollFor(page, () => getCellColor(8, 1), [22, 23, 24, 255]);
});
it('foreground 16-255', async () => {
let data = '';
for (let y = 0; y < 240 / 16; y++) {
for (let x = 0; x < 16; x++) {
data += `\\x1b[38;5;${16 + y * 16 + x}m█\x1b[0m`;
}
data += '\\r\\n';
}
await writeSync(data);
for (let y = 0; y < 240 / 16; y++) {
for (let x = 0; x < 16; x++) {
const cssColor = COLORS_16_TO_255[y * 16 + x];
const r = parseInt(cssColor.substr(1, 2), 16);
const g = parseInt(cssColor.substr(3, 2), 16);
const b = parseInt(cssColor.substr(5, 2), 16);
await pollFor(page, () => getCellColor(x + 1, y + 1), [r, g, b, 255]);
}
}
});
it('background 16-255', async () => {
let data = '';
for (let y = 0; y < 240 / 16; y++) {
for (let x = 0; x < 16; x++) {
data += `\\x1b[48;5;${16 + y * 16 + x}m \x1b[0m`;
}
data += '\\r\\n';
}
await writeSync(data);
for (let y = 0; y < 240 / 16; y++) {
for (let x = 0; x < 16; x++) {
const cssColor = COLORS_16_TO_255[y * 16 + x];
const r = parseInt(cssColor.substr(1, 2), 16);
const g = parseInt(cssColor.substr(3, 2), 16);
const b = parseInt(cssColor.substr(5, 2), 16);
await pollFor(page, () => getCellColor(x + 1, y + 1), [r, g, b, 255]);
}
}
});
it('foreground 16-255 inverse', async () => {
let data = '';
for (let y = 0; y < 240 / 16; y++) {
for (let x = 0; x < 16; x++) {
data += `\\x1b[7;38;5;${16 + y * 16 + x}m \x1b[0m`;
}
data += '\\r\\n';
}
await writeSync(data);
for (let y = 0; y < 240 / 16; y++) {
for (let x = 0; x < 16; x++) {
const cssColor = COLORS_16_TO_255[y * 16 + x];
const r = parseInt(cssColor.substr(1, 2), 16);
const g = parseInt(cssColor.substr(3, 2), 16);
const b = parseInt(cssColor.substr(5, 2), 16);
await pollFor(page, () => getCellColor(x + 1, y + 1), [r, g, b, 255]);
}
}
});
it('background 16-255 inverse', async () => {
let data = '';
for (let y = 0; y < 240 / 16; y++) {
for (let x = 0; x < 16; x++) {
data += `\\x1b[7;48;5;${16 + y * 16 + x}m█\x1b[0m`;
}
data += '\\r\\n';
}
await writeSync(data);
for (let y = 0; y < 240 / 16; y++) {
for (let x = 0; x < 16; x++) {
const cssColor = COLORS_16_TO_255[y * 16 + x];
const r = parseInt(cssColor.substr(1, 2), 16);
const g = parseInt(cssColor.substr(3, 2), 16);
const b = parseInt(cssColor.substr(5, 2), 16);
await pollFor(page, () => getCellColor(x + 1, y + 1), [r, g, b, 255]);
}
}
});
it('foreground true color red', async () => {
let data = '';
for (let y = 0; y < 16; y++) {
for (let x = 0; x < 16; x++) {
const i = y * 16 + x;
data += `\\x1b[38;2;${i};0;0m█\x1b[0m`;
}
data += '\\r\\n';
}
await writeSync(data);
for (let y = 0; y < 16; y++) {
for (let x = 0; x < 16; x++) {
const i = y * 16 + x;
await pollFor(page, () => getCellColor(x + 1, y + 1), [i, 0, 0, 255]);
}
}
});
it('background true color red', async () => {
let data = '';
for (let y = 0; y < 16; y++) {
for (let x = 0; x < 16; x++) {
const i = y * 16 + x;
data += `\\x1b[48;2;${i};0;0m \x1b[0m`;
}
data += '\\r\\n';
}
await writeSync(data);
for (let y = 0; y < 16; y++) {
for (let x = 0; x < 16; x++) {
const i = y * 16 + x;
await pollFor(page, () => getCellColor(x + 1, y + 1), [i, 0, 0, 255]);
}
}
});
it('foreground true color green', async () => {
let data = '';
for (let y = 0; y < 16; y++) {
for (let x = 0; x < 16; x++) {
const i = y * 16 + x;
data += `\\x1b[38;2;0;${i};0m█\x1b[0m`;
}
data += '\\r\\n';
}
await writeSync(data);
for (let y = 0; y < 16; y++) {
for (let x = 0; x < 16; x++) {
const i = y * 16 + x;
await pollFor(page, () => getCellColor(x + 1, y + 1), [0, i, 0, 255]);
}
}
});
it('background true color green', async () => {
let data = '';
for (let y = 0; y < 16; y++) {
for (let x = 0; x < 16; x++) {
const i = y * 16 + x;
data += `\\x1b[48;2;0;${i};0m \x1b[0m`;
}
data += '\\r\\n';
}
await writeSync(data);
for (let y = 0; y < 16; y++) {
for (let x = 0; x < 16; x++) {
const i = y * 16 + x;
await pollFor(page, () => getCellColor(x + 1, y + 1), [0, i, 0, 255]);
}
}
});
it('foreground true color blue', async () => {
let data = '';
for (let y = 0; y < 16; y++) {
for (let x = 0; x < 16; x++) {
const i = y * 16 + x;
data += `\\x1b[38;2;0;0;${i}m█\x1b[0m`;
}
data += '\\r\\n';
}
await writeSync(data);
for (let y = 0; y < 16; y++) {
for (let x = 0; x < 16; x++) {
const i = y * 16 + x;
await pollFor(page, () => getCellColor(x + 1, y + 1), [0, 0, i, 255]);
}
}
});
it('background true color blue', async () => {
let data = '';
for (let y = 0; y < 16; y++) {
for (let x = 0; x < 16; x++) {
const i = y * 16 + x;
data += `\\x1b[48;2;0;0;${i}m \x1b[0m`;
}
data += '\\r\\n';
}
await writeSync(data);
for (let y = 0; y < 16; y++) {
for (let x = 0; x < 16; x++) {
const i = y * 16 + x;
await pollFor(page, () => getCellColor(x + 1, y + 1), [0, 0, i, 255]);
}
}
});
it('foreground true color grey', async () => {
let data = '';
for (let y = 0; y < 16; y++) {
for (let x = 0; x < 16; x++) {
const i = y * 16 + x;
data += `\\x1b[38;2;${i};${i};${i}m█\x1b[0m`;
}
data += '\\r\\n';
}
await writeSync(data);
for (let y = 0; y < 16; y++) {
for (let x = 0; x < 16; x++) {
const i = y * 16 + x;
await pollFor(page, () => getCellColor(x + 1, y + 1), [i, i, i, 255]);
}
}
});
it('background true color grey', async () => {
let data = '';
for (let y = 0; y < 16; y++) {
for (let x = 0; x < 16; x++) {
const i = y * 16 + x;
data += `\\x1b[48;2;${i};${i};${i}m \x1b[0m`;
}
data += '\\r\\n';
}
await writeSync(data);
for (let y = 0; y < 16; y++) {
for (let x = 0; x < 16; x++) {
const i = y * 16 + x;
await pollFor(page, () => getCellColor(x + 1, y + 1), [i, i, i, 255]);
}
}
});
it('foreground true color red inverse', async function(): Promise<void> {
let data = '';
for (let y = 0; y < 16; y++) {
for (let x = 0; x < 16; x++) {
const i = y * 16 + x;
data += `\x1b[7;38;2;${i};0;0m \x1b[0m`;
}
data += '\\r\\n';
}
await writeSync(data);
for (let y = 0; y < 16; y++) {
for (let x = 0; x < 16; x++) {
const i = y * 16 + x;
await pollFor(page, () => getCellColor(x + 1, y + 1), [i, 0, 0, 255]);
}
}
});
it('background true color red inverse', async function(): Promise<void> {
let data = '';
for (let y = 0; y < 16; y++) {
for (let x = 0; x < 16; x++) {
const i = y * 16 + x;
data += `\\x1b[7;48;2;${i};0;0m█\\x1b[0m`;
}
data += '\\r\\n';
}
await writeSync(data);
for (let y = 0; y < 16; y++) {
for (let x = 0; x < 16; x++) {
const i = y * 16 + x;
await pollFor(page, () => getCellColor(x + 1, y + 1), [i, 0, 0, 255]);
}
}
});
it('foreground true color green inverse', async () => {
let data = '';
for (let y = 0; y < 16; y++) {
for (let x = 0; x < 16; x++) {
const i = y * 16 + x;
data += `\\x1b[7;38;2;0;${i};0m \x1b[0m`;
}
data += '\\r\\n';
}
await writeSync(data);
for (let y = 0; y < 16; y++) {
for (let x = 0; x < 16; x++) {
const i = y * 16 + x;
await pollFor(page, () => getCellColor(x + 1, y + 1), [0, i, 0, 255]);
}
}
});
it('background true color green inverse', async () => {
let data = '';
for (let y = 0; y < 16; y++) {
for (let x = 0; x < 16; x++) {
const i = y * 16 + x;
data += `\\x1b[7;48;2;0;${i};0m█\x1b[0m`;
}
data += '\\r\\n';
}
await writeSync(data);
for (let y = 0; y < 16; y++) {
for (let x = 0; x < 16; x++) {
const i = y * 16 + x;
await pollFor(page, () => getCellColor(x + 1, y + 1), [0, i, 0, 255]);
}
}
});
it('foreground true color blue inverse', async () => {
let data = '';
for (let y = 0; y < 16; y++) {
for (let x = 0; x < 16; x++) {
const i = y * 16 + x;
data += `\\x1b[7;38;2;0;0;${i}m \x1b[0m`;
}
data += '\\r\\n';
}
await writeSync(data);
for (let y = 0; y < 16; y++) {
for (let x = 0; x < 16; x++) {
const i = y * 16 + x;
await pollFor(page, () => getCellColor(x + 1, y + 1), [0, 0, i, 255]);
}
}
});
it('background true color blue inverse', async () => {
let data = '';
for (let y = 0; y < 16; y++) {
for (let x = 0; x < 16; x++) {
const i = y * 16 + x;
data += `\\x1b[7;48;2;0;0;${i}m█\x1b[0m`;
}
data += '\\r\\n';
}
await writeSync(data);
for (let y = 0; y < 16; y++) {
for (let x = 0; x < 16; x++) {
const i = y * 16 + x;
await pollFor(page, () => getCellColor(x + 1, y + 1), [0, 0, i, 255]);
}
}
});
it('foreground true color grey inverse', async () => {
let data = '';
for (let y = 0; y < 16; y++) {
for (let x = 0; x < 16; x++) {
const i = y * 16 + x;
data += `\\x1b[7;38;2;${i};${i};${i}m \x1b[0m`;
}
data += '\\r\\n';
}
await writeSync(data);
for (let y = 0; y < 16; y++) {
for (let x = 0; x < 16; x++) {
const i = y * 16 + x;
await pollFor(page, () => getCellColor(x + 1, y + 1), [i, i, i, 255]);
}
}
});
it('background true color grey inverse', async () => {
let data = '';
for (let y = 0; y < 16; y++) {
for (let x = 0; x < 16; x++) {
const i = y * 16 + x;
data += `\\x1b[7;48;2;${i};${i};${i}m█\x1b[0m`;
}
data += '\\r\\n';
}
await writeSync(data);
for (let y = 0; y < 16; y++) {
for (let x = 0; x < 16; x++) {
const i = y * 16 + x;
await pollFor(page, () => getCellColor(x + 1, y + 1), [i, i, i, 255]);
}
}
});
});
describe('minimumContrastRatio', async () => {
before(async () => setupBrowser());
after(async () => browser.close());
beforeEach(async () => page.evaluate(`window.term.reset()`));
it('should adjust 0-15 colors on black background', async () => {
const theme: ITheme = {
black: '#2e3436',
red: '#cc0000',
green: '#4e9a06',
yellow: '#c4a000',
blue: '#3465a4',
magenta: '#75507b',
cyan: '#06989a',
white: '#d3d7cf',
brightBlack: '#555753',
brightRed: '#ef2929',
brightGreen: '#8ae234',
brightYellow: '#fce94f',
brightBlue: '#729fcf',
brightMagenta: '#ad7fa8',
brightCyan: '#34e2e2',
brightWhite: '#eeeeec'
};
await page.evaluate(`
window.term.setOption('theme', ${JSON.stringify(theme)});
window.term.setOption('minimumContrastRatio', 1);
`);
await writeSync(
`\\x1b[30m█\\x1b[31m█\\x1b[32m█\\x1b[33m█\\x1b[34m█\\x1b[35m█\\x1b[36m█\\x1b[37m█\\r\\n` +
`\\x1b[90m█\\x1b[91m█\\x1b[92m█\\x1b[93m█\\x1b[94m█\\x1b[95m█\\x1b[96m█\\x1b[97m█`
);
// Validate before minimumContrastRatio is applied
await pollFor(page, () => getCellColor(1, 1), [0x2e, 0x34, 0x36, 255]);
await pollFor(page, () => getCellColor(2, 1), [0xcc, 0x00, 0x00, 255]);
await pollFor(page, () => getCellColor(3, 1), [0x4e, 0x9a, 0x06, 255]);
await pollFor(page, () => getCellColor(4, 1), [0xc4, 0xa0, 0x00, 255]);
await pollFor(page, () => getCellColor(5, 1), [0x34, 0x65, 0xa4, 255]);
await pollFor(page, () => getCellColor(6, 1), [0x75, 0x50, 0x7b, 255]);
await pollFor(page, () => getCellColor(7, 1), [0x06, 0x98, 0x9a, 255]);
await pollFor(page, () => getCellColor(8, 1), [0xd3, 0xd7, 0xcf, 255]);
await pollFor(page, () => getCellColor(1, 2), [0x55, 0x57, 0x53, 255]);
await pollFor(page, () => getCellColor(2, 2), [0xef, 0x29, 0x29, 255]);
await pollFor(page, () => getCellColor(3, 2), [0x8a, 0xe2, 0x34, 255]);
await pollFor(page, () => getCellColor(4, 2), [0xfc, 0xe9, 0x4f, 255]);
await pollFor(page, () => getCellColor(5, 2), [0x72, 0x9f, 0xcf, 255]);
await pollFor(page, () => getCellColor(6, 2), [0xad, 0x7f, 0xa8, 255]);
await pollFor(page, () => getCellColor(7, 2), [0x34, 0xe2, 0xe2, 255]);
await pollFor(page, () => getCellColor(8, 2), [0xee, 0xee, 0xec, 255]);
// Setting and check for minimum contrast values, note that these are note
// exact to the contrast ratio, if the increase luminance algorithm
// changes then these will probably fail
await page.evaluate(`window.term.setOption('minimumContrastRatio', 10);`);
await pollFor(page, () => getCellColor(1, 1), [176, 180, 180, 255]);
await pollFor(page, () => getCellColor(2, 1), [238, 158, 158, 255]);
await pollFor(page, () => getCellColor(3, 1), [197, 223, 171, 255]);
await pollFor(page, () => getCellColor(4, 1), [235, 221, 158, 255]);
await pollFor(page, () => getCellColor(5, 1), [124, 156, 198, 255]);
await pollFor(page, () => getCellColor(6, 1), [183, 165, 187, 255]);
await pollFor(page, () => getCellColor(7, 1), [110, 197, 198, 255]);
await pollFor(page, () => getCellColor(8, 1), [211, 215, 207, 255]);
await pollFor(page, () => getCellColor(1, 2), [183, 185, 183, 255]);
await pollFor(page, () => getCellColor(2, 2), [249, 156, 156, 255]);
await pollFor(page, () => getCellColor(3, 2), [138, 226, 52, 255]);
await pollFor(page, () => getCellColor(4, 2), [252, 233, 79, 255]);
await pollFor(page, () => getCellColor(5, 2), [114, 159, 207, 255]);
await pollFor(page, () => getCellColor(6, 2), [190, 152, 185, 255]);
// Unchanged
await pollFor(page, () => getCellColor(7, 2), [0x34, 0xe2, 0xe2, 255]);
await pollFor(page, () => getCellColor(8, 2), [0xee, 0xee, 0xec, 255]);
});
it('should adjust 0-15 colors on white background', async () => {
const theme: ITheme = {
background: '#ffffff',
black: '#2e3436',
red: '#cc0000',
green: '#4e9a06',
yellow: '#c4a000',
blue: '#3465a4',
magenta: '#75507b',
cyan: '#06989a',
white: '#d3d7cf',
brightBlack: '#555753',
brightRed: '#ef2929',
brightGreen: '#8ae234',
brightYellow: '#fce94f',
brightBlue: '#729fcf',
brightMagenta: '#ad7fa8',
brightCyan: '#34e2e2',
brightWhite: '#eeeeec'
};
await page.evaluate(`
window.term.setOption('theme', ${JSON.stringify(theme)});
window.term.setOption('minimumContrastRatio', 1);
`);
await writeSync(
`\\x1b[30m█\\x1b[31m█\\x1b[32m█\\x1b[33m█\\x1b[34m█\\x1b[35m█\\x1b[36m█\\x1b[37m█\\r\\n` +
`\\x1b[90m█\\x1b[91m█\\x1b[92m█\\x1b[93m█\\x1b[94m█\\x1b[95m█\\x1b[96m█\\x1b[97m█`
);
// Validate before minimumContrastRatio is applied
await pollFor(page, () => getCellColor(1, 1), [0x2e, 0x34, 0x36, 255]);
await pollFor(page, () => getCellColor(2, 1), [0xcc, 0x00, 0x00, 255]);
await pollFor(page, () => getCellColor(3, 1), [0x4e, 0x9a, 0x06, 255]);
await pollFor(page, () => getCellColor(4, 1), [0xc4, 0xa0, 0x00, 255]);
await pollFor(page, () => getCellColor(5, 1), [0x34, 0x65, 0xa4, 255]);
await pollFor(page, () => getCellColor(6, 1), [0x75, 0x50, 0x7b, 255]);
await pollFor(page, () => getCellColor(7, 1), [0x06, 0x98, 0x9a, 255]);
await pollFor(page, () => getCellColor(8, 1), [0xd3, 0xd7, 0xcf, 255]);
await pollFor(page, () => getCellColor(1, 2), [0x55, 0x57, 0x53, 255]);
await pollFor(page, () => getCellColor(2, 2), [0xef, 0x29, 0x29, 255]);
await pollFor(page, () => getCellColor(3, 2), [0x8a, 0xe2, 0x34, 255]);
await pollFor(page, () => getCellColor(4, 2), [0xfc, 0xe9, 0x4f, 255]);
await pollFor(page, () => getCellColor(5, 2), [0x72, 0x9f, 0xcf, 255]);
await pollFor(page, () => getCellColor(6, 2), [0xad, 0x7f, 0xa8, 255]);
await pollFor(page, () => getCellColor(7, 2), [0x34, 0xe2, 0xe2, 255]);
await pollFor(page, () => getCellColor(8, 2), [0xee, 0xee, 0xec, 255]);
// Setting and check for minimum contrast values, note that these are note
// exact to the contrast ratio, if the increase luminance algorithm
// changes then these will probably fail
await page.evaluate(`window.term.setOption('minimumContrastRatio', 10);`);
await pollFor(page, () => getCellColor(1, 1), [46, 52, 54, 255]);
await pollFor(page, () => getCellColor(2, 1), [132, 0, 0, 255]);
await pollFor(page, () => getCellColor(3, 1), [78, 154, 6, 255]);
await pollFor(page, () => getCellColor(4, 1), [114, 93, 0, 255]);
await pollFor(page, () => getCellColor(5, 1), [19, 40, 68, 255]);
await pollFor(page, () => getCellColor(6, 1), [60, 40, 64, 255]);
await pollFor(page, () => getCellColor(7, 1), [0, 71, 72, 255]);
await pollFor(page, () => getCellColor(8, 1), [64, 64, 63, 255]);
await pollFor(page, () => getCellColor(1, 2), [61, 63, 59, 255]);
await pollFor(page, () => getCellColor(2, 2), [125, 19, 19, 255]);
await pollFor(page, () => getCellColor(3, 2), [89, 146, 32, 255]);
await pollFor(page, () => getCellColor(4, 2), [105, 98, 32, 255]);
await pollFor(page, () => getCellColor(5, 2), [36, 52, 70, 255]);
await pollFor(page, () => getCellColor(6, 2), [64, 45, 63, 255]);
await pollFor(page, () => getCellColor(7, 2), [13, 67, 67, 255]);
await pollFor(page, () => getCellColor(8, 2), [64, 64, 64, 255]);
});
});
describe('allowTransparency', async () => {
before(async () => setupBrowser({ rendererType: 'dom', allowTransparency: true}));
after(async () => browser.close());
beforeEach(async () => page.evaluate(`window.term.reset()`));
it('transparent background inverse', async () => {
const theme: ITheme = {
background: '#ff000080'
};
await page.evaluate(`window.term.setOption('theme', ${JSON.stringify(theme)});`);
const data = `\\x1b[7m█\x1b[0m`;
await writeSync(data);
// Inverse background should be opaque
await pollFor(page, () => getCellColor(1, 1), [255, 0, 0, 255]);
});
});
});
@@ -161,3 +747,48 @@ async function getCellColor(col: number, row: number): Promise<number[]> {
`);
return await page.evaluate(`Array.from(window.result)`);
}
async function setupBrowser(options: ITerminalOptions = { rendererType: 'dom' }): Promise<void> {
browser = await puppeteer.launch({
headless: process.argv.indexOf('--headless') !== -1,
args: [`--window-size=${width},${height}`, `--no-sandbox`]
});
page = (await browser.pages())[0];
await page.setViewport({ width, height });
await page.goto(APP);
await openTerminal(options);
await page.evaluate(`
window.addon = new WebglAddon(true);
window.term.loadAddon(window.addon);
`);
}
export async function pollFor<T>(page: puppeteer.Page, evalOrFn: string | (() => Promise<T>), val: T, preFn?: () => Promise<void>): Promise<void> {
if (preFn) {
await preFn();
}
const result = typeof evalOrFn === 'string' ? await page.evaluate(evalOrFn) : await evalOrFn();
if (!deepEqual(result, val)) {
return new Promise<void>(r => {
setTimeout(() => r(pollFor(page, evalOrFn, val, preFn)), 1);
});
}
}
const COLORS_16_TO_255 = [
'#000000', '#00005f', '#000087', '#0000af', '#0000d7', '#0000ff', '#005f00', '#005f5f', '#005f87', '#005faf', '#005fd7', '#005fff', '#008700', '#00875f', '#008787', '#0087af',
'#0087d7', '#0087ff', '#00af00', '#00af5f', '#00af87', '#00afaf', '#00afd7', '#00afff', '#00d700', '#00d75f', '#00d787', '#00d7af', '#00d7d7', '#00d7ff', '#00ff00', '#00ff5f',
'#00ff87', '#00ffaf', '#00ffd7', '#00ffff', '#5f0000', '#5f005f', '#5f0087', '#5f00af', '#5f00d7', '#5f00ff', '#5f5f00', '#5f5f5f', '#5f5f87', '#5f5faf', '#5f5fd7', '#5f5fff',
'#5f8700', '#5f875f', '#5f8787', '#5f87af', '#5f87d7', '#5f87ff', '#5faf00', '#5faf5f', '#5faf87', '#5fafaf', '#5fafd7', '#5fafff', '#5fd700', '#5fd75f', '#5fd787', '#5fd7af',
'#5fd7d7', '#5fd7ff', '#5fff00', '#5fff5f', '#5fff87', '#5fffaf', '#5fffd7', '#5fffff', '#870000', '#87005f', '#870087', '#8700af', '#8700d7', '#8700ff', '#875f00', '#875f5f',
'#875f87', '#875faf', '#875fd7', '#875fff', '#878700', '#87875f', '#878787', '#8787af', '#8787d7', '#8787ff', '#87af00', '#87af5f', '#87af87', '#87afaf', '#87afd7', '#87afff',
'#87d700', '#87d75f', '#87d787', '#87d7af', '#87d7d7', '#87d7ff', '#87ff00', '#87ff5f', '#87ff87', '#87ffaf', '#87ffd7', '#87ffff', '#af0000', '#af005f', '#af0087', '#af00af',
'#af00d7', '#af00ff', '#af5f00', '#af5f5f', '#af5f87', '#af5faf', '#af5fd7', '#af5fff', '#af8700', '#af875f', '#af8787', '#af87af', '#af87d7', '#af87ff', '#afaf00', '#afaf5f',
'#afaf87', '#afafaf', '#afafd7', '#afafff', '#afd700', '#afd75f', '#afd787', '#afd7af', '#afd7d7', '#afd7ff', '#afff00', '#afff5f', '#afff87', '#afffaf', '#afffd7', '#afffff',
'#d70000', '#d7005f', '#d70087', '#d700af', '#d700d7', '#d700ff', '#d75f00', '#d75f5f', '#d75f87', '#d75faf', '#d75fd7', '#d75fff', '#d78700', '#d7875f', '#d78787', '#d787af',
'#d787d7', '#d787ff', '#d7af00', '#d7af5f', '#d7af87', '#d7afaf', '#d7afd7', '#d7afff', '#d7d700', '#d7d75f', '#d7d787', '#d7d7af', '#d7d7d7', '#d7d7ff', '#d7ff00', '#d7ff5f',
'#d7ff87', '#d7ffaf', '#d7ffd7', '#d7ffff', '#ff0000', '#ff005f', '#ff0087', '#ff00af', '#ff00d7', '#ff00ff', '#ff5f00', '#ff5f5f', '#ff5f87', '#ff5faf', '#ff5fd7', '#ff5fff',
'#ff8700', '#ff875f', '#ff8787', '#ff87af', '#ff87d7', '#ff87ff', '#ffaf00', '#ffaf5f', '#ffaf87', '#ffafaf', '#ffafd7', '#ffafff', '#ffd700', '#ffd75f', '#ffd787', '#ffd7af',
'#ffd7d7', '#ffd7ff', '#ffff00', '#ffff5f', '#ffff87', '#ffffaf', '#ffffd7', '#ffffff', '#080808', '#121212', '#1c1c1c', '#262626', '#303030', '#3a3a3a', '#444444', '#4e4e4e',
'#585858', '#626262', '#6c6c6c', '#767676', '#808080', '#8a8a8a', '#949494', '#9e9e9e', '#a8a8a8', '#b2b2b2', '#bcbcbc', '#c6c6c6', '#d0d0d0', '#dadada', '#e4e4e4', '#eeeeee'
];
+40 -58
View File
@@ -11,19 +11,15 @@ import { acquireCharAtlas } from './atlas/CharAtlasCache';
import { WebglCharAtlas } from './atlas/WebglCharAtlas';
import { RectangleRenderer } from './RectangleRenderer';
import { IWebGL2RenderingContext } from './Types';
import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants';
import { RenderModel, COMBINED_CHAR_BIT_MASK } from './RenderModel';
import { RenderModel, COMBINED_CHAR_BIT_MASK, RENDER_MODEL_BG_OFFSET, RENDER_MODEL_FG_OFFSET, RENDER_MODEL_INDICIES_PER_CELL } from './RenderModel';
import { Disposable } from 'common/Lifecycle';
import { DEFAULT_COLOR, CHAR_DATA_CHAR_INDEX, CHAR_DATA_CODE_INDEX, NULL_CELL_CODE } from 'common/buffer/Constants';
import { Terminal } from 'xterm';
import { getLuminance } from './ColorUtils';
import { NULL_CELL_CODE } from 'common/buffer/Constants';
import { Terminal, IEvent } from 'xterm';
import { IRenderLayer } from './renderLayer/Types';
import { IRenderDimensions, IRenderer } from 'browser/renderer/Types';
import { IRenderDimensions, IRenderer, IRequestRefreshRowsEvent } from 'browser/renderer/Types';
import { IColorSet } from 'browser/Types';
import { FLAGS } from './Constants';
import { getCompatAttr } from './CharDataCompat';
export const INDICIES_PER_CELL = 4;
import { EventEmitter } from 'common/EventEmitter';
import { CellData } from 'common/buffer/CellData';
export class WebglRenderer extends Disposable implements IRenderer {
private _renderLayers: IRenderLayer[];
@@ -31,6 +27,7 @@ export class WebglRenderer extends Disposable implements IRenderer {
private _devicePixelRatio: number;
private _model: RenderModel = new RenderModel();
private _workCell: CellData = new CellData();
private _canvas: HTMLCanvasElement;
private _gl: IWebGL2RenderingContext;
@@ -40,6 +37,10 @@ export class WebglRenderer extends Disposable implements IRenderer {
public dimensions: IRenderDimensions;
private _core: ITerminal;
private _isAttached: boolean;
private _onRequestRefreshRows = new EventEmitter<IRequestRefreshRowsEvent>();
public get onRequestRefreshRows(): IEvent<IRequestRefreshRowsEvent> { return this._onRequestRefreshRows.event; }
constructor(
private _terminal: Terminal,
@@ -50,11 +51,9 @@ export class WebglRenderer extends Disposable implements IRenderer {
this._core = (<any>this._terminal)._core;
this._applyBgLuminanceBasedSelection();
this._renderLayers = [
new LinkRenderLayer(this._core.screenElement, 2, this._colors, this._core),
new CursorRenderLayer(this._core.screenElement, 3, this._colors)
new CursorRenderLayer(this._core.screenElement, 3, this._colors, this._onRequestRefreshRows)
];
this.dimensions = {
scaledCharWidth: 0,
@@ -91,6 +90,8 @@ export class WebglRenderer extends Disposable implements IRenderer {
// Update dimensions and acquire char atlas
this.onCharSizeChanged();
this._isAttached = document.body.contains(this._core.screenElement);
}
public dispose(): void {
@@ -99,20 +100,8 @@ export class WebglRenderer extends Disposable implements IRenderer {
super.dispose();
}
private _applyBgLuminanceBasedSelection(): void {
// HACK: This is needed until webgl renderer adds support for selection colors
if (getLuminance(this._colors.background) > 0.5) {
this._colors.selection = { css: '#000', rgba: 255 };
} else {
this._colors.selection = { css: '#fff', rgba: 4294967295 };
}
}
public setColors(colors: IColorSet): void {
this._colors = colors;
this._applyBgLuminanceBasedSelection();
// Clear layers and force a full render
this._renderLayers.forEach(l => {
l.setColors(this._terminal, this._colors);
@@ -185,8 +174,7 @@ export class WebglRenderer extends Disposable implements IRenderer {
this._rectangleRenderer.updateSelection(this._model.selection, columnSelectMode);
this._glyphRenderer.updateSelection(this._model, columnSelectMode);
// TODO: #2102 Should this move to RenderCoordinator?
this._core.refresh(0, this._terminal.rows - 1);
this._onRequestRefreshRows.fire({ start: 0, end: this._terminal.rows - 1 });
}
public onCursorMove(): void {
@@ -206,6 +194,8 @@ export class WebglRenderer extends Disposable implements IRenderer {
*/
private _refreshCharAtlas(): void {
if (this.dimensions.scaledCharWidth <= 0 && this.dimensions.scaledCharHeight <= 0) {
// Mark as not attached so char atlas gets refreshed on next render
this._isAttached = false;
return;
}
@@ -231,6 +221,16 @@ export class WebglRenderer extends Disposable implements IRenderer {
}
public renderRows(start: number, end: number): void {
if (!this._isAttached) {
if (document.body.contains(this._core.screenElement) && (<any>this._core)._charSizeService.width && (<any>this._core)._charSizeService.height) {
this._updateDimensions();
this._refreshCharAtlas();
this._isAttached = true;
} else {
return;
}
}
// Update render layers
this._renderLayers.forEach(l => l.onGridChanged(this._terminal, start, end));
@@ -255,52 +255,34 @@ export class WebglRenderer extends Disposable implements IRenderer {
const line = terminal.buffer.lines.get(row)!;
this._model.lineLengths[y] = 0;
for (let x = 0; x < terminal.cols; x++) {
const charData = line.get(x);
const chars = charData[CHAR_DATA_CHAR_INDEX];
let code = charData[CHAR_DATA_CODE_INDEX];
const attr = getCompatAttr(line, x); // charData[CHAR_DATA_ATTR_INDEX];
const i = ((y * terminal.cols) + x) * INDICIES_PER_CELL;
line.loadCell(x, this._workCell);
const chars = this._workCell.getChars();
let code = this._workCell.getCode();
const i = ((y * terminal.cols) + x) * RENDER_MODEL_INDICIES_PER_CELL;
if (code !== NULL_CELL_CODE) {
this._model.lineLengths[y] = x + 1;
}
// Nothing has changed, no updates needed
if (this._model.cells[i] === code && this._model.cells[i + 1] === attr) {
if (this._model.cells[i] === code &&
this._model.cells[i + RENDER_MODEL_BG_OFFSET] === this._workCell.bg &&
this._model.cells[i + RENDER_MODEL_FG_OFFSET] === this._workCell.fg) {
continue;
}
// Resolve bg and fg and cache in the model
const flags = attr >> 18;
let bg = attr & 0x1ff;
let fg = (attr >> 9) & 0x1ff;
// If inverse flag is on, the foreground should become the background.
if (flags & FLAGS.INVERSE) {
const temp = bg;
bg = fg;
fg = temp;
if (fg === DEFAULT_COLOR) {
fg = INVERTED_DEFAULT_COLOR;
}
if (bg === DEFAULT_COLOR) {
bg = INVERTED_DEFAULT_COLOR;
}
}
const drawInBrightColor = terminal.options.drawBoldTextInBrightColors && !!(flags & FLAGS.BOLD) && fg < 8 && fg !== INVERTED_DEFAULT_COLOR;
fg += drawInBrightColor ? 8 : 0;
// Flag combined chars with a bit mask so they're easily identifiable
if (chars.length > 1) {
code = code | COMBINED_CHAR_BIT_MASK;
}
this._model.cells[i ] = code;
this._model.cells[i + 1] = attr;
this._model.cells[i + 2] = bg;
this._model.cells[i + 3] = fg;
// Cache the results in the model
this._model.cells[i] = code;
this._model.cells[i + RENDER_MODEL_BG_OFFSET] = this._workCell.bg;
this._model.cells[i + RENDER_MODEL_FG_OFFSET] = this._workCell.fg;
this._glyphRenderer.updateCell(x, y, code, attr, bg, fg, chars);
this._glyphRenderer.updateCell(x, y, code, this._workCell.bg, this._workCell.fg, chars);
}
}
this._rectangleRenderer.updateBackgrounds(this._model);
@@ -4,7 +4,7 @@
*/
import { ICharAtlasConfig } from './Types';
import { DEFAULT_COLOR } from 'common/buffer/Constants';
import { Attributes } from 'common/buffer/Constants';
import { Terminal, FontWeight } from 'xterm';
import { IColorSet, IColor } from 'browser/Types';
@@ -21,9 +21,11 @@ export function generateConfig(scaledCharWidth: number, scaledCharHeight: number
cursor: NULL_COLOR,
cursorAccent: NULL_COLOR,
selection: NULL_COLOR,
selectionOpaque: NULL_COLOR,
// For the static char atlas, we only use the first 16 colors, but we need all 256 for the
// dynamic character atlas.
ansi: colors.ansi.slice()
ansi: colors.ansi.slice(),
contrastCache: colors.contrastCache
};
return {
devicePixelRatio: window.devicePixelRatio,
@@ -34,6 +36,8 @@ export function generateConfig(scaledCharWidth: number, scaledCharHeight: number
fontWeight: terminal.getOption('fontWeight') as FontWeight,
fontWeightBold: terminal.getOption('fontWeightBold') as FontWeight,
allowTransparency: terminal.getOption('allowTransparency'),
drawBoldTextInBrightColors: terminal.getOption('drawBoldTextInBrightColors'),
minimumContrastRatio: terminal.getOption('minimumContrastRatio'),
colors: clonedColors
};
}
@@ -52,10 +56,12 @@ export function configEquals(a: ICharAtlasConfig, b: ICharAtlasConfig): boolean
a.allowTransparency === b.allowTransparency &&
a.scaledCharWidth === b.scaledCharWidth &&
a.scaledCharHeight === b.scaledCharHeight &&
a.drawBoldTextInBrightColors === b.drawBoldTextInBrightColors &&
a.minimumContrastRatio === b.minimumContrastRatio &&
a.colors.foreground === b.colors.foreground &&
a.colors.background === b.colors.background;
}
export function is256Color(colorCode: number): boolean {
return colorCode < DEFAULT_COLOR;
return (colorCode & Attributes.CM_MASK) === Attributes.CM_P16 || (colorCode & Attributes.CM_MASK) === Attributes.CM_P256;
}
+2
View File
@@ -25,5 +25,7 @@ export interface ICharAtlasConfig {
scaledCharWidth: number;
scaledCharHeight: number;
allowTransparency: boolean;
drawBoldTextInBrightColors: boolean;
minimumContrastRatio: number;
colors: IColorSet;
}
@@ -4,14 +4,14 @@
*/
import { ICharAtlasConfig } from './Types';
import { DIM_OPACITY, INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants';
import { DIM_OPACITY } from 'browser/renderer/atlas/Constants';
import { IRasterizedGlyph, IBoundingBox, IRasterizedGlyphSet } from '../Types';
import { DEFAULT_COLOR, DEFAULT_ATTR } from 'common/buffer/Constants';
import { is256Color } from './CharAtlasUtils';
import { DEFAULT_COLOR, Attributes } from 'common/buffer/Constants';
import { throwIfFalsy } from '../WebglUtils';
import { IColor } from 'browser/Types';
import { FLAGS } from '../Constants';
import { IDisposable } from 'xterm';
import { AttributeData } from 'common/buffer/AttributeData';
import { toCss, ensureContrastRatioRgba } from 'browser/Color';
// In practice we're probably never going to exhaust a texture this large. For debugging purposes,
// however, it can be useful to set this to a really tiny value, to verify that LRU eviction works.
@@ -68,8 +68,12 @@ export class WebglCharAtlas implements IDisposable {
public hasCanvasChanged = false;
private _workBoundingBox: IBoundingBox = { top: 0, left: 0, bottom: 0, right: 0 };
private _workAttributeData: AttributeData = new AttributeData();
constructor(document: Document, private _config: ICharAtlasConfig) {
constructor(
document: Document,
private _config: ICharAtlasConfig
) {
this.cacheCanvas = document.createElement('canvas');
this.cacheCanvas.width = TEXTURE_WIDTH;
this.cacheCanvas.height = TEXTURE_HEIGHT;
@@ -103,9 +107,11 @@ export class WebglCharAtlas implements IDisposable {
protected _doWarmUp(): void {
// Pre-fill with ASCII 33-126
for (let i = 33; i < 126; i++) {
const rasterizedGlyph = this._drawToCache(i, DEFAULT_ATTR, DEFAULT_COLOR, DEFAULT_COLOR);
const rasterizedGlyph = this._drawToCache(i, DEFAULT_COLOR, DEFAULT_COLOR);
this._cacheMap[i] = {
[DEFAULT_ATTR]: rasterizedGlyph
[DEFAULT_COLOR]: {
[DEFAULT_COLOR]: rasterizedGlyph
}
};
}
}
@@ -123,16 +129,23 @@ export class WebglCharAtlas implements IDisposable {
return false;
}
public getRasterizedGlyphCombinedChar(chars: string, attr: number, bg: number, fg: number): IRasterizedGlyph {
public getRasterizedGlyphCombinedChar(chars: string, bg: number, fg: number): IRasterizedGlyph {
let rasterizedGlyphSet = this._cacheMapCombined[chars];
if (!rasterizedGlyphSet) {
rasterizedGlyphSet = {};
this._cacheMapCombined[chars] = rasterizedGlyphSet;
}
let rasterizedGlyph = rasterizedGlyphSet[attr];
let rasterizedGlyph: IRasterizedGlyph | undefined;
const rasterizedGlyphSetBg = rasterizedGlyphSet[bg];
if (rasterizedGlyphSetBg) {
rasterizedGlyph = rasterizedGlyphSetBg[fg];
}
if (!rasterizedGlyph) {
rasterizedGlyph = this._drawToCache(chars, attr, bg, fg);
rasterizedGlyphSet[attr] = rasterizedGlyph;
rasterizedGlyph = this._drawToCache(chars, bg, fg);
if (!rasterizedGlyphSet[bg]) {
rasterizedGlyphSet[bg] = {};
}
rasterizedGlyphSet[bg]![fg] = rasterizedGlyph;
}
return rasterizedGlyph;
}
@@ -140,16 +153,23 @@ export class WebglCharAtlas implements IDisposable {
/**
* Gets the glyphs texture coords, drawing the texture if it's not already
*/
public getRasterizedGlyph(code: number, attr: number, bg: number, fg: number): IRasterizedGlyph {
public getRasterizedGlyph(code: number, bg: number, fg: number): IRasterizedGlyph {
let rasterizedGlyphSet = this._cacheMap[code];
if (!rasterizedGlyphSet) {
rasterizedGlyphSet = {};
this._cacheMap[code] = rasterizedGlyphSet;
}
let rasterizedGlyph = rasterizedGlyphSet[attr];
let rasterizedGlyph: IRasterizedGlyph | undefined;
const rasterizedGlyphSetBg = rasterizedGlyphSet[bg];
if (rasterizedGlyphSetBg) {
rasterizedGlyph = rasterizedGlyphSetBg[fg];
}
if (!rasterizedGlyph) {
rasterizedGlyph = this._drawToCache(code, attr, bg, fg);
rasterizedGlyphSet[attr] = rasterizedGlyph;
rasterizedGlyph = this._drawToCache(code, bg, fg);
if (!rasterizedGlyphSet[bg]) {
rasterizedGlyphSet[bg] = {};
}
rasterizedGlyphSet[bg]![fg] = rasterizedGlyph;
}
return rasterizedGlyph;
}
@@ -161,48 +181,160 @@ export class WebglCharAtlas implements IDisposable {
return this._config.colors.ansi[idx];
}
private _getBackgroundColor(bg: number): IColor {
private _getBackgroundColor(bgColorMode: number, bgColor: number, inverse: boolean): IColor {
if (this._config.allowTransparency) {
// The background color might have some transparency, so we need to render it as fully
// transparent in the atlas. Otherwise we'd end up drawing the transparent background twice
// around the anti-aliased edges of the glyph, and it would look too dark.
return TRANSPARENT_COLOR;
} else if (bg === INVERTED_DEFAULT_COLOR) {
return this._config.colors.foreground;
} else if (is256Color(bg)) {
return this._getColorFromAnsiIndex(bg);
}
// TODO: Support true color
return this._config.colors.background;
switch (bgColorMode) {
case Attributes.CM_P16:
case Attributes.CM_P256:
return this._getColorFromAnsiIndex(bgColor);
case Attributes.CM_RGB:
const arr = AttributeData.toColorRGB(bgColor);
// TODO: This object creation is slow
return {
rgba: bgColor << 8,
css: `#${toPaddedHex(arr[0])}${toPaddedHex(arr[1])}${toPaddedHex(arr[2])}`
};
case Attributes.CM_DEFAULT:
default:
if (inverse) {
return this._config.colors.foreground;
}
return this._config.colors.background;
}
}
private _getForegroundColor(fg: number): IColor {
if (fg === INVERTED_DEFAULT_COLOR) {
return this._config.colors.background;
} else if (is256Color(fg)) {
return this._getColorFromAnsiIndex(fg);
private _getForegroundCss(bg: number, bgColorMode: number, bgColor: number, fg: number, fgColorMode: number, fgColor: number, inverse: boolean, bold: boolean): string {
const minimumContrastCss = this._getMinimumContrastCss(bg, bgColorMode, bgColor, fg, fgColorMode, fgColor, inverse, bold);
if (minimumContrastCss) {
return minimumContrastCss;
}
switch (fgColorMode) {
case Attributes.CM_P16:
case Attributes.CM_P256:
if (this._config.drawBoldTextInBrightColors && bold && fgColor < 8) {
fgColor += 8;
}
return this._getColorFromAnsiIndex(fgColor).css;
case Attributes.CM_RGB:
const arr = AttributeData.toColorRGB(fgColor);
return toCss(arr[0], arr[1], arr[2]);
case Attributes.CM_DEFAULT:
default:
if (inverse) {
const bg = this._config.colors.background.css;
if (bg.length === 9) {
// Remove bg alpha channel if present
return bg.substr(0, 7);
}
return bg;
}
return this._config.colors.foreground.css;
}
// TODO: Support true color
return this._config.colors.foreground;
}
private _drawToCache(code: number, attr: number, bg: number, fg: number): IRasterizedGlyph;
private _drawToCache(chars: string, attr: number, bg: number, fg: number): IRasterizedGlyph;
private _drawToCache(codeOrChars: number | string, attr: number, bg: number, fg: number): IRasterizedGlyph {
private _resolveBackgroundRgba(bgColorMode: number, bgColor: number, inverse: boolean): number {
switch (bgColorMode) {
case Attributes.CM_P16:
case Attributes.CM_P256:
return this._getColorFromAnsiIndex(bgColor).rgba;
case Attributes.CM_RGB:
return bgColor << 8;
case Attributes.CM_DEFAULT:
default:
if (inverse) {
return this._config.colors.foreground.rgba;
}
return this._config.colors.background.rgba;
}
}
private _resolveForegroundRgba(fgColorMode: number, fgColor: number, inverse: boolean, bold: boolean): number {
switch (fgColorMode) {
case Attributes.CM_P16:
case Attributes.CM_P256:
if (this._config.drawBoldTextInBrightColors && bold && fgColor < 8) {
fgColor += 8;
}
return this._getColorFromAnsiIndex(fgColor).rgba;
case Attributes.CM_RGB:
return fgColor << 8;
case Attributes.CM_DEFAULT:
default:
if (inverse) {
return this._config.colors.background.rgba;
}
return this._config.colors.foreground.rgba;
}
}
private _getMinimumContrastCss(bg: number, bgColorMode: number, bgColor: number, fg: number, fgColorMode: number, fgColor: number, inverse: boolean, bold: boolean): string | undefined {
if (this._config.minimumContrastRatio === 1) {
return undefined;
}
// Try get from cache first
const adjustedColor = this._config.colors.contrastCache.getCss(bg, fg);
if (adjustedColor !== undefined) {
return adjustedColor || undefined;
}
const bgRgba = this._resolveBackgroundRgba(bgColorMode, bgColor, inverse);
const fgRgba = this._resolveForegroundRgba(fgColorMode, fgColor, inverse, bold);
const result = ensureContrastRatioRgba(bgRgba, fgRgba, this._config.minimumContrastRatio);
if (!result) {
this._config.colors.contrastCache.setCss(bg, fg, null);
return undefined;
}
const css = toCss(
(result >> 24) & 0xFF,
(result >> 16) & 0xFF,
(result >> 8) & 0xFF
);
this._config.colors.contrastCache.setCss(bg, fg, css);
return css;
}
private _drawToCache(code: number, bg: number, fg: number): IRasterizedGlyph;
private _drawToCache(chars: string, bg: number, fg: number): IRasterizedGlyph;
private _drawToCache(codeOrChars: number | string, bg: number, fg: number): IRasterizedGlyph {
const chars = typeof codeOrChars === 'number' ? String.fromCharCode(codeOrChars) : codeOrChars;
this.hasCanvasChanged = true;
const flags = attr >> 18;
const bold = !!(flags & FLAGS.BOLD);
const dim = !!(flags & FLAGS.DIM);
const italic = !!(flags & FLAGS.ITALIC);
this._tmpCtx.save();
this._workAttributeData.fg = fg;
this._workAttributeData.bg = bg;
const bold = !!this._workAttributeData.isBold();
const inverse = !!this._workAttributeData.isInverse();
const dim = !!this._workAttributeData.isDim();
const italic = !!this._workAttributeData.isItalic();
let fgColor = this._workAttributeData.getFgColor();
let fgColorMode = this._workAttributeData.getFgColorMode();
let bgColor = this._workAttributeData.getBgColor();
let bgColorMode = this._workAttributeData.getBgColorMode();
if (inverse) {
const temp = fgColor;
fgColor = bgColor;
bgColor = temp;
const temp2 = fgColorMode;
fgColorMode = bgColorMode;
bgColorMode = temp2;
}
// draw the background
const backgroundColor = this._getBackgroundColor(bg);
const backgroundColor = this._getBackgroundColor(bgColorMode, bgColor, inverse);
// Use a 'copy' composite operation to clear any existing glyph out of _tmpCtxWithAlpha, regardless of
// transparency in backgroundColor
this._tmpCtx.globalCompositeOperation = 'copy';
@@ -217,7 +349,7 @@ export class WebglCharAtlas implements IDisposable {
`${fontStyle} ${fontWeight} ${this._config.fontSize * this._config.devicePixelRatio}px ${this._config.fontFamily}`;
this._tmpCtx.textBaseline = 'top';
this._tmpCtx.fillStyle = this._getForegroundColor(fg).css;
this._tmpCtx.fillStyle = this._getForegroundCss(bg, bgColorMode, bgColor, fg, fgColorMode, fgColor, inverse, bold);
// Apply alpha to dim the character
if (dim) {
@@ -398,3 +530,8 @@ function clearColor(imageData: ImageData, color: IColor): boolean {
}
return isEmpty;
}
function toPaddedHex(c: number): string {
const s = c.toString(16);
return s.length < 2 ? '0' + s : s;
}
@@ -4,7 +4,6 @@
*/
import { IRenderLayer } from './Types';
import { IGlyphIdentifier } from '../atlas/Types';
import { acquireCharAtlas } from '../atlas/CharAtlasCache';
import { Terminal } from 'xterm';
import { IColorSet } from 'browser/Types';
@@ -25,19 +24,6 @@ export abstract class BaseRenderLayer implements IRenderLayer {
protected _charAtlas: WebglCharAtlas | undefined;
/**
* An object that's reused when drawing glyphs in order to reduce GC.
*/
private _currentGlyphIdentifier: IGlyphIdentifier = {
chars: '',
code: 0,
bg: 0,
fg: 0,
bold: false,
dim: false,
italic: false
};
constructor(
private _container: HTMLElement,
id: string,
@@ -8,7 +8,8 @@ import { BaseRenderLayer } from './BaseRenderLayer';
import { ICellData } from 'common/Types';
import { CellData } from 'common/buffer/CellData';
import { IColorSet } from 'browser/Types';
import { IRenderDimensions } from 'browser/renderer/Types';
import { IRenderDimensions, IRequestRefreshRowsEvent } from 'browser/renderer/Types';
import { IEventEmitter } from 'common/EventEmitter';
interface ICursorState {
x: number;
@@ -29,7 +30,12 @@ export class CursorRenderLayer extends BaseRenderLayer {
private _cursorBlinkStateManager: CursorBlinkStateManager | undefined;
private _cell: ICellData = new CellData();
constructor(container: HTMLElement, zIndex: number, colors: IColorSet) {
constructor(
container: HTMLElement,
zIndex: number,
colors: IColorSet,
private _onRequestRefreshRowsEvent: IEventEmitter<IRequestRefreshRowsEvent>
) {
super(container, 'cursor', zIndex, true, colors);
this._state = {
x: 0,
@@ -70,14 +76,14 @@ export class CursorRenderLayer extends BaseRenderLayer {
if (this._cursorBlinkStateManager) {
this._cursorBlinkStateManager.pause();
}
terminal.refresh(terminal.buffer.cursorY, terminal.buffer.cursorY);
this._onRequestRefreshRowsEvent.fire({ start: terminal.buffer.cursorY, end: terminal.buffer.cursorY });
}
public onFocus(terminal: Terminal): void {
if (this._cursorBlinkStateManager) {
this._cursorBlinkStateManager.resume(terminal);
} else {
terminal.refresh(terminal.buffer.cursorY, terminal.buffer.cursorY);
this._onRequestRefreshRowsEvent.fire({ start: terminal.buffer.cursorY, end: terminal.buffer.cursorY });
}
}
@@ -89,13 +95,12 @@ export class CursorRenderLayer extends BaseRenderLayer {
});
}
} else {
if (this._cursorBlinkStateManager) {
this._cursorBlinkStateManager.dispose();
}
this._cursorBlinkStateManager?.dispose();
this._cursorBlinkStateManager = undefined;
}
// Request a refresh from the terminal as management of rendering is being
// moved back to the terminal
terminal.refresh(terminal.buffer.cursorY, terminal.buffer.cursorY);
this._onRequestRefreshRowsEvent.fire({ start: terminal.buffer.cursorY, end: terminal.buffer.cursorY });
}
public onCursorMove(terminal: Terminal): void {
@@ -115,7 +120,7 @@ export class CursorRenderLayer extends BaseRenderLayer {
private _render(terminal: Terminal, triggeredByAnimationFrame: boolean): void {
// Don't draw the cursor if it's hidden
// TODO: Need to expose API for this
if (!(terminal as any)._core.cursorState || (terminal as any)._core.cursorHidden) {
if (!(terminal as any)._core._coreService.isCursorInitialized || (terminal as any)._core._coreService.isCursorHidden) {
this._clearCursor();
return;
}
+53 -31
View File
@@ -7,59 +7,78 @@ jobs:
- job: Linux
pool:
vmImage: 'ubuntu-16.04'
variables:
PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: 1
steps:
- task: NodeTool@0
inputs:
versionSpec: '8.x'
displayName: 'Install Node.js'
- task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@2
- task: YarnInstaller@3
inputs:
versionSpec: "1.9.4"
versionSpec: '1.x'
displayName: 'Install Yarn'
- script: |
yarn
- task: CacheBeta@1
inputs:
key: yarn2 | $(Agent.OS) | yarn.lock
path: node_modules
displayName: Cache node modules
- script: yarn --frozen-lockfile
displayName: 'Install dependencies and build'
- script: |
yarn test-unit --forbid-only
yarn test-unit-coverage --forbid-only
./node_modules/.bin/nyc report --reporter=cobertura
displayName: 'Unit tests'
- script: |
yarn lint
- script: yarn lint
displayName: 'Lint'
- task: PublishCodeCoverageResults@1
inputs:
codeCoverageTool: Cobertura
summaryFileLocation: '$(System.DefaultWorkingDirectory)/**/*coverage.xml'
displayName: 'Publish coverage'
- job: macOS
pool:
vmImage: 'xcode9-macos10.13'
variables:
PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: 1
steps:
- task: NodeTool@0
inputs:
versionSpec: '8.x'
displayName: 'Install Node.js'
- script: |
yarn
- task: CacheBeta@1
inputs:
key: yarn2 | $(Agent.OS) | yarn.lock
path: node_modules
displayName: Cache node modules
- script: yarn --frozen-lockfile
displayName: 'Install dependencies and build'
- script: |
yarn test-unit --forbid-only
- script: yarn test-unit --forbid-only
displayName: 'Unit tests'
- script: |
yarn lint
- script: yarn lint
displayName: 'Lint'
- job: Windows
pool:
vmImage: 'vs2017-win2016'
variables:
PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: 1
steps:
- task: NodeTool@0
inputs:
versionSpec: '8.x'
displayName: 'Install Node.js'
- script: |
yarn
- task: CacheBeta@1
inputs:
key: yarn2 | $(Agent.OS) | yarn.lock
path: node_modules
displayName: Cache node modules
- script: yarn --frozen-lockfile
displayName: 'Install dependencies and build'
- script: |
yarn test-unit --forbid-only
- script: yarn test-unit --forbid-only
displayName: 'Unit tests'
- script: |
yarn lint
- script: yarn lint
displayName: 'Lint'
- job: Linux_IntegrationTests
@@ -70,12 +89,11 @@ jobs:
inputs:
versionSpec: '8.x'
displayName: 'Install Node.js'
- task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@2
- task: YarnInstaller@3
inputs:
versionSpec: "1.9.4"
versionSpec: '1.x'
displayName: 'Install Yarn'
- script: |
yarn
- script: yarn --frozen-lockfile
displayName: 'Install dependencies and build'
- script: |
yarn start &
@@ -91,8 +109,7 @@ jobs:
inputs:
versionSpec: '8.x'
displayName: 'Install Node.js'
- script: |
yarn
- script: yarn --frozen-lockfile
displayName: 'Install dependencies and build'
- script: |
yarn start &
@@ -110,18 +127,23 @@ jobs:
condition: and(succeeded(), or(eq(variables['Build.SourceBranch'], 'refs/heads/master'), eq(variables['FORCE_RELEASE'], 'true')))
pool:
vmImage: 'ubuntu-16.04'
variables:
PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: 1
steps:
- task: NodeTool@0
inputs:
versionSpec: '8.x'
displayName: 'Install Node.js'
- task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@2
- task: YarnInstaller@3
inputs:
versionSpec: "1.9.4"
versionSpec: '1.x'
displayName: 'Install Yarn'
- script: |
yarn
- task: CacheBeta@1
inputs:
key: yarn2 | $(Agent.OS) | yarn.lock
path: node_modules
displayName: Cache node modules
- script: yarn --frozen-lockfile
displayName: 'Install dependencies and build'
- script: |
NPM_AUTH_TOKEN="$(NPM_AUTH_TOKEN)" node ./bin/publish.js
- script: NPM_AUTH_TOKEN="$(NPM_AUTH_TOKEN)" node ./bin/publish.js
displayName: 'Package and publish to npm'
+56
View File
@@ -0,0 +1,56 @@
/**
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
* @license MIT
*
* Script to initialize addon packages under "addons/" with outer deps.
*/
const path = require('path');
const cp = require('child_process');
const fs = require('fs');
const PACKAGE_ROOT = path.join(__dirname, '..');
// install addon deps
const addonsPath = path.join(PACKAGE_ROOT, 'addons');
if (fs.existsSync(addonsPath)) {
console.log('pulling addon dependencies...');
// whether to use yarn or npm
let hasYarn = false;
try {
cp.execSync('yarn --version').toString();
hasYarn = true;
} catch(e) {}
// walk all addon folders
fs.readdir(addonsPath, (err, files) => {
files.forEach(folder => {
const addonPath = path.join(addonsPath, folder);
// install only if there are dependencies listed
let packageJson;
try {
packageJson = require(path.join(addonPath, 'package.json'));
} catch (e) {
// swallow as changing branches can leave folders around
}
if (packageJson
&& (
(packageJson.devDependencies && Object.keys(packageJson.devDependencies).length)
|| (packageJson.dependencies && Object.keys(packageJson.dependencies).length)
)
)
{
console.log('Preparing', folder);
if (hasYarn) {
cp.execSync('yarn', {cwd: addonPath});
} else {
cp.execSync('npm install', {cwd: addonPath});
}
} else {
console.log('Skipped', folder);
}
});
});
}
+3 -3
View File
@@ -97,7 +97,7 @@ function getNextBetaVersion(packageJson) {
const nextStableVersion = `${stableVersion[0]}.${parseInt(stableVersion[1]) + 1}.0`;
const publishedVersions = getPublishedVersions(packageJson, nextStableVersion, tag);
if (publishedVersions.length === 0) {
return `${nextStableVersion}-${tag}1`;
return `${nextStableVersion}-${tag}.1`;
}
const latestPublishedVersion = publishedVersions.sort((a, b) => {
const aVersion = parseInt(a.substr(a.search(/[0-9]+$/)));
@@ -105,14 +105,14 @@ function getNextBetaVersion(packageJson) {
return aVersion > bVersion ? -1 : 1;
})[0];
const latestTagVersion = parseInt(latestPublishedVersion.substr(latestPublishedVersion.search(/[0-9]+$/)), 10);
return `${nextStableVersion}-${tag}${latestTagVersion + 1}`;
return `${nextStableVersion}-${tag}.${latestTagVersion + 1}`;
}
function getPublishedVersions(packageJson, 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.filter(v => !v.search(new RegExp(`${version}-${tag}.[0-9]+`)));
}
return versionsJson;
}
+27 -1
View File
@@ -6,6 +6,8 @@
const cp = require('child_process');
const path = require('path');
const COVERAGE_LINES_THRESHOLD = 60;
// Add `out` to the NODE_PATH so absolute paths can be resolved.
const env = { ...process.env };
env.NODE_PATH = path.resolve(__dirname, '../out');
@@ -28,8 +30,28 @@ if (process.argv.length > 2) {
}
}
const checkCoverage = flagArgs.indexOf('--coverage') >= 0;
if (checkCoverage) {
flagArgs.splice(flagArgs.indexOf('--coverage'), 1);
const executable = npmBinScript('nyc');
const args = ['--check-coverage', `--lines=${COVERAGE_LINES_THRESHOLD}`, npmBinScript('mocha'), ...testFiles, ...flagArgs];
console.info('executable', executable);
console.info('args', args);
const run = cp.spawnSync(
executable,
args,
{
cwd: path.resolve(__dirname, '..'),
env,
stdio: 'inherit'
}
);
process.exit(run.status);
}
const run = cp.spawnSync(
path.resolve(__dirname, '../node_modules/.bin/mocha'),
npmBinScript('mocha'),
[...testFiles, ...flagArgs],
{
cwd: path.resolve(__dirname, '..'),
@@ -38,4 +60,8 @@ const run = cp.spawnSync(
}
);
function npmBinScript(script) {
return path.resolve(__dirname, `../node_modules/.bin/` + (process.platform === 'win32' ? `${script}.cmd` : script));
}
process.exit(run.status);
+76 -13
View File
@@ -39,13 +39,39 @@ export interface IWindowWithTerminal extends Window {
declare let window: IWindowWithTerminal;
let term;
let fitAddon: FitAddon;
let searchAddon: SearchAddon;
let protocol;
let socketURL;
let socket;
let pid;
type AddonType = 'attach' | 'fit' | 'search' | 'web-links' | 'webgl';
interface IDemoAddon<T extends AddonType> {
name: T;
canChange: boolean;
ctor:
T extends 'attach' ? typeof AttachAddon :
T extends 'fit' ? typeof FitAddon :
T extends 'search' ? typeof SearchAddon :
T extends 'web-links' ? typeof WebLinksAddon :
typeof WebglAddon;
instance?:
T extends 'attach' ? AttachAddon :
T extends 'fit' ? FitAddon :
T extends 'search' ? SearchAddon :
T extends 'web-links' ? WebLinksAddon :
T extends 'webgl' ? WebglAddon :
never;
}
const addons: { [T in AddonType]: IDemoAddon<T>} = {
attach: { name: 'attach', ctor: AttachAddon, canChange: false },
fit: { name: 'fit', ctor: FitAddon, canChange: false },
search: { name: 'search', ctor: SearchAddon, canChange: true },
'web-links': { name: 'web-links', ctor: WebLinksAddon, canChange: true },
webgl: { name: 'webgl', ctor: WebglAddon, canChange: true }
};
const terminalContainer = document.getElementById('terminal-container');
const actionElements = {
findNext: <HTMLInputElement>document.querySelector('#find-next'),
@@ -92,7 +118,6 @@ if (document.location.pathname === '/test') {
} else {
createTerminal();
document.getElementById('dispose').addEventListener('click', disposeRecreateButtonHandler);
document.getElementById('webgl').addEventListener('click', () => term.loadAddon(new WebglAddon()));
}
function createTerminal(): void {
@@ -108,11 +133,12 @@ function createTerminal(): void {
// Load addons
const typedTerm = term as TerminalType;
typedTerm.loadAddon(new WebLinksAddon());
searchAddon = new SearchAddon();
typedTerm.loadAddon(searchAddon);
fitAddon = new FitAddon();
typedTerm.loadAddon(fitAddon);
addons['web-links'].instance = new WebLinksAddon();
addons.search.instance = new SearchAddon();
addons.fit.instance = new FitAddon();
typedTerm.loadAddon(addons['web-links'].instance);
typedTerm.loadAddon(addons.search.instance);
typedTerm.loadAddon(addons.fit.instance);
window.term = term; // Expose `term` to window for debugging purposes
term.onResize((size: { cols: number, rows: number }) => {
@@ -129,17 +155,17 @@ function createTerminal(): void {
socketURL = protocol + location.hostname + ((location.port) ? (':' + location.port) : '') + '/terminals/';
term.open(terminalContainer);
fitAddon.fit();
addons.fit.instance!.fit();
term.focus();
addDomListener(paddingElement, 'change', setPadding);
addDomListener(actionElements.findNext, 'keyup', (e) => {
searchAddon.findNext(actionElements.findNext.value, getSearchOptions(e));
addons.search.instance.findNext(actionElements.findNext.value, getSearchOptions(e));
});
addDomListener(actionElements.findPrevious, 'keyup', (e) => {
searchAddon.findPrevious(actionElements.findPrevious.value, getSearchOptions(e));
addons.search.instance.findPrevious(actionElements.findPrevious.value, getSearchOptions(e));
});
// fit is called within a setTimeout, cols and rows need this.
@@ -167,8 +193,10 @@ function createTerminal(): void {
}
function runRealTerminal(): void {
term.loadAddon(new AttachAddon(socket));
addons.attach.instance = new AttachAddon(socket);
term.loadAddon(addons.attach.instance);
term._initialized = true;
initAddons(term);
}
function runFakeTerminal(): void {
@@ -177,6 +205,7 @@ function runFakeTerminal(): void {
}
term._initialized = true;
initAddons(term);
term.prompt = () => {
term.write('\r\n$ ');
@@ -300,6 +329,40 @@ function initOptions(term: TerminalType): void {
});
}
function initAddons(term: TerminalType): void {
const fragment = document.createDocumentFragment();
Object.keys(addons).forEach((name: AddonType) => {
const addon = addons[name];
const checkbox = document.createElement('input') as HTMLInputElement;
checkbox.type = 'checkbox';
checkbox.checked = !!addon.instance;
if (!addon.canChange) {
checkbox.disabled = true;
}
checkbox.addEventListener('change', () => {
if (checkbox.checked) {
addon.instance = new addon.ctor();
term.loadAddon(addon.instance);
} else {
addon.instance!.dispose();
addon.instance = undefined;
}
});
const label = document.createElement('label');
label.classList.add('addon');
if (!addon.canChange) {
label.title = 'This addon is needed for the demo to operate';
}
label.appendChild(checkbox);
label.appendChild(document.createTextNode(name));
const wrapper = document.createElement('div');
wrapper.classList.add('addon');
wrapper.appendChild(label);
fragment.appendChild(wrapper);
});
document.getElementById('addons-container').appendChild(fragment);
}
function addDomListener(element: HTMLElement, type: string, handler: (...args: any[]) => any): void {
element.addEventListener(type, handler);
term._core.register({ dispose: () => element.removeEventListener(type, handler) });
@@ -312,5 +375,5 @@ function updateTerminalSize(): void {
const height = (rows * term._core._renderService.dimensions.actualCellHeight).toString() + 'px';
terminalContainer.style.width = width;
terminalContainer.style.height = height;
fitAddon.fit();
addons.fit.instance.fit();
}
+5 -1
View File
@@ -26,6 +26,11 @@
<p>These options can be set in the <code>Terminal</code> constructor or using the <code>Terminal.setOption</code> function.</p>
<div id="options-container"></div>
</div>
<div>
<h3>Addons</h3>
<p>Addons can be loaded and unloaded on a particular terminal to extend its functionality.</p>
<div id="addons-container"></div>
</div>
<div>
<h3>Style</h3>
<div style="display: inline-block; margin-right: 16px;">
@@ -36,7 +41,6 @@
<hr/>
<p><strong>Attention:</strong> The demo is a barebones implementation and is designed for the development and evaluation of xterm.js only. Exposing the demo to the public as is would introduce security risks for the host.</p>
<button id="dispose" title="This is used to testing memory leaks">Dispose terminal</button>
<button id="webgl">Enable WebGL Renderer</button>
<script src="dist/client-bundle.js" defer ></script>
</body>
</html>
+10 -4
View File
@@ -1,7 +1,7 @@
{
"name": "xterm",
"description": "Full xterm terminal, in your browser",
"version": "4.2.0",
"version": "4.3.0",
"main": "lib/xterm.js",
"style": "css/xterm.css",
"types": "typings/xterm.d.ts",
@@ -11,13 +11,16 @@
"prepackage": "npm run build",
"package": "webpack",
"start": "node demo/start",
"lint": "tslint 'src/**/*.ts' 'addons/**/*.ts'",
"lint": "tslint 'src/**/*.ts' 'addons/*/src/**/*.ts'",
"test": "npm run test-unit",
"posttest": "npm run lint",
"test-api": "mocha \"**/*.api.js\"",
"test-unit": "node ./bin/test.js",
"test-unit-coverage": "node ./bin/test.js --coverage",
"build": "tsc -b ./tsconfig.all.json",
"prepare": "npm run build",
"prepare": "npm run setup",
"setup": "npm run build",
"presetup": "node ./bin/install-addons.js",
"prepublishOnly": "npm run package",
"watch": "tsc -b -w ./tsconfig.all.json --preserveWatchOutput",
"benchmark": "NODE_PATH=./out xterm-benchmark -r 5 -c test/benchmark/benchmark.json",
@@ -27,6 +30,7 @@
},
"devDependencies": {
"@types/chai": "^3.4.34",
"@types/deep-equal": "^1.0.1",
"@types/glob": "^5.0.35",
"@types/jsdom": "11.0.1",
"@types/mocha": "^2.2.33",
@@ -36,18 +40,20 @@
"@types/webpack": "^4.4.11",
"@types/ws": "^6.0.1",
"chai": "3.5.0",
"deep-equal": "^1.1.0",
"express": "^4.17.1",
"express-ws": "^4.0.0",
"glob": "^7.0.5",
"jsdom": "^11.11.0",
"mocha": "^6.1.4",
"node-pty": "0.7.6",
"nyc": "13",
"puppeteer": "^1.15.0",
"source-map-loader": "^0.2.4",
"ts-loader": "^6.0.4",
"tslint": "^5.18.0",
"tslint-consistent-codestyle": "^1.13.0",
"typescript": "3.6",
"typescript": "3.7",
"utf8": "^3.0.0",
"webpack": "^4.35.3",
"webpack-cli": "^3.1.0",
+13 -3
View File
@@ -347,6 +347,9 @@ export class InputHandler extends Disposable implements IInputHandler {
}
}
// Clear the dirty row service so we know which lines changed as a result of parsing
this._dirtyRowService.clearRange();
// process big data in smaller chunks
if (data.length > MAX_PARSEBUFFER_LENGTH) {
for (let i = 0; i < data.length; i += MAX_PARSEBUFFER_LENGTH) {
@@ -367,6 +370,8 @@ export class InputHandler extends Disposable implements IInputHandler {
if (buffer.x !== cursorStartX || buffer.y !== cursorStartY) {
this._onCursorMove.fire();
}
// Refresh any dirty rows accumulated as part of parsing
this._terminal.refresh(this._dirtyRowService.start, this._dirtyRowService.end);
}
@@ -541,6 +546,7 @@ export class InputHandler extends Disposable implements IInputHandler {
// make buffer local for faster access
const buffer = this._bufferService.buffer;
this._dirtyRowService.markDirty(buffer.y);
if (this._optionsService.options.convertEol) {
buffer.x = 0;
}
@@ -555,6 +561,7 @@ export class InputHandler extends Disposable implements IInputHandler {
if (buffer.x >= this._bufferService.cols) {
buffer.x--;
}
this._dirtyRowService.markDirty(buffer.y);
this._onLineFeed.fire();
}
@@ -619,12 +626,14 @@ export class InputHandler extends Disposable implements IInputHandler {
this._bufferService.buffer.y = this._terminal.originMode
? Math.min(this._bufferService.buffer.scrollBottom, Math.max(this._bufferService.buffer.scrollTop, this._bufferService.buffer.y))
: Math.min(this._bufferService.rows - 1, Math.max(0, this._bufferService.buffer.y));
this._dirtyRowService.markDirty(this._bufferService.buffer.y);
}
/**
* Set absolute cursor position.
*/
private _setCursor(x: number, y: number): void {
this._dirtyRowService.markDirty(this._bufferService.buffer.y);
if (this._terminal.originMode) {
this._bufferService.buffer.x = x;
this._bufferService.buffer.y = this._bufferService.buffer.scrollTop + y;
@@ -633,6 +642,7 @@ export class InputHandler extends Disposable implements IInputHandler {
this._bufferService.buffer.y = y;
}
this._restrictCursor();
this._dirtyRowService.markDirty(this._bufferService.buffer.y);
}
/**
@@ -1444,7 +1454,7 @@ export class InputHandler extends Disposable implements IInputHandler {
this._logService.debug('DECSET 1015 not supported (see #2507)');
break;
case 25: // show cursor
this._terminal.cursorHidden = false;
this._coreService.isCursorHidden = false;
break;
case 1048: // alt screen cursor
this.saveCursor();
@@ -1615,7 +1625,7 @@ export class InputHandler extends Disposable implements IInputHandler {
this._logService.debug('DECRST 1015 not supported (see #2507)');
break;
case 25: // hide cursor
this._terminal.cursorHidden = true;
this._coreService.isCursorHidden = true;
break;
case 1048: // alt screen cursor
this.restoreCursor();
@@ -1949,7 +1959,7 @@ export class InputHandler extends Disposable implements IInputHandler {
* http://vt100.net/docs/vt220-rm/table4-10.html
*/
public softReset(params: IParams): void {
this._terminal.cursorHidden = false;
this._coreService.isCursorHidden = false;
this._terminal.insertMode = false;
this._terminal.originMode = false;
this._terminal.wraparoundMode = true; // defaults: xterm - true, vt100 - false
-11
View File
@@ -163,17 +163,6 @@ describe('Terminal', () => {
});
});
describe('reset', () => {
it('should not affect cursorState', () => {
term.cursorState = 1;
term.reset();
assert.equal(term.cursorState, 1);
term.cursorState = 0;
term.reset();
assert.equal(term.cursorState, 0);
});
});
describe('clear', () => {
it('should clear a buffer equal to rows', () => {
const promptLine = term.buffer.lines.get(term.buffer.ybase + term.buffer.y);
+47 -84
View File
@@ -28,7 +28,7 @@ import { Viewport } from 'browser/Viewport';
import { rightClickHandler, moveTextAreaUnderMouseCursor, handlePasteEvent, copyHandler, paste } from 'browser/Clipboard';
import { C0 } from 'common/data/EscapeSequences';
import { InputHandler } from './InputHandler';
import { Renderer } from './renderer/Renderer';
import { Renderer } from 'browser/renderer/Renderer';
import { Linkifier } from 'browser/Linkifier';
import { SelectionService } from 'browser/services/SelectionService';
import * as Browser from 'common/Platform';
@@ -38,17 +38,17 @@ import { SoundService } from 'browser/services/SoundService';
import { MouseZoneManager } from 'browser/MouseZoneManager';
import { AccessibilityManager } from './AccessibilityManager';
import { ITheme, IMarker, IDisposable, ISelectionPosition } from 'xterm';
import { DomRenderer } from './renderer/dom/DomRenderer';
import { DomRenderer } from 'browser/renderer/dom/DomRenderer';
import { IKeyboardEvent, KeyboardResultType, ICharset, IBufferLine, IAttributeData, CoreMouseEventType, CoreMouseButton, CoreMouseAction } from 'common/Types';
import { evaluateKeyboardEvent } from 'common/input/Keyboard';
import { EventEmitter, IEvent } from 'common/EventEmitter';
import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine';
import { applyWindowsMode } from './WindowsMode';
import { handleWindowsModeLineFeed } from 'common/WindowsMode';
import { ColorManager } from 'browser/ColorManager';
import { RenderService } from 'browser/services/RenderService';
import { IOptionsService, IBufferService, ICoreMouseService, ICoreService, ILogService, IDirtyRowService, IInstantiationService } from 'common/services/Services';
import { OptionsService } from 'common/services/OptionsService';
import { ICharSizeService, IRenderService, IMouseService, ISelectionService, ISoundService } from 'browser/services/Services';
import { ICharSizeService, IRenderService, IMouseService, ISelectionService, ISoundService, ICoreBrowserService } from 'browser/services/Services';
import { CharSizeService } from 'browser/services/CharSizeService';
import { BufferService, MINIMUM_COLS, MINIMUM_ROWS } from 'common/services/BufferService';
import { Disposable } from 'common/Lifecycle';
@@ -63,6 +63,7 @@ import { DirtyRowService } from 'common/services/DirtyRowService';
import { InstantiationService } from 'common/services/InstantiationService';
import { CoreMouseService } from 'common/services/CoreMouseService';
import { WriteBuffer } from 'common/input/WriteBuffer';
import { CoreBrowserService } from 'browser/services/CoreBrowserService';
// Let it work inside Node.js for automated testing purposes.
const document = (typeof window !== 'undefined') ? window.document : null;
@@ -90,10 +91,6 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
// TODO: We should remove options once components adopt optionsService
public get options(): ITerminalOptions { return this.optionsService.options; }
// TODO: This can be changed to an enum or boolean, 0 and 1 seem to be the only options
public cursorState: number;
public cursorHidden: boolean;
private _customKeyEventHandler: CustomKeyEventHandler;
// common services
@@ -172,6 +169,8 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
public get onCursorMove(): IEvent<void> { return this._onCursorMove.event; }
private _onData = new EventEmitter<string>();
public get onData(): IEvent<string> { return this._onData.event; }
private _onBinary = new EventEmitter<string>();
public get onBinary(): IEvent<string> { return this._onBinary.event; }
private _onKey = new EventEmitter<{ key: string, domEvent: KeyboardEvent }>();
public get onKey(): IEvent<{ key: string, domEvent: KeyboardEvent }> { return this._onKey.event; }
private _onLineFeed = new EventEmitter<void>();
@@ -224,6 +223,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
this._coreService = this._instantiationService.createInstance(CoreService, () => this.scrollToBottom());
this._instantiationService.setService(ICoreService, this._coreService);
this._coreService.onData(e => this._onData.fire(e));
this._coreService.onBinary(e => this._onBinary.fire(e));
this._coreMouseService = this._instantiationService.createInstance(CoreMouseService);
this._instantiationService.setService(ICoreMouseService, this._coreMouseService);
this._dirtyRowService = this._instantiationService.createInstance(DirtyRowService);
@@ -240,25 +240,17 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
return;
}
super.dispose();
if (this._windowsMode) {
this._windowsMode.dispose();
this._windowsMode = undefined;
}
if (this._renderService) {
this._renderService.dispose();
}
this._windowsMode?.dispose();
this._windowsMode = undefined;
this._renderService?.dispose();
this._customKeyEventHandler = null;
this.write = () => {};
if (this.element && this.element.parentNode) {
this.element.parentNode.removeChild(this.element);
}
this.element?.parentNode?.removeChild(this.element);
}
private _setup(): void {
this._parent = document ? document.body : null;
this.cursorState = 0;
this.cursorHidden = false;
this._customKeyEventHandler = null;
// modes
@@ -292,7 +284,13 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
this.linkifier = this.linkifier || new Linkifier(this._bufferService, this._logService);
if (this.options.windowsMode) {
this._windowsMode = applyWindowsMode(this);
this._enableWindowsMode();
}
}
private _enableWindowsMode(): void {
if (!this._windowsMode) {
this._windowsMode = this.onLineFeed(handleWindowsModeLineFeed.bind(null, this._bufferService));
}
}
@@ -325,10 +323,6 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
}
}
public get isFocused(): boolean {
return document.activeElement === this.textarea && document.hasFocus();
}
private _setupOptionsListeners(): void {
// TODO: These listeners should be owned by individual components
this.optionsService.onOptionChange(key => {
@@ -336,18 +330,15 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
case 'fontFamily':
case 'fontSize':
// When the font changes the size of the cells may change which requires a renderer clear
if (this._renderService) {
this._renderService.clear();
}
if (this._charSizeService) {
this._charSizeService.measure();
}
this._renderService?.clear();
this._charSizeService?.measure();
break;
case 'drawBoldTextInBrightColors':
case 'letterSpacing':
case 'lineHeight':
case 'fontWeight':
case 'fontWeightBold':
case 'minimumContrastRatio':
// When the font changes the size of the cells may change which requires a renderer clear
if (this._renderService) {
this._renderService.clear();
@@ -363,9 +354,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
break;
case 'scrollback':
this.buffers.resize(this.cols, this.rows);
if (this.viewport) {
this.viewport.syncScrollArea();
}
this.viewport?.syncScrollArea();
break;
case 'screenReaderMode':
if (this.optionsService.options.screenReaderMode) {
@@ -373,10 +362,8 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
this._accessibilityManager = new AccessibilityManager(this, this._renderService);
}
} else {
if (this._accessibilityManager) {
this._accessibilityManager.dispose();
this._accessibilityManager = null;
}
this._accessibilityManager?.dispose();
this._accessibilityManager = null;
}
break;
case 'tabStopWidth': this.buffers.setupTabStops(); break;
@@ -385,14 +372,10 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
break;
case 'windowsMode':
if (this.optionsService.options.windowsMode) {
if (!this._windowsMode) {
this._windowsMode = applyWindowsMode(this);
}
this._enableWindowsMode();
} else {
if (this._windowsMode) {
this._windowsMode.dispose();
this._windowsMode = undefined;
}
this._windowsMode?.dispose();
this._windowsMode = undefined;
}
break;
}
@@ -553,6 +536,9 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
this.register(addDisposableDomListener(this.textarea, 'blur', () => this._onTextAreaBlur()));
this._helperContainer.appendChild(this.textarea);
const coreBrowserService = this._instantiationService.createInstance(CoreBrowserService, this.textarea);
this._instantiationService.setService(ICoreBrowserService, coreBrowserService);
this._charSizeService = this._instantiationService.createInstance(CharSizeService, this._document, this._helperContainer);
this._instantiationService.setService(ICharSizeService, this._charSizeService);
@@ -567,6 +553,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
this._theme = this.options.theme || this._theme;
this.options.theme = undefined;
this._colorManager = new ColorManager(document, this.options.allowTransparency);
this.optionsService.onOptionChange(e => this._colorManager.onOptionsChange(e));
this._colorManager.setTheme(this._theme);
const renderer = this._createRenderer();
@@ -653,8 +640,8 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
private _createRenderer(): IRenderer {
switch (this.options.rendererType) {
case 'canvas': return new Renderer(this._colorManager.colors, this, this._bufferService, this._charSizeService, this.optionsService);
case 'dom': return new DomRenderer(this, this._colorManager.colors, this._charSizeService, this.optionsService);
case 'canvas': return this._instantiationService.createInstance(Renderer, this._colorManager.colors, this.screenElement, this.linkifier);
case 'dom': return this._instantiationService.createInstance(DomRenderer, this._colorManager.colors, this.element, this.screenElement, this._viewportElement, this.linkifier);
default: throw new Error(`Unrecognized rendererType "${this.options.rendererType}"`);
}
}
@@ -665,15 +652,9 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
*/
private _setTheme(theme: ITheme): void {
this._theme = theme;
if (this._colorManager) {
this._colorManager.setTheme(theme);
}
if (this._renderService) {
this._renderService.setColors(this._colorManager.colors);
}
if (this.viewport) {
this.viewport.onThemeChange(this._colorManager.colors);
}
this._colorManager?.setTheme(theme);
this._renderService?.setColors(this._colorManager.colors);
this.viewport?.onThemeChange(this._colorManager.colors);
}
/**
@@ -940,9 +921,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
* @param end The row to end at (between start and this.rows - 1).
*/
public refresh(start: number, end: number): void {
if (this._renderService) {
this._renderService.refreshRows(start, end);
}
this._renderService?.refreshRows(start, end);
}
/**
@@ -951,9 +930,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
* @param end The row to end at (between start and this.rows - 1).
*/
private _queueLinkification(start: number, end: number): void {
if (this.linkifier) {
this.linkifier.linkifyRows(start, end);
}
this.linkifier?.linkifyRows(start, end);
}
/**
@@ -971,8 +948,8 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
* Display the cursor element
*/
public showCursor(): void {
if (!this.cursorState) {
this.cursorState = 1;
if (!this._coreService.isCursorInitialized) {
this._coreService.isCursorInitialized = true;
this.refresh(this.buffer.y, this.buffer.y);
}
}
@@ -1233,24 +1210,18 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
* Clears the current terminal selection.
*/
public clearSelection(): void {
if (this._selectionService) {
this._selectionService.clearSelection();
}
this._selectionService?.clearSelection();
}
/**
* Selects all text within the terminal.
*/
public selectAll(): void {
if (this._selectionService) {
this._selectionService.selectAll();
}
this._selectionService?.selectAll();
}
public selectLines(start: number, end: number): void {
if (this._selectionService) {
this._selectionService.selectLines(start, end);
}
this._selectionService?.selectLines(start, end);
}
/**
@@ -1457,9 +1428,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
this._bufferService.resize(x, y);
this.buffers.setupTabStops(this.cols);
if (this._charSizeService) {
this._charSizeService.measure();
}
this._charSizeService?.measure();
// Sync the scroll area to make sure scroll events don't fire and scroll the viewport to an
// invalid location
@@ -1544,28 +1513,22 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
this.options.cols = this.cols;
const customKeyEventHandler = this._customKeyEventHandler;
const inputHandler = this._inputHandler;
const cursorState = this.cursorState;
const userScrolling = this._userScrolling;
this._setup();
this._bufferService.reset();
this._coreService.reset();
this._coreMouseService.reset();
if (this._selectionService) {
this._selectionService.reset();
}
this._selectionService?.reset();
// reattach
this._customKeyEventHandler = customKeyEventHandler;
this._inputHandler = inputHandler;
this.cursorState = cursorState;
this._userScrolling = userScrolling;
// do a full screen refresh
this.refresh(0, this.rows - 1);
if (this.viewport) {
this.viewport.syncScrollArea();
}
this.viewport?.syncScrollArea();
}
// TODO: Remove cancel function and cancelEvents option
+7 -2
View File
@@ -34,7 +34,6 @@ if (os.platform() === 'darwin') {
// filter skipFilenames
const FILES = TESTFILES.filter(value => SKIP_FILES.indexOf(value.split('/').slice(-1)[0]) === -1);
describe('Escape Sequence Files', function(): void {
this.timeout(1000);
@@ -44,6 +43,9 @@ describe('Escape Sequence Files', function(): void {
let customHandler: IDisposable | undefined;
before(() => {
if (process.platform === 'win32') {
return;
}
ptyTerm = (pty as any).open({cols: COLS, rows: ROWS});
slaveEnd = ptyTerm._slave;
term = new Terminal({cols: COLS, rows: ROWS});
@@ -51,12 +53,15 @@ describe('Escape Sequence Files', function(): void {
});
after(() => {
if (process.platform === 'win32') {
return;
}
ptyTerm._master.end();
ptyTerm._master.destroy();
});
FILES.forEach(filename => {
it(filename.split('/').slice(-1)[0], async () => {
(process.platform === 'win32' ? it.skip : it)(filename.split('/').slice(-1)[0], async () => {
// reset terminal and handler
if (customHandler) {
customHandler.dispose();
+3 -1
View File
@@ -3,7 +3,7 @@
* @license MIT
*/
import { IRenderer, IRenderDimensions, CharacterJoinerHandler } from 'browser/renderer/Types';
import { IRenderer, IRenderDimensions, CharacterJoinerHandler, IRequestRefreshRowsEvent } from 'browser/renderer/Types';
import { IInputHandlingTerminal, ICompositionHelper, ITerminal, IBrowser, ITerminalOptions } from './Types';
import { IBuffer, IBufferStringIterator, IBufferSet } from 'common/buffer/Types';
import { IBufferLine, ICellData, IAttributeData, ICircularList, XtermListener, ICharset, CoreMouseEventType } from 'common/Types';
@@ -32,6 +32,7 @@ export class MockTerminal implements ITerminal {
onLineFeed: IEvent<void>;
onSelectionChange: IEvent<void>;
onData: IEvent<string>;
onBinary: IEvent<string>;
onTitleChange: IEvent<string>;
onScroll: IEvent<number>;
onKey: IEvent<{ key: string; domEvent: KeyboardEvent; }>;
@@ -362,6 +363,7 @@ export class MockBuffer implements IBuffer {
}
export class MockRenderer implements IRenderer {
onRequestRefreshRows: IEvent<IRequestRefreshRowsEvent>;
onCanvasResize: IEvent<{ width: number; height: number; }>;
onRender: IEvent<{ start: number; end: number; }>;
dispose(): void {
+1 -4
View File
@@ -38,7 +38,6 @@ export interface IInputHandlingTerminal {
savedCols: number;
mouseEvents: CoreMouseEventType;
sendFocus: boolean;
cursorHidden: boolean;
buffers: IBufferSet;
buffer: IBuffer;
@@ -154,11 +153,8 @@ export interface IInputHandler {
export interface ITerminal extends IPublicTerminal, IElementAccessor, IBufferAccessor, ILinkifierAccessor {
screenElement: HTMLElement;
browser: IBrowser;
cursorHidden: boolean;
cursorState: number;
buffer: IBuffer;
buffers: IBufferSet;
isFocused: boolean;
viewport: IViewport;
bracketedPasteMode: boolean;
optionsService: IOptionsService;
@@ -184,6 +180,7 @@ export interface IPublicTerminal extends IDisposable {
markers: IMarker[];
onCursorMove: IEvent<void>;
onData: IEvent<string>;
onBinary: IEvent<string>;
onKey: IEvent<{ key: string, domEvent: KeyboardEvent }>;
onLineFeed: IEvent<void>;
onScroll: IEvent<number>;
+6 -6
View File
@@ -82,12 +82,12 @@ export function moveTextAreaUnderMouseCursor(ev: MouseEvent, textarea: HTMLTextA
// 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;
textarea.style.position = '';
textarea.style.width = '';
textarea.style.height = '';
textarea.style.left = '';
textarea.style.top = '';
textarea.style.zIndex = '';
}, 200);
}
+267
View File
@@ -0,0 +1,267 @@
/**
* Copyright (c) 2017 The xterm.js authors. All rights reserved.
* @license MIT
*/
import { assert } from 'chai';
import { blend, fromCss, toPaddedHex, toCss, toRgba, fromRgba, opaque, rgbRelativeLuminance, contrastRatio, ensureContrastRatioRgba } from 'browser/Color';
describe('Color', () => {
describe('blend', () => {
it('should blend colors based on the alpha channel', () => {
assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFF00', rgba: 0xFFFFFF00 }), { css: '#000000', rgba: 0x000000FF });
assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFF10', rgba: 0xFFFFFF10 }), { css: '#101010', rgba: 0x101010FF });
assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFF20', rgba: 0xFFFFFF20 }), { css: '#202020', rgba: 0x202020FF });
assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFF30', rgba: 0xFFFFFF30 }), { css: '#303030', rgba: 0x303030FF });
assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFF40', rgba: 0xFFFFFF40 }), { css: '#404040', rgba: 0x404040FF });
assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFF50', rgba: 0xFFFFFF50 }), { css: '#505050', rgba: 0x505050FF });
assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFF60', rgba: 0xFFFFFF60 }), { css: '#606060', rgba: 0x606060FF });
assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFF70', rgba: 0xFFFFFF70 }), { css: '#707070', rgba: 0x707070FF });
assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFF80', rgba: 0xFFFFFF80 }), { css: '#808080', rgba: 0x808080FF });
assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFF90', rgba: 0xFFFFFF90 }), { css: '#909090', rgba: 0x909090FF });
assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFFA0', rgba: 0xFFFFFFA0 }), { css: '#a0a0a0', rgba: 0xA0A0A0FF });
assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFFB0', rgba: 0xFFFFFFB0 }), { css: '#b0b0b0', rgba: 0xB0B0B0FF });
assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFFC0', rgba: 0xFFFFFFC0 }), { css: '#c0c0c0', rgba: 0xC0C0C0FF });
assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFFD0', rgba: 0xFFFFFFD0 }), { css: '#d0d0d0', rgba: 0xD0D0D0FF });
assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFFE0', rgba: 0xFFFFFFE0 }), { css: '#e0e0e0', rgba: 0xE0E0E0FF });
assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFFF0', rgba: 0xFFFFFFF0 }), { css: '#f0f0f0', rgba: 0xF0F0F0FF });
assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFFFF', rgba: 0xFFFFFFFF }), { css: '#FFFFFFFF', rgba: 0xFFFFFFFF });
});
});
describe('fromCss', () => {
it('should covert a CSS string to an IColor', () => {
assert.deepEqual(fromCss('#000000'), { css: '#000000', rgba: 0x000000FF });
assert.deepEqual(fromCss('#101010'), { css: '#101010', rgba: 0x101010FF });
assert.deepEqual(fromCss('#202020'), { css: '#202020', rgba: 0x202020FF });
assert.deepEqual(fromCss('#303030'), { css: '#303030', rgba: 0x303030FF });
assert.deepEqual(fromCss('#404040'), { css: '#404040', rgba: 0x404040FF });
assert.deepEqual(fromCss('#505050'), { css: '#505050', rgba: 0x505050FF });
assert.deepEqual(fromCss('#606060'), { css: '#606060', rgba: 0x606060FF });
assert.deepEqual(fromCss('#707070'), { css: '#707070', rgba: 0x707070FF });
assert.deepEqual(fromCss('#808080'), { css: '#808080', rgba: 0x808080FF });
assert.deepEqual(fromCss('#909090'), { css: '#909090', rgba: 0x909090FF });
assert.deepEqual(fromCss('#a0a0a0'), { css: '#a0a0a0', rgba: 0xa0a0a0FF });
assert.deepEqual(fromCss('#b0b0b0'), { css: '#b0b0b0', rgba: 0xb0b0b0FF });
assert.deepEqual(fromCss('#c0c0c0'), { css: '#c0c0c0', rgba: 0xc0c0c0FF });
assert.deepEqual(fromCss('#d0d0d0'), { css: '#d0d0d0', rgba: 0xd0d0d0FF });
assert.deepEqual(fromCss('#e0e0e0'), { css: '#e0e0e0', rgba: 0xe0e0e0FF });
assert.deepEqual(fromCss('#f0f0f0'), { css: '#f0f0f0', rgba: 0xf0f0f0FF });
assert.deepEqual(fromCss('#ffffff'), { css: '#ffffff', rgba: 0xffffffFF });
});
});
describe('toPaddedHex', () => {
it('should convert numbers to 2-digit hex values', () => {
assert.equal(toPaddedHex(0x00), '00');
assert.equal(toPaddedHex(0x10), '10');
assert.equal(toPaddedHex(0x20), '20');
assert.equal(toPaddedHex(0x30), '30');
assert.equal(toPaddedHex(0x40), '40');
assert.equal(toPaddedHex(0x50), '50');
assert.equal(toPaddedHex(0x60), '60');
assert.equal(toPaddedHex(0x70), '70');
assert.equal(toPaddedHex(0x80), '80');
assert.equal(toPaddedHex(0x90), '90');
assert.equal(toPaddedHex(0xa0), 'a0');
assert.equal(toPaddedHex(0xb0), 'b0');
assert.equal(toPaddedHex(0xc0), 'c0');
assert.equal(toPaddedHex(0xd0), 'd0');
assert.equal(toPaddedHex(0xe0), 'e0');
assert.equal(toPaddedHex(0xf0), 'f0');
assert.equal(toPaddedHex(0xff), 'ff');
});
});
describe('toCss', () => {
it('should convert an rgb array to css hex string', () => {
assert.equal(toCss(0x00, 0x00, 0x00), '#000000');
assert.equal(toCss(0x10, 0x10, 0x10), '#101010');
assert.equal(toCss(0x20, 0x20, 0x20), '#202020');
assert.equal(toCss(0x30, 0x30, 0x30), '#303030');
assert.equal(toCss(0x40, 0x40, 0x40), '#404040');
assert.equal(toCss(0x50, 0x50, 0x50), '#505050');
assert.equal(toCss(0x60, 0x60, 0x60), '#606060');
assert.equal(toCss(0x70, 0x70, 0x70), '#707070');
assert.equal(toCss(0x80, 0x80, 0x80), '#808080');
assert.equal(toCss(0x90, 0x90, 0x90), '#909090');
assert.equal(toCss(0xa0, 0xa0, 0xa0), '#a0a0a0');
assert.equal(toCss(0xb0, 0xb0, 0xb0), '#b0b0b0');
assert.equal(toCss(0xc0, 0xc0, 0xc0), '#c0c0c0');
assert.equal(toCss(0xd0, 0xd0, 0xd0), '#d0d0d0');
assert.equal(toCss(0xe0, 0xe0, 0xe0), '#e0e0e0');
assert.equal(toCss(0xf0, 0xf0, 0xf0), '#f0f0f0');
assert.equal(toCss(0xff, 0xff, 0xff), '#ffffff');
});
});
describe('toRgba', () => {
it('should convert an rgb array to an rgba number', () => {
assert.equal(toRgba(0x00, 0x00, 0x00), 0x000000FF);
assert.equal(toRgba(0x10, 0x10, 0x10), 0x101010FF);
assert.equal(toRgba(0x20, 0x20, 0x20), 0x202020FF);
assert.equal(toRgba(0x30, 0x30, 0x30), 0x303030FF);
assert.equal(toRgba(0x40, 0x40, 0x40), 0x404040FF);
assert.equal(toRgba(0x50, 0x50, 0x50), 0x505050FF);
assert.equal(toRgba(0x60, 0x60, 0x60), 0x606060FF);
assert.equal(toRgba(0x70, 0x70, 0x70), 0x707070FF);
assert.equal(toRgba(0x80, 0x80, 0x80), 0x808080FF);
assert.equal(toRgba(0x90, 0x90, 0x90), 0x909090FF);
assert.equal(toRgba(0xa0, 0xa0, 0xa0), 0xa0a0a0FF);
assert.equal(toRgba(0xb0, 0xb0, 0xb0), 0xb0b0b0FF);
assert.equal(toRgba(0xc0, 0xc0, 0xc0), 0xc0c0c0FF);
assert.equal(toRgba(0xd0, 0xd0, 0xd0), 0xd0d0d0FF);
assert.equal(toRgba(0xe0, 0xe0, 0xe0), 0xe0e0e0FF);
assert.equal(toRgba(0xf0, 0xf0, 0xf0), 0xf0f0f0FF);
assert.equal(toRgba(0xff, 0xff, 0xff), 0xffffffFF);
});
it('should convert an rgba array to an rgba number', () => {
assert.equal(toRgba(0x00, 0x00, 0x00, 0x00), 0x00000000);
assert.equal(toRgba(0x10, 0x10, 0x10, 0x10), 0x10101010);
assert.equal(toRgba(0x20, 0x20, 0x20, 0x20), 0x20202020);
assert.equal(toRgba(0x30, 0x30, 0x30, 0x30), 0x30303030);
assert.equal(toRgba(0x40, 0x40, 0x40, 0x40), 0x40404040);
assert.equal(toRgba(0x50, 0x50, 0x50, 0x50), 0x50505050);
assert.equal(toRgba(0x60, 0x60, 0x60, 0x60), 0x60606060);
assert.equal(toRgba(0x70, 0x70, 0x70, 0x70), 0x70707070);
assert.equal(toRgba(0x80, 0x80, 0x80, 0x80), 0x80808080);
assert.equal(toRgba(0x90, 0x90, 0x90, 0x90), 0x90909090);
assert.equal(toRgba(0xa0, 0xa0, 0xa0, 0xa0), 0xa0a0a0a0);
assert.equal(toRgba(0xb0, 0xb0, 0xb0, 0xb0), 0xb0b0b0b0);
assert.equal(toRgba(0xc0, 0xc0, 0xc0, 0xc0), 0xc0c0c0c0);
assert.equal(toRgba(0xd0, 0xd0, 0xd0, 0xd0), 0xd0d0d0d0);
assert.equal(toRgba(0xe0, 0xe0, 0xe0, 0xe0), 0xe0e0e0e0);
assert.equal(toRgba(0xf0, 0xf0, 0xf0, 0xf0), 0xf0f0f0f0);
assert.equal(toRgba(0xff, 0xff, 0xff, 0xff), 0xffffffff);
});
});
describe('fromRgba', () => {
it('should convert an rgba number to an rgba array', () => {
assert.deepEqual(fromRgba(0x00000000), [0x00, 0x00, 0x00, 0x00]);
assert.deepEqual(fromRgba(0x10101010), [0x10, 0x10, 0x10, 0x10]);
assert.deepEqual(fromRgba(0x20202020), [0x20, 0x20, 0x20, 0x20]);
assert.deepEqual(fromRgba(0x30303030), [0x30, 0x30, 0x30, 0x30]);
assert.deepEqual(fromRgba(0x40404040), [0x40, 0x40, 0x40, 0x40]);
assert.deepEqual(fromRgba(0x50505050), [0x50, 0x50, 0x50, 0x50]);
assert.deepEqual(fromRgba(0x60606060), [0x60, 0x60, 0x60, 0x60]);
assert.deepEqual(fromRgba(0x70707070), [0x70, 0x70, 0x70, 0x70]);
assert.deepEqual(fromRgba(0x80808080), [0x80, 0x80, 0x80, 0x80]);
assert.deepEqual(fromRgba(0x90909090), [0x90, 0x90, 0x90, 0x90]);
assert.deepEqual(fromRgba(0xa0a0a0a0), [0xa0, 0xa0, 0xa0, 0xa0]);
assert.deepEqual(fromRgba(0xb0b0b0b0), [0xb0, 0xb0, 0xb0, 0xb0]);
assert.deepEqual(fromRgba(0xc0c0c0c0), [0xc0, 0xc0, 0xc0, 0xc0]);
assert.deepEqual(fromRgba(0xd0d0d0d0), [0xd0, 0xd0, 0xd0, 0xd0]);
assert.deepEqual(fromRgba(0xe0e0e0e0), [0xe0, 0xe0, 0xe0, 0xe0]);
assert.deepEqual(fromRgba(0xf0f0f0f0), [0xf0, 0xf0, 0xf0, 0xf0]);
assert.deepEqual(fromRgba(0xffffffff), [0xff, 0xff, 0xff, 0xff]);
});
});
describe('opaque', () => {
it('should make the color opaque', () => {
assert.deepEqual(opaque({ css: '#00000000', rgba: 0x00000000 }), { css: '#000000', rgba: 0x000000FF });
assert.deepEqual(opaque({ css: '#10101010', rgba: 0x10101010 }), { css: '#101010', rgba: 0x101010FF });
assert.deepEqual(opaque({ css: '#20202020', rgba: 0x20202020 }), { css: '#202020', rgba: 0x202020FF });
assert.deepEqual(opaque({ css: '#30303030', rgba: 0x30303030 }), { css: '#303030', rgba: 0x303030FF });
assert.deepEqual(opaque({ css: '#40404040', rgba: 0x40404040 }), { css: '#404040', rgba: 0x404040FF });
assert.deepEqual(opaque({ css: '#50505050', rgba: 0x50505050 }), { css: '#505050', rgba: 0x505050FF });
assert.deepEqual(opaque({ css: '#60606060', rgba: 0x60606060 }), { css: '#606060', rgba: 0x606060FF });
assert.deepEqual(opaque({ css: '#70707070', rgba: 0x70707070 }), { css: '#707070', rgba: 0x707070FF });
assert.deepEqual(opaque({ css: '#80808080', rgba: 0x80808080 }), { css: '#808080', rgba: 0x808080FF });
assert.deepEqual(opaque({ css: '#90909090', rgba: 0x90909090 }), { css: '#909090', rgba: 0x909090FF });
assert.deepEqual(opaque({ css: '#a0a0a0a0', rgba: 0xa0a0a0a0 }), { css: '#a0a0a0', rgba: 0xa0a0a0FF });
assert.deepEqual(opaque({ css: '#b0b0b0b0', rgba: 0xb0b0b0b0 }), { css: '#b0b0b0', rgba: 0xb0b0b0FF });
assert.deepEqual(opaque({ css: '#c0c0c0c0', rgba: 0xc0c0c0c0 }), { css: '#c0c0c0', rgba: 0xc0c0c0FF });
assert.deepEqual(opaque({ css: '#d0d0d0d0', rgba: 0xd0d0d0d0 }), { css: '#d0d0d0', rgba: 0xd0d0d0FF });
assert.deepEqual(opaque({ css: '#e0e0e0e0', rgba: 0xe0e0e0e0 }), { css: '#e0e0e0', rgba: 0xe0e0e0FF });
assert.deepEqual(opaque({ css: '#f0f0f0f0', rgba: 0xf0f0f0f0 }), { css: '#f0f0f0', rgba: 0xf0f0f0FF });
assert.deepEqual(opaque({ css: '#ffffffff', rgba: 0xffffffff }), { css: '#ffffff', rgba: 0xffffffFF });
});
});
describe('rgbRelativeLuminance', () => {
it('should calculate the relative luminance of the color', () => {
assert.equal(rgbRelativeLuminance(0x000000), 0);
assert.equal(rgbRelativeLuminance(0x101010).toFixed(4), '0.0052');
assert.equal(rgbRelativeLuminance(0x202020).toFixed(4), '0.0144');
assert.equal(rgbRelativeLuminance(0x303030).toFixed(4), '0.0296');
assert.equal(rgbRelativeLuminance(0x404040).toFixed(4), '0.0513');
assert.equal(rgbRelativeLuminance(0x505050).toFixed(4), '0.0802');
assert.equal(rgbRelativeLuminance(0x606060).toFixed(4), '0.1170');
assert.equal(rgbRelativeLuminance(0x707070).toFixed(4), '0.1620');
assert.equal(rgbRelativeLuminance(0x808080).toFixed(4), '0.2159');
assert.equal(rgbRelativeLuminance(0x909090).toFixed(4), '0.2789');
assert.equal(rgbRelativeLuminance(0xA0A0A0).toFixed(4), '0.3515');
assert.equal(rgbRelativeLuminance(0xB0B0B0).toFixed(4), '0.4342');
assert.equal(rgbRelativeLuminance(0xC0C0C0).toFixed(4), '0.5271');
assert.equal(rgbRelativeLuminance(0xD0D0D0).toFixed(4), '0.6308');
assert.equal(rgbRelativeLuminance(0xE0E0E0).toFixed(4), '0.7454');
assert.equal(rgbRelativeLuminance(0xF0F0F0).toFixed(4), '0.8714');
assert.equal(rgbRelativeLuminance(0xFFFFFF), 1);
});
});
describe('contrastRatio', () => {
it('should calculate the relative luminance of the color', () => {
assert.equal(contrastRatio(0, 0), 1);
assert.equal(contrastRatio(0, 0.5), 11);
assert.equal(contrastRatio(0, 1), 21);
});
it('should work regardless of the parameter order', () => {
assert.equal(contrastRatio(0, 1), 21);
assert.equal(contrastRatio(1, 0), 21);
});
});
describe('ensureContrastRatioRgba', () => {
it('should return undefined if the color already meets the contrast ratio (black bg)', () => {
assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 1), undefined);
assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 2), undefined);
assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 3), undefined);
});
it('should return a color that meets the contrast ratio (black bg)', () => {
assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 4), 0x707070ff);
assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 5), 0x7f7f7fff);
assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 6), 0x8c8c8cff);
assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 7), 0x989898ff);
assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 8), 0xa3a3a3ff);
assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 9), 0xadadadff);
assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 10), 0xb6b6b6ff);
assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 11), 0xbebebeff);
assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 12), 0xc5c5c5ff);
assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 13), 0xd1d1d1ff);
assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 14), 0xd6d6d6ff);
assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 15), 0xdbdbdbff);
assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 16), 0xe3e3e3ff);
assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 17), 0xe9e9e9ff);
assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 18), 0xeeeeeeff);
assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 19), 0xf4f4f4ff);
assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 20), 0xfafafaff);
assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 21), 0xffffffff);
});
it('should return undefined if the color already meets the contrast ratio (white bg)', () => {
assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 1), undefined);
assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 2), undefined);
assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 3), undefined);
assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 4), undefined);
assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 5), undefined);
assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 6), undefined);
});
it('should return a color that meets the contrast ratio (white bg)', () => {
assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 7), 0x565656ff);
assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 8), 0x4d4d4dff);
assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 9), 0x454545ff);
assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 10), 0x3e3e3eff);
assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 11), 0x373737ff);
assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 12), 0x313131ff);
assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 13), 0x313131ff);
assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 14), 0x272727ff);
assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 15), 0x232323ff);
assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 16), 0x1f1f1fff);
assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 17), 0x1b1b1bff);
assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 18), 0x151515ff);
assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 19), 0x101010ff);
assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 20), 0x080808ff);
assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 21), 0x000000ff);
});
});
});
+181
View File
@@ -0,0 +1,181 @@
/**
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
* @license MIT
*/
import { IColor } from 'browser/Types';
export function blend(bg: IColor, fg: IColor): IColor {
const a = (fg.rgba & 0xFF) / 255;
if (a === 1) {
return {
css: fg.css,
rgba: fg.rgba
};
}
const fgR = (fg.rgba >> 24) & 0xFF;
const fgG = (fg.rgba >> 16) & 0xFF;
const fgB = (fg.rgba >> 8) & 0xFF;
const bgR = (bg.rgba >> 24) & 0xFF;
const bgG = (bg.rgba >> 16) & 0xFF;
const bgB = (bg.rgba >> 8) & 0xFF;
const r = bgR + Math.round((fgR - bgR) * a);
const g = bgG + Math.round((fgG - bgG) * a);
const b = bgB + Math.round((fgB - bgB) * a);
const css = toCss(r, g, b);
const rgba = toRgba(r, g, b);
return { css, rgba };
}
export function fromCss(css: string): IColor {
return {
css,
rgba: (parseInt(css.slice(1), 16) << 8 | 0xFF) >>> 0
};
}
export function toPaddedHex(c: number): string {
const s = c.toString(16);
return s.length < 2 ? '0' + s : s;
}
export function toCss(r: number, g: number, b: number, a?: number): string {
if (a !== undefined) {
return `#${toPaddedHex(r)}${toPaddedHex(g)}${toPaddedHex(b)}${toPaddedHex(a)}`;
}
return `#${toPaddedHex(r)}${toPaddedHex(g)}${toPaddedHex(b)}`;
}
export function toRgba(r: number, g: number, b: number, a: number = 0xFF): number {
// >>> 0 forces an unsigned int
return (r << 24 | g << 16 | b << 8 | a) >>> 0;
}
export function fromRgba(value: number): [number, number, number, number] {
return [(value >> 24) & 0xFF, (value >> 16) & 0xFF, (value >> 8) & 0xFF, value & 0xFF];
}
export function opaque(color: IColor): IColor {
const rgba = (color.rgba | 0xFF) >>> 0;
const [r, g, b] = fromRgba(rgba);
return {
css: toCss(r, g, b),
rgba
};
}
/**
* Gets the relative luminance of an RGB color, this is useful in determining the contrast ratio
* between two colors.
* @param rgb The color to use.
* @see https://www.w3.org/TR/WCAG20/#relativeluminancedef
*/
export function rgbRelativeLuminance(rgb: number): number {
return rgbRelativeLuminance2(
(rgb >> 16) & 0xFF,
(rgb >> 8 ) & 0xFF,
(rgb ) & 0xFF);
}
/**
* Gets the relative luminance of an RGB color, this is useful in determining the contrast ratio
* between two colors.
* @param r The red channel (0x00 to 0xFF).
* @param g The green channel (0x00 to 0xFF).
* @param b The blue channel (0x00 to 0xFF).
* @see https://www.w3.org/TR/WCAG20/#relativeluminancedef
*/
export function rgbRelativeLuminance2(r: number, g: number, b: number): number {
const rs = r / 255;
const gs = g / 255;
const bs = b / 255;
const rr = rs <= 0.03928 ? rs / 12.92 : Math.pow((rs + 0.055) / 1.055, 2.4);
const rg = gs <= 0.03928 ? gs / 12.92 : Math.pow((gs + 0.055) / 1.055, 2.4);
const rb = bs <= 0.03928 ? bs / 12.92 : Math.pow((bs + 0.055) / 1.055, 2.4);
return rr * 0.2126 + rg * 0.7152 + rb * 0.0722;
}
/**
* Gets the contrast ratio between two relative luminance values.
* @param l1 The first relative luminance.
* @param l2 The first relative luminance.
* @see https://www.w3.org/TR/WCAG20/#contrast-ratiodef
*/
export function contrastRatio(l1: number, l2: number): number {
if (l1 < l2) {
return (l2 + 0.05) / (l1 + 0.05);
}
return (l1 + 0.05) / (l2 + 0.05);
}
export function rgbaToColor(r: number, g: number, b: number): IColor {
return {
css: toCss(r, g, b),
rgba: toRgba(r, g, b)
};
}
export function ensureContrastRatioRgba(bgRgba: number, fgRgba: number, ratio: number): number | undefined {
const bgL = rgbRelativeLuminance(bgRgba >> 8);
const fgL = rgbRelativeLuminance(fgRgba >> 8);
const cr = contrastRatio(bgL, fgL);
if (cr < ratio) {
if (fgL < bgL) {
return reduceLuminance(bgRgba, fgRgba, ratio);
}
return increaseLuminance(bgRgba, fgRgba, ratio);
}
return undefined;
}
export function ensureContrastRatio(bg: IColor, fg: IColor, ratio: number): IColor | undefined {
const result = ensureContrastRatioRgba(bg.rgba, fg.rgba, ratio);
if (!result) {
return undefined;
}
return rgbaToColor(
(result >> 24 & 0xFF),
(result >> 16 & 0xFF),
(result >> 8 & 0xFF)
);
}
export function reduceLuminance(bgRgba: number, fgRgba: number, ratio: number): number {
// This is a naive but fast approach to reducing luminance as converting to
// HSL and back is expensive
const bgR = (bgRgba >> 24) & 0xFF;
const bgG = (bgRgba >> 16) & 0xFF;
const bgB = (bgRgba >> 8) & 0xFF;
let fgR = (fgRgba >> 24) & 0xFF;
let fgG = (fgRgba >> 16) & 0xFF;
let fgB = (fgRgba >> 8) & 0xFF;
let cr = contrastRatio(rgbRelativeLuminance2(fgR, fgB, fgG), rgbRelativeLuminance2(bgR, bgG, bgB));
while (cr < ratio && (fgR > 0 || fgG > 0 || fgB > 0)) {
// Reduce by 10% until the ratio is hit
fgR -= Math.max(0, Math.ceil(fgR * 0.1));
fgG -= Math.max(0, Math.ceil(fgG * 0.1));
fgB -= Math.max(0, Math.ceil(fgB * 0.1));
cr = contrastRatio(rgbRelativeLuminance2(fgR, fgB, fgG), rgbRelativeLuminance2(bgR, bgG, bgB));
}
return (fgR << 24 | fgG << 16 | fgB << 8 | 0xFF) >>> 0;
}
export function increaseLuminance(bgRgba: number, fgRgba: number, ratio: number): number {
// This is a naive but fast approach to increasing luminance as converting to
// HSL and back is expensive
const bgR = (bgRgba >> 24) & 0xFF;
const bgG = (bgRgba >> 16) & 0xFF;
const bgB = (bgRgba >> 8) & 0xFF;
let fgR = (fgRgba >> 24) & 0xFF;
let fgG = (fgRgba >> 16) & 0xFF;
let fgB = (fgRgba >> 8) & 0xFF;
let cr = contrastRatio(rgbRelativeLuminance2(fgR, fgB, fgG), rgbRelativeLuminance2(bgR, bgG, bgB));
while (cr < ratio && (fgR < 0xFF || fgG < 0xFF || fgB < 0xFF)) {
// Increase by 10% until the ratio is hit
fgR = Math.min(0xFF, fgR + Math.ceil((255 - fgR) * 0.1));
fgG = Math.min(0xFF, fgG + Math.ceil((255 - fgG) * 0.1));
fgB = Math.min(0xFF, fgB + Math.ceil((255 - fgB) * 0.1));
cr = contrastRatio(rgbRelativeLuminance2(fgR, fgB, fgG), rgbRelativeLuminance2(bgR, bgG, bgB));
}
return (fgR << 24 | fgG << 16 | fgB << 8 | 0xFF) >>> 0;
}
+43
View File
@@ -0,0 +1,43 @@
/**
* Copyright (c) 2017 The xterm.js authors. All rights reserved.
* @license MIT
*/
import { assert } from 'chai';
import { ColorContrastCache } from 'browser/ColorContrastCache';
describe('ColorContrastCache', () => {
let cache: ColorContrastCache;
beforeEach(() => {
cache = new ColorContrastCache();
});
it('should save and get color values', () => {
assert.strictEqual(cache.getColor(0x01, 0x00), undefined);
cache.setColor(0x01, 0x01, null);
assert.strictEqual(cache.getColor(0x01, 0x01), null);
cache.setColor(0x01, 0x02, { css: '#030303', rgba: 0x030303ff});
assert.deepEqual(cache.getColor(0x01, 0x02), { css: '#030303', rgba: 0x030303ff});
});
it('should save and get css values', () => {
assert.strictEqual(cache.getCss(0x01, 0x00), undefined);
cache.setCss(0x01, 0x01, null);
assert.strictEqual(cache.getCss(0x01, 0x01), null);
cache.setCss(0x01, 0x02, '#030303');
assert.deepEqual(cache.getCss(0x01, 0x02), '#030303');
});
it('should clear all values on clear', () => {
cache.setColor(0x01, 0x01, null);
cache.setColor(0x01, 0x02, { css: '#030303', rgba: 0x030303ff});
cache.setCss(0x01, 0x01, null);
cache.setCss(0x01, 0x02, '#030303');
cache.clear();
assert.strictEqual(cache.getColor(0x01, 0x01), undefined);
assert.strictEqual(cache.getColor(0x01, 0x02), undefined);
assert.strictEqual(cache.getCss(0x01, 0x01), undefined);
assert.strictEqual(cache.getCss(0x01, 0x02), undefined);
});
});
+38
View File
@@ -0,0 +1,38 @@
/**
* Copyright (c) 2017 The xterm.js authors. All rights reserved.
* @license MIT
*/
import { IColor, IColorContrastCache } from 'browser/Types';
export class ColorContrastCache implements IColorContrastCache {
private _color: { [bg: number]: { [fg: number]: IColor | null | undefined } | undefined } = {};
private _rgba: { [bg: number]: { [fg: number]: string | null | undefined } | undefined } = {};
public clear(): void {
this._color = {};
this._rgba = {};
}
public setCss(bg: number, fg: number, value: string | null): void {
if (!this._rgba[bg]) {
this._rgba[bg] = {};
}
this._rgba[bg]![fg] = value;
}
public getCss(bg: number, fg: number): string | null | undefined {
return this._rgba[bg] ? this._rgba[bg]![fg] : undefined;
}
public setColor(bg: number, fg: number, value: IColor | null): void {
if (!this._color[bg]) {
this._color[bg] = {};
}
this._color[bg]![fg] = value;
}
public getColor(bg: number, fg: number): IColor | null | undefined {
return this._color[bg] ? this._color[bg]![fg] : undefined;
}
}
+1 -1
View File
@@ -34,7 +34,7 @@ describe('ColorManager', () => {
describe('constructor', () => {
it('should fill all colors with values', () => {
for (const key of Object.keys(cm.colors)) {
if (key !== 'ansi') {
if (key !== 'ansi' && key !== 'contrastCache') {
// A #rrggbb or rgba(...)
assert.ok((<any>cm.colors)[key].css.length >= 7);
}
+92 -64
View File
@@ -3,16 +3,18 @@
* @license MIT
*/
import { IColorManager, IColor, IColorSet } from 'browser/Types';
import { IColorManager, IColor, IColorSet, IColorContrastCache } from 'browser/Types';
import { ITheme } from 'common/services/Services';
import { fromCss, toCss, blend, toRgba, toPaddedHex } from 'browser/Color';
import { ColorContrastCache } from 'browser/ColorContrastCache';
const DEFAULT_FOREGROUND = fromHex('#ffffff');
const DEFAULT_BACKGROUND = fromHex('#000000');
const DEFAULT_CURSOR = fromHex('#ffffff');
const DEFAULT_CURSOR_ACCENT = fromHex('#000000');
const DEFAULT_FOREGROUND = fromCss('#ffffff');
const DEFAULT_BACKGROUND = fromCss('#000000');
const DEFAULT_CURSOR = fromCss('#ffffff');
const DEFAULT_CURSOR_ACCENT = fromCss('#000000');
const DEFAULT_SELECTION = {
css: 'rgba(255, 255, 255, 0.3)',
rgba: 0xFFFFFF77
rgba: 0xFFFFFF4D
};
// An IIFE to generate DEFAULT_ANSI_COLORS. Do not mutate DEFAULT_ANSI_COLORS, instead make a copy
@@ -20,23 +22,23 @@ const DEFAULT_SELECTION = {
export const DEFAULT_ANSI_COLORS = (() => {
const colors = [
// dark:
fromHex('#2e3436'),
fromHex('#cc0000'),
fromHex('#4e9a06'),
fromHex('#c4a000'),
fromHex('#3465a4'),
fromHex('#75507b'),
fromHex('#06989a'),
fromHex('#d3d7cf'),
fromCss('#2e3436'),
fromCss('#cc0000'),
fromCss('#4e9a06'),
fromCss('#c4a000'),
fromCss('#3465a4'),
fromCss('#75507b'),
fromCss('#06989a'),
fromCss('#d3d7cf'),
// bright:
fromHex('#555753'),
fromHex('#ef2929'),
fromHex('#8ae234'),
fromHex('#fce94f'),
fromHex('#729fcf'),
fromHex('#ad7fa8'),
fromHex('#34e2e2'),
fromHex('#eeeeec')
fromCss('#555753'),
fromCss('#ef2929'),
fromCss('#8ae234'),
fromCss('#fce94f'),
fromCss('#729fcf'),
fromCss('#ad7fa8'),
fromCss('#34e2e2'),
fromCss('#eeeeec')
];
// Fill in the remaining 240 ANSI colors.
@@ -47,37 +49,23 @@ export const DEFAULT_ANSI_COLORS = (() => {
const g = v[(i / 6) % 6 | 0];
const b = v[i % 6];
colors.push({
css: `#${toPaddedHex(r)}${toPaddedHex(g)}${toPaddedHex(b)}`,
// Use >>> 0 to force a conversion to an unsigned int
rgba: ((r << 24) | (g << 16) | (b << 8) | 0xFF) >>> 0
css: toCss(r, g, b),
rgba: toRgba(r, g, b)
});
}
// Generate greys (232-255)
for (let i = 0; i < 24; i++) {
const c = 8 + i * 10;
const ch = toPaddedHex(c);
colors.push({
css: `#${ch}${ch}${ch}`,
rgba: ((c << 24) | (c << 16) | (c << 8) | 0xFF) >>> 0
css: toCss(c, c, c),
rgba: toRgba(c, c, c)
});
}
return colors;
})();
function fromHex(css: string): IColor {
return {
css,
rgba: parseInt(css.slice(1), 16) << 8 | 0xFF
};
}
function toPaddedHex(c: number): string {
const s = c.toString(16);
return s.length < 2 ? '0' + s : s;
}
/**
* Manages the source of truth for a terminal's colors.
*/
@@ -85,6 +73,7 @@ export class ColorManager implements IColorManager {
public colors: IColorSet;
private _ctx: CanvasRenderingContext2D;
private _litmusColor: CanvasGradient;
private _contrastCache: IColorContrastCache;
constructor(document: Document, public allowTransparency: boolean) {
const canvas = document.createElement('canvas');
@@ -97,16 +86,25 @@ export class ColorManager implements IColorManager {
this._ctx = ctx;
this._ctx.globalCompositeOperation = 'copy';
this._litmusColor = this._ctx.createLinearGradient(0, 0, 1, 1);
this._contrastCache = new ColorContrastCache();
this.colors = {
foreground: DEFAULT_FOREGROUND,
background: DEFAULT_BACKGROUND,
cursor: DEFAULT_CURSOR,
cursorAccent: DEFAULT_CURSOR_ACCENT,
selection: DEFAULT_SELECTION,
ansi: DEFAULT_ANSI_COLORS.slice()
selectionOpaque: blend(DEFAULT_BACKGROUND, DEFAULT_SELECTION),
ansi: DEFAULT_ANSI_COLORS.slice(),
contrastCache: this._contrastCache
};
}
public onOptionsChange(key: string): void {
if (key === 'minimumContrastRatio') {
this._contrastCache.clear();
}
}
/**
* Sets the terminal's theme.
* @param theme The theme to use. If a partial theme is provided then default
@@ -118,6 +116,7 @@ export class ColorManager implements IColorManager {
this.colors.cursor = this._parseColor(theme.cursor, DEFAULT_CURSOR, true);
this.colors.cursorAccent = this._parseColor(theme.cursorAccent, DEFAULT_CURSOR_ACCENT, true);
this.colors.selection = this._parseColor(theme.selection, DEFAULT_SELECTION, true);
this.colors.selectionOpaque = blend(this.colors.background, this.colors.selection);
this.colors.ansi[0] = this._parseColor(theme.black, DEFAULT_ANSI_COLORS[0]);
this.colors.ansi[1] = this._parseColor(theme.red, DEFAULT_ANSI_COLORS[1]);
this.colors.ansi[2] = this._parseColor(theme.green, DEFAULT_ANSI_COLORS[2]);
@@ -134,6 +133,8 @@ export class ColorManager implements IColorManager {
this.colors.ansi[13] = this._parseColor(theme.brightMagenta, DEFAULT_ANSI_COLORS[13]);
this.colors.ansi[14] = this._parseColor(theme.brightCyan, DEFAULT_ANSI_COLORS[14]);
this.colors.ansi[15] = this._parseColor(theme.brightWhite, DEFAULT_ANSI_COLORS[15]);
// Clear our the cache
this._contrastCache.clear();
}
private _parseColor(
@@ -158,33 +159,60 @@ export class ColorManager implements IColorManager {
this._ctx.fillRect(0, 0, 1, 1);
const data = this._ctx.getImageData(0, 0, 1, 1).data;
if (!allowTransparency && data[3] !== 0xFF) {
// Ideally we'd just ignore the alpha channel, but...
//
// Browsers may not give back exactly the same RGB values we put in, because most/all
// convert the color to a pre-multiplied representation. getImageData converts that back to
// a un-premultipled representation, but the precision loss may make the RGB channels unuable
// on their own.
//
// E.g. In Chrome #12345610 turns into #10305010, and in the extreme case, 0xFFFFFF00 turns
// into 0x00000000.
//
// "Note: Due to the lossy nature of converting to and from premultiplied alpha color values,
// pixels that have just been set using putImageData() might be returned to an equivalent
// getImageData() as different values."
// -- https://html.spec.whatwg.org/multipage/canvas.html#pixel-manipulation
//
// So let's just use the fallback color in this case instead.
console.warn(
`Color: ${css} is using transparency, but allowTransparency is false. ` +
`Using fallback ${fallback.css}.`
);
return fallback;
// Check if the printed color was transparent
if (data[3] !== 0xFF) {
if (!allowTransparency) {
// Ideally we'd just ignore the alpha channel, but...
//
// Browsers may not give back exactly the same RGB values we put in, because most/all
// convert the color to a pre-multiplied representation. getImageData converts that back to
// a un-premultipled representation, but the precision loss may make the RGB channels unuable
// on their own.
//
// E.g. In Chrome #12345610 turns into #10305010, and in the extreme case, 0xFFFFFF00 turns
// into 0x00000000.
//
// "Note: Due to the lossy nature of converting to and from premultiplied alpha color values,
// pixels that have just been set using putImageData() might be returned to an equivalent
// getImageData() as different values."
// -- https://html.spec.whatwg.org/multipage/canvas.html#pixel-manipulation
//
// So let's just use the fallback color in this case instead.
console.warn(
`Color: ${css} is using transparency, but allowTransparency is false. ` +
`Using fallback ${fallback.css}.`
);
return fallback;
}
let r: number;
let g: number;
let b: number;
let a: number;
let rgba: number;
if (css.length === 5) {
const num = parseInt(css.substr(1), 16);
r = ((num >> 12) & 0xF) * 16;
g = ((num >> 8) & 0xF) * 16;
b = ((num >> 4) & 0xF) * 16;
a = (num & 0xF) * 16;
rgba = toRgba(r, g, b, a);
} else {
rgba = parseInt(css.substr(1), 16);
r = (rgba >> 24) & 0xFF;
g = (rgba >> 16) & 0xFF;
b = (rgba >> 8) & 0xFF;
a = (rgba ) & 0xFF;
}
return {
rgba,
css: toCss(r, g, b, a)
};
}
return {
css,
rgba: (data[0] << 24 | data[1] << 16 | data[2] << 8 | data[3]) >>> 0
rgba: toRgba(data[0], data[1], data[2], data[3])
};
}
}
+3 -5
View File
@@ -175,11 +175,9 @@ export class MouseZoneManager extends Disposable implements IMouseZoneManager {
// Find the active zone, prevent event propagation if found to prevent other
// components from handling the mouse event.
const zone = this._findZoneEventAt(e);
if (zone) {
if (zone.willLinkActivate(e)) {
e.preventDefault();
e.stopImmediatePropagation();
}
if (zone?.willLinkActivate(e)) {
e.preventDefault();
e.stopImmediatePropagation();
}
}
+12
View File
@@ -8,6 +8,7 @@ import { IDisposable } from 'common/Types';
export interface IColorManager {
colors: IColorSet;
onOptionsChange(key: string): void;
}
export interface IColor {
@@ -21,7 +22,18 @@ export interface IColorSet {
cursor: IColor;
cursorAccent: IColor;
selection: IColor;
/** The selection blended on top of background. */
selectionOpaque: IColor;
ansi: IColor[];
contrastCache: IColorContrastCache;
}
export interface IColorContrastCache {
clear(): void;
setCss(bg: number, fg: number, value: string | null): void;
getCss(bg: number, fg: number): string | null | undefined;
setColor(bg: number, fg: number, value: IColor | null): void;
getColor(bg: number, fg: number): IColor | null | undefined;
}
export interface IPartialColorSet {
+104 -13
View File
@@ -5,16 +5,17 @@
import { IRenderDimensions, IRenderLayer } from 'browser/renderer/Types';
import { ICellData } from 'common/Types';
import { DEFAULT_COLOR, WHITESPACE_CELL_CHAR, WHITESPACE_CELL_CODE } from 'common/buffer/Constants';
import { DEFAULT_COLOR, WHITESPACE_CELL_CHAR, WHITESPACE_CELL_CODE, Attributes } from 'common/buffer/Constants';
import { IGlyphIdentifier } from 'browser/renderer/atlas/Types';
import { DIM_OPACITY, INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants';
import { BaseCharAtlas } from 'browser/renderer/atlas/BaseCharAtlas';
import { acquireCharAtlas } from 'browser/renderer/atlas/CharAtlasCache';
import { AttributeData } from 'common/buffer/AttributeData';
import { IColorSet } from 'browser/Types';
import { IColorSet, IColor } from 'browser/Types';
import { CellData } from 'common/buffer/CellData';
import { IBufferService, IOptionsService } from 'common/services/Services';
import { throwIfFalsy } from 'browser/renderer/RendererUtils';
import { toCss, ensureContrastRatioRgba, opaque } from 'browser/Color';
export abstract class BaseRenderLayer implements IRenderLayer {
private _canvas: HTMLCanvasElement;
@@ -60,9 +61,7 @@ export abstract class BaseRenderLayer implements IRenderLayer {
public dispose(): void {
this._container.removeChild(this._canvas);
if (this._charAtlas) {
this._charAtlas.dispose();
}
this._charAtlas?.dispose();
}
private _initCanvas(): void {
@@ -264,13 +263,14 @@ export abstract class BaseRenderLayer implements IRenderLayer {
* @param bold Whether the text is bold.
*/
protected _drawChars(cell: ICellData, x: number, y: number): void {
const contrastColor = this._getContrastColor(cell);
// skip cache right away if we draw in RGB
// Note: to avoid bad runtime JoinedCellData will be skipped
// in the cache handler itself (atlasDidDraw == false) and
// fall through to uncached later down below
if (cell.isFgRGB() || cell.isBgRGB()) {
this._drawUncachedChars(cell, x, y);
if (contrastColor || cell.isFgRGB() || cell.isBgRGB()) {
this._drawUncachedChars(cell, x, y, contrastColor);
return;
}
@@ -284,7 +284,7 @@ export abstract class BaseRenderLayer implements IRenderLayer {
fg = (cell.isFgDefault()) ? DEFAULT_COLOR : cell.getFgColor();
}
const drawInBrightColor = this._optionsService.options.drawBoldTextInBrightColors && cell.isBold() && fg < 8 && fg !== INVERTED_DEFAULT_COLOR;
const drawInBrightColor = this._optionsService.options.drawBoldTextInBrightColors && cell.isBold() && fg < 8;
fg += drawInBrightColor ? 8 : 0;
this._currentGlyphIdentifier.chars = cell.getChars() || WHITESPACE_CELL_CHAR;
@@ -316,21 +316,29 @@ export abstract class BaseRenderLayer implements IRenderLayer {
* @param x The column to draw at.
* @param y The row to draw at.
*/
private _drawUncachedChars(cell: ICellData, x: number, y: number): void {
private _drawUncachedChars(cell: ICellData, x: number, y: number, fgOverride?: IColor): void {
this._ctx.save();
this._ctx.font = this._getFont(!!cell.isBold(), !!cell.isItalic());
this._ctx.textBaseline = 'middle';
if (cell.isInverse()) {
if (cell.isBgDefault()) {
this._ctx.fillStyle = this._colors.background.css;
if (fgOverride) {
this._ctx.fillStyle = fgOverride.css;
} else if (cell.isBgDefault()) {
this._ctx.fillStyle = opaque(this._colors.background).css;
} else if (cell.isBgRGB()) {
this._ctx.fillStyle = `rgb(${AttributeData.toColorRGB(cell.getBgColor()).join(',')})`;
} else {
this._ctx.fillStyle = this._colors.ansi[cell.getBgColor()].css;
let bg = cell.getBgColor();
if (this._optionsService.options.drawBoldTextInBrightColors && cell.isBold() && bg < 8) {
bg += 8;
}
this._ctx.fillStyle = this._colors.ansi[bg].css;
}
} else {
if (cell.isFgDefault()) {
if (fgOverride) {
this._ctx.fillStyle = fgOverride.css;
} else if (cell.isFgDefault()) {
this._ctx.fillStyle = this._colors.foreground.css;
} else if (cell.isFgRGB()) {
this._ctx.fillStyle = `rgb(${AttributeData.toColorRGB(cell.getFgColor()).join(',')})`;
@@ -381,5 +389,88 @@ export abstract class BaseRenderLayer implements IRenderLayer {
return `${fontStyle} ${fontWeight} ${this._optionsService.options.fontSize * window.devicePixelRatio}px ${this._optionsService.options.fontFamily}`;
}
private _getContrastColor(cell: CellData): IColor | undefined {
if (this._optionsService.options.minimumContrastRatio === 1) {
return undefined;
}
// Try get from cache first
const adjustedColor = this._colors.contrastCache.getColor(cell.bg, cell.fg);
if (adjustedColor !== undefined) {
return adjustedColor || undefined;
}
let fgColor = cell.getFgColor();
let fgColorMode = cell.getFgColorMode();
let bgColor = cell.getBgColor();
let bgColorMode = cell.getBgColorMode();
const isInverse = !!cell.isInverse();
const isBold = !!cell.isInverse();
if (isInverse) {
const temp = fgColor;
fgColor = bgColor;
bgColor = temp;
const temp2 = fgColorMode;
fgColorMode = bgColorMode;
bgColorMode = temp2;
}
const bgRgba = this._resolveBackgroundRgba(bgColorMode, bgColor, isInverse);
const fgRgba = this._resolveForegroundRgba(fgColorMode, fgColor, isInverse, isBold);
const result = ensureContrastRatioRgba(bgRgba, fgRgba, this._optionsService.options.minimumContrastRatio);
if (!result) {
this._colors.contrastCache.setColor(cell.bg, cell.fg, null);
return undefined;
}
const color: IColor = {
css: toCss(
(result >> 24) & 0xFF,
(result >> 16) & 0xFF,
(result >> 8) & 0xFF
),
rgba: result
};
this._colors.contrastCache.setColor(cell.bg, cell.fg, color);
return color;
}
private _resolveBackgroundRgba(bgColorMode: number, bgColor: number, inverse: boolean): number {
switch (bgColorMode) {
case Attributes.CM_P16:
case Attributes.CM_P256:
return this._colors.ansi[bgColor].rgba;
case Attributes.CM_RGB:
return bgColor << 8;
case Attributes.CM_DEFAULT:
default:
if (inverse) {
return this._colors.foreground.rgba;
}
return this._colors.background.rgba;
}
}
private _resolveForegroundRgba(fgColorMode: number, fgColor: number, inverse: boolean, bold: boolean): number {
switch (fgColorMode) {
case Attributes.CM_P16:
case Attributes.CM_P256:
if (this._optionsService.options.drawBoldTextInBrightColors && bold && fgColor < 8) {
fgColor += 8;
}
return this._colors.ansi[fgColor].rgba;
case Attributes.CM_RGB:
return fgColor << 8;
case Attributes.CM_DEFAULT:
default:
if (inverse) {
return this._colors.background.rgba;
}
return this._colors.foreground.rgba;
}
}
}
@@ -3,13 +3,14 @@
* @license MIT
*/
import { IRenderDimensions } from 'browser/renderer/Types';
import { BaseRenderLayer } from '../browser/renderer/BaseRenderLayer';
import { ITerminal } from '../Types';
import { IRenderDimensions, IRequestRefreshRowsEvent } from 'browser/renderer/Types';
import { BaseRenderLayer } from 'browser/renderer/BaseRenderLayer';
import { ICellData } from 'common/Types';
import { CellData } from 'common/buffer/CellData';
import { IColorSet } from 'browser/Types';
import { IBufferService, IOptionsService } from 'common/services/Services';
import { IBufferService, IOptionsService, ICoreService } from 'common/services/Services';
import { IEventEmitter } from 'common/EventEmitter';
import { ICoreBrowserService } from 'browser/services/Services';
interface ICursorState {
x: number;
@@ -27,25 +28,27 @@ const BLINK_INTERVAL = 600;
export class CursorRenderLayer extends BaseRenderLayer {
private _state: ICursorState;
private _cursorRenderers: {[key: string]: (x: number, y: number, cell: ICellData) => void};
private _cursorBlinkStateManager: CursorBlinkStateManager;
private _cursorBlinkStateManager: CursorBlinkStateManager | undefined;
private _cell: ICellData = new CellData();
constructor(
container: HTMLElement,
zIndex: number,
colors: IColorSet,
private _terminal: ITerminal,
rendererId: number,
private _onRequestRefreshRowsEvent: IEventEmitter<IRequestRefreshRowsEvent>,
readonly bufferService: IBufferService,
readonly optionsService: IOptionsService
readonly optionsService: IOptionsService,
private readonly _coreService: ICoreService,
private readonly _coreBrowserService: ICoreBrowserService
) {
super(container, 'cursor', zIndex, true, colors, rendererId, bufferService, optionsService);
this._state = {
x: null,
y: null,
isFocused: null,
style: null,
width: null
x: 0,
y: 0,
isFocused: false,
style: '',
width: 0
};
this._cursorRenderers = {
'bar': this._renderBarCursor.bind(this),
@@ -59,11 +62,11 @@ export class CursorRenderLayer extends BaseRenderLayer {
super.resize(dim);
// Resizing the canvas discards the contents of the canvas so clear state
this._state = {
x: null,
y: null,
isFocused: null,
style: null,
width: null
x: 0,
y: 0,
isFocused: false,
style: '',
width: 0
};
}
@@ -71,7 +74,7 @@ export class CursorRenderLayer extends BaseRenderLayer {
this._clearCursor();
if (this._cursorBlinkStateManager) {
this._cursorBlinkStateManager.dispose();
this._cursorBlinkStateManager = null;
this._cursorBlinkStateManager = undefined;
this.onOptionsChanged();
}
}
@@ -80,33 +83,31 @@ export class CursorRenderLayer extends BaseRenderLayer {
if (this._cursorBlinkStateManager) {
this._cursorBlinkStateManager.pause();
}
this._terminal.refresh(this._bufferService.buffer.y, this._bufferService.buffer.y);
this._onRequestRefreshRowsEvent.fire({ start: this._bufferService.buffer.y, end: this._bufferService.buffer.y });
}
public onFocus(): void {
if (this._cursorBlinkStateManager) {
this._cursorBlinkStateManager.resume();
} else {
this._terminal.refresh(this._bufferService.buffer.y, this._bufferService.buffer.y);
this._onRequestRefreshRowsEvent.fire({ start: this._bufferService.buffer.y, end: this._bufferService.buffer.y });
}
}
public onOptionsChanged(): void {
if (this._optionsService.options.cursorBlink) {
if (!this._cursorBlinkStateManager) {
this._cursorBlinkStateManager = new CursorBlinkStateManager(this._terminal.isFocused, () => {
this._cursorBlinkStateManager = new CursorBlinkStateManager(this._coreBrowserService.isFocused, () => {
this._render(true);
});
}
} else {
if (this._cursorBlinkStateManager) {
this._cursorBlinkStateManager.dispose();
this._cursorBlinkStateManager = null;
}
this._cursorBlinkStateManager?.dispose();
this._cursorBlinkStateManager = undefined;
}
// Request a refresh from the terminal as management of rendering is being
// moved back to the terminal
this._terminal.refresh(this._bufferService.buffer.y, this._bufferService.buffer.y);
this._onRequestRefreshRowsEvent.fire({ start: this._bufferService.buffer.y, end: this._bufferService.buffer.y });
}
public onCursorMove(): void {
@@ -125,7 +126,7 @@ export class CursorRenderLayer extends BaseRenderLayer {
private _render(triggeredByAnimationFrame: boolean): void {
// Don't draw the cursor if it's hidden
if (!this._terminal.cursorState || this._terminal.cursorHidden) {
if (!this._coreService.isCursorInitialized || this._coreService.isCursorHidden) {
this._clearCursor();
return;
}
@@ -139,12 +140,12 @@ export class CursorRenderLayer extends BaseRenderLayer {
return;
}
this._bufferService.buffer.lines.get(cursorY).loadCell(this._bufferService.buffer.x, this._cell);
this._bufferService.buffer.lines.get(cursorY)!.loadCell(this._bufferService.buffer.x, this._cell);
if (this._cell.content === undefined) {
return;
}
if (!this._terminal.isFocused) {
if (!this._coreBrowserService.isFocused) {
this._clearCursor();
this._ctx.save();
this._ctx.fillStyle = this._colors.cursor.css;
@@ -173,7 +174,7 @@ export class CursorRenderLayer extends BaseRenderLayer {
// The cursor is already in the correct spot, don't redraw
if (this._state.x === this._bufferService.buffer.x &&
this._state.y === viewportRelativeCursorY &&
this._state.isFocused === this._terminal.isFocused &&
this._state.isFocused === this._coreBrowserService.isFocused &&
this._state.style === this._optionsService.options.cursorStyle &&
this._state.width === this._cell.getWidth()) {
return;
@@ -196,11 +197,11 @@ export class CursorRenderLayer extends BaseRenderLayer {
if (this._state) {
this._clearCells(this._state.x, this._state.y, this._state.width, 1);
this._state = {
x: null,
y: null,
isFocused: null,
style: null,
width: null
x: 0,
y: 0,
isFocused: false,
style: '',
width: 0
};
}
}
@@ -239,16 +240,16 @@ export class CursorRenderLayer extends BaseRenderLayer {
class CursorBlinkStateManager {
public isCursorVisible: boolean;
private _animationFrame: number;
private _blinkStartTimeout: number;
private _blinkInterval: number;
private _animationFrame: number | undefined;
private _blinkStartTimeout: number | undefined;
private _blinkInterval: number | undefined;
/**
* The time at which the animation frame was restarted, this is used on the
* next render to restart the timers so they don't need to restart the timers
* multiple times over a short period.
*/
private _animationTimeRestarted: number;
private _animationTimeRestarted: number | undefined;
constructor(
isFocused: boolean,
@@ -265,15 +266,15 @@ class CursorBlinkStateManager {
public dispose(): void {
if (this._blinkInterval) {
window.clearInterval(this._blinkInterval);
this._blinkInterval = null;
this._blinkInterval = undefined;
}
if (this._blinkStartTimeout) {
window.clearTimeout(this._blinkStartTimeout);
this._blinkStartTimeout = null;
this._blinkStartTimeout = undefined;
}
if (this._animationFrame) {
window.cancelAnimationFrame(this._animationFrame);
this._animationFrame = null;
this._animationFrame = undefined;
}
}
@@ -288,7 +289,7 @@ class CursorBlinkStateManager {
if (!this._animationFrame) {
this._animationFrame = window.requestAnimationFrame(() => {
this._renderCallback();
this._animationFrame = null;
this._animationFrame = undefined;
});
}
}
@@ -308,7 +309,7 @@ class CursorBlinkStateManager {
// started
if (this._animationTimeRestarted) {
const time = BLINK_INTERVAL - (Date.now() - this._animationTimeRestarted);
this._animationTimeRestarted = null;
this._animationTimeRestarted = undefined;
if (time > 0) {
this._restartInterval(time);
return;
@@ -319,7 +320,7 @@ class CursorBlinkStateManager {
this.isCursorVisible = false;
this._animationFrame = window.requestAnimationFrame(() => {
this._renderCallback();
this._animationFrame = null;
this._animationFrame = undefined;
});
// Setup the blink interval
@@ -329,7 +330,7 @@ class CursorBlinkStateManager {
// calc time diff
// Make restart interval do a setTimeout initially?
const time = BLINK_INTERVAL - (Date.now() - this._animationTimeRestarted);
this._animationTimeRestarted = null;
this._animationTimeRestarted = undefined;
this._restartInterval(time);
return;
}
@@ -338,7 +339,7 @@ class CursorBlinkStateManager {
this.isCursorVisible = !this.isCursorVisible;
this._animationFrame = window.requestAnimationFrame(() => {
this._renderCallback();
this._animationFrame = null;
this._animationFrame = undefined;
});
}, BLINK_INTERVAL);
}, timeToStart);
@@ -348,20 +349,20 @@ class CursorBlinkStateManager {
this.isCursorVisible = true;
if (this._blinkInterval) {
window.clearInterval(this._blinkInterval);
this._blinkInterval = null;
this._blinkInterval = undefined;
}
if (this._blinkStartTimeout) {
window.clearTimeout(this._blinkStartTimeout);
this._blinkStartTimeout = null;
this._blinkStartTimeout = undefined;
}
if (this._animationFrame) {
window.cancelAnimationFrame(this._animationFrame);
this._animationFrame = null;
this._animationFrame = undefined;
}
}
public resume(): void {
this._animationTimeRestarted = null;
this._animationTimeRestarted = undefined;
this._restartInterval();
this.restartBlinkAnimation();
}
@@ -3,18 +3,18 @@
* @license MIT
*/
import { TextRenderLayer } from '../browser/renderer/TextRenderLayer';
import { SelectionRenderLayer } from '../browser/renderer/SelectionRenderLayer';
import { CursorRenderLayer } from './CursorRenderLayer';
import { IRenderLayer, IRenderer, IRenderDimensions, CharacterJoinerHandler, ICharacterJoinerRegistry } from 'browser/renderer/Types';
import { ITerminal } from '../Types';
import { LinkRenderLayer } from '../browser/renderer/LinkRenderLayer';
import { TextRenderLayer } from 'browser/renderer/TextRenderLayer';
import { SelectionRenderLayer } from 'browser/renderer/SelectionRenderLayer';
import { CursorRenderLayer } from 'browser/renderer/CursorRenderLayer';
import { IRenderLayer, IRenderer, IRenderDimensions, CharacterJoinerHandler, ICharacterJoinerRegistry, IRequestRefreshRowsEvent } from 'browser/renderer/Types';
import { LinkRenderLayer } from 'browser/renderer/LinkRenderLayer';
import { CharacterJoinerRegistry } from 'browser/renderer/CharacterJoinerRegistry';
import { Disposable } from 'common/Lifecycle';
import { IColorSet } from 'browser/Types';
import { ICharSizeService } from 'browser/services/Services';
import { IBufferService, IOptionsService } from 'common/services/Services';
import { IColorSet, ILinkifier } from 'browser/Types';
import { ICharSizeService, ICoreBrowserService } from 'browser/services/Services';
import { IBufferService, IOptionsService, ICoreService } from 'common/services/Services';
import { removeTerminalFromCache } from 'browser/renderer/atlas/CharAtlasCache';
import { EventEmitter, IEvent } from 'common/EventEmitter';
let nextRendererId = 1;
@@ -27,36 +27,42 @@ export class Renderer extends Disposable implements IRenderer {
public dimensions: IRenderDimensions;
private _onRequestRefreshRows = new EventEmitter<IRequestRefreshRowsEvent>();
public get onRequestRefreshRows(): IEvent<IRequestRefreshRowsEvent> { return this._onRequestRefreshRows.event; }
constructor(
private _colors: IColorSet,
private readonly _terminal: ITerminal,
readonly bufferService: IBufferService,
private readonly _charSizeService: ICharSizeService,
readonly optionsService: IOptionsService
private readonly _screenElement: HTMLElement,
private readonly _linkifier: ILinkifier,
@IBufferService private readonly _bufferService: IBufferService,
@ICharSizeService private readonly _charSizeService: ICharSizeService,
@IOptionsService private readonly _optionsService: IOptionsService,
@ICoreService readonly coreService: ICoreService,
@ICoreBrowserService readonly coreBrowserService: ICoreBrowserService
) {
super();
const allowTransparency = this._terminal.options.allowTransparency;
this._characterJoinerRegistry = new CharacterJoinerRegistry(bufferService);
const allowTransparency = this._optionsService.options.allowTransparency;
this._characterJoinerRegistry = new CharacterJoinerRegistry(this._bufferService);
this._renderLayers = [
new TextRenderLayer(this._terminal.screenElement, 0, this._colors, this._characterJoinerRegistry, allowTransparency, this._id, bufferService, optionsService),
new SelectionRenderLayer(this._terminal.screenElement, 1, this._colors, this._id, bufferService, optionsService),
new LinkRenderLayer(this._terminal.screenElement, 2, this._colors, this._id, this._terminal.linkifier, bufferService, optionsService),
new CursorRenderLayer(this._terminal.screenElement, 3, this._colors, this._terminal, this._id, bufferService, optionsService)
new TextRenderLayer(this._screenElement, 0, this._colors, this._characterJoinerRegistry, allowTransparency, this._id, this._bufferService, _optionsService),
new SelectionRenderLayer(this._screenElement, 1, this._colors, this._id, this._bufferService, _optionsService),
new LinkRenderLayer(this._screenElement, 2, this._colors, this._id, this._linkifier, this._bufferService, _optionsService),
new CursorRenderLayer(this._screenElement, 3, this._colors, this._id, this._onRequestRefreshRows, this._bufferService, _optionsService, coreService, coreBrowserService)
];
this.dimensions = {
scaledCharWidth: null,
scaledCharHeight: null,
scaledCellWidth: null,
scaledCellHeight: null,
scaledCharLeft: null,
scaledCharTop: null,
scaledCanvasWidth: null,
scaledCanvasHeight: null,
canvasWidth: null,
canvasHeight: null,
actualCellWidth: null,
actualCellHeight: null
scaledCharWidth: 0,
scaledCharHeight: 0,
scaledCellWidth: 0,
scaledCellHeight: 0,
scaledCharLeft: 0,
scaledCharTop: 0,
scaledCanvasWidth: 0,
scaledCanvasHeight: 0,
canvasWidth: 0,
canvasHeight: 0,
actualCellWidth: 0,
actualCellHeight: 0
};
this._devicePixelRatio = window.devicePixelRatio;
this._updateDimensions();
@@ -74,7 +80,7 @@ export class Renderer extends Disposable implements IRenderer {
// and the terminal needs to refreshed
if (this._devicePixelRatio !== window.devicePixelRatio) {
this._devicePixelRatio = window.devicePixelRatio;
this.onResize(this._terminal.cols, this._terminal.rows);
this.onResize(this._bufferService.cols, this._bufferService.rows);
}
}
@@ -96,12 +102,12 @@ export class Renderer extends Disposable implements IRenderer {
this._renderLayers.forEach(l => l.resize(this.dimensions));
// Resize the screen
this._terminal.screenElement.style.width = `${this.dimensions.canvasWidth}px`;
this._terminal.screenElement.style.height = `${this.dimensions.canvasHeight}px`;
this._screenElement.style.width = `${this.dimensions.canvasWidth}px`;
this._screenElement.style.height = `${this.dimensions.canvasHeight}px`;
}
public onCharSizeChanged(): void {
this.onResize(this._terminal.cols, this._terminal.rows);
this.onResize(this._bufferService.cols, this._bufferService.rows);
}
public onBlur(): void {
@@ -163,23 +169,23 @@ export class Renderer extends Disposable implements IRenderer {
// will be floored because since lineHeight can never be lower then 1, there
// is a guarentee that the scaled line height will always be larger than
// scaled char height.
this.dimensions.scaledCellHeight = Math.floor(this.dimensions.scaledCharHeight * this._terminal.options.lineHeight);
this.dimensions.scaledCellHeight = Math.floor(this.dimensions.scaledCharHeight * this._optionsService.options.lineHeight);
// Calculate the y coordinate within a cell that text should draw from in
// order to draw in the center of a cell.
this.dimensions.scaledCharTop = this._terminal.options.lineHeight === 1 ? 0 : Math.round((this.dimensions.scaledCellHeight - this.dimensions.scaledCharHeight) / 2);
this.dimensions.scaledCharTop = this._optionsService.options.lineHeight === 1 ? 0 : Math.round((this.dimensions.scaledCellHeight - this.dimensions.scaledCharHeight) / 2);
// Calculate the scaled cell width, taking the letterSpacing into account.
this.dimensions.scaledCellWidth = this.dimensions.scaledCharWidth + Math.round(this._terminal.options.letterSpacing);
this.dimensions.scaledCellWidth = this.dimensions.scaledCharWidth + Math.round(this._optionsService.options.letterSpacing);
// Calculate the x coordinate with a cell that text should draw from in
// order to draw in the center of a cell.
this.dimensions.scaledCharLeft = Math.floor(this._terminal.options.letterSpacing / 2);
this.dimensions.scaledCharLeft = Math.floor(this._optionsService.options.letterSpacing / 2);
// Recalculate the canvas dimensions; scaled* define the actual number of
// pixel in the canvas
this.dimensions.scaledCanvasHeight = this._terminal.rows * this.dimensions.scaledCellHeight;
this.dimensions.scaledCanvasWidth = this._terminal.cols * this.dimensions.scaledCellWidth;
this.dimensions.scaledCanvasHeight = this._bufferService.rows * this.dimensions.scaledCellHeight;
this.dimensions.scaledCanvasWidth = this._bufferService.cols * this.dimensions.scaledCellWidth;
// The the size of the canvas on the page. It's very important that this
// rounds to nearest integer and not ceils as browsers often set
@@ -194,8 +200,8 @@ export class Renderer extends Disposable implements IRenderer {
// account window.devicePixelRatio. ICharSizeService.width/height by itself
// is insufficient when the page is not at 100% zoom level as it's measured
// in CSS pixels, but the actual char size on the canvas can differ.
this.dimensions.actualCellHeight = this.dimensions.canvasHeight / this._terminal.rows;
this.dimensions.actualCellWidth = this.dimensions.canvasWidth / this._terminal.cols;
this.dimensions.actualCellHeight = this.dimensions.canvasHeight / this._bufferService.rows;
this.dimensions.actualCellWidth = this.dimensions.canvasWidth / this._bufferService.cols;
}
public registerCharacterJoiner(handler: CharacterJoinerHandler): number {
+5 -1
View File
@@ -227,7 +227,11 @@ export class TextRenderLayer extends BaseRenderLayer {
} else if (cell.isBgRGB()) {
this._ctx.fillStyle = `rgb(${AttributeData.toColorRGB(cell.getBgColor()).join(',')})`;
} else {
this._ctx.fillStyle = this._colors.ansi[cell.getBgColor()].css;
let bg = cell.getBgColor();
if (this._optionsService.options.drawBoldTextInBrightColors && cell.isBold() && bg < 8) {
bg += 8;
}
this._ctx.fillStyle = this._colors.ansi[bg].css;
}
} else {
if (cell.isFgDefault()) {
+8
View File
@@ -5,6 +5,7 @@
import { IDisposable } from 'common/Types';
import { IColorSet } from 'browser/Types';
import { IEvent } from 'common/EventEmitter';
export type CharacterJoinerHandler = (text: string) => [number, number][];
@@ -23,6 +24,11 @@ export interface IRenderDimensions {
actualCellHeight: number;
}
export interface IRequestRefreshRowsEvent {
start: number;
end: number;
}
/**
* Note that IRenderer implementations should emit the refresh event after
* rendering rows to the screen.
@@ -30,6 +36,8 @@ export interface IRenderDimensions {
export interface IRenderer extends IDisposable {
readonly dimensions: IRenderDimensions;
readonly onRequestRefreshRows: IEvent<IRequestRefreshRowsEvent>;
dispose(): void;
setColors(colors: IColorSet): void;
onDevicePixelRatioChange(): void;
@@ -11,6 +11,7 @@ import { LRUMap } from 'browser/renderer/atlas/LRUMap';
import { isFirefox, isSafari } from 'common/Platform';
import { IColor } from 'browser/Types';
import { throwIfFalsy } from 'browser/renderer/RendererUtils';
import { opaque } from 'browser/Color';
// In practice we're probably never going to exhaust a texture this large. For debugging purposes,
// however, it can be useful to set this to a really tiny value, to verify that LRU eviction works.
@@ -222,7 +223,7 @@ export class DynamicCharAtlas extends BaseCharAtlas {
private _getForegroundColor(glyph: IGlyphIdentifier): IColor {
if (glyph.fg === INVERTED_DEFAULT_COLOR) {
return this._config.colors.background;
return opaque(this._config.colors.background);
} else if (glyph.fg < 256) {
// 256 color support
return this._getColorFromAnsiIndex(glyph.fg);
@@ -3,14 +3,15 @@
* @license MIT
*/
import { IRenderer, IRenderDimensions, CharacterJoinerHandler } from 'browser/renderer/Types';
import { ITerminal } from '../../Types';
import { IRenderer, IRenderDimensions, CharacterJoinerHandler, IRequestRefreshRowsEvent } from 'browser/renderer/Types';
import { BOLD_CLASS, ITALIC_CLASS, CURSOR_CLASS, CURSOR_STYLE_BLOCK_CLASS, CURSOR_BLINK_CLASS, CURSOR_STYLE_BAR_CLASS, CURSOR_STYLE_UNDERLINE_CLASS, DomRendererRowFactory } from 'browser/renderer/dom/DomRendererRowFactory';
import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants';
import { Disposable } from 'common/Lifecycle';
import { IColorSet, ILinkifierEvent } from 'browser/Types';
import { IColorSet, ILinkifierEvent, ILinkifier } from 'browser/Types';
import { ICharSizeService } from 'browser/services/Services';
import { IOptionsService } from 'common/services/Services';
import { IOptionsService, IBufferService } from 'common/services/Services';
import { EventEmitter, IEvent } from 'common/EventEmitter';
import { opaque } from 'browser/Color';
const TERMINAL_CLASS_PREFIX = 'xterm-dom-renderer-owner-';
const ROW_CONTAINER_CLASS = 'xterm-rows';
@@ -30,19 +31,26 @@ export class DomRenderer extends Disposable implements IRenderer {
private _rowFactory: DomRendererRowFactory;
private _terminalClass: number = nextTerminalId++;
private _themeStyleElement: HTMLStyleElement;
private _dimensionsStyleElement: HTMLStyleElement;
private _themeStyleElement!: HTMLStyleElement;
private _dimensionsStyleElement!: HTMLStyleElement;
private _rowContainer: HTMLElement;
private _rowElements: HTMLElement[] = [];
private _selectionContainer: HTMLElement;
public dimensions: IRenderDimensions;
private _onRequestRefreshRows = new EventEmitter<IRequestRefreshRowsEvent>();
public get onRequestRefreshRows(): IEvent<IRequestRefreshRowsEvent> { return this._onRequestRefreshRows.event; }
constructor(
private _terminal: ITerminal,
private _colors: IColorSet,
private _charSizeService: ICharSizeService,
private _optionsService: IOptionsService
private readonly _element: HTMLElement,
private readonly _screenElement: HTMLElement,
private readonly _viewportElement: HTMLElement,
private readonly _linkifier: ILinkifier,
@ICharSizeService private readonly _charSizeService: ICharSizeService,
@IOptionsService private readonly _optionsService: IOptionsService,
@IBufferService private readonly _bufferService: IBufferService
) {
super();
@@ -50,60 +58,60 @@ export class DomRenderer extends Disposable implements IRenderer {
this._rowContainer.classList.add(ROW_CONTAINER_CLASS);
this._rowContainer.style.lineHeight = 'normal';
this._rowContainer.setAttribute('aria-hidden', 'true');
this._refreshRowElements(this._terminal.cols, this._terminal.rows);
this._refreshRowElements(this._bufferService.cols, this._bufferService.rows);
this._selectionContainer = document.createElement('div');
this._selectionContainer.classList.add(SELECTION_CLASS);
this._selectionContainer.setAttribute('aria-hidden', 'true');
this.dimensions = {
scaledCharWidth: null,
scaledCharHeight: null,
scaledCellWidth: null,
scaledCellHeight: null,
scaledCharLeft: null,
scaledCharTop: null,
scaledCanvasWidth: null,
scaledCanvasHeight: null,
canvasWidth: null,
canvasHeight: null,
actualCellWidth: null,
actualCellHeight: null
scaledCharWidth: 0,
scaledCharHeight: 0,
scaledCellWidth: 0,
scaledCellHeight: 0,
scaledCharLeft: 0,
scaledCharTop: 0,
scaledCanvasWidth: 0,
scaledCanvasHeight: 0,
canvasWidth: 0,
canvasHeight: 0,
actualCellWidth: 0,
actualCellHeight: 0
};
this._updateDimensions();
this._injectCss();
this._rowFactory = new DomRendererRowFactory(document, this._optionsService);
this._rowFactory = new DomRendererRowFactory(document, this._optionsService, this._colors);
this._terminal.element.classList.add(TERMINAL_CLASS_PREFIX + this._terminalClass);
this._terminal.screenElement.appendChild(this._rowContainer);
this._terminal.screenElement.appendChild(this._selectionContainer);
this._element.classList.add(TERMINAL_CLASS_PREFIX + this._terminalClass);
this._screenElement.appendChild(this._rowContainer);
this._screenElement.appendChild(this._selectionContainer);
this._terminal.linkifier.onLinkHover(e => this._onLinkHover(e));
this._terminal.linkifier.onLinkLeave(e => this._onLinkLeave(e));
this._linkifier.onLinkHover(e => this._onLinkHover(e));
this._linkifier.onLinkLeave(e => this._onLinkLeave(e));
}
public dispose(): void {
this._terminal.element.classList.remove(TERMINAL_CLASS_PREFIX + this._terminalClass);
this._terminal.screenElement.removeChild(this._rowContainer);
this._terminal.screenElement.removeChild(this._selectionContainer);
this._terminal.screenElement.removeChild(this._themeStyleElement);
this._terminal.screenElement.removeChild(this._dimensionsStyleElement);
this._element.classList.remove(TERMINAL_CLASS_PREFIX + this._terminalClass);
this._screenElement.removeChild(this._rowContainer);
this._screenElement.removeChild(this._selectionContainer);
this._screenElement.removeChild(this._themeStyleElement);
this._screenElement.removeChild(this._dimensionsStyleElement);
super.dispose();
}
private _updateDimensions(): void {
this.dimensions.scaledCharWidth = this._charSizeService.width * window.devicePixelRatio;
this.dimensions.scaledCharHeight = Math.ceil(this._charSizeService.height * window.devicePixelRatio);
this.dimensions.scaledCellWidth = this.dimensions.scaledCharWidth + Math.round(this._terminal.options.letterSpacing);
this.dimensions.scaledCellHeight = Math.floor(this.dimensions.scaledCharHeight * this._terminal.options.lineHeight);
this.dimensions.scaledCellWidth = this.dimensions.scaledCharWidth + Math.round(this._optionsService.options.letterSpacing);
this.dimensions.scaledCellHeight = Math.floor(this.dimensions.scaledCharHeight * this._optionsService.options.lineHeight);
this.dimensions.scaledCharLeft = 0;
this.dimensions.scaledCharTop = 0;
this.dimensions.scaledCanvasWidth = this.dimensions.scaledCellWidth * this._terminal.cols;
this.dimensions.scaledCanvasHeight = this.dimensions.scaledCellHeight * this._terminal.rows;
this.dimensions.scaledCanvasWidth = this.dimensions.scaledCellWidth * this._bufferService.cols;
this.dimensions.scaledCanvasHeight = this.dimensions.scaledCellHeight * this._bufferService.rows;
this.dimensions.canvasWidth = Math.round(this.dimensions.scaledCanvasWidth / window.devicePixelRatio);
this.dimensions.canvasHeight = Math.round(this.dimensions.scaledCanvasHeight / window.devicePixelRatio);
this.dimensions.actualCellWidth = this.dimensions.canvasWidth / this._terminal.cols;
this.dimensions.actualCellHeight = this.dimensions.canvasHeight / this._terminal.rows;
this.dimensions.actualCellWidth = this.dimensions.canvasWidth / this._bufferService.cols;
this.dimensions.actualCellHeight = this.dimensions.canvasHeight / this._bufferService.rows;
this._rowElements.forEach(element => {
element.style.width = `${this.dimensions.canvasWidth}px`;
@@ -115,7 +123,7 @@ export class DomRenderer extends Disposable implements IRenderer {
if (!this._dimensionsStyleElement) {
this._dimensionsStyleElement = document.createElement('style');
this._terminal.screenElement.appendChild(this._dimensionsStyleElement);
this._screenElement.appendChild(this._dimensionsStyleElement);
}
const styles =
@@ -128,9 +136,9 @@ export class DomRenderer extends Disposable implements IRenderer {
this._dimensionsStyleElement.innerHTML = styles;
this._selectionContainer.style.height = (<any>this._terminal)._viewportElement.style.height;
this._terminal.screenElement.style.width = `${this.dimensions.canvasWidth}px`;
this._terminal.screenElement.style.height = `${this.dimensions.canvasHeight}px`;
this._selectionContainer.style.height = this._viewportElement.style.height;
this._screenElement.style.width = `${this.dimensions.canvasWidth}px`;
this._screenElement.style.height = `${this.dimensions.canvasHeight}px`;
}
public setColors(colors: IColorSet): void {
@@ -141,7 +149,7 @@ export class DomRenderer extends Disposable implements IRenderer {
private _injectCss(): void {
if (!this._themeStyleElement) {
this._themeStyleElement = document.createElement('style');
this._terminal.screenElement.appendChild(this._themeStyleElement);
this._screenElement.appendChild(this._themeStyleElement);
}
// Base CSS
@@ -149,16 +157,16 @@ export class DomRenderer extends Disposable implements IRenderer {
`${this._terminalSelector} .${ROW_CONTAINER_CLASS} {` +
` color: ${this._colors.foreground.css};` +
` background-color: ${this._colors.background.css};` +
` font-family: ${this._terminal.options.fontFamily};` +
` font-size: ${this._terminal.options.fontSize}px;` +
` font-family: ${this._optionsService.options.fontFamily};` +
` font-size: ${this._optionsService.options.fontSize}px;` +
`}`;
// Text styles
styles +=
`${this._terminalSelector} span:not(.${BOLD_CLASS}) {` +
` font-weight: ${this._terminal.options.fontWeight};` +
` font-weight: ${this._optionsService.options.fontWeight};` +
`}` +
`${this._terminalSelector} span.${BOLD_CLASS} {` +
` font-weight: ${this._terminal.options.fontWeightBold};` +
` font-weight: ${this._optionsService.options.fontWeightBold};` +
`}` +
`${this._terminalSelector} span.${ITALIC_CLASS} {` +
` font-style: italic;` +
@@ -223,7 +231,7 @@ export class DomRenderer extends Disposable implements IRenderer {
`${this._terminalSelector} .${BG_CLASS_PREFIX}${i} { background-color: ${c.css}; }`;
});
styles +=
`${this._terminalSelector} .${FG_CLASS_PREFIX}${INVERTED_DEFAULT_COLOR} { color: ${this._colors.background.css}; }` +
`${this._terminalSelector} .${FG_CLASS_PREFIX}${INVERTED_DEFAULT_COLOR} { color: ${opaque(this._colors.background).css}; }` +
`${this._terminalSelector} .${BG_CLASS_PREFIX}${INVERTED_DEFAULT_COLOR} { background-color: ${this._colors.foreground.css}; }`;
this._themeStyleElement.innerHTML = styles;
@@ -242,7 +250,7 @@ export class DomRenderer extends Disposable implements IRenderer {
}
// Remove excess elements
while (this._rowElements.length > rows) {
this._rowContainer.removeChild(this._rowElements.pop());
this._rowContainer.removeChild(this._rowElements.pop()!);
}
}
@@ -275,13 +283,13 @@ export class DomRenderer extends Disposable implements IRenderer {
}
// Translate from buffer position to viewport position
const viewportStartRow = start[1] - this._terminal.buffer.ydisp;
const viewportEndRow = end[1] - this._terminal.buffer.ydisp;
const viewportStartRow = start[1] - this._bufferService.buffer.ydisp;
const viewportEndRow = end[1] - this._bufferService.buffer.ydisp;
const viewportCappedStartRow = Math.max(viewportStartRow, 0);
const viewportCappedEndRow = Math.min(viewportEndRow, this._terminal.rows - 1);
const viewportCappedEndRow = Math.min(viewportEndRow, this._bufferService.rows - 1);
// No need to draw the selection
if (viewportCappedStartRow >= this._terminal.rows || viewportCappedEndRow < 0) {
if (viewportCappedStartRow >= this._bufferService.rows || viewportCappedEndRow < 0) {
return;
}
@@ -295,15 +303,15 @@ export class DomRenderer extends Disposable implements IRenderer {
} else {
// Draw first row
const startCol = viewportStartRow === viewportCappedStartRow ? start[0] : 0;
const endCol = viewportCappedStartRow === viewportCappedEndRow ? end[0] : this._terminal.cols;
const endCol = viewportCappedStartRow === viewportCappedEndRow ? end[0] : this._bufferService.cols;
documentFragment.appendChild(this._createSelectionElement(viewportCappedStartRow, startCol, endCol));
// Draw middle rows
const middleRowsCount = viewportCappedEndRow - viewportCappedStartRow - 1;
documentFragment.appendChild(this._createSelectionElement(viewportCappedStartRow + 1, 0, this._terminal.cols, middleRowsCount));
documentFragment.appendChild(this._createSelectionElement(viewportCappedStartRow + 1, 0, this._bufferService.cols, middleRowsCount));
// Draw final row
if (viewportCappedStartRow !== viewportCappedEndRow) {
// Only draw viewportEndRow if it's not the same as viewporttartRow
const endCol = viewportEndRow === viewportCappedEndRow ? end[0] : this._terminal.cols;
const endCol = viewportEndRow === viewportCappedEndRow ? end[0] : this._bufferService.cols;
documentFragment.appendChild(this._createSelectionElement(viewportCappedEndRow, 0, endCol));
}
}
@@ -333,7 +341,6 @@ export class DomRenderer extends Disposable implements IRenderer {
// Force a refresh
this._updateDimensions();
this._injectCss();
this._terminal.refresh(0, this._terminal.rows - 1);
}
public clear(): void {
@@ -341,20 +348,18 @@ export class DomRenderer extends Disposable implements IRenderer {
}
public renderRows(start: number, end: number): void {
const terminal = this._terminal;
const cursorAbsoluteY = terminal.buffer.ybase + terminal.buffer.y;
const cursorX = this._terminal.buffer.x;
const cursorBlink = this._terminal.options.cursorBlink;
const cursorAbsoluteY = this._bufferService.buffer.ybase + this._bufferService.buffer.y;
const cursorX = this._bufferService.buffer.x;
const cursorBlink = this._optionsService.options.cursorBlink;
for (let y = start; y <= end; y++) {
const rowElement = this._rowElements[y];
rowElement.innerHTML = '';
const row = y + terminal.buffer.ydisp;
const lineData = terminal.buffer.lines.get(row);
const cursorStyle = terminal.options.cursorStyle;
rowElement.appendChild(this._rowFactory.createRow(lineData, row === cursorAbsoluteY, cursorStyle, cursorX, cursorBlink, this.dimensions.actualCellWidth, terminal.cols));
const row = y + this._bufferService.buffer.ydisp;
const lineData = this._bufferService.buffer.lines.get(row);
const cursorStyle = this._optionsService.options.cursorStyle;
rowElement.appendChild(this._rowFactory.createRow(lineData!, row === cursorAbsoluteY, cursorStyle, cursorX, cursorBlink, this.dimensions.actualCellWidth, this._bufferService.cols));
}
}
@@ -11,6 +11,7 @@ import { BufferLine, DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine';
import { IBufferLine } from 'common/Types';
import { CellData } from 'common/buffer/CellData';
import { MockOptionsService } from 'common/TestUtils.test';
import { fromCss } from 'browser/Color';
describe('DomRendererRowFactory', () => {
let dom: jsdom.JSDOM;
@@ -19,7 +20,30 @@ describe('DomRendererRowFactory', () => {
beforeEach(() => {
dom = new jsdom.JSDOM('');
rowFactory = new DomRendererRowFactory(dom.window.document, new MockOptionsService({ drawBoldTextInBrightColors: true }));
rowFactory = new DomRendererRowFactory(dom.window.document, new MockOptionsService({ drawBoldTextInBrightColors: true }), {
background: fromCss('#010101'),
foreground: fromCss('#020202'),
ansi: [
// dark:
fromCss('#2e3436'),
fromCss('#cc0000'),
fromCss('#4e9a06'),
fromCss('#c4a000'),
fromCss('#3465a4'),
fromCss('#75507b'),
fromCss('#06989a'),
fromCss('#d3d7cf'),
// bright:
fromCss('#555753'),
fromCss('#ef2929'),
fromCss('#8ae234'),
fromCss('#fce94f'),
fromCss('#729fcf'),
fromCss('#ad7fa8'),
fromCss('#34e2e2'),
fromCss('#eeeeec')
]
} as any);
lineData = createEmptyLineData(2);
});
@@ -142,7 +166,7 @@ describe('DomRendererRowFactory', () => {
lineData.setCell(0, cell);
const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20);
assert.equal(getFragmentHtml(fragment),
'<span class="xterm-bg-2 xterm-fg-1">a</span>'
'<span class="xterm-fg-1 xterm-bg-2">a</span>'
);
});
@@ -153,7 +177,7 @@ describe('DomRendererRowFactory', () => {
lineData.setCell(0, cell);
const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20);
assert.equal(getFragmentHtml(fragment),
'<span class="xterm-bg-257 xterm-fg-1">a</span>'
'<span class="xterm-fg-1 xterm-bg-257">a</span>'
);
});
@@ -163,7 +187,7 @@ describe('DomRendererRowFactory', () => {
lineData.setCell(0, cell);
const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20);
assert.equal(getFragmentHtml(fragment),
'<span class="xterm-bg-1 xterm-fg-257">a</span>'
'<span class="xterm-fg-257 xterm-bg-1">a</span>'
);
});
@@ -188,7 +212,7 @@ describe('DomRendererRowFactory', () => {
lineData.setCell(0, cell);
const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20);
assert.equal(getFragmentHtml(fragment),
'<span style="color:rgb(1,2,3);background-color:rgb(4,5,6);">a</span>'
'<span style="color:#010203;background-color:#040506;">a</span>'
);
});
@@ -199,7 +223,7 @@ describe('DomRendererRowFactory', () => {
lineData.setCell(0, cell);
const fragment = rowFactory.createRow(lineData, false, undefined, 0, false, 5, 20);
assert.equal(getFragmentHtml(fragment),
'<span style="background-color:rgb(1,2,3);color:rgb(4,5,6);">a</span>'
'<span style="color:#040506;background-color:#010203;">a</span>'
);
});
});
@@ -5,10 +5,11 @@
import { IBufferLine } from 'common/Types';
import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants';
import { AttributeData } from 'common/buffer/AttributeData';
import { NULL_CELL_CODE, WHITESPACE_CELL_CHAR } from 'common/buffer/Constants';
import { NULL_CELL_CODE, WHITESPACE_CELL_CHAR, Attributes } from 'common/buffer/Constants';
import { CellData } from 'common/buffer/CellData';
import { IOptionsService } from 'common/services/Services';
import { ensureContrastRatio, rgbaToColor } from 'browser/Color';
import { IColorSet, IColor } from 'browser/Types';
export const BOLD_CLASS = 'xterm-bold';
export const DIM_CLASS = 'xterm-dim';
@@ -24,11 +25,16 @@ export class DomRendererRowFactory {
private _workCell: CellData = new CellData();
constructor(
private _document: Document,
private _optionsService: IOptionsService
private readonly _document: Document,
private readonly _optionsService: IOptionsService,
private _colors: IColorSet
) {
}
public setColors(colors: IColorSet): void {
this._colors = colors;
}
public createRow(lineData: IBufferLine, isCursorRow: boolean, cursorStyle: string | undefined, cursorX: number, cursorBlink: boolean, cellWidth: number, cols: number): DocumentFragment {
const fragment = this._document.createDocumentFragment();
@@ -97,36 +103,101 @@ export class DomRendererRowFactory {
charElement.textContent = this._workCell.getChars() || WHITESPACE_CELL_CHAR;
const swapColor = this._workCell.isInverse();
// fg
if (this._workCell.isFgRGB()) {
let style = charElement.getAttribute('style') || '';
style += `${swapColor ? 'background-' : ''}color:rgb(${(AttributeData.toColorRGB(this._workCell.getFgColor())).join(',')});`;
charElement.setAttribute('style', style);
} else if (this._workCell.isFgPalette()) {
let fg = this._workCell.getFgColor();
if (this._workCell.isBold() && fg < 8 && !swapColor && this._optionsService.options.drawBoldTextInBrightColors) {
fg += 8;
}
charElement.classList.add(`xterm-${swapColor ? 'b' : 'f'}g-${fg}`);
} else if (swapColor) {
charElement.classList.add(`xterm-bg-${INVERTED_DEFAULT_COLOR}`);
let fg = this._workCell.getFgColor();
let fgColorMode = this._workCell.getFgColorMode();
let bg = this._workCell.getBgColor();
let bgColorMode = this._workCell.getBgColorMode();
const isInverse = !!this._workCell.isInverse();
if (isInverse) {
const temp = fg;
fg = bg;
bg = temp;
const temp2 = fgColorMode;
fgColorMode = bgColorMode;
bgColorMode = temp2;
}
// bg
if (this._workCell.isBgRGB()) {
let style = charElement.getAttribute('style') || '';
style += `${swapColor ? '' : 'background-'}color:rgb(${(AttributeData.toColorRGB(this._workCell.getBgColor())).join(',')});`;
charElement.setAttribute('style', style);
} else if (this._workCell.isBgPalette()) {
charElement.classList.add(`xterm-${swapColor ? 'f' : 'b'}g-${this._workCell.getBgColor()}`);
} else if (swapColor) {
charElement.classList.add(`xterm-fg-${INVERTED_DEFAULT_COLOR}`);
// Foreground
switch (fgColorMode) {
case Attributes.CM_P16:
case Attributes.CM_P256:
if (this._workCell.isBold() && fg < 8 && this._optionsService.options.drawBoldTextInBrightColors) {
fg += 8;
}
if (!this._applyMinimumContrast(charElement, this._colors.background, this._colors.ansi[fg])) {
charElement.classList.add(`xterm-fg-${fg}`);
}
break;
case Attributes.CM_RGB:
const color = rgbaToColor(
(fg >> 16) & 0xFF,
(fg >> 8) & 0xFF,
(fg ) & 0xFF
);
if (!this._applyMinimumContrast(charElement, this._colors.background, color)) {
this._addStyle(charElement, `color:#${padStart(fg.toString(16), '0', 6)}`);
}
break;
case Attributes.CM_DEFAULT:
default:
if (!this._applyMinimumContrast(charElement, this._colors.background, this._colors.foreground)) {
if (isInverse) {
charElement.classList.add(`xterm-fg-${INVERTED_DEFAULT_COLOR}`);
}
}
}
// Background
switch (bgColorMode) {
case Attributes.CM_P16:
case Attributes.CM_P256:
charElement.classList.add(`xterm-bg-${bg}`);
break;
case Attributes.CM_RGB:
this._addStyle(charElement, `background-color:#${padStart(bg.toString(16), '0', 6)}`);
break;
case Attributes.CM_DEFAULT:
default:
if (isInverse) {
charElement.classList.add(`xterm-bg-${INVERTED_DEFAULT_COLOR}`);
}
}
fragment.appendChild(charElement);
}
return fragment;
}
private _applyMinimumContrast(element: HTMLElement, bg: IColor, fg: IColor): boolean {
if (this._optionsService.options.minimumContrastRatio === 1) {
return false;
}
// Try get from cache first
let adjustedColor = this._colors.contrastCache.getColor(this._workCell.bg, this._workCell.fg);
// Calculate and store in cache
if (adjustedColor === undefined) {
adjustedColor = ensureContrastRatio(bg, fg, this._optionsService.options.minimumContrastRatio);
this._colors.contrastCache.setColor(this._workCell.bg, this._workCell.fg, adjustedColor ?? null);
}
if (adjustedColor) {
this._addStyle(element, `color:${adjustedColor.css}`);
return true;
}
return false;
}
private _addStyle(element: HTMLElement, style: string): void {
element.setAttribute('style', `${element.getAttribute('style') || ''}${style};`);
}
}
function padStart(text: string, padChar: string, length: number): string {
while (text.length < length) {
text = padChar + text;
}
return text;
}
@@ -0,0 +1,19 @@
/**
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
* @license MIT
*/
import { ICoreBrowserService } from './Services';
export class CoreBrowserService implements ICoreBrowserService {
serviceBrand: any;
constructor(
private _textarea: HTMLTextAreaElement
) {
}
public get isFocused(): boolean {
return document.activeElement === this._textarea && document.hasFocus();
}
}
+6
View File
@@ -51,6 +51,9 @@ export class RenderService extends Disposable implements IRenderService {
this.register(optionsService.onOptionChange(() => this._renderer.onOptionsChanged()));
this.register(charSizeService.onCharSizeChange(() => this.onCharSizeChanged()));
// No need to register this as renderer is explicitly disposed in RenderService.dispose
this._renderer.onRequestRefreshRows(e => this.refreshRows(e.start, e.end));
// dprchange should handle this case, we need this as well for browsers that don't support the
// matchMedia query.
this.register(addDisposableDomListener(window, 'resize', () => this.onDevicePixelRatioChange()));
@@ -92,6 +95,7 @@ export class RenderService extends Disposable implements IRenderService {
public changeOptions(): void {
this._renderer.onOptionsChanged();
this.refreshRows(0, this._rowCount - 1);
this._fireOnCanvasResize();
}
@@ -105,12 +109,14 @@ export class RenderService extends Disposable implements IRenderService {
public dispose(): void {
this._renderer.dispose();
super.dispose();
}
public setRenderer(renderer: IRenderer): void {
// TODO: RenderService should be the only one to dispose the renderer
this._renderer.dispose();
this._renderer = renderer;
this._renderer.onRequestRefreshRows(e => this.refreshRows(e.start, e.end));
this.refreshRows(0, this._rowCount - 1);
}
+1 -3
View File
@@ -688,9 +688,7 @@ export class SelectionService implements ISelectionService {
// 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.
if (this._trimListener) {
this._trimListener.dispose();
}
this._trimListener.dispose();
this._trimListener = e.activeBuffer.lines.onTrim(amount => this._onTrim(amount));
}
+7
View File
@@ -23,6 +23,13 @@ export interface ICharSizeService {
measure(): void;
}
export const ICoreBrowserService = createDecorator<ICoreBrowserService>('CoreBrowserService');
export interface ICoreBrowserService {
serviceBrand: any;
readonly isFocused: boolean;
}
export const IMouseService = createDecorator<IMouseService>('MouseService');
export interface IMouseService {
serviceBrand: any;
+15 -4
View File
@@ -16,11 +16,13 @@ export interface IEvent<T> {
export interface IEventEmitter<T> {
event: IEvent<T>;
fire(data: T): void;
dispose(): void;
}
export class EventEmitter<T> implements IEventEmitter<T> {
private _listeners: IListener<T>[] = [];
private _event?: IEvent<T>;
private _disposed: boolean = false;
public get event(): IEvent<T> {
if (!this._event) {
@@ -28,10 +30,12 @@ export class EventEmitter<T> implements IEventEmitter<T> {
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;
if (!this._disposed) {
for (let i = 0; i < this._listeners.length; i++) {
if (this._listeners[i] === listener) {
this._listeners.splice(i, 1);
return;
}
}
}
}
@@ -51,4 +55,11 @@ export class EventEmitter<T> implements IEventEmitter<T> {
queue[i].call(undefined, data);
}
}
public dispose(): void {
if (this._listeners) {
this._listeners.length = 0;
}
this._disposed = true;
}
}
+5
View File
@@ -43,12 +43,17 @@ export class MockCoreMouseService implements ICoreMouseService {
}
export class MockCoreService implements ICoreService {
isCursorInitialized: boolean = false;
isCursorHidden: boolean = false;
isFocused: boolean = false;
serviceBrand: any;
decPrivateModes: IDecPrivateModes = {} as any;
onData: IEvent<string> = new EventEmitter<string>().event;
onUserInput: IEvent<void> = new EventEmitter<void>().event;
onBinary: IEvent<string> = new EventEmitter<string>().event;
reset(): void {}
triggerDataEvent(data: string, wasUserInput?: boolean): void {}
triggerBinaryEvent(data: string): void {}
}
export class MockDirtyRowService implements IDirtyRowService {
+3
View File
@@ -249,5 +249,8 @@ export interface ICoreMouseProtocol {
* The tracking encoding can be registered and activated at the CoreMouseService.
* If a ICoreMouseEvent passes all procotol restrictions it will be encoded
* with the active encoding and sent out.
* Note: Returning an empty string will supress sending a mouse report,
* which can be used to skip creating falsey reports in limited encodings
* (DEFAULT only supports up to 223 1-based as coord value).
*/
export type CoreMouseEncoding = (event: ICoreMouseEvent) => string;
@@ -3,11 +3,10 @@
* @license MIT
*/
import { IDisposable } from 'xterm';
import { ITerminal } from './Types';
import { CHAR_DATA_CODE_INDEX, NULL_CELL_CODE, WHITESPACE_CELL_CODE } from 'common/buffer/Constants';
import { IBufferService } from 'common/services/Services';
export function applyWindowsMode(terminal: ITerminal): IDisposable {
export function handleWindowsModeLineFeed(bufferService: IBufferService): void {
// 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
@@ -18,11 +17,11 @@ export function applyWindowsMode(terminal: ITerminal): IDisposable {
// 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);
const line = bufferService.buffer.lines.get(bufferService.buffer.ybase + bufferService.buffer.y - 1);
const lastChar = line?.get(bufferService.cols - 1);
const nextLine = terminal.buffer.lines.get(terminal.buffer.ybase + terminal.buffer.y);
const nextLine = bufferService.buffer.lines.get(bufferService.buffer.ybase + bufferService.buffer.y);
if (nextLine && lastChar) {
nextLine.isWrapped = (lastChar[CHAR_DATA_CODE_INDEX] !== NULL_CELL_CODE && lastChar[CHAR_DATA_CODE_INDEX] !== WHITESPACE_CELL_CODE);
});
}
}
+5 -4
View File
@@ -79,6 +79,7 @@ describe('CoreMouseService', () => {
cms = new CoreMouseService(bufferService, coreService);
reports = [];
coreService.triggerDataEvent = (data: string, userInput?: boolean) => reports.push(data);
coreService.triggerBinaryEvent = (data: string) => reports.push(data);
});
it('NONE', () => {
assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.DOWN }), false);
@@ -143,11 +144,11 @@ describe('CoreMouseService', () => {
cms.activeProtocol = 'ANY';
for (let i = 0; i < bufferService.cols; ++i) {
assert.equal(cms.triggerMouseEvent({ col: i, row: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.DOWN }), true);
// capped at 95
if (i < 95) {
assert.deepEqual(toBytes(reports.pop()), [0x1b, 0x5b, 0x4d, 0x20, i + 33, 0x21]);
if (i > 222) {
// supress mouse reports if we are out of addressible range (max. 222)
assert.deepEqual(toBytes(reports.pop()), []);
} else {
assert.deepEqual(toBytes(reports.pop()), [0x1b, 0x5b, 0x4d, 0x20, 0x7f, 0x21]);
assert.deepEqual(toBytes(reports.pop()), [0x1b, 0x5b, 0x4d, 0x20, i + 33, 0x21]);
}
}
});
+17 -8
View File
@@ -121,15 +121,17 @@ const DEFAULT_ENCODINGS: {[key: string]: CoreMouseEncoding} = {
/**
* DEFAULT - CSI M Pb Px Py
* Single byte encoding for coords and event code.
* Can encode values up to 223. The Encoding of higher
* values is not UTF-8 compatible (and currently limited
* to 95 in xterm.js).
* Can encode values up to 223 (1-based).
*/
DEFAULT: (e: ICoreMouseEvent) => {
let params = [eventCode(e, false) + 32, e.col + 32, e.row + 32];
// FIXME: we are currently limited to ASCII range
params = params.map(v => (v > 127) ? 127 : v);
// FIXED: params = params.map(v => (v > 255) ? 0 : value);
const params = [eventCode(e, false) + 32, e.col + 32, e.row + 32];
// supress mouse report if we exceed addressible range
// Note this is handled differently by emulators
// - xterm: sends 0;0 coords instead
// - vte, konsole: no report
if (params[0] > 255 || params[1] > 255 || params[2] > 255) {
return '';
}
return `\x1b[M${S(params[0])}${S(params[1])}${S(params[2])}`;
},
/**
@@ -266,7 +268,14 @@ export class CoreMouseService implements ICoreMouseService {
// encode report and send
const report = this._encodings[this._activeEncoding](e);
this._coreService.triggerDataEvent(report, true);
if (report) {
// always send DEFAULT as binary data
if (this._activeEncoding === 'DEFAULT') {
this._coreService.triggerBinaryEvent(report);
} else {
this._coreService.triggerDataEvent(report, true);
}
}
this._lastEvent = e;
+32
View File
@@ -0,0 +1,32 @@
/**
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
* @license MIT
*/
import { ICoreService } from 'common/services/Services';
import { CoreService } from 'common/services/CoreService';
import { MockBufferService, MockLogService, MockOptionsService } from 'common/TestUtils.test';
import { assert } from 'chai';
describe('CoreService', () => {
let coreService: ICoreService;
beforeEach(() => {
coreService = new CoreService(
() => {},
new MockBufferService(80, 30),
new MockLogService(),
new MockOptionsService());
});
describe('reset', () => {
it('should not affect isCursorInitialized', () => {
coreService.isCursorInitialized = true;
coreService.reset();
assert.equal(coreService.isCursorInitialized, true);
coreService.isCursorInitialized = false;
coreService.reset();
assert.equal(coreService.isCursorInitialized, false);
});
});
});
+12
View File
@@ -15,12 +15,16 @@ const DEFAULT_DEC_PRIVATE_MODES: IDecPrivateModes = Object.freeze({
export class CoreService implements ICoreService {
serviceBrand: any;
public isCursorInitialized: boolean = false;
public isCursorHidden: boolean = false;
public decPrivateModes: IDecPrivateModes;
private _onData = new EventEmitter<string>();
public get onData(): IEvent<string> { return this._onData.event; }
private _onUserInput = new EventEmitter<void>();
public get onUserInput(): IEvent<void> { return this._onUserInput.event; }
private _onBinary = new EventEmitter<string>();
public get onBinary(): IEvent<string> { return this._onBinary.event; }
constructor(
// TODO: Move this into a service
@@ -57,4 +61,12 @@ export class CoreService implements ICoreService {
this._logService.debug(`sending data "${data}"`, () => data.split('').map(e => e.charCodeAt(0)));
this._onData.fire(data);
}
public triggerBinaryEvent(data: string): void {
if (this._optionsService.options.disableStdin) {
return;
}
this._logService.debug(`sending binary "${data}"`, () => data.split('').map(e => e.charCodeAt(0)));
this._onBinary.fire(data);
}
}
+4 -1
View File
@@ -37,6 +37,7 @@ export const DEFAULT_OPTIONS: ITerminalOptions = Object.freeze({
screenReaderMode: false,
macOptionIsMeta: false,
macOptionClickForcesSelection: false,
minimumContrastRatio: 1,
disableStdin: false,
allowTransparency: false,
tabStopWidth: 8,
@@ -50,7 +51,7 @@ export const DEFAULT_OPTIONS: ITerminalOptions = Object.freeze({
screenKeys: false,
cancelEvents: false,
useFlowControl: false,
wordSeparator: ' ()[]{}\',:;"'
wordSeparator: ' ()[]{}\',:;"`'
});
/**
@@ -116,6 +117,8 @@ export class OptionsService implements IOptionsService {
throw new Error(`${key} cannot be less than 1, value: ${value}`);
}
break;
case 'minimumContrastRatio':
value = Math.max(1, Math.min(21, Math.round(value * 10) / 10));
case 'scrollback':
value = Math.min(value, 4294967295);
if (value < 0) {
+15 -1
View File
@@ -58,10 +58,17 @@ export const ICoreService = createDecorator<ICoreService>('CoreService');
export interface ICoreService {
serviceBrand: any;
/**
* Initially the cursor will not be visible until the first time the terminal
* is focused.
*/
isCursorInitialized: boolean;
isCursorHidden: boolean;
readonly decPrivateModes: IDecPrivateModes;
readonly onData: IEvent<string>;
readonly onUserInput: IEvent<void>;
readonly onBinary: IEvent<string>;
reset(): void;
@@ -72,8 +79,14 @@ export interface ICoreService {
* resulting from parsing incoming data). When true this will also:
* - Scroll to the bottom of the buffer.s
* - Fire the `onUserInput` event (so selection can be cleared).
*/
*/
triggerDataEvent(data: string, wasUserInput?: boolean): void;
/**
* Triggers the onBinary event in the public API.
* @param data The data that is being emitted.
*/
triggerBinaryEvent(data: string): void;
}
export const IDirtyRowService = createDecorator<IDirtyRowService>('DirtyRowService');
@@ -223,6 +236,7 @@ export interface ITerminalOptions {
logLevel: LogLevel;
macOptionIsMeta: boolean;
macOptionClickForcesSelection: boolean;
minimumContrastRatio: number;
rendererType: RendererType;
rightClickSelectsWord: boolean;
rows: number;
+1
View File
@@ -27,6 +27,7 @@ export class Terminal implements ITerminalApi {
public get onLineFeed(): IEvent<void> { return this._core.onLineFeed; }
public get onSelectionChange(): IEvent<void> { return this._core.onSelectionChange; }
public get onData(): IEvent<string> { return this._core.onData; }
public get onBinary(): IEvent<string> { return this._core.onBinary; }
public get onTitleChange(): IEvent<string> { return this._core.onTitleChange; }
public get onScroll(): IEvent<number> { return this._core.onScroll; }
public get onKey(): IEvent<{ key: string, domEvent: KeyboardEvent }> { return this._core.onKey; }
+7 -10
View File
@@ -4,8 +4,8 @@
*/
import * as puppeteer from 'puppeteer';
import { assert } from 'chai';
import { ITerminalOptions } from 'xterm';
import { pollFor } from './TestUtils';
const APP = 'http://127.0.0.1:3000/test';
@@ -15,12 +15,9 @@ const width = 800;
const height = 600;
describe('CharWidth 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}`, `--no-sandbox`]
});
page = (await browser.pages())[0];
@@ -41,37 +38,37 @@ describe('CharWidth Integration Tests', function(): void {
it('ASCII chars', async function(): Promise<void> {
const input = 'This is just ASCII text.#';
await page.evaluate(`window.term.write('${input}')`);
assert.equal(25, await sumWidths(0, 1, '#'));
await pollFor(page, () => sumWidths(0, 1, '#'), 25);
});
it('combining chars', async function(): Promise<void> {
const input = 'e\u0301e\u0301e\u0301e\u0301e\u0301e\u0301e\u0301e\u0301e\u0301#';
await page.evaluate(`window.term.write('${input}')`);
assert.equal(10, await sumWidths(0, 1, '#'));
await pollFor(page, () => sumWidths(0, 1, '#'), 10);
});
it('surrogate chars', async function(): Promise<void> {
const input = '𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞#';
await page.evaluate(`window.term.write('${input}')`);
assert.equal(28, await sumWidths(0, 1, '#'));
await pollFor(page, () => sumWidths(0, 1, '#'), 28);
});
it('surrogate combining chars', async function(): Promise<void> {
const input = '𓂀\u0301𓂀\u0301𓂀\u0301𓂀\u0301𓂀\u0301𓂀\u0301𓂀\u0301𓂀\u0301𓂀\u0301𓂀\u0301𓂀\u0301#';
await page.evaluate(`window.term.write('${input}')`);
assert.equal(12, await sumWidths(0, 1, '#'));
await pollFor(page, () => sumWidths(0, 1, '#'), 12);
});
it('fullwidth chars', async function(): Promise<void> {
const input = '1234567890#';
await page.evaluate(`window.term.write('${input}')`);
assert.equal(21, await sumWidths(0, 1, '#'));
await pollFor(page, () => sumWidths(0, 1, '#'), 21);
});
it('fullwidth chars offset 1', async function(): Promise<void> {
const input = 'a1234567890#';
await page.evaluate(`window.term.write('${input}')`);
assert.equal(22, await sumWidths(0, 1, '#'));
await pollFor(page, () => sumWidths(0, 1, '#'), 22);
});
// TODO: multiline tests once #1685 is resolved
+43 -44
View File
@@ -6,6 +6,7 @@
import * as puppeteer from 'puppeteer';
import { assert } from 'chai';
import { ITerminalOptions } from 'xterm';
import { pollFor } from './TestUtils';
const APP = 'http://127.0.0.1:3000/test';
@@ -15,12 +16,9 @@ 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}`, `--no-sandbox`]
});
page = (await browser.pages())[0];
@@ -45,7 +43,7 @@ describe('InputHandler Integration Tests', function(): void {
// Explicit
window.term.write('bar\\x1b[3D\\x1b[4@')
`);
assert.deepEqual(await getLinesAsArray(2), [' foo', ' bar']);
await pollFor(page, () => getLinesAsArray(2), [' foo', ' bar']);
});
it('CUU: Cursor Up Ps Times (default = 1) - CSI Ps A', async function(): Promise<any> {
@@ -55,7 +53,7 @@ describe('InputHandler Integration Tests', function(): void {
// Explicit
window.term.write('\x1b[2Ab')
`);
assert.deepEqual(await getLinesAsArray(4), ['', ' b', '', 'a']);
await pollFor(page, () => getLinesAsArray(4), ['', ' b', '', 'a']);
});
it('CUD: Cursor Down Ps Times (default = 1) - CSI Ps B', async function(): Promise<any> {
@@ -65,7 +63,7 @@ describe('InputHandler Integration Tests', function(): void {
// Explicit
window.term.write('\x1b[2Bb')
`);
assert.deepEqual(await getLinesAsArray(4), ['', 'a', '', ' b']);
await pollFor(page, () => getLinesAsArray(4), ['', 'a', '', ' b']);
});
it('CUF: Cursor Forward Ps Times (default = 1) - CSI Ps C', async function(): Promise<any> {
@@ -75,7 +73,7 @@ describe('InputHandler Integration Tests', function(): void {
// Explicit
window.term.write('\x1b[2Cb')
`);
assert.deepEqual(await getLinesAsArray(1), [' a b']);
await pollFor(page, () => getLinesAsArray(1), [' a b']);
});
it('CUB: Cursor Backward Ps Times (default = 1) - CSI Ps D', async function(): Promise<any> {
@@ -85,7 +83,7 @@ describe('InputHandler Integration Tests', function(): void {
// Explicit
window.term.write('\x1b[2Db')
`);
assert.deepEqual(await getLinesAsArray(1), ['fba']);
await pollFor(page, () => getLinesAsArray(1), ['fba']);
});
it('CNL: Cursor Next Line Ps Times (default = 1) - CSI Ps E', async function(): Promise<any> {
@@ -95,7 +93,7 @@ describe('InputHandler Integration Tests', function(): void {
// Explicit
window.term.write('\x1b[2Eb')
`);
assert.deepEqual(await getLinesAsArray(4), ['', 'a', '', 'b']);
await pollFor(page, () => getLinesAsArray(4), ['', 'a', '', 'b']);
});
it('CPL: Cursor Preceding Line Ps Times (default = 1) - CSI Ps F', async function(): Promise<any> {
@@ -105,7 +103,7 @@ describe('InputHandler Integration Tests', function(): void {
// Explicit
window.term.write('\x1b[2Fb')
`);
assert.deepEqual(await getLinesAsArray(5), ['', 'b', '', 'a', '']);
await pollFor(page, () => getLinesAsArray(5), ['', 'b', '', 'a', '']);
});
it('CHA: Cursor Character Absolute [column] (default = [row,1]) - CSI Ps G', async function(): Promise<any> {
@@ -115,7 +113,7 @@ describe('InputHandler Integration Tests', function(): void {
// Explicit
window.term.write('\x1b[10Gb')
`);
assert.deepEqual(await getLinesAsArray(1), ['aoo b']);
await pollFor(page, () => getLinesAsArray(1), ['aoo b']);
});
it('CUP: Cursor Position [row;column] (default = [1,1]) - CSI Ps ; Ps H', async function(): Promise<any> {
@@ -125,7 +123,7 @@ describe('InputHandler Integration Tests', function(): void {
// Explicit
window.term.write('\x1b[3;3Hb')
`);
assert.deepEqual(await getLinesAsArray(3), ['aoo', '', ' b']);
await pollFor(page, () => getLinesAsArray(3), ['aoo', '', ' b']);
});
it('CHT: Cursor Forward Tabulation Ps tab stops (default = 1) - CSI Ps I', async function(): Promise<any> {
@@ -135,7 +133,7 @@ describe('InputHandler Integration Tests', function(): void {
// Explicit
window.term.write('\\n\\r\x1b[2Ib')
`);
assert.deepEqual(await getLinesAsArray(2), [' a', ' b']);
await pollFor(page, () => getLinesAsArray(2), [' a', ' b']);
});
it('ED: Erase in Display, VT100 - CSI Ps J', async function(): Promise<any> {
@@ -145,26 +143,26 @@ describe('InputHandler Integration Tests', function(): void {
window.term.resize(5, 5);
window.term.write('${fixture}\x1b[J')
`);
assert.deepEqual(await getLinesAsArray(3), ['abc', 'd', '']);
await pollFor(page, () => 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 pollFor(page, () => 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 pollFor(page, () => 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']);
await pollFor(page, () => page.evaluate(`window.term.buffer.length`), 5);
await pollFor(page, () => getLinesAsArray(5), [' 4', ' 5', 'abc', 'def', 'ghi']);
});
it('DECSED: Erase in Display, VT220 - CSI ? Ps J', async function(): Promise<any> {
@@ -174,26 +172,26 @@ describe('InputHandler Integration Tests', function(): void {
window.term.resize(5, 5);
window.term.write('${fixture}\x1b[?J')
`);
assert.deepEqual(await getLinesAsArray(3), ['abc', 'd', '']);
await pollFor(page, () => 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 pollFor(page, () => 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 pollFor(page, () => 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']);
await pollFor(page, () => page.evaluate(`window.term.buffer.length`), 5);
await pollFor(page, () => getLinesAsArray(5), [' 4', ' 5', 'abc', 'def', 'ghi']);
});
it('IL: Insert Ps Line(s) (default = 1) - CSI Ps L', async function(): Promise<any> {
@@ -203,7 +201,7 @@ describe('InputHandler Integration Tests', function(): void {
// Explicit
window.term.write('\x1b[2Lb')
`);
assert.deepEqual(await getLinesAsArray(4), ['b', '', 'a', 'foo']);
await pollFor(page, () => getLinesAsArray(4), ['b', '', 'a', 'foo']);
});
it('DL: Delete Ps Line(s) (default = 1) - CSI Ps M', async function(): Promise<any> {
@@ -213,7 +211,7 @@ describe('InputHandler Integration Tests', function(): void {
// Explicit
window.term.write('\x1b[1Ed\\ne\\nf\x1b[2F\x1b[2M')
`);
assert.deepEqual(await getLinesAsArray(5), [' b', ' f', '', '', '']);
await pollFor(page, () => getLinesAsArray(5), [' b', ' f', '', '', '']);
});
it('DCH: Delete Ps Character(s) (default = 1) - CSI Ps P', async function(): Promise<any> {
@@ -223,7 +221,7 @@ describe('InputHandler Integration Tests', function(): void {
// Explicit
window.term.write('\\n\\rdef\x1b[2;1H\x1b[2P')
`);
assert.deepEqual(await getLinesAsArray(2), ['bc', 'f']);
await pollFor(page, () => getLinesAsArray(2), ['bc', 'f']);
});
describe('DSR: Device Status Report', () => {
@@ -232,31 +230,31 @@ describe('InputHandler Integration Tests', function(): void {
window.term.onData(e => window.result = e);
window.term.write('\\x1b[5n');
`);
assert.equal(await page.evaluate(`window.result`), '\x1b[0n');
await pollFor(page, () => 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(`
await pollFor(page, () => 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');
await pollFor(page, () => 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(`
await pollFor(page, () => 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');
await pollFor(page, () => page.evaluate(`window.result`), '\x1b[?3;4R');
});
});
@@ -277,22 +275,23 @@ describe('InputHandler Integration Tests', function(): void {
await page.mouse.up();
// Clear selection
await page.mouse.click((coords.left + coords.right) / 2, (coords.top + coords.bottom) / 2);
assert.equal(await page.evaluate(`window.term.getSelection().length`), 0);
await pollFor(page, () => page.evaluate(`window.term.getSelection().length`), 0);
// Enable mouse events
await page.evaluate(`window.term.write('\x1b[?1003h')`);
// Click and drag and ensure there is no selection
await page.mouse.click((coords.left + coords.right) / 2, (coords.top + coords.bottom) / 2);
await page.mouse.down();
await page.mouse.move((coords.left + coords.right) / 2, (coords.top + coords.bottom) / 4);
assert.equal(await page.evaluate(`window.term.getSelection().length`), 0, 'mouse events are on so there should be no selection');
// mouse events are on so there should be no selection
await pollFor(page, () => page.evaluate(`window.term.getSelection().length`), 0);
await page.mouse.up();
});
it('Pm = 2004, Set bracketed paste mode', async function(): Promise<any> {
assert.equal(await simulatePaste('foo'), 'foo');
await pollFor(page, () => simulatePaste('foo'), 'foo');
await page.evaluate(`window.term.write('\x1b[?2004h')`);
assert.equal(await simulatePaste('bar'), '\x1b[200~bar\x1b[201~');
await pollFor(page, () => simulatePaste('bar'), '\x1b[200~bar\x1b[201~');
await page.evaluate(`window.term.write('\x1b[?2004l')`);
assert.equal(await simulatePaste('baz'), 'baz');
await pollFor(page, () => simulatePaste('baz'), 'baz');
});
});
});
@@ -309,32 +308,32 @@ describe('InputHandler Integration Tests', function(): void {
window.term.writeln('');
window.term.write('#\x1b[5b');
`);
assert.deepEqual(await getLinesAsArray(4), ['##', '##', '##', '######']);
assert.deepEqual(await getCursor(), {col: 6, row: 3});
await pollFor(page, () => getLinesAsArray(4), ['##', '##', '##', '######']);
await pollFor(page, () => getCursor(), {col: 6, row: 3});
// should not repeat on fullwidth chars
await page.evaluate(`
window.term.reset();
window.term.write('¥\x1b[10b');
`);
assert.deepEqual(await getLinesAsArray(1), ['¥']);
await pollFor(page, () => getLinesAsArray(1), ['¥']);
// should repeat only base char of combining
await page.evaluate(`
window.term.reset();
window.term.write('e\u0301\x1b[5b');
`);
assert.deepEqual(await getLinesAsArray(1), ['e\u0301eeeee']);
await pollFor(page, () => getLinesAsArray(1), ['e\u0301eeeee']);
// should wrap correctly
await page.evaluate(`
window.term.reset();
window.term.write('#\x1b[15b');
`);
assert.deepEqual(await getLinesAsArray(2), ['##########', '######']);
await pollFor(page, () => getLinesAsArray(2), ['##########', '######']);
await page.evaluate(`
window.term.reset();
window.term.write('\x1b[?7l'); // disable wrap around
window.term.write('#\x1b[15b');
`);
assert.deepEqual(await getLinesAsArray(2), ['##########', '']);
await pollFor(page, () => getLinesAsArray(2), ['##########', '']);
// any successful sequence should reset REP
await page.evaluate(`
window.term.reset();
@@ -344,7 +343,7 @@ describe('InputHandler Integration Tests', function(): void {
window.term.writeln('');
window.term.write('abcdefg\x1b[3D\x1b[10b#\x1b[3b');
`);
assert.deepEqual(await getLinesAsArray(3), ['#', ' #', 'abcd####']);
await pollFor(page, () => getLinesAsArray(3), ['#', ' #', 'abcd####']);
});
});
@@ -360,7 +359,7 @@ describe('InputHandler Integration Tests', function(): void {
window.term.resize(10, 4);
window.term.write('\\x1b[?47l\\x1b8');
`);
assert.deepEqual(await getCursor(), {col: 1, row: 3});
await pollFor(page, () => getCursor(), {col: 1, row: 3});
});
});
});
+154 -156
View File
@@ -4,8 +4,8 @@
*/
import * as puppeteer from 'puppeteer';
import { assert } from 'chai';
import { ITerminalOptions } from 'xterm';
import { pollFor, writeSync } from './TestUtils';
const APP = 'http://127.0.0.1:3000/test';
@@ -198,22 +198,17 @@ function parseReport(encoding: string, msg: number[]): {state: any; row: number;
/**
* Mouse tracking tests.
*/
describe('Mouse Tracking Tests', function(): void {
this.timeout(60000);
before(async function(): Promise<any> {
describe('Mouse Tracking Tests', () => {
before(async function(): Promise<void> {
browser = await puppeteer.launch({
headless: process.argv.indexOf('--headless') !== -1,
slowMo: 80,
args: [`--window-size=${width},${height}`, `--no-sandbox`]
});
page = (await browser.pages())[0];
await page.setViewport({ width, height });
});
after(() => {
browser.close();
});
after(async () => browser.close());
beforeEach(async () => {
await page.goto(APP);
@@ -225,6 +220,7 @@ describe('Mouse Tracking Tests', function(): void {
await page.evaluate(`
window.calls = [];
window.term.onData(e => calls.push( Array.from(e).map(el => el.charCodeAt(0)) ));
window.term.onBinary(e => calls.push( Array.from(e).map(el => el.charCodeAt(0)) ));
window.term.setOption('fontSize', ${fontSize});
window.term.resize(${cols}, ${rows});
`);
@@ -242,30 +238,35 @@ describe('Mouse Tracking Tests', function(): void {
const encoding = 'DEFAULT';
await resetMouseModes();
await mouseMove(0, 0);
await page.evaluate(`window.term.write('\x1b[?9h');`);
await writeSync(page, '\x1b[?9h');
// test at 0,0
await mouseDown('left');
assert.deepEqual(await getReports(encoding), [{col: 1, row: 1, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]);
await pollFor(page, () => getReports(encoding), [{col: 1, row: 1, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]);
// mouseup should not report
await mouseUp('left');
assert.deepEqual(await getReports(encoding), []);
await pollFor(page, () => getReports(encoding), []);
// mousemove should not report
await mouseMove(50, 10);
assert.deepEqual(await getReports(encoding), []);
await pollFor(page, () => getReports(encoding), []);
await mouseDown('left');
await mouseUp('left');
assert.deepEqual(await getReports(encoding), [{col: 51, row: 11, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]);
await pollFor(page, () => getReports(encoding), [{col: 51, row: 11, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]);
// test at max rows/cols
// bug: we are capped at col 95 currently
// fix: allow values up to 223, any bigger should drop to 0
await mouseMove(cols - 1, rows - 1);
// capped at 223 (1-based)
await mouseMove(223 - 1, rows - 1);
await mouseDown('left');
await mouseUp('left');
assert.deepEqual(await getReports(encoding), [{col: 95, row: rows, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]);
await pollFor(page, () => getReports(encoding), [{col: 223, row: rows, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]);
// higher than 223 should not report at all
await mouseMove(257, rows - 1);
await mouseDown('left');
await mouseUp('left');
await pollFor(page, () => getReports(encoding), []);
// button press/move/release tests
// left button
@@ -274,7 +275,7 @@ describe('Mouse Tracking Tests', function(): void {
await mouseDown('left');
await mouseMove(44, 24);
await mouseUp('left');
assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]);
await pollFor(page, () => getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]);
// middle button
// bug: default action not cancelled (adds data to getReports from clipboard under X11)
// await mouseMove(43, 24);
@@ -282,7 +283,7 @@ describe('Mouse Tracking Tests', function(): void {
// await mouseDown('middle');
// await mouseMove(44, 24);
// await mouseUp('middle');
// assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'middle', modifier: {control: false, shift: false, meta: false}}}]);
// await pollFor(page, () => getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'middle', modifier: {control: false, shift: false, meta: false}}}]);
// right button
// bug: default action not cancelled (popup shown)
await mouseMove(43, 24);
@@ -290,15 +291,15 @@ describe('Mouse Tracking Tests', function(): void {
await mouseDown('right');
await mouseMove(44, 24);
await mouseUp('right');
assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'right', modifier: {control: false, shift: false, meta: false}}}]);
await pollFor(page, () => getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'right', modifier: {control: false, shift: false, meta: false}}}]);
// wheel
await mouseMove(43, 24);
await getReports(encoding); // clear reports
await wheelUp();
assert.deepEqual(await getReports(encoding), []);
await pollFor(page, () => getReports(encoding), []);
await wheelDown();
assert.deepEqual(await getReports(encoding), []);
await pollFor(page, () => getReports(encoding), []);
// modifiers
// CTRL
@@ -310,7 +311,7 @@ describe('Mouse Tracking Tests', function(): void {
await mouseUp('left');
await wheelDown();
await page.keyboard.up('Control');
assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]);
await pollFor(page, () => getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]);
// ALT
@@ -322,7 +323,7 @@ describe('Mouse Tracking Tests', function(): void {
await mouseUp('left');
await wheelDown();
await page.keyboard.up('Alt');
assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]);
await pollFor(page, () => getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]);
// SHIFT
// note: caught by selection manager
@@ -337,9 +338,9 @@ describe('Mouse Tracking Tests', function(): void {
await wheelDown();
await page.keyboard.up('Shift');
if (noShift) {
assert.deepEqual(await getReports(encoding), []);
await pollFor(page, () => getReports(encoding), []);
} else {
assert.deepEqual(await getReports(encoding), [
await pollFor(page, () => getReports(encoding), [
{col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}
]);
}
@@ -358,34 +359,34 @@ describe('Mouse Tracking Tests', function(): void {
await page.keyboard.up('Control');
await page.keyboard.up('Alt');
// await page.keyboard.up('Shift');
assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]);
await pollFor(page, () => getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]);
});
it('SGR encoding', async () => {
const encoding = 'SGR';
await resetMouseModes();
await mouseMove(0, 0);
await page.evaluate(`window.term.write('\x1b[?9h\x1b[?1006h');`);
await writeSync(page, '\x1b[?9h\x1b[?1006h');
// test at 0,0
await mouseDown('left');
assert.deepEqual(await getReports(encoding), [{col: 1, row: 1, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]);
await pollFor(page, () => getReports(encoding), [{col: 1, row: 1, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]);
// mouseup should not report
await mouseUp('left');
assert.deepEqual(await getReports(encoding), []);
await pollFor(page, () => getReports(encoding), []);
// mousemove should not report
await mouseMove(50, 10);
assert.deepEqual(await getReports(encoding), []);
await pollFor(page, () => getReports(encoding), []);
await mouseDown('left');
await mouseUp('left');
assert.deepEqual(await getReports(encoding), [{col: 51, row: 11, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]);
await pollFor(page, () => getReports(encoding), [{col: 51, row: 11, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]);
// test at max rows/cols
await mouseMove(cols - 1, rows - 1);
await mouseDown('left');
await mouseUp('left');
assert.deepEqual(await getReports(encoding), [{col: cols, row: rows, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]);
await pollFor(page, () => getReports(encoding), [{col: cols, row: rows, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]);
// button press/move/release tests
// left button
@@ -394,7 +395,7 @@ describe('Mouse Tracking Tests', function(): void {
await mouseDown('left');
await mouseMove(44, 24);
await mouseUp('left');
assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]);
await pollFor(page, () => getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]);
// middle button
// bug: default action not cancelled (adds data to getReports from clipboard under X11)
// await mouseMove(43, 24);
@@ -402,7 +403,7 @@ describe('Mouse Tracking Tests', function(): void {
// await mouseDown('middle');
// await mouseMove(44, 24);
// await mouseUp('middle');
// assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'middle', modifier: {control: false, shift: false, meta: false}}}]);
// await pollFor(page, () => getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'middle', modifier: {control: false, shift: false, meta: false}}}]);
// right button
// bug: default action not cancelled (popup shown)
await mouseMove(43, 24);
@@ -410,15 +411,15 @@ describe('Mouse Tracking Tests', function(): void {
await mouseDown('right');
await mouseMove(44, 24);
await mouseUp('right');
assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'right', modifier: {control: false, shift: false, meta: false}}}]);
await pollFor(page, () => getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'right', modifier: {control: false, shift: false, meta: false}}}]);
// wheel
await mouseMove(43, 24);
await getReports(encoding); // clear reports
await wheelUp();
assert.deepEqual(await getReports(encoding), []);
await pollFor(page, () => getReports(encoding), []);
await wheelDown();
assert.deepEqual(await getReports(encoding), []);
await pollFor(page, () => getReports(encoding), []);
// modifiers
// CTRL
@@ -430,7 +431,7 @@ describe('Mouse Tracking Tests', function(): void {
await mouseUp('left');
await wheelDown();
await page.keyboard.up('Control');
assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]);
await pollFor(page, () => getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]);
// ALT
@@ -442,7 +443,7 @@ describe('Mouse Tracking Tests', function(): void {
await mouseUp('left');
await wheelDown();
await page.keyboard.up('Alt');
assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]);
await pollFor(page, () => getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]);
// SHIFT
// note: caught by selection manager
@@ -455,9 +456,9 @@ describe('Mouse Tracking Tests', function(): void {
await wheelDown();
await page.keyboard.up('Shift');
if (noShift) {
assert.deepEqual(await getReports(encoding), []);
await pollFor(page, () => getReports(encoding), []);
} else {
assert.deepEqual(await getReports(encoding), [
await pollFor(page, () => getReports(encoding), [
{col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}
]);
}
@@ -476,7 +477,7 @@ describe('Mouse Tracking Tests', function(): void {
await page.keyboard.up('Control');
await page.keyboard.up('Alt');
// await page.keyboard.up('Shift');
assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]);
await pollFor(page, () => getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]);
});
});
describe('DECSET 1000 (VT200 mouse)', () => {
@@ -491,39 +492,38 @@ describe('Mouse Tracking Tests', function(): void {
const encoding = 'DEFAULT';
await resetMouseModes();
await mouseMove(0, 0);
await page.evaluate(`window.term.write('\x1b[?1000h');`);
await writeSync(page, '\x1b[?1000h');
// test at 0,0
await mouseDown('left');
assert.deepEqual(await getReports(encoding), [
await pollFor(page, () => getReports(encoding), [
{col: 1, row: 1, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}
]);
// mouseup should report, encoding cannot report released button
await mouseUp('left');
assert.deepEqual(await getReports(encoding), [
await pollFor(page, () => getReports(encoding), [
{col: 1, row: 1, state: {action: 'release', button: '<none>', modifier: {control: false, shift: false, meta: false}}}
]);
// mousemove should not report
await mouseMove(50, 10);
assert.deepEqual(await getReports(encoding), []);
await pollFor(page, () => getReports(encoding), []);
await mouseDown('left');
await mouseUp('left');
assert.deepEqual(await getReports(encoding), [
await pollFor(page, () => getReports(encoding), [
{col: 51, row: 11, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}},
{col: 51, row: 11, state: {action: 'release', button: '<none>', modifier: {control: false, shift: false, meta: false}}}
]);
// test at max rows/cols
// bug: we are capped at col 95 currently
// fix: allow values up to 223, any bigger should drop to 0
await mouseMove(cols - 1, rows - 1);
// capped at 223 (1-based)
await mouseMove(223 - 1, rows - 1);
await mouseDown('left');
await mouseUp('left');
assert.deepEqual(await getReports(encoding), [
{col: 95, row: rows, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}},
{col: 95, row: rows, state: {action: 'release', button: '<none>', modifier: {control: false, shift: false, meta: false}}}
await pollFor(page, () => getReports(encoding), [
{col: 223, row: rows, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}},
{col: 223, row: rows, state: {action: 'release', button: '<none>', modifier: {control: false, shift: false, meta: false}}}
]);
// button press/move/release tests
@@ -533,7 +533,7 @@ describe('Mouse Tracking Tests', function(): void {
await mouseDown('left');
await mouseMove(44, 24);
await mouseUp('left');
assert.deepEqual(await getReports(encoding), [
await pollFor(page, () => getReports(encoding), [
{col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}},
{col: 45, row: 25, state: {action: 'release', button: '<none>', modifier: {control: false, shift: false, meta: false}}}
]);
@@ -544,7 +544,7 @@ describe('Mouse Tracking Tests', function(): void {
// await mouseDown('middle');
// await mouseMove(44, 24);
// await mouseUp('middle');
// assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'middle', modifier: {control: false, shift: false, meta: false}}}]);
// await pollFor(page, () => getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'middle', modifier: {control: false, shift: false, meta: false}}}]);
// right button
// bug: default action not cancelled (popup shown)
await mouseMove(43, 24);
@@ -552,7 +552,7 @@ describe('Mouse Tracking Tests', function(): void {
await mouseDown('right');
await mouseMove(44, 24);
await mouseUp('right');
assert.deepEqual(await getReports(encoding), [
await pollFor(page, () => getReports(encoding), [
{col: 44, row: 25, state: {action: 'press', button: 'right', modifier: {control: false, shift: false, meta: false}}},
{col: 45, row: 25, state: {action: 'release', button: '<none>', modifier: {control: false, shift: false, meta: false}}}
]);
@@ -561,9 +561,9 @@ describe('Mouse Tracking Tests', function(): void {
await mouseMove(43, 24);
await getReports(encoding); // clear reports
await wheelUp();
assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'up', button: 'wheel', modifier: {control: false, shift: false, meta: false}}}]);
await pollFor(page, () => getReports(encoding), [{col: 44, row: 25, state: {action: 'up', button: 'wheel', modifier: {control: false, shift: false, meta: false}}}]);
await wheelDown();
assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: false}}}]);
await pollFor(page, () => getReports(encoding), [{col: 44, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: false}}}]);
// modifiers
// CTRL
@@ -575,7 +575,7 @@ describe('Mouse Tracking Tests', function(): void {
await mouseUp('left');
await wheelDown();
await page.keyboard.up('Control');
assert.deepEqual(await getReports(encoding), [
await pollFor(page, () => getReports(encoding), [
{col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: true, shift: false, meta: false}}},
{col: 45, row: 25, state: {action: 'release', button: '<none>', modifier: {control: true, shift: false, meta: false}}},
{col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: true, shift: false, meta: false}}}
@@ -590,7 +590,7 @@ describe('Mouse Tracking Tests', function(): void {
await mouseUp('left');
await wheelDown();
await page.keyboard.up('Alt');
assert.deepEqual(await getReports(encoding), [
await pollFor(page, () => getReports(encoding), [
{col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: true}}},
{col: 45, row: 25, state: {action: 'release', button: '<none>', modifier: {control: false, shift: false, meta: true}}},
{col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: true}}}
@@ -608,11 +608,11 @@ describe('Mouse Tracking Tests', function(): void {
await wheelDown();
await page.keyboard.up('Shift');
if (noShift) {
assert.deepEqual(await getReports(encoding), [
await pollFor(page, () => getReports(encoding), [
{col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: true, meta: false}}}
]);
} else {
assert.deepEqual(await getReports(encoding), [
await pollFor(page, () => getReports(encoding), [
{col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: true, meta: false}}},
{col: 45, row: 25, state: {action: 'release', button: '<none>', modifier: {control: false, shift: true, meta: false}}},
{col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: true, meta: false}}}
@@ -633,7 +633,7 @@ describe('Mouse Tracking Tests', function(): void {
await page.keyboard.up('Control');
await page.keyboard.up('Alt');
// await page.keyboard.up('Shift');
assert.deepEqual(await getReports(encoding), [
await pollFor(page, () => getReports(encoding), [
{col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: true, shift: false, meta: true}}},
{col: 45, row: 25, state: {action: 'release', button: '<none>', modifier: {control: true, shift: false, meta: true}}},
{col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: true, shift: false, meta: true}}}
@@ -643,26 +643,26 @@ describe('Mouse Tracking Tests', function(): void {
const encoding = 'SGR';
await resetMouseModes();
await mouseMove(0, 0);
await page.evaluate(`window.term.write('\x1b[?1000h\x1b[?1006h');`);
await writeSync(page, '\x1b[?1000h\x1b[?1006h');
// test at 0,0
await mouseDown('left');
assert.deepEqual(await getReports(encoding), [
await pollFor(page, () => getReports(encoding), [
{col: 1, row: 1, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}
]);
// mouseup should report
await mouseUp('left');
assert.deepEqual(await getReports(encoding), [
await pollFor(page, () => getReports(encoding), [
{col: 1, row: 1, state: {action: 'release', button: 'left', modifier: {control: false, shift: false, meta: false}}}
]);
// mousemove should not report
await mouseMove(50, 10);
assert.deepEqual(await getReports(encoding), []);
await pollFor(page, () => getReports(encoding), []);
await mouseDown('left');
await mouseUp('left');
assert.deepEqual(await getReports(encoding), [
await pollFor(page, () => getReports(encoding), [
{col: 51, row: 11, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}},
{col: 51, row: 11, state: {action: 'release', button: 'left', modifier: {control: false, shift: false, meta: false}}}
]);
@@ -671,7 +671,7 @@ describe('Mouse Tracking Tests', function(): void {
await mouseMove(cols - 1, rows - 1);
await mouseDown('left');
await mouseUp('left');
assert.deepEqual(await getReports(encoding), [
await pollFor(page, () => getReports(encoding), [
{col: cols, row: rows, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}},
{col: cols, row: rows, state: {action: 'release', button: 'left', modifier: {control: false, shift: false, meta: false}}}
]);
@@ -683,7 +683,7 @@ describe('Mouse Tracking Tests', function(): void {
await mouseDown('left');
await mouseMove(44, 24);
await mouseUp('left');
assert.deepEqual(await getReports(encoding), [
await pollFor(page, () => getReports(encoding), [
{col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}},
{col: 45, row: 25, state: {action: 'release', button: 'left', modifier: {control: false, shift: false, meta: false}}}
]);
@@ -694,7 +694,7 @@ describe('Mouse Tracking Tests', function(): void {
// await mouseDown('middle');
// await mouseMove(44, 24);
// await mouseUp('middle');
// assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'middle', modifier: {control: false, shift: false, meta: false}}}]);
// await pollFor(page, () => getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'middle', modifier: {control: false, shift: false, meta: false}}}]);
// right button
// bug: default action not cancelled (popup shown)
await mouseMove(43, 24);
@@ -702,7 +702,7 @@ describe('Mouse Tracking Tests', function(): void {
await mouseDown('right');
await mouseMove(44, 24);
await mouseUp('right');
assert.deepEqual(await getReports(encoding), [
await pollFor(page, () => getReports(encoding), [
{col: 44, row: 25, state: {action: 'press', button: 'right', modifier: {control: false, shift: false, meta: false}}},
{col: 45, row: 25, state: {action: 'release', button: 'right', modifier: {control: false, shift: false, meta: false}}}
]);
@@ -711,9 +711,9 @@ describe('Mouse Tracking Tests', function(): void {
await mouseMove(43, 24);
await getReports(encoding); // clear reports
await wheelUp();
assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'up', button: 'wheel', modifier: {control: false, shift: false, meta: false}}}]);
await pollFor(page, () => getReports(encoding), [{col: 44, row: 25, state: {action: 'up', button: 'wheel', modifier: {control: false, shift: false, meta: false}}}]);
await wheelDown();
assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: false}}}]);
await pollFor(page, () => getReports(encoding), [{col: 44, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: false}}}]);
// modifiers
// CTRL
@@ -725,7 +725,7 @@ describe('Mouse Tracking Tests', function(): void {
await mouseUp('left');
await wheelDown();
await page.keyboard.up('Control');
assert.deepEqual(await getReports(encoding), [
await pollFor(page, () => getReports(encoding), [
{col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: true, shift: false, meta: false}}},
{col: 45, row: 25, state: {action: 'release', button: 'left', modifier: {control: true, shift: false, meta: false}}},
{col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: true, shift: false, meta: false}}}
@@ -740,7 +740,7 @@ describe('Mouse Tracking Tests', function(): void {
await mouseUp('left');
await wheelDown();
await page.keyboard.up('Alt');
assert.deepEqual(await getReports(encoding), [
await pollFor(page, () => getReports(encoding), [
{col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: true}}},
{col: 45, row: 25, state: {action: 'release', button: 'left', modifier: {control: false, shift: false, meta: true}}},
{col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: true}}}
@@ -757,11 +757,11 @@ describe('Mouse Tracking Tests', function(): void {
await wheelDown();
await page.keyboard.up('Shift');
if (noShift) {
assert.deepEqual(await getReports(encoding), [
await pollFor(page, () => getReports(encoding), [
{col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: true, meta: false}}}
]);
} else {
assert.deepEqual(await getReports(encoding), [
await pollFor(page, () => getReports(encoding), [
{col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: true, meta: false}}},
{col: 45, row: 25, state: {action: 'release', button: 'left', modifier: {control: false, shift: true, meta: false}}},
{col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: true, meta: false}}}
@@ -782,7 +782,7 @@ describe('Mouse Tracking Tests', function(): void {
await page.keyboard.up('Control');
await page.keyboard.up('Alt');
// await page.keyboard.up('Shift');
assert.deepEqual(await getReports(encoding), [
await pollFor(page, () => getReports(encoding), [
{col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: true, shift: false, meta: true}}},
{col: 45, row: 25, state: {action: 'release', button: 'left', modifier: {control: true, shift: false, meta: true}}},
{col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: true, shift: false, meta: true}}}
@@ -801,39 +801,38 @@ describe('Mouse Tracking Tests', function(): void {
const encoding = 'DEFAULT';
await resetMouseModes();
await mouseMove(0, 0);
await page.evaluate(`window.term.write('\x1b[?1002h');`);
await writeSync(page, '\x1b[?1002h');
// test at 0,0
await mouseDown('left');
assert.deepEqual(await getReports(encoding), [
await pollFor(page, () => getReports(encoding), [
{col: 1, row: 1, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}
]);
// mouseup should report, encoding cannot report released button
await mouseUp('left');
assert.deepEqual(await getReports(encoding), [
await pollFor(page, () => getReports(encoding), [
{col: 1, row: 1, state: {action: 'release', button: '<none>', modifier: {control: false, shift: false, meta: false}}}
]);
// mousemove should not report
await mouseMove(50, 10);
assert.deepEqual(await getReports(encoding), []);
await pollFor(page, () => getReports(encoding), []);
await mouseDown('left');
await mouseUp('left');
assert.deepEqual(await getReports(encoding), [
await pollFor(page, () => getReports(encoding), [
{col: 51, row: 11, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}},
{col: 51, row: 11, state: {action: 'release', button: '<none>', modifier: {control: false, shift: false, meta: false}}}
]);
// test at max rows/cols
// bug: we are capped at col 95 currently
// fix: allow values up to 223, any bigger should drop to 0
await mouseMove(cols - 1, rows - 1);
// capped at 223 (1-based)
await mouseMove(223 - 1, rows - 1);
await mouseDown('left');
await mouseUp('left');
assert.deepEqual(await getReports(encoding), [
{col: 95, row: rows, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}},
{col: 95, row: rows, state: {action: 'release', button: '<none>', modifier: {control: false, shift: false, meta: false}}}
await pollFor(page, () => getReports(encoding), [
{col: 223, row: rows, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}},
{col: 223, row: rows, state: {action: 'release', button: '<none>', modifier: {control: false, shift: false, meta: false}}}
]);
// button press/move/release tests
@@ -843,7 +842,7 @@ describe('Mouse Tracking Tests', function(): void {
await mouseDown('left');
await mouseMove(44, 24);
await mouseUp('left');
assert.deepEqual(await getReports(encoding), [
await pollFor(page, () => getReports(encoding), [
{col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}},
{col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: false, shift: false, meta: false}}},
{col: 45, row: 25, state: {action: 'release', button: '<none>', modifier: {control: false, shift: false, meta: false}}}
@@ -855,7 +854,7 @@ describe('Mouse Tracking Tests', function(): void {
// await mouseDown('middle');
// await mouseMove(44, 24);
// await mouseUp('middle');
// assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'middle', modifier: {control: false, shift: false, meta: false}}}]);
// await pollFor(page, () => getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'middle', modifier: {control: false, shift: false, meta: false}}}]);
// right button
// bug: default action not cancelled (popup shown)
await mouseMove(43, 24);
@@ -863,7 +862,7 @@ describe('Mouse Tracking Tests', function(): void {
await mouseDown('right');
await mouseMove(44, 24);
await mouseUp('right');
assert.deepEqual(await getReports(encoding), [
await pollFor(page, () => getReports(encoding), [
{col: 44, row: 25, state: {action: 'press', button: 'right', modifier: {control: false, shift: false, meta: false}}},
{col: 45, row: 25, state: {action: 'move', button: 'right', modifier: {control: false, shift: false, meta: false}}},
{col: 45, row: 25, state: {action: 'release', button: '<none>', modifier: {control: false, shift: false, meta: false}}}
@@ -873,9 +872,9 @@ describe('Mouse Tracking Tests', function(): void {
await mouseMove(43, 24);
await getReports(encoding); // clear reports
await wheelUp();
assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'up', button: 'wheel', modifier: {control: false, shift: false, meta: false}}}]);
await pollFor(page, () => getReports(encoding), [{col: 44, row: 25, state: {action: 'up', button: 'wheel', modifier: {control: false, shift: false, meta: false}}}]);
await wheelDown();
assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: false}}}]);
await pollFor(page, () => getReports(encoding), [{col: 44, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: false}}}]);
// modifiers
// CTRL
@@ -887,7 +886,7 @@ describe('Mouse Tracking Tests', function(): void {
await mouseUp('left');
await wheelDown();
await page.keyboard.up('Control');
assert.deepEqual(await getReports(encoding), [
await pollFor(page, () => getReports(encoding), [
{col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: true, shift: false, meta: false}}},
{col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: true, shift: false, meta: false}}},
{col: 45, row: 25, state: {action: 'release', button: '<none>', modifier: {control: true, shift: false, meta: false}}},
@@ -903,7 +902,7 @@ describe('Mouse Tracking Tests', function(): void {
await mouseUp('left');
await wheelDown();
await page.keyboard.up('Alt');
assert.deepEqual(await getReports(encoding), [
await pollFor(page, () => getReports(encoding), [
{col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: true}}},
{col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: false, shift: false, meta: true}}},
{col: 45, row: 25, state: {action: 'release', button: '<none>', modifier: {control: false, shift: false, meta: true}}},
@@ -921,11 +920,11 @@ describe('Mouse Tracking Tests', function(): void {
await wheelDown();
await page.keyboard.up('Shift');
if (noShift) {
assert.deepEqual(await getReports(encoding), [
await pollFor(page, () => getReports(encoding), [
{col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: true, meta: false}}}
]);
} else {
assert.deepEqual(await getReports(encoding), [
await pollFor(page, () => getReports(encoding), [
{col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: true, meta: false}}},
{col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: false, shift: true, meta: false}}},
{col: 45, row: 25, state: {action: 'release', button: '<none>', modifier: {control: false, shift: true, meta: false}}},
@@ -947,7 +946,7 @@ describe('Mouse Tracking Tests', function(): void {
await page.keyboard.up('Control');
await page.keyboard.up('Alt');
// await page.keyboard.up('Shift');
assert.deepEqual(await getReports(encoding), [
await pollFor(page, () => getReports(encoding), [
{col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: true, shift: false, meta: true}}},
{col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: true, shift: false, meta: true}}},
{col: 45, row: 25, state: {action: 'release', button: '<none>', modifier: {control: true, shift: false, meta: true}}},
@@ -958,27 +957,27 @@ describe('Mouse Tracking Tests', function(): void {
const encoding = 'SGR';
await resetMouseModes();
await mouseMove(0, 0);
await page.evaluate(`window.term.write('\x1b[?1002h\x1b[?1006h');`);
await writeSync(page, '\x1b[?1002h\x1b[?1006h');
// test at 0,0
// bug: release is fired immediately
await mouseDown('left');
assert.deepEqual(await getReports(encoding), [
await pollFor(page, () => getReports(encoding), [
{col: 1, row: 1, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}
]);
// mouseup should report, encoding cannot report released button
await mouseUp('left');
assert.deepEqual(await getReports(encoding), [
await pollFor(page, () => getReports(encoding), [
{col: 1, row: 1, state: {action: 'release', button: 'left', modifier: {control: false, shift: false, meta: false}}}
]);
// mousemove should not report
await mouseMove(50, 10);
assert.deepEqual(await getReports(encoding), []);
await pollFor(page, () => getReports(encoding), []);
await mouseDown('left');
await mouseUp('left');
assert.deepEqual(await getReports(encoding), [
await pollFor(page, () => getReports(encoding), [
{col: 51, row: 11, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}},
{col: 51, row: 11, state: {action: 'release', button: 'left', modifier: {control: false, shift: false, meta: false}}}
]);
@@ -987,7 +986,7 @@ describe('Mouse Tracking Tests', function(): void {
await mouseMove(cols - 1, rows - 1);
await mouseDown('left');
await mouseUp('left');
assert.deepEqual(await getReports(encoding), [
await pollFor(page, () => getReports(encoding), [
{col: cols, row: rows, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}},
{col: cols, row: rows, state: {action: 'release', button: 'left', modifier: {control: false, shift: false, meta: false}}}
]);
@@ -999,7 +998,7 @@ describe('Mouse Tracking Tests', function(): void {
await mouseDown('left');
await mouseMove(44, 24);
await mouseUp('left');
assert.deepEqual(await getReports(encoding), [
await pollFor(page, () => getReports(encoding), [
{col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}},
{col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: false, shift: false, meta: false}}},
{col: 45, row: 25, state: {action: 'release', button: 'left', modifier: {control: false, shift: false, meta: false}}}
@@ -1011,7 +1010,7 @@ describe('Mouse Tracking Tests', function(): void {
// await mouseDown('middle');
// await mouseMove(44, 24);
// await mouseUp('middle');
// assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'middle', modifier: {control: false, shift: false, meta: false}}}]);
// await pollFor(page, () => getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'middle', modifier: {control: false, shift: false, meta: false}}}]);
// right button
// bug: default action not cancelled (popup shown)
await mouseMove(43, 24);
@@ -1019,7 +1018,7 @@ describe('Mouse Tracking Tests', function(): void {
await mouseDown('right');
await mouseMove(44, 24);
await mouseUp('right');
assert.deepEqual(await getReports(encoding), [
await pollFor(page, () => getReports(encoding), [
{col: 44, row: 25, state: {action: 'press', button: 'right', modifier: {control: false, shift: false, meta: false}}},
{col: 45, row: 25, state: {action: 'move', button: 'right', modifier: {control: false, shift: false, meta: false}}},
{col: 45, row: 25, state: {action: 'release', button: 'right', modifier: {control: false, shift: false, meta: false}}}
@@ -1029,9 +1028,9 @@ describe('Mouse Tracking Tests', function(): void {
await mouseMove(43, 24);
await getReports(encoding); // clear reports
await wheelUp();
assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'up', button: 'wheel', modifier: {control: false, shift: false, meta: false}}}]);
await pollFor(page, () => getReports(encoding), [{col: 44, row: 25, state: {action: 'up', button: 'wheel', modifier: {control: false, shift: false, meta: false}}}]);
await wheelDown();
assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: false}}}]);
await pollFor(page, () => getReports(encoding), [{col: 44, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: false}}}]);
// modifiers
// CTRL
@@ -1043,7 +1042,7 @@ describe('Mouse Tracking Tests', function(): void {
await mouseUp('left');
await wheelDown();
await page.keyboard.up('Control');
assert.deepEqual(await getReports(encoding), [
await pollFor(page, () => getReports(encoding), [
{col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: true, shift: false, meta: false}}},
{col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: true, shift: false, meta: false}}},
{col: 45, row: 25, state: {action: 'release', button: 'left', modifier: {control: true, shift: false, meta: false}}},
@@ -1059,7 +1058,7 @@ describe('Mouse Tracking Tests', function(): void {
await mouseUp('left');
await wheelDown();
await page.keyboard.up('Alt');
assert.deepEqual(await getReports(encoding), [
await pollFor(page, () => getReports(encoding), [
{col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: true}}},
{col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: false, shift: false, meta: true}}},
{col: 45, row: 25, state: {action: 'release', button: 'left', modifier: {control: false, shift: false, meta: true}}},
@@ -1077,11 +1076,11 @@ describe('Mouse Tracking Tests', function(): void {
await wheelDown();
await page.keyboard.up('Shift');
if (noShift) {
assert.deepEqual(await getReports(encoding), [
await pollFor(page, () => getReports(encoding), [
{col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: true, meta: false}}}
]);
} else {
assert.deepEqual(await getReports(encoding), [
await pollFor(page, () => getReports(encoding), [
{col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: true, meta: false}}},
{col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: false, shift: true, meta: false}}},
{col: 45, row: 25, state: {action: 'release', button: 'left', modifier: {control: false, shift: true, meta: false}}},
@@ -1103,7 +1102,7 @@ describe('Mouse Tracking Tests', function(): void {
await page.keyboard.up('Control');
await page.keyboard.up('Alt');
// await page.keyboard.up('Shift');
assert.deepEqual(await getReports(encoding), [
await pollFor(page, () => getReports(encoding), [
{col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: true, shift: false, meta: true}}},
{col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: true, shift: false, meta: true}}},
{col: 45, row: 25, state: {action: 'release', button: 'left', modifier: {control: true, shift: false, meta: true}}},
@@ -1120,42 +1119,41 @@ describe('Mouse Tracking Tests', function(): void {
const encoding = 'DEFAULT';
await resetMouseModes();
await mouseMove(0, 0);
await page.evaluate(`window.term.write('\x1b[?1003h');`);
await writeSync(page, '\x1b[?1003h');
// test at 0,0
await mouseDown('left');
assert.deepEqual(await getReports(encoding), [
await pollFor(page, () => getReports(encoding), [
{col: 1, row: 1, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}
]);
// mouseup should report, encoding cannot report released button
await mouseUp('left');
assert.deepEqual(await getReports(encoding), [
await pollFor(page, () => getReports(encoding), [
{col: 1, row: 1, state: {action: 'release', button: '<none>', modifier: {control: false, shift: false, meta: false}}}
]);
// mousemove should report
await mouseMove(50, 10);
assert.deepEqual(await getReports(encoding), [
await pollFor(page, () => getReports(encoding), [
{col: 51, row: 11, state: {action: 'move', button: '<none>', modifier: {control: false, shift: false, meta: false}}}
]);
await mouseDown('left');
await mouseUp('left');
assert.deepEqual(await getReports(encoding), [
await pollFor(page, () => getReports(encoding), [
{col: 51, row: 11, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}},
{col: 51, row: 11, state: {action: 'release', button: '<none>', modifier: {control: false, shift: false, meta: false}}}
]);
// test at max rows/cols
// bug: we are capped at col 95 currently
// fix: allow values up to 223, any bigger should drop to 0
await mouseMove(cols - 1, rows - 1);
// capped at 223 (1-based)
await mouseMove(223 - 1, rows - 1);
await mouseDown('left');
await mouseUp('left');
assert.deepEqual(await getReports(encoding), [
{col: 95, row: rows, state: {action: 'move', button: '<none>', modifier: {control: false, shift: false, meta: false}}},
{col: 95, row: rows, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}},
{col: 95, row: rows, state: {action: 'release', button: '<none>', modifier: {control: false, shift: false, meta: false}}}
await pollFor(page, () => getReports(encoding), [
{col: 223, row: rows, state: {action: 'move', button: '<none>', modifier: {control: false, shift: false, meta: false}}},
{col: 223, row: rows, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}},
{col: 223, row: rows, state: {action: 'release', button: '<none>', modifier: {control: false, shift: false, meta: false}}}
]);
// button press/move/release tests
@@ -1164,7 +1162,7 @@ describe('Mouse Tracking Tests', function(): void {
await mouseDown('left');
await mouseMove(44, 24);
await mouseUp('left');
assert.deepEqual(await getReports(encoding), [
await pollFor(page, () => getReports(encoding), [
{col: 44, row: 25, state: {action: 'move', button: '<none>', modifier: {control: false, shift: false, meta: false}}},
{col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}},
{col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: false, shift: false, meta: false}}},
@@ -1177,14 +1175,14 @@ describe('Mouse Tracking Tests', function(): void {
// await mouseDown('middle');
// await mouseMove(44, 24);
// await mouseUp('middle');
// assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'middle', modifier: {control: false, shift: false, meta: false}}}]);
// await pollFor(page, () => getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'middle', modifier: {control: false, shift: false, meta: false}}}]);
// right button
// bug: default action not cancelled (popup shown)
await mouseMove(43, 24);
await mouseDown('right');
await mouseMove(44, 24);
await mouseUp('right');
assert.deepEqual(await getReports(encoding), [
await pollFor(page, () => getReports(encoding), [
{col: 44, row: 25, state: {action: 'move', button: '<none>', modifier: {control: false, shift: false, meta: false}}},
{col: 44, row: 25, state: {action: 'press', button: 'right', modifier: {control: false, shift: false, meta: false}}},
{col: 45, row: 25, state: {action: 'move', button: 'right', modifier: {control: false, shift: false, meta: false}}},
@@ -1195,9 +1193,9 @@ describe('Mouse Tracking Tests', function(): void {
await mouseMove(43, 24);
await getReports(encoding); // clear reports
await wheelUp();
assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'up', button: 'wheel', modifier: {control: false, shift: false, meta: false}}}]);
await pollFor(page, () => getReports(encoding), [{col: 44, row: 25, state: {action: 'up', button: 'wheel', modifier: {control: false, shift: false, meta: false}}}]);
await wheelDown();
assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: false}}}]);
await pollFor(page, () => getReports(encoding), [{col: 44, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: false}}}]);
// modifiers
// CTRL
@@ -1208,7 +1206,7 @@ describe('Mouse Tracking Tests', function(): void {
await mouseUp('left');
await wheelDown();
await page.keyboard.up('Control');
assert.deepEqual(await getReports(encoding), [
await pollFor(page, () => getReports(encoding), [
{col: 44, row: 25, state: {action: 'move', button: '<none>', modifier: {control: true, shift: false, meta: false}}},
{col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: true, shift: false, meta: false}}},
{col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: true, shift: false, meta: false}}},
@@ -1224,7 +1222,7 @@ describe('Mouse Tracking Tests', function(): void {
await mouseUp('left');
await wheelDown();
await page.keyboard.up('Alt');
assert.deepEqual(await getReports(encoding), [
await pollFor(page, () => getReports(encoding), [
{col: 44, row: 25, state: {action: 'move', button: '<none>', modifier: {control: false, shift: false, meta: true}}},
{col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: true}}},
{col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: false, shift: false, meta: true}}},
@@ -1242,12 +1240,12 @@ describe('Mouse Tracking Tests', function(): void {
await wheelDown();
await page.keyboard.up('Shift');
if (noShift) {
assert.deepEqual(await getReports(encoding), [
await pollFor(page, () => getReports(encoding), [
{col: 44, row: 25, state: {action: 'move', button: '<none>', modifier: {control: false, shift: true, meta: false}}},
{col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: true, meta: false}}}
]);
} else {
assert.deepEqual(await getReports(encoding), [
await pollFor(page, () => getReports(encoding), [
{col: 44, row: 25, state: {action: 'move', button: '<none>', modifier: {control: false, shift: true, meta: false}}},
{col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: true, meta: false}}},
{col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: false, shift: true, meta: false}}},
@@ -1269,7 +1267,7 @@ describe('Mouse Tracking Tests', function(): void {
await page.keyboard.up('Control');
await page.keyboard.up('Alt');
// await page.keyboard.up('Shift');
assert.deepEqual(await getReports(encoding), [
await pollFor(page, () => getReports(encoding), [
{col: 44, row: 25, state: {action: 'move', button: '<none>', modifier: {control: true, shift: false, meta: true}}},
{col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: true, shift: false, meta: true}}},
{col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: true, shift: false, meta: true}}},
@@ -1281,28 +1279,28 @@ describe('Mouse Tracking Tests', function(): void {
const encoding = 'SGR';
await resetMouseModes();
await mouseMove(0, 0);
await page.evaluate(`window.term.write('\x1b[?1003h\x1b[?1006h');`);
await writeSync(page, '\x1b[?1003h\x1b[?1006h');
// test at 0,0
await mouseDown('left');
assert.deepEqual(await getReports(encoding), [
await pollFor(page, () => getReports(encoding), [
{col: 1, row: 1, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}
]);
// mouseup should report, encoding cannot report released button
await mouseUp('left');
assert.deepEqual(await getReports(encoding), [
await pollFor(page, () => getReports(encoding), [
{col: 1, row: 1, state: {action: 'release', button: 'left', modifier: {control: false, shift: false, meta: false}}}
]);
// mousemove should report
await mouseMove(50, 10);
assert.deepEqual(await getReports(encoding), [
await pollFor(page, () => getReports(encoding), [
{col: 51, row: 11, state: {action: 'move', button: '<none>', modifier: {control: false, shift: false, meta: false}}}
]);
await mouseDown('left');
await mouseUp('left');
assert.deepEqual(await getReports(encoding), [
await pollFor(page, () => getReports(encoding), [
{col: 51, row: 11, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}},
{col: 51, row: 11, state: {action: 'release', button: 'left', modifier: {control: false, shift: false, meta: false}}}
]);
@@ -1313,7 +1311,7 @@ describe('Mouse Tracking Tests', function(): void {
await mouseMove(cols - 1, rows - 1);
await mouseDown('left');
await mouseUp('left');
assert.deepEqual(await getReports(encoding), [
await pollFor(page, () => getReports(encoding), [
{col: cols, row: rows, state: {action: 'move', button: '<none>', modifier: {control: false, shift: false, meta: false}}},
{col: cols, row: rows, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}},
{col: cols, row: rows, state: {action: 'release', button: 'left', modifier: {control: false, shift: false, meta: false}}}
@@ -1325,7 +1323,7 @@ describe('Mouse Tracking Tests', function(): void {
await mouseDown('left');
await mouseMove(44, 24);
await mouseUp('left');
assert.deepEqual(await getReports(encoding), [
await pollFor(page, () => getReports(encoding), [
{col: 44, row: 25, state: {action: 'move', button: '<none>', modifier: {control: false, shift: false, meta: false}}},
{col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}},
{col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: false, shift: false, meta: false}}},
@@ -1338,14 +1336,14 @@ describe('Mouse Tracking Tests', function(): void {
// await mouseDown('middle');
// await mouseMove(44, 24);
// await mouseUp('middle');
// assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'middle', modifier: {control: false, shift: false, meta: false}}}]);
// await pollFor(page, () => getReports(encoding), [{col: 44, row: 25, state: {action: 'press', button: 'middle', modifier: {control: false, shift: false, meta: false}}}]);
// right button
// bug: default action not cancelled (popup shown)
await mouseMove(43, 24);
await mouseDown('right');
await mouseMove(44, 24);
await mouseUp('right');
assert.deepEqual(await getReports(encoding), [
await pollFor(page, () => getReports(encoding), [
{col: 44, row: 25, state: {action: 'move', button: '<none>', modifier: {control: false, shift: false, meta: false}}},
{col: 44, row: 25, state: {action: 'press', button: 'right', modifier: {control: false, shift: false, meta: false}}},
{col: 45, row: 25, state: {action: 'move', button: 'right', modifier: {control: false, shift: false, meta: false}}},
@@ -1356,9 +1354,9 @@ describe('Mouse Tracking Tests', function(): void {
await mouseMove(43, 24);
await getReports(encoding); // clear reports
await wheelUp();
assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'up', button: 'wheel', modifier: {control: false, shift: false, meta: false}}}]);
await pollFor(page, () => getReports(encoding), [{col: 44, row: 25, state: {action: 'up', button: 'wheel', modifier: {control: false, shift: false, meta: false}}}]);
await wheelDown();
assert.deepEqual(await getReports(encoding), [{col: 44, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: false}}}]);
await pollFor(page, () => getReports(encoding), [{col: 44, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: false, meta: false}}}]);
// modifiers
// CTRL
@@ -1369,7 +1367,7 @@ describe('Mouse Tracking Tests', function(): void {
await mouseUp('left');
await wheelDown();
await page.keyboard.up('Control');
assert.deepEqual(await getReports(encoding), [
await pollFor(page, () => getReports(encoding), [
{col: 44, row: 25, state: {action: 'move', button: '<none>', modifier: {control: true, shift: false, meta: false}}},
{col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: true, shift: false, meta: false}}},
{col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: true, shift: false, meta: false}}},
@@ -1385,7 +1383,7 @@ describe('Mouse Tracking Tests', function(): void {
await mouseUp('left');
await wheelDown();
await page.keyboard.up('Alt');
assert.deepEqual(await getReports(encoding), [
await pollFor(page, () => getReports(encoding), [
{col: 44, row: 25, state: {action: 'move', button: '<none>', modifier: {control: false, shift: false, meta: true}}},
{col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: true}}},
{col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: false, shift: false, meta: true}}},
@@ -1403,12 +1401,12 @@ describe('Mouse Tracking Tests', function(): void {
await wheelDown();
await page.keyboard.up('Shift');
if (noShift) {
assert.deepEqual(await getReports(encoding), [
await pollFor(page, () => getReports(encoding), [
{col: 44, row: 25, state: {action: 'move', button: '<none>', modifier: {control: false, shift: true, meta: false}}},
{col: 45, row: 25, state: {action: 'down', button: 'wheel', modifier: {control: false, shift: true, meta: false}}}
]);
} else {
assert.deepEqual(await getReports(encoding), [
await pollFor(page, () => getReports(encoding), [
{col: 44, row: 25, state: {action: 'move', button: '<none>', modifier: {control: false, shift: true, meta: false}}},
{col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: false, shift: true, meta: false}}},
{col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: false, shift: true, meta: false}}},
@@ -1430,7 +1428,7 @@ describe('Mouse Tracking Tests', function(): void {
await page.keyboard.up('Control');
await page.keyboard.up('Alt');
// await page.keyboard.up('Shift');
assert.deepEqual(await getReports(encoding), [
await pollFor(page, () => getReports(encoding), [
{col: 44, row: 25, state: {action: 'move', button: '<none>', modifier: {control: true, shift: false, meta: true}}},
{col: 44, row: 25, state: {action: 'press', button: 'left', modifier: {control: true, shift: false, meta: true}}},
{col: 45, row: 25, state: {action: 'move', button: 'left', modifier: {control: true, shift: false, meta: true}}},
+6 -18
View File
@@ -6,6 +6,7 @@
import * as puppeteer from 'puppeteer';
import { assert } from 'chai';
import { ITerminalOptions } from 'xterm';
import { writeSync } from './TestUtils';
const APP = 'http://127.0.0.1:3000/test';
@@ -15,12 +16,9 @@ const width = 800;
const height = 600;
describe('Parser 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}`, `--no-sandbox`]
});
page = (await browser.pages())[0];
@@ -29,9 +27,7 @@ describe('Parser Integration Tests', function(): void {
await openTerminal();
});
after(() => {
browser.close();
});
after(async () => browser.close());
describe('addCsiHandler', () => {
it('should call custom CSI handler with js array params', async () => {
@@ -43,9 +39,7 @@ describe('Parser Integration Tests', function(): void {
return false;
}, '');
`);
await page.evaluate(`
window.term.write('\x1b[38;5;123mparams\x1b[38:2::50:100:150msubparams');
`);
await writeSync(page, '\x1b[38;5;123mparams\x1b[38:2::50:100:150msubparams');
assert.deepEqual(await page.evaluate(`(() => _customCsiHandlerParams)();`), [[38, 5, 123], [38, [2, -1, 50, 100, 150]]]);
});
});
@@ -67,9 +61,7 @@ describe('Parser Integration Tests', function(): void {
return false;
});
`);
await page.evaluate(`
window.term.write('\x1bP1;2+psome data\x1b\\\\');
`);
await writeSync(page, '\x1bP1;2+psome data\x1b\\\\');
assert.deepEqual(await page.evaluate(`(() => _customDcsHandlerCallStack)();`), [['C', [1, 2], 'some data'], ['B', [1, 2], 'some data']]);
});
});
@@ -91,9 +83,7 @@ describe('Parser Integration Tests', function(): void {
return false;
});
`);
await page.evaluate(`
window.term.write('\x1b(B');
`);
await writeSync(page, '\x1b(B');
assert.deepEqual(await page.evaluate(`(() => _customEscHandlerCallStack)();`), ['C', 'B']);
});
});
@@ -115,9 +105,7 @@ describe('Parser Integration Tests', function(): void {
return false;
});
`);
await page.evaluate(`
window.term.write('\x1b]1234;some data\x07');
`);
await writeSync(page, '\x1b]1234;some data\x07');
assert.deepEqual(await page.evaluate(`(() => _customOscHandlerCallStack)();`), [['C', 'some data'], ['B', 'some data']]);
});
});
+108 -108
View File
@@ -6,6 +6,7 @@
import * as puppeteer from 'puppeteer';
import { assert } from 'chai';
import { ITerminalOptions } from 'xterm';
import { pollFor, timeout, writeSync } from './TestUtils';
const APP = 'http://127.0.0.1:3000/test';
@@ -15,54 +16,46 @@ const width = 800;
const height = 600;
describe('API Integration Tests', function(): void {
this.timeout(20000);
before(async function(): Promise<any> {
before(async () => {
browser = await puppeteer.launch({
headless: process.argv.indexOf('--headless') !== -1,
slowMo: 80,
args: [`--window-size=${width},${height}`, `--no-sandbox`]
});
page = (await browser.pages())[0];
await page.setViewport({ width, height });
});
after(() => {
browser.close();
});
after(async () => browser.close());
beforeEach(async () => page.goto(APP));
beforeEach(async () => {
await page.goto(APP);
});
it('Default options', async function(): Promise<any> {
it('Default options', async () => {
await openTerminal();
assert.equal(await page.evaluate(`window.term.cols`), 80);
assert.equal(await page.evaluate(`window.term.rows`), 24);
});
it('write', async function(): Promise<any> {
it('write', async () => {
await openTerminal();
await page.evaluate(`
window.term.write('foo');
window.term.write('bar');
window.term.write('文');
`);
assert.equal(await page.evaluate(`window.term.buffer.getLine(0).translateToString(true)`), 'foobar文');
await pollFor(page, `window.term.buffer.getLine(0).translateToString(true)`, 'foobar文');
});
it('write with callback', async function(): Promise<any> {
it('write with callback', async () => {
await openTerminal();
await page.evaluate(`
window.term.write('foo', () => { window.__x = 'a'; });
window.term.write('bar', () => { window.__x += 'b'; });
window.term.write('文', () => { window.__x += 'c'; });
`);
assert.equal(await page.evaluate(`window.term.buffer.getLine(0).translateToString(true)`), 'foobar文');
assert.equal(await page.evaluate(`window.__x`), 'abc');
await pollFor(page, `window.term.buffer.getLine(0).translateToString(true)`, 'foobar文');
await pollFor(page, `window.__x`, 'abc');
});
it('write - bytes (UTF8)', async function(): Promise<any> {
it('write - bytes (UTF8)', async () => {
await openTerminal();
await page.evaluate(`
// foo
@@ -72,10 +65,10 @@ describe('API Integration Tests', function(): void {
// 文
window.term.write(new Uint8Array([230, 150, 135]));
`);
assert.equal(await page.evaluate(`window.term.buffer.getLine(0).translateToString(true)`), 'foobar文');
await pollFor(page, `window.term.buffer.getLine(0).translateToString(true)`, 'foobar文');
});
it('write - bytes (UTF8) with callback', async function(): Promise<any> {
it('write - bytes (UTF8) with callback', async () => {
await openTerminal();
await page.evaluate(`
// foo
@@ -85,48 +78,48 @@ describe('API Integration Tests', function(): void {
// 文
window.term.write(new Uint8Array([230, 150, 135]), () => { window.__x += 'C'; });
`);
assert.equal(await page.evaluate(`window.term.buffer.getLine(0).translateToString(true)`), 'foobar文');
assert.equal(await page.evaluate(`window.__x`), 'ABC');
await pollFor(page, `window.term.buffer.getLine(0).translateToString(true)`, 'foobar文');
await pollFor(page, `window.__x`, 'ABC');
});
it('writeln', async function(): Promise<any> {
it('writeln', async () => {
await openTerminal();
await page.evaluate(`
window.term.writeln('foo');
window.term.writeln('bar');
window.term.writeln('文');
`);
assert.equal(await page.evaluate(`window.term.buffer.getLine(0).translateToString(true)`), 'foo');
assert.equal(await page.evaluate(`window.term.buffer.getLine(1).translateToString(true)`), 'bar');
assert.equal(await page.evaluate(`window.term.buffer.getLine(2).translateToString(true)`), '文');
await pollFor(page, `window.term.buffer.getLine(0).translateToString(true)`, 'foo');
await pollFor(page, `window.term.buffer.getLine(1).translateToString(true)`, 'bar');
await pollFor(page, `window.term.buffer.getLine(2).translateToString(true)`, '文');
});
it('writeln with callback', async function(): Promise<any> {
it('writeln with callback', async () => {
await openTerminal();
await page.evaluate(`
window.term.writeln('foo', () => { window.__x = '1'; });
window.term.writeln('bar', () => { window.__x += '2'; });
window.term.writeln('文', () => { window.__x += '3'; });
`);
assert.equal(await page.evaluate(`window.term.buffer.getLine(0).translateToString(true)`), 'foo');
assert.equal(await page.evaluate(`window.term.buffer.getLine(1).translateToString(true)`), 'bar');
assert.equal(await page.evaluate(`window.term.buffer.getLine(2).translateToString(true)`), '文');
assert.equal(await page.evaluate(`window.__x`), '123');
await pollFor(page, `window.term.buffer.getLine(0).translateToString(true)`, 'foo');
await pollFor(page, `window.term.buffer.getLine(1).translateToString(true)`, 'bar');
await pollFor(page, `window.term.buffer.getLine(2).translateToString(true)`, '文');
await pollFor(page, `window.__x`, '123');
});
it('writeln - bytes (UTF8)', async function(): Promise<any> {
it('writeln - bytes (UTF8)', async () => {
await openTerminal();
await page.evaluate(`
window.term.writeln(new Uint8Array([102, 111, 111]));
window.term.writeln(new Uint8Array([98, 97, 114]));
window.term.writeln(new Uint8Array([230, 150, 135]));
`);
assert.equal(await page.evaluate(`window.term.buffer.getLine(0).translateToString(true)`), 'foo');
assert.equal(await page.evaluate(`window.term.buffer.getLine(1).translateToString(true)`), 'bar');
assert.equal(await page.evaluate(`window.term.buffer.getLine(2).translateToString(true)`), '文');
await pollFor(page, `window.term.buffer.getLine(0).translateToString(true)`, 'foo');
await pollFor(page, `window.term.buffer.getLine(1).translateToString(true)`, 'bar');
await pollFor(page, `window.term.buffer.getLine(2).translateToString(true)`, '文');
});
it('paste', async function(): Promise<any> {
it('paste', async () => {
await openTerminal();
await page.evaluate(`
window.calls = [];
@@ -137,26 +130,28 @@ describe('API Integration Tests', function(): void {
window.term.paste('foo');
});
`);
assert.deepEqual(await page.evaluate(`window.calls`), ['foo', '\rfoo\rbar\r', '\x1b[200~foo\x1b[201~']);
await pollFor(page, `window.calls`, ['foo', '\rfoo\rbar\r', '\x1b[200~foo\x1b[201~']);
});
it('clear', async function(): Promise<any> {
it('clear', async () => {
await openTerminal({ rows: 5 });
await page.evaluate(`
window.term.write('test0');
window.parsed = 0;
for (let i = 1; i < 10; i++) {
window.term.write('\\n\\rtest' + i);
window.term.write('\\n\\rtest' + i, () => window.parsed++);
}
`);
await pollFor(page, `window.parsed`, 9);
await page.evaluate(`window.term.clear()`);
assert.equal(await page.evaluate(`window.term.buffer.length`), '5');
assert.equal(await page.evaluate(`window.term.buffer.getLine(0).translateToString(true)`), 'test9');
await pollFor(page, `window.term.buffer.length`, 5);
await pollFor(page, `window.term.buffer.getLine(0).translateToString(true)`, 'test9');
for (let i = 1; i < 5; i++) {
assert.equal(await page.evaluate(`window.term.buffer.getLine(${i}).translateToString(true)`), '');
await pollFor(page, `window.term.buffer.getLine(${i}).translateToString(true)`, '');
}
});
it('getOption, setOption', async function(): Promise<any> {
it('getOption, setOption', async () => {
await openTerminal();
assert.equal(await page.evaluate(`window.term.getOption('rendererType')`), 'canvas');
await page.evaluate(`window.term.setOption('rendererType', 'dom')`);
@@ -164,9 +159,10 @@ describe('API Integration Tests', function(): void {
});
describe('renderer', () => {
it('foreground', async function(): Promise<any> {
it('foreground', async () => {
await openTerminal({ rendererType: 'dom' });
await page.evaluate(`window.term.write('\\x1b[30m0\\x1b[31m1\\x1b[32m2\\x1b[33m3\\x1b[34m4\\x1b[35m5\\x1b[36m6\\x1b[37m7')`);
await writeSync(page, '\\x1b[30m0\\x1b[31m1\\x1b[32m2\\x1b[33m3\\x1b[34m4\\x1b[35m5\\x1b[36m6\\x1b[37m7');
await pollFor(page, `document.querySelectorAll('.xterm-rows > :nth-child(1) > *').length`, 9);
assert.deepEqual(await page.evaluate(`
[
document.querySelector('.xterm-rows > :nth-child(1) > :nth-child(1)').className,
@@ -188,9 +184,10 @@ describe('API Integration Tests', function(): void {
]);
});
it('background', async function(): Promise<any> {
it('background', async () => {
await openTerminal({ rendererType: 'dom' });
await page.evaluate(`window.term.write('\\x1b[40m0\\x1b[41m1\\x1b[42m2\\x1b[43m3\\x1b[44m4\\x1b[45m5\\x1b[46m6\\x1b[47m7')`);
await writeSync(page, '\\x1b[40m0\\x1b[41m1\\x1b[42m2\\x1b[43m3\\x1b[44m4\\x1b[45m5\\x1b[46m6\\x1b[47m7');
await pollFor(page, `document.querySelectorAll('.xterm-rows > :nth-child(1) > *').length`, 9);
assert.deepEqual(await page.evaluate(`
[
document.querySelector('.xterm-rows > :nth-child(1) > :nth-child(1)').className,
@@ -213,7 +210,7 @@ describe('API Integration Tests', function(): void {
});
});
it('selection', async function(): Promise<any> {
it('selection', async () => {
await openTerminal({ rows: 5, cols: 5 });
await page.evaluate(`window.term.write('\\n\\nfoo\\n\\n\\rbar\\n\\n\\rbaz')`);
assert.equal(await page.evaluate(`window.term.hasSelection()`), false);
@@ -233,7 +230,7 @@ describe('API Integration Tests', function(): void {
assert.deepEqual(await page.evaluate(`window.term.getSelectionPosition()`), { startColumn: 1, startRow: 2, endColumn: 3, endRow: 2 });
});
it('focus, blur', async function(): Promise<any> {
it('focus, blur', async () => {
await openTerminal();
assert.equal(await page.evaluate(`document.activeElement.className`), '');
await page.evaluate(`window.term.focus()`);
@@ -243,7 +240,7 @@ describe('API Integration Tests', function(): void {
});
describe('loadAddon', () => {
it('constructor', async function(): Promise<any> {
it('constructor', async () => {
await openTerminal({ cols: 5 });
await page.evaluate(`
window.cols = 0;
@@ -255,7 +252,7 @@ describe('API Integration Tests', function(): void {
assert.equal(await page.evaluate(`window.cols`), 5);
});
it('dispose (addon)', async function(): Promise<any> {
it('dispose (addon)', async () => {
await openTerminal();
await page.evaluate(`
window.disposeCalled = false
@@ -270,7 +267,7 @@ describe('API Integration Tests', function(): void {
assert.equal(await page.evaluate(`window.disposeCalled`), true);
});
it('dispose (terminal)', async function(): Promise<any> {
it('dispose (terminal)', async () => {
await openTerminal();
await page.evaluate(`
window.disposeCalled = false
@@ -286,19 +283,19 @@ describe('API Integration Tests', function(): void {
});
describe('Events', () => {
it('onCursorMove', async function(): Promise<any> {
it('onCursorMove', async () => {
await openTerminal();
await page.evaluate(`
window.callCount = 0;
window.term.onCursorMove(e => window.callCount++);
window.term.write('foo');
`);
assert.equal(await page.evaluate(`window.callCount`), 1);
await pollFor(page, `window.callCount`, 1);
await page.evaluate(`window.term.write('bar')`);
assert.equal(await page.evaluate(`window.callCount`), 2);
await pollFor(page, `window.callCount`, 2);
});
it('onData', async function(): Promise<any> {
it('onData', async () => {
await openTerminal();
await page.evaluate(`
window.calls = [];
@@ -308,7 +305,7 @@ describe('API Integration Tests', function(): void {
assert.deepEqual(await page.evaluate(`window.calls`), ['f', 'o', 'o']);
});
it('onKey', async function(): Promise<any> {
it('onKey', async () => {
await openTerminal();
await page.evaluate(`
window.calls = [];
@@ -318,19 +315,19 @@ describe('API Integration Tests', function(): void {
assert.deepEqual(await page.evaluate(`window.calls`), ['f', 'o', 'o']);
});
it('onLineFeed', async function(): Promise<any> {
it('onLineFeed', async () => {
await openTerminal();
await page.evaluate(`
window.callCount = 0;
window.term.onLineFeed(() => callCount++);
window.term.writeln('foo');
`);
assert.equal(await page.evaluate(`window.callCount`), 1);
await pollFor(page, `window.callCount`, 1);
await page.evaluate(`window.term.writeln('bar')`);
assert.equal(await page.evaluate(`window.callCount`), 2);
await pollFor(page, `window.callCount`, 2);
});
it('onScroll', async function(): Promise<any> {
it('onScroll', async () => {
await openTerminal({ rows: 5 });
await page.evaluate(`
window.calls = [];
@@ -339,94 +336,97 @@ describe('API Integration Tests', function(): void {
window.term.writeln('foo');
}
`);
assert.deepEqual(await page.evaluate(`window.calls`), []);
await pollFor(page, `window.calls`, []);
await page.evaluate(`window.term.writeln('bar')`);
assert.deepEqual(await page.evaluate(`window.calls`), [1]);
await pollFor(page, `window.calls`, [1]);
await page.evaluate(`window.term.writeln('baz')`);
assert.deepEqual(await page.evaluate(`window.calls`), [1, 2]);
await pollFor(page, `window.calls`, [1, 2]);
});
it('onSelectionChange', async function(): Promise<any> {
it('onSelectionChange', async () => {
await openTerminal();
await page.evaluate(`
window.callCount = 0;
window.term.onSelectionChange(() => window.callCount++);
`);
assert.equal(await page.evaluate(`window.callCount`), 0);
await pollFor(page, `window.callCount`, 0);
await page.evaluate(`window.term.selectAll()`);
assert.equal(await page.evaluate(`window.callCount`), 1);
await pollFor(page, `window.callCount`, 1);
await page.evaluate(`window.term.clearSelection()`);
assert.equal(await page.evaluate(`window.callCount`), 2);
await pollFor(page, `window.callCount`, 2);
});
it('onRender', async function(): Promise<any> {
it('onRender', async function (): Promise<void> {
this.retries(3);
await openTerminal();
await timeout(20); // Ensure all init events are fired
await page.evaluate(`
window.calls = [];
window.term.onRender(e => window.calls.push([e.start, e.end]));
`);
assert.deepEqual(await page.evaluate(`window.calls`), []);
await pollFor(page, `window.calls`, []);
await page.evaluate(`window.term.write('foo')`);
assert.deepEqual(await page.evaluate(`window.calls`), [[0, 0]]);
await pollFor(page, `window.calls`, [[0, 0]]);
await page.evaluate(`window.term.write('bar\\n\\nbaz')`);
assert.deepEqual(await page.evaluate(`window.calls`), [[0, 0], [0, 2]]);
await pollFor(page, `window.calls`, [[0, 0], [0, 2]]);
});
it('onResize', async function(): Promise<any> {
it('onResize', async () => {
await openTerminal();
await timeout(20); // Ensure all init events are fired
await page.evaluate(`
window.calls = [];
window.term.onResize(e => window.calls.push([e.cols, e.rows]));
`);
assert.deepEqual(await page.evaluate(`window.calls`), []);
await pollFor(page, `window.calls`, []);
await page.evaluate(`window.term.resize(10, 5)`);
assert.deepEqual(await page.evaluate(`window.calls`), [[10, 5]]);
await pollFor(page, `window.calls`, [[10, 5]]);
await page.evaluate(`window.term.resize(20, 15)`);
assert.deepEqual(await page.evaluate(`window.calls`), [[10, 5], [20, 15]]);
await pollFor(page, `window.calls`, [[10, 5], [20, 15]]);
});
it('onTitleChange', async function(): Promise<any> {
it('onTitleChange', async () => {
await openTerminal();
await page.evaluate(`
window.calls = [];
window.term.onTitleChange(e => window.calls.push(e));
`);
assert.deepEqual(await page.evaluate(`window.calls`), []);
await pollFor(page, `window.calls`, []);
await page.evaluate(`window.term.write('\\x1b]2;foo\\x9c')`);
assert.deepEqual(await page.evaluate(`window.calls`), ['foo']);
await pollFor(page, `window.calls`, ['foo']);
});
});
describe('buffer', () => {
it('cursorX, cursorY', async function(): Promise<any> {
it('cursorX, cursorY', async () => {
await openTerminal({ rows: 5, cols: 5 });
assert.equal(await page.evaluate(`window.term.buffer.cursorX`), 0);
assert.equal(await page.evaluate(`window.term.buffer.cursorY`), 0);
await page.evaluate(`window.term.write('foo')`);
await writeSync(page, 'foo');
assert.equal(await page.evaluate(`window.term.buffer.cursorX`), 3);
assert.equal(await page.evaluate(`window.term.buffer.cursorY`), 0);
await page.evaluate(`window.term.write('\\n')`);
await writeSync(page, '\\n');
assert.equal(await page.evaluate(`window.term.buffer.cursorX`), 3);
assert.equal(await page.evaluate(`window.term.buffer.cursorY`), 1);
await page.evaluate(`window.term.write('\\r')`);
await writeSync(page, '\\r');
assert.equal(await page.evaluate(`window.term.buffer.cursorX`), 0);
assert.equal(await page.evaluate(`window.term.buffer.cursorY`), 1);
await page.evaluate(`window.term.write('abcde')`);
await writeSync(page, 'abcde');
assert.equal(await page.evaluate(`window.term.buffer.cursorX`), 5);
assert.equal(await page.evaluate(`window.term.buffer.cursorY`), 1);
await page.evaluate(`window.term.write('\\n\\r\\n\\n\\n\\n\\n')`);
await writeSync(page, '\\n\\r\\n\\n\\n\\n\\n');
assert.equal(await page.evaluate(`window.term.buffer.cursorX`), 0);
assert.equal(await page.evaluate(`window.term.buffer.cursorY`), 4);
});
it('viewportY', async function(): Promise<any> {
it('viewportY', async () => {
await openTerminal({ rows: 5 });
assert.equal(await page.evaluate(`window.term.buffer.viewportY`), 0);
await page.evaluate(`window.term.write('\\n\\n\\n\\n')`);
await writeSync(page, '\\n\\n\\n\\n');
assert.equal(await page.evaluate(`window.term.buffer.viewportY`), 0);
await page.evaluate(`window.term.write('\\n')`);
await writeSync(page, '\\n');
assert.equal(await page.evaluate(`window.term.buffer.viewportY`), 1);
await page.evaluate(`window.term.write('\\n\\n\\n\\n')`);
await writeSync(page, '\\n\\n\\n\\n');
assert.equal(await page.evaluate(`window.term.buffer.viewportY`), 5);
await page.evaluate(`window.term.scrollLines(-1)`);
assert.equal(await page.evaluate(`window.term.buffer.viewportY`), 4);
@@ -434,14 +434,14 @@ describe('API Integration Tests', function(): void {
assert.equal(await page.evaluate(`window.term.buffer.viewportY`), 0);
});
it('baseY', async function(): Promise<any> {
it('baseY', async () => {
await openTerminal({ rows: 5 });
assert.equal(await page.evaluate(`window.term.buffer.baseY`), 0);
await page.evaluate(`window.term.write('\\n\\n\\n\\n')`);
await writeSync(page, '\\n\\n\\n\\n');
assert.equal(await page.evaluate(`window.term.buffer.baseY`), 0);
await page.evaluate(`window.term.write('\\n')`);
await writeSync(page, '\\n');
assert.equal(await page.evaluate(`window.term.buffer.baseY`), 1);
await page.evaluate(`window.term.write('\\n\\n\\n\\n')`);
await writeSync(page, '\\n\\n\\n\\n');
assert.equal(await page.evaluate(`window.term.buffer.baseY`), 5);
await page.evaluate(`window.term.scrollLines(-1)`);
assert.equal(await page.evaluate(`window.term.buffer.baseY`), 5);
@@ -449,44 +449,44 @@ describe('API Integration Tests', function(): void {
assert.equal(await page.evaluate(`window.term.buffer.baseY`), 5);
});
it('length', async function(): Promise<any> {
it('length', async () => {
await openTerminal({ rows: 5 });
assert.equal(await page.evaluate(`window.term.buffer.length`), 5);
await page.evaluate(`window.term.write('\\n\\n\\n\\n')`);
await writeSync(page, '\\n\\n\\n\\n');
assert.equal(await page.evaluate(`window.term.buffer.length`), 5);
await page.evaluate(`window.term.write('\\n')`);
await writeSync(page, '\\n');
assert.equal(await page.evaluate(`window.term.buffer.length`), 6);
await page.evaluate(`window.term.write('\\n\\n\\n\\n')`);
await writeSync(page, '\\n\\n\\n\\n');
assert.equal(await page.evaluate(`window.term.buffer.length`), 10);
});
describe('getLine', () => {
it('invalid index', async function(): Promise<any> {
it('invalid index', async () => {
await openTerminal({ rows: 5 });
assert.equal(await page.evaluate(`window.term.buffer.getLine(-1)`), undefined);
assert.equal(await page.evaluate(`window.term.buffer.getLine(5)`), undefined);
});
it('isWrapped', async function(): Promise<any> {
it('isWrapped', async () => {
await openTerminal({ cols: 5 });
assert.equal(await page.evaluate(`window.term.buffer.getLine(0).isWrapped`), false);
assert.equal(await page.evaluate(`window.term.buffer.getLine(1).isWrapped`), false);
await page.evaluate(`window.term.write('abcde')`);
await writeSync(page, 'abcde');
assert.equal(await page.evaluate(`window.term.buffer.getLine(0).isWrapped`), false);
assert.equal(await page.evaluate(`window.term.buffer.getLine(1).isWrapped`), false);
await page.evaluate(`window.term.write('f')`);
await writeSync(page, 'f');
assert.equal(await page.evaluate(`window.term.buffer.getLine(0).isWrapped`), false);
assert.equal(await page.evaluate(`window.term.buffer.getLine(1).isWrapped`), true);
});
it('translateToString', async function(): Promise<any> {
it('translateToString', async () => {
await openTerminal({ cols: 5 });
assert.equal(await page.evaluate(`window.term.buffer.getLine(0).translateToString()`), ' ');
assert.equal(await page.evaluate(`window.term.buffer.getLine(0).translateToString(true)`), '');
await page.evaluate(`window.term.write('foo')`);
await writeSync(page, 'foo');
assert.equal(await page.evaluate(`window.term.buffer.getLine(0).translateToString()`), 'foo ');
assert.equal(await page.evaluate(`window.term.buffer.getLine(0).translateToString(true)`), 'foo');
await page.evaluate(`window.term.write('bar')`);
await writeSync(page, 'bar');
assert.equal(await page.evaluate(`window.term.buffer.getLine(0).translateToString()`), 'fooba');
assert.equal(await page.evaluate(`window.term.buffer.getLine(0).translateToString(true)`), 'fooba');
assert.equal(await page.evaluate(`window.term.buffer.getLine(1).translateToString(true)`), 'r');
@@ -494,13 +494,13 @@ describe('API Integration Tests', function(): void {
assert.equal(await page.evaluate(`window.term.buffer.getLine(0).translateToString(false, 1, 3)`), 'oo');
});
it('getCell', async function(): Promise<any> {
it('getCell', async () => {
await openTerminal({ cols: 5 });
assert.equal(await page.evaluate(`window.term.buffer.getLine(0).getCell(-1)`), undefined);
assert.equal(await page.evaluate(`window.term.buffer.getLine(0).getCell(5)`), undefined);
assert.equal(await page.evaluate(`window.term.buffer.getLine(0).getCell(0).char`), '');
assert.equal(await page.evaluate(`window.term.buffer.getLine(0).getCell(0).width`), 1);
await page.evaluate(`window.term.write('a文')`);
await writeSync(page, 'a文');
assert.equal(await page.evaluate(`window.term.buffer.getLine(0).getCell(0).char`), 'a');
assert.equal(await page.evaluate(`window.term.buffer.getLine(0).getCell(0).width`), 1);
assert.equal(await page.evaluate(`window.term.buffer.getLine(0).getCell(1).char`), '文');
@@ -511,7 +511,7 @@ describe('API Integration Tests', function(): void {
});
});
it('dispose', async function(): Promise<any> {
it('dispose', async () => {
await page.evaluate(`
window.term = new Terminal();
window.term.dispose();
@@ -519,7 +519,7 @@ describe('API Integration Tests', function(): void {
assert.equal(await page.evaluate(`window.term._core._isDisposed`), true);
});
it('dispose (opened)', async function(): Promise<any> {
it('dispose (opened)', async () => {
await openTerminal();
await page.evaluate(`window.term.dispose()`);
assert.equal(await page.evaluate(`window.term._core._isDisposed`), true);
+31
View File
@@ -0,0 +1,31 @@
/**
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
* @license MIT
*/
import * as puppeteer from 'puppeteer';
import deepEqual = require('deep-equal');
export async function pollFor<T>(page: puppeteer.Page, evalOrFn: string | (() => Promise<T>), val: T, preFn?: () => Promise<void>): Promise<void> {
if (preFn) {
await preFn();
}
const result = typeof evalOrFn === 'string' ? await page.evaluate(evalOrFn) : await evalOrFn();
if (!deepEqual(result, val)) {
return new Promise<void>(r => {
setTimeout(() => r(pollFor(page, evalOrFn, val, preFn)), 1);
});
}
}
export async function writeSync(page: puppeteer.Page, data: string): Promise<void> {
await page.evaluate(`
window.ready = false;
window.term.write('${data}', () => window.ready = true);
`);
await pollFor(page, 'window.ready', true);
}
export async function timeout(ms: number): Promise<void> {
return new Promise<void>(r => setTimeout(r, ms));
}
+31 -8
View File
@@ -49,7 +49,7 @@ declare module 'xterm' {
/**
* When enabled the cursor will be set to the beginning of the next line
* with every new line. This equivalent to sending '\r\n' for each '\n'.
* with every new line. This is equivalent to sending '\r\n' for each '\n'.
* Normally the termios settings of the underlying PTY deals with the
* translation of '\n' to '\r\n' and this setting should not be used. If you
* deal with data from a non-PTY related source, this settings might be
@@ -148,6 +148,18 @@ declare module 'xterm' {
*/
macOptionClickForcesSelection?: boolean;
/**
* The minimum contrast ratio for text in the terminal, setting this will
* change the foreground color dynamically depending on whether the contrast
* ratio is met. Example values:
*
* - 1: The default, do nothing.
* - 4.5: Minimum for WCAG AA compliance.
* - 7: Minimum for WCAG AAA compliance.
* - 21: White on black or black on white.
*/
minimumContrastRatio?: number;
/**
* The type of renderer to use, this allows using the fallback DOM renderer
* when canvas is too slow for the environment. The following features do
@@ -421,6 +433,17 @@ declare module 'xterm' {
*/
constructor(options?: ITerminalOptions);
/**
* Adds an event listener for when a binary event fires. This is used to
* enable non UTF-8 conformant binary messages to be sent to the backend.
* Currently this is only used for a certain type of mouse reports that
* happen to be not UTF-8 compatible.
* The event value is a JS string, pass it to the underlying pty as
* binary data, e.g. `pty.write(Buffer.from(data, 'binary'))`.
* @returns an `IDisposable` to stop listening.
*/
onBinary: IEvent<string>;
/**
* Adds an event listener for the cursor moves.
* @returns an `IDisposable` to stop listening.
@@ -437,7 +460,7 @@ declare module 'xterm' {
onData: IEvent<string>;
/**
* Adds an event listener for a key is pressed. The event value contains the
* Adds an event listener for when a key is pressed. The event value contains the
* string that will be sent in the data event as well as the DOM event that
* triggered it.
* @returns an `IDisposable` to stop listening.
@@ -609,7 +632,7 @@ declare module 'xterm' {
/**
* Selects text within the terminal.
* @param column The column the selection starts at..
* @param column The column the selection starts at.
* @param row The row the selection starts at.
* @param length The length of the selection.
*/
@@ -918,7 +941,7 @@ declare module 'xterm' {
/**
* The line within the buffer where the top of the bottom page is (when
* fully scrolled down);
* fully scrolled down).
*/
readonly baseY: number;
@@ -1047,7 +1070,7 @@ declare module 'xterm' {
* array will contain subarrays with their numercial values.
* Return true if the sequence was handled; false if we should try
* a previous handler (set by addCsiHandler or setCsiHandler).
* The most recently-added handler is tried first.
* The most recently added handler is tried first.
* @return An IDisposable you can call to remove this handler.
*/
addCsiHandler(id: IFunctionIdentifier, callback: (params: (number | number[])[]) => boolean): IDisposable;
@@ -1066,7 +1089,7 @@ declare module 'xterm' {
* The function gets the payload and numerical parameters as arguments.
* Return true if the sequence was handled; false if we should try
* a previous handler (set by addDcsHandler or setDcsHandler).
* The most recently-added handler is tried first.
* The most recently added handler is tried first.
* @return An IDisposable you can call to remove this handler.
*/
addDcsHandler(id: IFunctionIdentifier, callback: (data: string, param: (number | number[])[]) => boolean): IDisposable;
@@ -1079,7 +1102,7 @@ declare module 'xterm' {
* @param callback The function to handle the sequence.
* Return true if the sequence was handled; false if we should try
* a previous handler (set by addEscHandler or setEscHandler).
* The most recently-added handler is tried first.
* The most recently added handler is tried first.
* @return An IDisposable you can call to remove this handler.
*/
addEscHandler(id: IFunctionIdentifier, handler: () => boolean): IDisposable;
@@ -1097,7 +1120,7 @@ declare module 'xterm' {
* The callback is called with OSC data string.
* Return true if the sequence was handled; false if we should try
* a previous handler (set by addOscHandler or setOscHandler).
* The most recently-added handler is tried first.
* The most recently added handler is tried first.
* @return An IDisposable you can call to remove this handler.
*/
addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable;
+550 -16
View File
@@ -9,6 +9,46 @@
dependencies:
"@babel/highlight" "^7.0.0"
"@babel/code-frame@^7.5.5":
version "7.5.5"
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.5.5.tgz#bc0782f6d69f7b7d49531219699b988f669a8f9d"
integrity sha512-27d4lZoomVyo51VegxI20xZPuSHusqbQag/ztrBC7wegWoQ1nLREPVSKSW8byhTlzTKyNE4ifaTA6lCp7JjpFw==
dependencies:
"@babel/highlight" "^7.0.0"
"@babel/generator@^7.4.0", "@babel/generator@^7.6.3":
version "7.6.4"
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.6.4.tgz#a4f8437287bf9671b07f483b76e3bb731bc97671"
integrity sha512-jsBuXkFoZxk0yWLyGI9llT9oiQ2FeTASmRFE32U+aaDTfoE92t78eroO7PTpU/OrYq38hlcDM6vbfLDaOLy+7w==
dependencies:
"@babel/types" "^7.6.3"
jsesc "^2.5.1"
lodash "^4.17.13"
source-map "^0.5.0"
"@babel/helper-function-name@^7.1.0":
version "7.1.0"
resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.1.0.tgz#a0ceb01685f73355d4360c1247f582bfafc8ff53"
integrity sha512-A95XEoCpb3TO+KZzJ4S/5uW5fNe26DjBGqf1o9ucyLyCmi1dXq/B3c8iaWTfBk3VvetUxl16e8tIrd5teOCfGw==
dependencies:
"@babel/helper-get-function-arity" "^7.0.0"
"@babel/template" "^7.1.0"
"@babel/types" "^7.0.0"
"@babel/helper-get-function-arity@^7.0.0":
version "7.0.0"
resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0.tgz#83572d4320e2a4657263734113c42868b64e49c3"
integrity sha512-r2DbJeg4svYvt3HOS74U4eWKsUAMRH01Z1ds1zx8KNTPtpTL5JAsdFv8BNyOpVqdFhHkkRDIg5B4AsxmkjAlmQ==
dependencies:
"@babel/types" "^7.0.0"
"@babel/helper-split-export-declaration@^7.4.4":
version "7.4.4"
resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.4.4.tgz#ff94894a340be78f53f06af038b205c49d993677"
integrity sha512-Ro/XkzLf3JFITkW6b+hNxzZ1n5OQ80NvIUdmHspih1XAhtN3vPTuUFT4eQnela+2MaZ5ulH+iyP513KJrxbN7Q==
dependencies:
"@babel/types" "^7.4.4"
"@babel/highlight@^7.0.0":
version "7.0.0"
resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.0.0.tgz#f710c38c8d458e6dd9a201afb637fcb781ce99e4"
@@ -18,6 +58,44 @@
esutils "^2.0.2"
js-tokens "^4.0.0"
"@babel/parser@^7.4.3", "@babel/parser@^7.6.0", "@babel/parser@^7.6.3":
version "7.6.4"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.6.4.tgz#cb9b36a7482110282d5cb6dd424ec9262b473d81"
integrity sha512-D8RHPW5qd0Vbyo3qb+YjO5nvUVRTXFLQ/FsDxJU2Nqz4uB5EnUN0ZQSEYpvTIbRuttig1XbHWU5oMeQwQSAA+A==
"@babel/template@^7.1.0", "@babel/template@^7.4.0":
version "7.6.0"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.6.0.tgz#7f0159c7f5012230dad64cca42ec9bdb5c9536e6"
integrity sha512-5AEH2EXD8euCk446b7edmgFdub/qfH1SN6Nii3+fyXP807QRx9Q73A2N5hNwRRslC2H9sNzaFhsPubkS4L8oNQ==
dependencies:
"@babel/code-frame" "^7.0.0"
"@babel/parser" "^7.6.0"
"@babel/types" "^7.6.0"
"@babel/traverse@^7.4.3":
version "7.6.3"
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.6.3.tgz#66d7dba146b086703c0fb10dd588b7364cec47f9"
integrity sha512-unn7P4LGsijIxaAJo/wpoU11zN+2IaClkQAxcJWBNCMS6cmVh802IyLHNkAjQ0iYnRS3nnxk5O3fuXW28IMxTw==
dependencies:
"@babel/code-frame" "^7.5.5"
"@babel/generator" "^7.6.3"
"@babel/helper-function-name" "^7.1.0"
"@babel/helper-split-export-declaration" "^7.4.4"
"@babel/parser" "^7.6.3"
"@babel/types" "^7.6.3"
debug "^4.1.0"
globals "^11.1.0"
lodash "^4.17.13"
"@babel/types@^7.0.0", "@babel/types@^7.4.0", "@babel/types@^7.4.4", "@babel/types@^7.6.0", "@babel/types@^7.6.3":
version "7.6.3"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.6.3.tgz#3f07d96f854f98e2fbd45c64b0cb942d11e8ba09"
integrity sha512-CqbcpTxMcpuQTMhjI37ZHVgjBkysg5icREQIEZ0eG1yCNwg3oy+5AaLiOKmjsCj6nqOsa6Hf0ObjRVwokb7srA==
dependencies:
esutils "^2.0.2"
lodash "^4.17.13"
to-fast-properties "^2.0.0"
"@fimbul/bifrost@^0.11.0":
version "0.11.0"
resolved "https://registry.yarnpkg.com/@fimbul/bifrost/-/bifrost-0.11.0.tgz#83cacc21464198b12e3cc1c2204ae6c6d7afd158"
@@ -52,6 +130,11 @@
resolved "https://registry.yarnpkg.com/@types/cli-table/-/cli-table-0.3.0.tgz#f1857156bf5fd115c6a2db260ba0be1f8fc5671c"
integrity sha512-QnZUISJJXyhyD6L1e5QwXDV/A5i2W1/gl6D6YMc8u0ncPepbv/B4w3S+izVvtAg60m6h+JP09+Y/0zF2mojlFQ==
"@types/deep-equal@^1.0.1":
version "1.0.1"
resolved "https://registry.yarnpkg.com/@types/deep-equal/-/deep-equal-1.0.1.tgz#71cfabb247c22bcc16d536111f50c0ed12476b03"
integrity sha512-mMUu4nWHLBlHtxXY17Fg6+ucS/MnndyOWyOe7MmwkoMYxvfQU2ajtRaEvqSUv+aVkMqH/C0NCI8UoVfRNQ10yg==
"@types/events@*":
version "1.2.0"
resolved "https://registry.yarnpkg.com/@types/events/-/events-1.2.0.tgz#81a6731ce4df43619e5c8c945383b3e62a89ea86"
@@ -439,11 +522,23 @@ app-root-path@^2.2.1:
resolved "https://registry.yarnpkg.com/app-root-path/-/app-root-path-2.2.1.tgz#d0df4a682ee408273583d43f6f79e9892624bc9a"
integrity sha512-91IFKeKk7FjfmezPKkwtaRvSpnUc4gDwPAjA1YZ9Gn0q0PPeW+vbeUsZuyDwjI7+QTHhcLen2v25fi/AmhvbJA==
append-transform@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/append-transform/-/append-transform-1.0.0.tgz#046a52ae582a228bd72f58acfbe2967c678759ab"
integrity sha512-P009oYkeHyU742iSZJzZZywj4QRJdnTWffaKuJQLablCZ1uz6/cW4yaRgcDaoQ+uwOxxnt0gRUcwfsNP2ri0gw==
dependencies:
default-require-extensions "^2.0.0"
aproba@^1.0.3, aproba@^1.1.1:
version "1.2.0"
resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a"
integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==
archy@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/archy/-/archy-1.0.0.tgz#f9c8c13757cc1dd7bc379ac77b2c62a5c2868c40"
integrity sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=
are-we-there-yet@~1.1.2:
version "1.1.5"
resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz#4b35c2944f062a8bfcda66410760350fe9ddfc21"
@@ -489,6 +584,11 @@ array-unique@^0.3.2:
resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428"
integrity sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=
arrify@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d"
integrity sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=
asn1.js@^4.0.0:
version "4.10.1"
resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-4.10.1.tgz#b9c2bf5805f1e64aadeed6df3a2bfafb5a73f5a0"
@@ -816,6 +916,16 @@ cache-base@^1.0.1:
union-value "^1.0.0"
unset-value "^1.0.0"
caching-transform@^3.0.1:
version "3.0.2"
resolved "https://registry.yarnpkg.com/caching-transform/-/caching-transform-3.0.2.tgz#601d46b91eca87687a281e71cef99791b0efca70"
integrity sha512-Mtgcv3lh3U0zRii/6qVgQODdPA4G3zhG+jtbCWj39RXuUFTMzH0vcdMtaJS1jPowd+It2Pqr6y3NJMQqOqCE2w==
dependencies:
hasha "^3.0.0"
make-dir "^2.0.0"
package-hash "^3.0.0"
write-file-atomic "^2.4.2"
camelcase@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd"
@@ -1075,6 +1185,11 @@ commander@^2.20.0:
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.0.tgz#d58bb2b5c1ee8f87b0d340027e9e94e222c5a422"
integrity sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==
commander@~2.20.3:
version "2.20.3"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
commondir@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b"
@@ -1134,6 +1249,13 @@ content-type@~1.0.4:
resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b"
integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==
convert-source-map@^1.6.0:
version "1.6.0"
resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.6.0.tgz#51b537a8c43e0f04dec1993bffcdd504e758ac20"
integrity sha512-eFu7XigvxdZ1ETfbgPBohgyQ/Z++C0eEhTor0qRwBw9unw+L0/6V8wkSuGgzdThkiS5lSpdptOQPD8Ak40a+7A==
dependencies:
safe-buffer "~5.1.1"
cookie-signature@1.0.6:
version "1.0.6"
resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c"
@@ -1197,6 +1319,14 @@ create-hmac@^1.1.0, create-hmac@^1.1.2, create-hmac@^1.1.4:
safe-buffer "^5.0.1"
sha.js "^2.4.8"
cross-spawn@^4:
version "4.0.2"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-4.0.2.tgz#7b9247621c23adfdd3856004a823cbe397424d41"
integrity sha1-e5JHYhwjrf3ThWAEqCPL45dCTUE=
dependencies:
lru-cache "^4.0.1"
which "^1.2.9"
cross-spawn@^5.0.1:
version "5.1.0"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449"
@@ -1293,7 +1423,7 @@ debug@^3.1.0:
dependencies:
ms "2.0.0"
debug@^4.0.1, debug@^4.1.0:
debug@^4.0.1, debug@^4.1.0, debug@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791"
integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==
@@ -1329,6 +1459,18 @@ deep-eql@^0.1.3:
dependencies:
type-detect "0.1.1"
deep-equal@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.1.0.tgz#3103cdf8ab6d32cf4a8df7865458f2b8d33f3745"
integrity sha512-ZbfWJq/wN1Z273o7mUSjILYqehAktR2NVoSrOukDkU9kg2v/Uv89yU4Cvz8seJeAmtN5oqiefKq8FPuXOboqLw==
dependencies:
is-arguments "^1.0.4"
is-date-object "^1.0.1"
is-regex "^1.0.4"
object-is "^1.0.1"
object-keys "^1.1.1"
regexp.prototype.flags "^1.2.0"
deep-extend@^0.6.0:
version "0.6.0"
resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac"
@@ -1339,6 +1481,13 @@ deep-is@~0.1.3:
resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34"
integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=
default-require-extensions@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/default-require-extensions/-/default-require-extensions-2.0.0.tgz#f5f8fbb18a7d6d50b21f641f649ebb522cfe24f7"
integrity sha1-9fj7sYp9bVCyH2QfZJ67Uiz+JPc=
dependencies:
strip-bom "^3.0.0"
defaults@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/defaults/-/defaults-1.0.3.tgz#c656051e9817d9ff08ed881477f3fe4019f3ef7d"
@@ -1536,6 +1685,13 @@ errno@^0.1.3, errno@~0.1.7:
dependencies:
prr "~1.0.1"
error-ex@^1.3.1:
version "1.3.2"
resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf"
integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==
dependencies:
is-arrayish "^0.2.1"
es-abstract@^1.5.1:
version "1.13.0"
resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.13.0.tgz#ac86145fdd5099d8dd49558ccba2eaf9b88e24e9"
@@ -1557,6 +1713,11 @@ es-to-primitive@^1.2.0:
is-date-object "^1.0.1"
is-symbol "^1.0.2"
es6-error@^4.0.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/es6-error/-/es6-error-4.1.1.tgz#9e3af407459deed47e9a91f9b885a84eb05c561d"
integrity sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==
es6-promise@^4.0.3:
version "4.2.6"
resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.6.tgz#b685edd8258886365ea62b57d30de28fadcd974f"
@@ -1878,7 +2039,7 @@ finalhandler@~1.1.2:
statuses "~1.5.0"
unpipe "~1.0.0"
find-cache-dir@^2.1.0:
find-cache-dir@^2.0.0, find-cache-dir@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-2.1.0.tgz#8d0f94cd13fe43c6c7c261a0d86115ca918c05f7"
integrity sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==
@@ -1921,6 +2082,14 @@ for-in@^1.0.2:
resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80"
integrity sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=
foreground-child@^1.5.6:
version "1.5.6"
resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-1.5.6.tgz#4fd71ad2dfde96789b980a5c0a295937cb2f5ce9"
integrity sha1-T9ca0t/elnibmApcCilZN8svXOk=
dependencies:
cross-spawn "^4"
signal-exit "^3.0.0"
forever-agent@~0.6.1:
version "0.6.1"
resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91"
@@ -2097,6 +2266,11 @@ global-modules-path@^2.1.0:
resolved "https://registry.yarnpkg.com/global-modules-path/-/global-modules-path-2.3.0.tgz#b0e2bac6beac39745f7db5c59d26a36a0b94f7dc"
integrity sha512-HchvMJNYh9dGSCy8pOQ2O8u/hoXaL+0XhnrwH0RyLiSXMMTl9W3N6KUU73+JFOg5PGjtzl6VZzUQsnrpm7Szag==
globals@^11.1.0:
version "11.12.0"
resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e"
integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==
graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2:
version "4.2.2"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.2.tgz#6f0952605d0140c1cfdb138ed005775b92d67b02"
@@ -2107,6 +2281,17 @@ growl@1.10.5:
resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.5.tgz#f2735dc2283674fa67478b10181059355c369e5e"
integrity sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==
handlebars@^4.1.2:
version "4.5.1"
resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.5.1.tgz#8a01c382c180272260d07f2d1aa3ae745715c7ba"
integrity sha512-C29UoFzHe9yM61lOsIlCE5/mQVGrnIOrOq7maQl76L7tYPCgC1og0Ajt6uWnX4ZTxBPnjw+CUvawphwCfJgUnA==
dependencies:
neo-async "^2.6.0"
optimist "^0.6.1"
source-map "^0.6.1"
optionalDependencies:
uglify-js "^3.1.4"
har-schema@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92"
@@ -2189,6 +2374,13 @@ hash.js@^1.0.0, hash.js@^1.0.3:
inherits "^2.0.3"
minimalistic-assert "^1.0.1"
hasha@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/hasha/-/hasha-3.0.0.tgz#52a32fab8569d41ca69a61ff1a214f8eb7c8bd39"
integrity sha1-UqMvq4Vp1BymmmH/GiFPjrfIvTk=
dependencies:
is-stream "^1.0.1"
he@1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
@@ -2203,6 +2395,11 @@ hmac-drbg@^1.0.0:
minimalistic-assert "^1.0.0"
minimalistic-crypto-utils "^1.0.1"
hosted-git-info@^2.1.4:
version "2.8.5"
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.5.tgz#759cfcf2c4d156ade59b0b2dfabddc42a6b9c70c"
integrity sha512-kssjab8CvdXfcXMXVcvsXum4Hwdq9XGtRD3TteMEvEbq0LXyiNQr6AprqKqfeaDXze7SxWvRxdpwE6ku7ikLkg==
html-encoding-sniffer@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz#e70d84b94da53aa375e11fe3a351be6642ca46f8"
@@ -2387,6 +2584,16 @@ is-accessor-descriptor@^1.0.0:
dependencies:
kind-of "^6.0.0"
is-arguments@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.0.4.tgz#3faf966c7cba0ff437fb31f6250082fcf0448cf3"
integrity sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA==
is-arrayish@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d"
integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=
is-arrayish@^0.3.1:
version "0.3.2"
resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03"
@@ -2525,7 +2732,7 @@ is-regex@^1.0.4:
dependencies:
has "^1.0.1"
is-stream@^1.1.0:
is-stream@^1.0.1, is-stream@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ=
@@ -2579,6 +2786,58 @@ isstream@~0.1.2:
resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=
istanbul-lib-coverage@^2.0.3, istanbul-lib-coverage@^2.0.5:
version "2.0.5"
resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz#675f0ab69503fad4b1d849f736baaca803344f49"
integrity sha512-8aXznuEPCJvGnMSRft4udDRDtb1V3pkQkMMI5LI+6HuQz5oQ4J2UFn1H82raA3qJtyOLkkwVqICBQkjnGtn5mA==
istanbul-lib-hook@^2.0.3:
version "2.0.7"
resolved "https://registry.yarnpkg.com/istanbul-lib-hook/-/istanbul-lib-hook-2.0.7.tgz#c95695f383d4f8f60df1f04252a9550e15b5b133"
integrity sha512-vrRztU9VRRFDyC+aklfLoeXyNdTfga2EI3udDGn4cZ6fpSXpHLV9X6CHvfoMCPtggg8zvDDmC4b9xfu0z6/llA==
dependencies:
append-transform "^1.0.0"
istanbul-lib-instrument@^3.1.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-3.3.0.tgz#a5f63d91f0bbc0c3e479ef4c5de027335ec6d630"
integrity sha512-5nnIN4vo5xQZHdXno/YDXJ0G+I3dAm4XgzfSVTPLQpj/zAV2dV6Juy0yaf10/zrJOJeHoN3fraFe+XRq2bFVZA==
dependencies:
"@babel/generator" "^7.4.0"
"@babel/parser" "^7.4.3"
"@babel/template" "^7.4.0"
"@babel/traverse" "^7.4.3"
"@babel/types" "^7.4.0"
istanbul-lib-coverage "^2.0.5"
semver "^6.0.0"
istanbul-lib-report@^2.0.4:
version "2.0.8"
resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-2.0.8.tgz#5a8113cd746d43c4889eba36ab10e7d50c9b4f33"
integrity sha512-fHBeG573EIihhAblwgxrSenp0Dby6tJMFR/HvlerBsrCTD5bkUuoNtn3gVh29ZCS824cGGBPn7Sg7cNk+2xUsQ==
dependencies:
istanbul-lib-coverage "^2.0.5"
make-dir "^2.1.0"
supports-color "^6.1.0"
istanbul-lib-source-maps@^3.0.2:
version "3.0.6"
resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-3.0.6.tgz#284997c48211752ec486253da97e3879defba8c8"
integrity sha512-R47KzMtDJH6X4/YW9XTx+jrLnZnscW4VpNN+1PViSYTejLVPWv7oov+Duf8YQSPyVRUvueQqz1TcsC6mooZTXw==
dependencies:
debug "^4.1.1"
istanbul-lib-coverage "^2.0.5"
make-dir "^2.1.0"
rimraf "^2.6.3"
source-map "^0.6.1"
istanbul-reports@^2.1.1:
version "2.2.6"
resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-2.2.6.tgz#7b4f2660d82b29303a8fe6091f8ca4bf058da1af"
integrity sha512-SKi4rnMyLBKe0Jy2uUdx28h8oG7ph2PPuQPvIAh31d+Ci+lSiEu4C+h3oBPuJ9+mPKhOyW0M8gY4U5NM1WLeXA==
dependencies:
handlebars "^4.1.2"
javascript-natural-sort@0.7.1:
version "0.7.1"
resolved "https://registry.yarnpkg.com/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz#f9e2303d4507f6d74355a73664d1440fb5a0ef59"
@@ -2634,7 +2893,12 @@ jsdom@^11.11.0:
ws "^4.0.0"
xml-name-validator "^3.0.0"
json-parse-better-errors@^1.0.2:
jsesc@^2.5.1:
version "2.5.2"
resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4"
integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==
json-parse-better-errors@^1.0.1, json-parse-better-errors@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9"
integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==
@@ -2739,6 +3003,16 @@ levn@~0.3.0:
prelude-ls "~1.1.2"
type-check "~0.3.2"
load-json-file@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-4.0.0.tgz#2f5f45ab91e33216234fd53adab668eb4ec0993b"
integrity sha1-L19Fq5HjMhYjT9U62rZo607AmTs=
dependencies:
graceful-fs "^4.1.2"
parse-json "^4.0.0"
pify "^3.0.0"
strip-bom "^3.0.0"
loader-runner@^2.4.0:
version "2.4.0"
resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.4.0.tgz#ed47066bfe534d7e84c4c7b9998c2a75607d9357"
@@ -2778,6 +3052,11 @@ locate-path@^3.0.0:
p-locate "^3.0.0"
path-exists "^3.0.0"
lodash.flattendeep@^4.4.0:
version "4.4.0"
resolved "https://registry.yarnpkg.com/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz#fb030917f86a3134e5bc9bec0d69e0013ddfedb2"
integrity sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=
lodash.sortby@^4.7.0:
version "4.7.0"
resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438"
@@ -2788,6 +3067,11 @@ lodash@^4.13.1, lodash@^4.17.10, lodash@^4.17.11:
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.14.tgz#9ce487ae66c96254fe20b599f21b6816028078ba"
integrity sha512-mmKYbW3GLuJeX+iGP+Y7Gp1AiGHGbXHCOh/jZmrawMmsE7MS4znI3RL2FsjbqOyMayHInjOeykW7PEajUk1/xw==
lodash@^4.17.13:
version "4.17.15"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548"
integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==
log-symbols@2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-2.2.0.tgz#5740e1c5d6f0dfda4ad9323b5332107ef6b4c40a"
@@ -2821,7 +3105,14 @@ lru-cache@^5.1.1:
dependencies:
yallist "^3.0.2"
make-dir@^2.0.0:
make-dir@^1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.3.0.tgz#79c1033b80515bd6d24ec9933e860ca75ee27f0c"
integrity sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==
dependencies:
pify "^3.0.0"
make-dir@^2.0.0, make-dir@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5"
integrity sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==
@@ -2910,6 +3201,13 @@ merge-descriptors@1.0.1:
resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61"
integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=
merge-source-map@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/merge-source-map/-/merge-source-map-1.1.0.tgz#2fdde7e6020939f70906a68f2d7ae685e4c8c646"
integrity sha512-Qkcp7P2ygktpMPh2mCQZaf3jhN6D3Z/qVZHSdWvQ+2Ef5HgRAPBO57A77+ENm0CPx2+1Ce/MYKi3ymqdfuqibw==
dependencies:
source-map "^0.6.1"
methods@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
@@ -3021,6 +3319,11 @@ minimist@^1.2.0:
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284"
integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=
minimist@~0.0.1:
version "0.0.10"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf"
integrity sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=
minipass@^2.2.1, minipass@^2.3.5:
version "2.5.0"
resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.5.0.tgz#dddb1d001976978158a05badfcbef4a771612857"
@@ -3164,7 +3467,7 @@ negotiator@0.6.2:
resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb"
integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==
neo-async@^2.5.0, neo-async@^2.6.1:
neo-async@^2.5.0, neo-async@^2.6.0, neo-async@^2.6.1:
version "2.6.1"
resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.1.tgz#ac27ada66167fa8849a6addd837f6b189ad2081c"
integrity sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==
@@ -3242,6 +3545,16 @@ nopt@^4.0.1:
abbrev "1"
osenv "^0.1.4"
normalize-package-data@^2.3.2:
version "2.5.0"
resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8"
integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==
dependencies:
hosted-git-info "^2.1.4"
resolve "^1.10.0"
semver "2 || 3 || 4 || 5"
validate-npm-package-license "^3.0.1"
normalize-path@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9"
@@ -3294,6 +3607,36 @@ nwsapi@^2.0.0:
resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.0.4.tgz#dc79040a5f77b97716dc79565fc7fc3ef7d50570"
integrity sha512-Zt6HRR6RcJkuj5/N9zeE7FN6YitRW//hK2wTOwX274IBphbY3Zf5+yn5mZ9v/SzAOTMjQNxZf9KkmPLWn0cV4g==
nyc@13:
version "13.3.0"
resolved "https://registry.yarnpkg.com/nyc/-/nyc-13.3.0.tgz#da4dbe91a9c8b9ead3f4f3344c76f353e3c78c75"
integrity sha512-P+FwIuro2aFG6B0Esd9ZDWUd51uZrAEoGutqZxzrVmYl3qSfkLgcQpBPBjtDFsUQLFY1dvTQJPOyeqr8S9GF8w==
dependencies:
archy "^1.0.0"
arrify "^1.0.1"
caching-transform "^3.0.1"
convert-source-map "^1.6.0"
find-cache-dir "^2.0.0"
find-up "^3.0.0"
foreground-child "^1.5.6"
glob "^7.1.3"
istanbul-lib-coverage "^2.0.3"
istanbul-lib-hook "^2.0.3"
istanbul-lib-instrument "^3.1.0"
istanbul-lib-report "^2.0.4"
istanbul-lib-source-maps "^3.0.2"
istanbul-reports "^2.1.1"
make-dir "^1.3.0"
merge-source-map "^1.1.0"
resolve-from "^4.0.0"
rimraf "^2.6.3"
signal-exit "^3.0.2"
spawn-wrap "^1.4.2"
test-exclude "^5.1.0"
uuid "^3.3.2"
yargs "^12.0.5"
yargs-parser "^11.1.1"
oauth-sign@~0.8.2:
version "0.8.2"
resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43"
@@ -3313,7 +3656,12 @@ object-copy@^0.1.0:
define-property "^0.2.5"
kind-of "^3.0.3"
object-keys@^1.0.11, object-keys@^1.0.12:
object-is@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.0.1.tgz#0aa60ec9989a0b3ed795cf4d06f62cf1ad6539b6"
integrity sha1-CqYOyZiaCz7Xlc9NBvYs8a1lObY=
object-keys@^1.0.11, object-keys@^1.0.12, object-keys@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e"
integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==
@@ -3376,6 +3724,14 @@ onetime@^2.0.0:
dependencies:
mimic-fn "^1.0.0"
optimist@^0.6.1:
version "0.6.1"
resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.6.1.tgz#da3ea74686fa21a19a111c326e90eb15a0196686"
integrity sha1-2j6nRob6IaGaERwybpDrFaAZZoY=
dependencies:
minimist "~0.0.1"
wordwrap "~0.0.2"
optionator@^0.8.1:
version "0.8.2"
resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.2.tgz#364c5e409d3f4d6301d6c0b4c05bba50180aeb64"
@@ -3393,7 +3749,7 @@ os-browserify@^0.3.0:
resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.3.0.tgz#854373c7f5c2315914fc9bfc6bd8238fdda1ec27"
integrity sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=
os-homedir@^1.0.0:
os-homedir@^1.0.0, os-homedir@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3"
integrity sha1-/7xJiDNuDoM94MFox+8VISGqf7M=
@@ -3482,6 +3838,16 @@ p-try@^2.0.0:
resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6"
integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==
package-hash@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/package-hash/-/package-hash-3.0.0.tgz#50183f2d36c9e3e528ea0a8605dff57ce976f88e"
integrity sha512-lOtmukMDVvtkL84rJHI7dpTYq+0rli8N2wlnqUcBuDWCfVhRUfOmnR9SsoHFMLpACvEV60dX7rd0rFaYDZI+FA==
dependencies:
graceful-fs "^4.1.15"
hasha "^3.0.0"
lodash.flattendeep "^4.4.0"
release-zalgo "^1.0.0"
pako@~1.0.5:
version "1.0.10"
resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.10.tgz#4328badb5086a426aa90f541977d4955da5c9732"
@@ -3508,6 +3874,14 @@ parse-asn1@^5.0.0:
pbkdf2 "^3.0.3"
safe-buffer "^5.1.1"
parse-json@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-4.0.0.tgz#be35f5425be1f7f6c747184f98a788cb99477ee0"
integrity sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=
dependencies:
error-ex "^1.3.1"
json-parse-better-errors "^1.0.1"
parse5@4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/parse5/-/parse5-4.0.0.tgz#6d78656e3da8d78b4ec0b906f7c08ef1dfe3f608"
@@ -3560,11 +3934,23 @@ path-parse@^1.0.5:
resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.5.tgz#3c1adf871ea9cd6c9431b6ea2bd74a0ff055c4c1"
integrity sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME=
path-parse@^1.0.6:
version "1.0.6"
resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c"
integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==
path-to-regexp@0.1.7:
version "0.1.7"
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c"
integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=
path-type@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/path-type/-/path-type-3.0.0.tgz#cef31dc8e0a1a3bb0d105c0cd97cf3bf47f4e36f"
integrity sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==
dependencies:
pify "^3.0.0"
pbkdf2@^3.0.3:
version "3.0.17"
resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.0.17.tgz#976c206530617b14ebb32114239f7b09336e93a6"
@@ -3591,6 +3977,11 @@ picomatch@^2.0.5:
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.0.7.tgz#514169d8c7cd0bdbeecc8a2609e34a7163de69f6"
integrity sha512-oLHIdio3tZ0qH76NybpeneBhYVj0QFTfXEFTc/B3zKQspYfYYkWYgFsmzo+4kvId/bQRcNkVeguI3y+CD22BtA==
pify@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176"
integrity sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=
pify@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231"
@@ -3813,6 +4204,23 @@ rc@^1.2.7:
minimist "^1.2.0"
strip-json-comments "~2.0.1"
read-pkg-up@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-4.0.0.tgz#1b221c6088ba7799601c808f91161c66e58f8978"
integrity sha512-6etQSH7nJGsK0RbG/2TeDzZFa8shjQ1um+SwQQ5cwKy0dhSXdOncEhb1CPpvQG4h7FyOV6EB6YlV0yJvZQNAkA==
dependencies:
find-up "^3.0.0"
read-pkg "^3.0.0"
read-pkg@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-3.0.0.tgz#9cbc686978fee65d16c00e2b19c237fcf6e38389"
integrity sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=
dependencies:
load-json-file "^4.0.0"
normalize-package-data "^2.3.2"
path-type "^3.0.0"
"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.6, readable-stream@~2.3.6:
version "2.3.6"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf"
@@ -3857,6 +4265,20 @@ regex-not@^1.0.0, regex-not@^1.0.2:
extend-shallow "^3.0.2"
safe-regex "^1.1.0"
regexp.prototype.flags@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.2.0.tgz#6b30724e306a27833eeb171b66ac8890ba37e41c"
integrity sha512-ztaw4M1VqgMwl9HlPpOuiYgItcHlunW0He2fE6eNfT6E/CF2FtYi9ofOYe4mKntstYk0Fyh/rDRBdS3AnxjlrA==
dependencies:
define-properties "^1.1.2"
release-zalgo@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/release-zalgo/-/release-zalgo-1.0.0.tgz#09700b7e5074329739330e535c5a90fb67851730"
integrity sha1-CXALflB0Mpc5Mw5TXFqQ+2eFFzA=
dependencies:
es6-error "^4.0.1"
remove-trailing-separator@^1.0.1:
version "1.1.0"
resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef"
@@ -3941,6 +4363,11 @@ resolve-from@^3.0.0:
resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-3.0.0.tgz#b22c7af7d9d6881bc8b6e653335eebcb0a188748"
integrity sha1-six699nWiBvItuZTM17rywoYh0g=
resolve-from@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6"
integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==
resolve-url@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a"
@@ -3951,6 +4378,13 @@ resolve@1.1.7:
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b"
integrity sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=
resolve@^1.10.0:
version "1.12.0"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.12.0.tgz#3fc644a35c84a48554609ff26ec52b66fa577df6"
integrity sha512-B/dOmuoAik5bKcD6s6nXDCjzUKnaDvdkRyAk6rsmsKLipWj4797iothd7jmmUhWTfinVMU+wc56rYKsit2Qy4w==
dependencies:
path-parse "^1.0.6"
resolve@^1.3.2:
version "1.8.1"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.8.1.tgz#82f1ec19a423ac1fbd080b0bab06ba36e84a7a26"
@@ -3971,7 +4405,7 @@ ret@~0.1.10:
resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc"
integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==
rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.3:
rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.2, rimraf@^2.6.3:
version "2.7.1"
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec"
integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==
@@ -4048,7 +4482,7 @@ seed-random@2.2.0:
resolved "https://registry.yarnpkg.com/seed-random/-/seed-random-2.2.0.tgz#2a9b19e250a817099231a5b99a4daf80b7fbed54"
integrity sha1-KpsZ4lCoFwmSMaW5mk2vgLf77VQ=
semver@^5.3.0, semver@^5.6.0:
"semver@2 || 3 || 4 || 5", semver@^5.3.0, semver@^5.6.0:
version "5.7.1"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==
@@ -4233,7 +4667,7 @@ source-map-url@^0.4.0:
resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3"
integrity sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=
source-map@^0.5.6:
source-map@^0.5.0, source-map@^0.5.6:
version "0.5.7"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc"
integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=
@@ -4243,6 +4677,44 @@ source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1:
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
spawn-wrap@^1.4.2:
version "1.4.3"
resolved "https://registry.yarnpkg.com/spawn-wrap/-/spawn-wrap-1.4.3.tgz#81b7670e170cca247d80bf5faf0cfb713bdcf848"
integrity sha512-IgB8md0QW/+tWqcavuFgKYR/qIRvJkRLPJDFaoXtLLUaVcCDK0+HeFTkmQHj3eprcYhc+gOl0aEA1w7qZlYezw==
dependencies:
foreground-child "^1.5.6"
mkdirp "^0.5.0"
os-homedir "^1.0.1"
rimraf "^2.6.2"
signal-exit "^3.0.2"
which "^1.3.0"
spdx-correct@^3.0.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.0.tgz#fb83e504445268f154b074e218c87c003cd31df4"
integrity sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==
dependencies:
spdx-expression-parse "^3.0.0"
spdx-license-ids "^3.0.0"
spdx-exceptions@^2.1.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz#2ea450aee74f2a89bfb94519c07fcd6f41322977"
integrity sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==
spdx-expression-parse@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz#99e119b7a5da00e05491c9fa338b7904823b41d0"
integrity sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==
dependencies:
spdx-exceptions "^2.1.0"
spdx-license-ids "^3.0.0"
spdx-license-ids@^3.0.0:
version "3.0.5"
resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz#3694b5804567a458d3c8045842a6358632f62654"
integrity sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==
split-string@^3.0.1, split-string@^3.0.2:
version "3.1.0"
resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2"
@@ -4401,6 +4873,11 @@ strip-ansi@^5.1.0:
dependencies:
ansi-regex "^4.1.0"
strip-bom@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3"
integrity sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=
strip-eof@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf"
@@ -4432,6 +4909,13 @@ supports-color@^5.4.0:
dependencies:
has-flag "^3.0.0"
supports-color@^6.1.0:
version "6.1.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.1.0.tgz#0764abc69c63d5ac842dd4867e8d025e880df8f3"
integrity sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==
dependencies:
has-flag "^3.0.0"
symbol-tree@^3.2.2:
version "3.2.2"
resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.2.tgz#ae27db38f660a7ae2e1c3b7d1bc290819b8519e6"
@@ -4479,6 +4963,16 @@ terser@^4.1.2:
source-map "~0.6.1"
source-map-support "~0.5.12"
test-exclude@^5.1.0:
version "5.2.3"
resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-5.2.3.tgz#c3d3e1e311eb7ee405e092dac10aefd09091eac0"
integrity sha512-M+oxtseCFO3EDtAaGH7iiej3CBkzXqFMbzqYAACdzKui4eZA+pq3tZEwChvOdNfa7xxy8BfbmgJSIr43cC/+2g==
dependencies:
glob "^7.1.3"
minimatch "^3.0.4"
read-pkg-up "^4.0.0"
require-main-filename "^2.0.0"
text-hex@1.0.x:
version "1.0.0"
resolved "https://registry.yarnpkg.com/text-hex/-/text-hex-1.0.0.tgz#69dc9c1b17446ee79a92bf5b884bb4b9127506f5"
@@ -4521,6 +5015,11 @@ to-arraybuffer@^1.0.0:
resolved "https://registry.yarnpkg.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43"
integrity sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=
to-fast-properties@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e"
integrity sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=
to-object-path@^0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/to-object-path/-/to-object-path-0.3.0.tgz#297588b7b0e7e0ac08e04e672f85c1f4999e17af"
@@ -4719,16 +5218,24 @@ typedarray@^0.0.6:
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
typescript@3.6:
version "3.6.3"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.6.3.tgz#fea942fabb20f7e1ca7164ff626f1a9f3f70b4da"
integrity sha512-N7bceJL1CtRQ2RiG0AQME13ksR7DiuQh/QehubYcghzv20tnh+MQnQIuJddTmsbqYj+dztchykemz0zFzlvdQw==
typescript@3.7:
version "3.7.2"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.7.2.tgz#27e489b95fa5909445e9fef5ee48d81697ad18fb"
integrity sha512-ml7V7JfiN2Xwvcer+XAf2csGO1bPBdRbFCkYBczNZggrBZ9c7G3riSUeJmqEU5uOtXNPMhE3n+R4FA/3YOAWOQ==
typescript@^3.5.1:
version "3.5.1"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.5.1.tgz#ba72a6a600b2158139c5dd8850f700e231464202"
integrity sha512-64HkdiRv1yYZsSe4xC1WVgamNigVYjlssIoaH2HcZF0+ijsk5YK2g0G34w9wJkze8+5ow4STd22AynfO6ZYYLw==
uglify-js@^3.1.4:
version "3.6.5"
resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.6.5.tgz#b0ee796d2ae7e25672e04f65629b997cd4b30bd6"
integrity sha512-7L3W+Npia1OCr5Blp4/Vw83tK1mu5gnoIURtT1fUVfQ3Kf8WStWV6NJz0fdoBJZls0KlweruRTLVe6XLafmy5g==
dependencies:
commander "~2.20.3"
source-map "~0.6.1"
union-value@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.1.tgz#0b6fe7b835aecda61c6ea4d4f02c14221e109847"
@@ -4830,11 +5337,24 @@ uuid@^3.1.0:
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131"
integrity sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==
uuid@^3.3.2:
version "3.3.3"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.3.tgz#4568f0216e78760ee1dbf3a4d2cf53e224112866"
integrity sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ==
v8-compile-cache@^2.0.0:
version "2.0.2"
resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.0.2.tgz#a428b28bb26790734c4fc8bc9fa106fccebf6a6c"
integrity sha512-1wFuMUIM16MDJRCrpbpuEPTUGmM5QMUg0cr3KFwra2XgOgFcPGDQHDh3CszSCD2Zewc/dh/pamNEW8CbfDebUw==
validate-npm-package-license@^3.0.1:
version "3.0.4"
resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a"
integrity sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==
dependencies:
spdx-correct "^3.0.0"
spdx-expression-parse "^3.0.0"
vary@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
@@ -4962,7 +5482,7 @@ which-module@^2.0.0:
resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a"
integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=
which@1.3.1, which@^1.2.9:
which@1.3.1, which@^1.2.9, which@^1.3.0:
version "1.3.1"
resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a"
integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==
@@ -4999,6 +5519,11 @@ winston@^3.2.1:
triple-beam "^1.3.0"
winston-transport "^4.3.0"
wordwrap@~0.0.2:
version "0.0.3"
resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.3.tgz#a3d5da6cd5c0bc0008d37234bbaf1bed63059107"
integrity sha1-o9XabNXAvAAI03I0u68b7WMFkQc=
wordwrap@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb"
@@ -5024,6 +5549,15 @@ wrappy@1:
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
write-file-atomic@^2.4.2:
version "2.4.3"
resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-2.4.3.tgz#1fd2e9ae1df3e75b8d8c367443c692d4ca81f481"
integrity sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==
dependencies:
graceful-fs "^4.1.11"
imurmurhash "^0.1.4"
signal-exit "^3.0.2"
ws@^4.0.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/ws/-/ws-4.1.0.tgz#a979b5d7d4da68bf54efe0408967c324869a7289"