Compare commits

...
Author SHA1 Message Date
copilot-swe-agent[bot]andTyriar 369ca9b525 Add error handling for dynamic imports
Co-authored-by: Tyriar <2193314+Tyriar@users.noreply.github.com>
2026-01-16 01:58:45 +00:00
copilot-swe-agent[bot]andTyriar a5422d1b11 Add dynamic imports for AccessibilityManager and OverviewRulerRenderer
Co-authored-by: Tyriar <2193314+Tyriar@users.noreply.github.com>
2026-01-16 01:56:13 +00:00
copilot-swe-agent[bot]andTyriar c2890d8a4d Add error handling for dynamic import in loadCustomGlyphDefinitions
Co-authored-by: Tyriar <2193314+Tyriar@users.noreply.github.com>
2026-01-16 01:14:01 +00:00
copilot-swe-agent[bot]andTyriar 7a7abddc45 Use dynamic imports for custom glyphs in WebGL addon
Co-authored-by: Tyriar <2193314+Tyriar@users.noreply.github.com>
2026-01-16 01:12:01 +00:00
copilot-swe-agent[bot]andTyriar 8c71bfd65f Revert @xterm/xterm alias to use package.json resolution
Co-authored-by: Tyriar <2193314+Tyriar@users.noreply.github.com>
2026-01-16 00:41:57 +00:00
copilot-swe-agent[bot]andTyriar 3c2b911439 Set up code splitting for ESM build
Co-authored-by: Tyriar <2193314+Tyriar@users.noreply.github.com>
2026-01-16 00:40:40 +00:00
copilot-swe-agent[bot] 5ee8760418 Initial plan 2026-01-16 00:31:02 +00:00
Daniel Imms 777a46445a Merge pull request #5609 from xtermjs/merogge/show-cursor
add `showCursorImmediately` option
2026-01-13 08:47:50 -08:00
meganrogge a20ff565b0 add showCursorImmediately option 2026-01-13 10:36:35 -06:00
Daniel Imms d28046ebed Merge pull request #5607 from Tyriar/5606
Fix blur due to canvas dim mismatch in glyph renderer
2026-01-13 06:35:40 -08:00
Daniel Imms 4ddd982e42 Fix blur due to canvas dim mismatch in glyph renderer
Fixes #5606
2026-01-13 06:23:10 -08:00
Daniel Imms a3228bd1b8 Merge pull request #5604 from Tyriar/2357_2
Fix capital letters and ctrl+letter in win32-input-mode
2026-01-11 09:10:31 -08:00
Daniel Imms a1fca4feb8 Fix ctrl+letter when inside pwsh->WSL 2026-01-11 09:02:44 -08:00
Daniel Imms afc6f0b7eb Fix caps not working in win32InputMode
Part of #2357
2026-01-11 09:00:08 -08:00
Daniel Imms f4c77d37b4 Merge pull request #5603 from Tyriar/2357
Implement win32-input-mode
2026-01-10 06:04:17 -08:00
Daniel Imms 9d0beb7c38 Use 0b over 0x for flags 2026-01-10 05:57:13 -08:00
Daniel Imms f88650ec1c Use win32InputMode in demo on Windows 2026-01-10 05:54:48 -08:00
Daniel Imms 8b35c67dd3 useWin32 -> useWin32InputMode 2026-01-10 05:52:19 -08:00
Daniel Imms 53dc85eca2 Add copyright 2026-01-10 05:48:48 -08:00
Daniel Imms 0946386cc4 Make unit tests more compact 2026-01-10 05:48:30 -08:00
Daniel Imms ebe18b3da5 Add more test cases 2026-01-10 05:42:17 -08:00
Daniel Imms 6c2792a1d5 Fix tests 2026-01-10 05:33:08 -08:00
Daniel Imms ee47054678 Implement win32-input-mode
Fixes #2357
2026-01-10 05:27:03 -08:00
Daniel Imms 91c4761b82 Merge pull request #5600 from Tyriar/4198_kitty
Implement kitty keyboard protocol (CSI =|?|>|< u)
2026-01-10 04:46:38 -08:00
Daniel Imms c9fc6fca8e Lint 2026-01-10 04:39:26 -08:00
Daniel Imms 73cba6dda3 Clean up, add sgr extension to api 2026-01-10 04:38:06 -08:00
Daniel Imms 50b0f3d7ee Add vtExtensions.kittySgrBoldFaintControl 2026-01-10 04:31:36 -08:00
Daniel Imms 686c67a14b Make mandatory browser services readonly 2026-01-10 04:20:29 -08:00
Daniel Imms ec78500f86 Create IKeyboardService 2026-01-10 04:19:23 -08:00
Daniel Imms 98ea14824a Make lint happy 2026-01-09 13:14:30 -08:00
Daniel Imms 4c905dad33 Update API jsdoc 2026-01-09 13:02:18 -08:00
Daniel Imms e3fdcc7869 Fix API lint 2026-01-09 13:01:22 -08:00
Daniel Imms 0674663a5a Remove unused import 2026-01-09 12:56:45 -08:00
Daniel Imms e07832a420 Merge branch 'master' into 4198_kitty 2026-01-09 12:51:48 -08:00
Daniel Imms 934b5dc8fa Merge pull request #5602 from Tyriar/5601
Add SGR 221 and 222
2026-01-09 12:21:43 -08:00
Daniel Imms 7a544d8346 Add SGR 221 and 222
Fixes #5601
2026-01-09 12:13:40 -08:00
Daniel Imms c624a47780 Maintain different buffer flags, stack limit, tests 2026-01-09 11:59:50 -08:00
Daniel Imms 5a29d489c3 Separate out kitty keyboard handlers 2026-01-09 11:46:57 -08:00
Daniel Imms 4a67836a5b Remove support responses
Wrong place for them
2026-01-09 11:42:00 -08:00
Daniel Imms 3adf6be988 Fix some edge cases 2026-01-09 10:36:13 -08:00
Daniel Imms 566589f7c1 More comprehensive tests 2026-01-09 07:23:12 -08:00
Daniel Imms 8dbc91dc24 Add vtExtensions to demo 2026-01-09 07:15:45 -08:00
Daniel Imms 992da3cc4d Hook up vt extensions option 2026-01-09 07:07:10 -08:00
Daniel Imms 3fbaef06ef Align behavior closer with kitty 2026-01-09 06:53:12 -08:00
Daniel Imms 2265fa2ade Implement kitty keyboard protocol (CSI u)
Fixes #4198
2026-01-09 05:59:57 -08:00
Daniel Imms 756c353237 Merge pull request #5593 from kallsyms/improve-lines-cache-perf
improve SearchLineCache performance
2026-01-09 05:59:54 -08:00
Daniel Imms 49a8ac8fc0 Merge pull request #5599 from Tyriar/flush
Flush writes on resize
2026-01-09 05:37:21 -08:00
Daniel Imms bf7c95b6cc Flush writes on resize
Fixes #5597
2026-01-09 05:25:22 -08:00
Daniel Imms 8112fbf210 Merge pull request #5598 from Tyriar/5588
Fix exception on resize during write
2026-01-08 18:10:41 -08:00
Daniel Imms 16a13f8504 Fix exception on resize during write
Fixes #5588
2026-01-08 18:03:42 -08:00
Daniel Imms 9bd128b315 Merge pull request #5594 from Tyriar/onSelectionChangeClear
Fire onSelectionChange on clear
2026-01-08 17:44:16 -08:00
Daniel Imms 729a670043 Merge pull request #5596 from Tyriar/lint_changes
New npm script to lint only changes
2026-01-08 17:39:34 -08:00
Daniel Imms bd8454db55 Another poll case 2026-01-08 17:37:44 -08:00
Daniel Imms d6f6a6af9a Lint instructions 2026-01-08 17:32:50 -08:00
Daniel Imms d65f9312ea Add lint-changes npm scripts 2026-01-08 17:32:15 -08:00
Daniel Imms a437d12004 Poll to avoid a race 2026-01-08 17:25:26 -08:00
Daniel Imms 437490308c Merge pull request #5595 from Tyriar/download_fonts
Remove download fonts
2026-01-08 17:18:36 -08:00
Daniel Imms 3250c31202 Remove download fonts 2026-01-08 17:09:26 -08:00
Daniel Imms 55d12e03d1 Remove unneeded test 2026-01-08 17:04:35 -08:00
Daniel Imms 6efb319ece Add tests more thorough tests for onSelectionChange 2026-01-08 17:01:56 -08:00
Daniel Imms b69afe5765 Merge remote-tracking branch 'upstream/master' into onSelectionChangeClear 2026-01-08 16:51:54 -08:00
Daniel Imms 613aa6ba23 Fire onSelectionChange when starting to drag 2026-01-08 16:51:45 -08:00
Nick Gregory a8c9128841 improve SearchLineCache performance 2026-01-07 16:04:49 -08:00
Daniel Imms 94a264679e Merge pull request #5591 from Tyriar/5586
Ensure mouse listeners are removed on dispose
2026-01-05 12:38:41 -08:00
Daniel Imms cb35d1b487 Ensure mouse listeners are removed on dispose
Also use ? over ! just in case

Fixes #5586
2026-01-05 12:31:54 -08:00
Daniel Imms c97abced76 Merge pull request #5590 from Tyriar/5587
Add undefined checks in erase calls
2026-01-05 11:17:40 -08:00
Daniel Imms 9db31db886 Add undefined checks in erase calls
This is likely a race condition during a resize which is hard to say
what the right behavior is. Instead of hard failing by throwing an
error, silently ignore if this happens.

Fixes #5587
2026-01-05 11:10:56 -08:00
Daniel Imms ccf9727b85 Merge pull request #5583 from Tyriar/root
Clean up root, remove auto approve settings
2026-01-04 06:44:57 -08:00
Daniel Imms 1dfdde9e27 Clean up root, remove auto approve settings
These are automatically auto approved in upcoming vscode
2026-01-04 06:38:53 -08:00
Daniel Imms 2712e88d0a Merge pull request #5582 from Tyriar/readme2
Update readme 2
2026-01-04 06:28:32 -08:00
Daniel Imms b5da8a28f6 Update readme 2 2026-01-04 06:27:22 -08:00
Daniel Imms 66c4f8b116 Merge pull request #5581 from Tyriar/readme
Update readme
2026-01-04 06:20:48 -08:00
Daniel Imms 9148c80d1a Update readme
- Added x2 scale logo
- Revised wording
- Added new addons
2026-01-04 06:18:50 -08:00
Daniel Imms e1bfc7dd1f Merge pull request #5178 from jerch/webfonts-addon
Add webfonts addon
2026-01-04 05:43:01 -08:00
Daniel Imms c6c4a2ffbc Delete unwanted eslintrc 2026-01-03 15:39:24 -08:00
Daniel Imms 2cf2929869 Publish webfonts 2026-01-03 15:36:36 -08:00
Daniel Imms b0145998c5 Fix lint 2026-01-03 15:36:31 -08:00
Daniel Imms 0e57643f16 Move fonts into folder 2026-01-03 15:33:13 -08:00
Daniel Imms a35223a819 Add web fonts window to demo 2026-01-03 15:31:24 -08:00
Daniel Imms 5e95dc1f10 Sync package lock 2026-01-03 15:24:11 -08:00
Daniel Imms b4e9a7f9ea Merge remote-tracking branch 'upstream/master' into pr/jerch/5178 2026-01-03 15:21:50 -08:00
Daniel Imms 73e3357526 Merge pull request #5579 from Tyriar/proposed
Make everything except unicode APIs stable
2026-01-03 15:04:25 -08:00
Daniel Imms 483c7b4a81 Merge pull request #5580 from Tyriar/5572
Update demo dims on font family change
2026-01-03 14:58:57 -08:00
Daniel Imms f4453b3e18 Merge pull request #5566 from xtermjs/dependabot/npm_and_yarn/qs-6.14.1
Bump qs from 6.14.0 to 6.14.1
2026-01-03 14:58:39 -08:00
Daniel Imms 7389a1097d Update tests to use unicode for proposed check 2026-01-03 14:57:55 -08:00
Daniel Imms 0c7b1cb448 Update demo dims on font family change
Fixes #5572
2026-01-03 14:52:32 -08:00
Daniel Imms 7a3b5339e7 Merge pull request #5578 from Tyriar/5576
Lint demo code
2026-01-03 14:41:48 -08:00
Daniel Imms 8193ceae12 Make everything except unicode APIs stable 2026-01-03 14:40:07 -08:00
Daniel Imms 064e299bb0 Merge pull request #5577 from Tyriar/ext_recommendation
Recommend eslint extension in vscode
2026-01-03 14:38:45 -08:00
Daniel Imms 6211bf078c Lint demo code
Fixes #5576
2026-01-03 14:35:39 -08:00
Daniel Imms a639808196 Recommend eslint extension in vscode
Fixes #5575
2026-01-03 14:32:16 -08:00
Daniel Imms b522e255f0 Merge pull request #5571 from Tyriar/5341
Fix isNode detection when process module is used in webpack
2026-01-03 14:25:33 -08:00
Daniel Imms d7850af9a2 Fix lint 2026-01-03 14:19:36 -08:00
Daniel Imms 4182f2226f Merge pull request #5573 from Tyriar/5570_oom
Fix infinite recursion OOM crash in addon-ligatures
2026-01-03 14:15:11 -08:00
Daniel Imms e2f45b5f4e Fix for modern node that includes a navigator.userAgent 2026-01-03 14:10:44 -08:00
Daniel Imms 8712f8067d Fix infinite recursion OOM crash in addon-ligatures
1. processLookahead/BacktrackPosition created separate entry objects
   for each glyph at each position (O(N^M) with N glyphs, M positions).
   Now all glyphs at a position share one entry object.
2. cloneEntry, cloneTree, flattenEntry, and mergeTreeEntry could
   infinitely recurse or duplicate work on shared/cyclic references.
   Added visited/merged tracking maps to cache results.

Note that parsing CommitMono locks up the renderer for some time still
so there is still a performance issue here, this fixes the OOM though.
This was mostly defensive generated code since the codebase is
unfamiliar to me.

Fixes #5570
2026-01-03 14:06:57 -08:00
Daniel Imms 08c49301b7 Fix isNode detection when process module is used in webpack
Fixes #5341
2026-01-03 11:19:23 -08:00
dependabot[bot] 917a8ded92 Bump qs from 6.14.0 to 6.14.1
Bumps [qs](https://github.com/ljharb/qs) from 6.14.0 to 6.14.1.
- [Changelog](https://github.com/ljharb/qs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ljharb/qs/compare/v6.14.0...v6.14.1)

---
updated-dependencies:
- dependency-name: qs
  dependency-version: 6.14.1
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-03 17:44:20 +00:00
Daniel Imms d06d278f0c Merge pull request #5569 from Tyriar/5568_font_ligatures
Merge font-ligatures package into addon-ligatures
2026-01-03 09:43:07 -08:00
Daniel Imms f84384c83a Fix lint 2026-01-03 09:31:45 -08:00
Daniel Imms ec03109c39 2 space indent 2026-01-03 09:25:39 -08:00
Daniel Imms 21b8f77afb Move unit tests to right folder, push font fixtures 2026-01-03 09:16:58 -08:00
Daniel Imms 363fc84fd2 Merge font-ligatures into addon-ligatures
Fixes #5568
2026-01-03 09:02:06 -08:00
Daniel Imms 8e4de23c11 Merge pull request #5565 from Tyriar/2797
Support ctrl+^ -> RS, move to physical key
2026-01-01 05:25:52 -08:00
Daniel Imms 1caf6dbe50 Fix tests 2026-01-01 05:19:56 -08:00
Daniel Imms 1c97e40a6a Support ctrl+^ -> RS, move to physical key
Fixes #2797
Part of #1862
2026-01-01 04:41:44 -08:00
Daniel Imms cdbafb231d Merge pull request #5564 from Tyriar/3364
Serialize showCursor
2025-12-31 17:14:02 -08:00
Daniel Imms 44a8c7efbc Fix test expectations 2025-12-31 17:08:05 -08:00
Daniel Imms 6277bb363c Serialize showCursor
Fixes #3364
2025-12-31 08:12:39 -08:00
Daniel Imms 40f2eefec7 Merge pull request #5563 from Tyriar/5489
Fix touch scrolling
2025-12-31 06:33:36 -08:00
Daniel Imms 519b1c5ef7 Fix touch scrolling
Fixes #5489
2025-12-31 06:27:45 -08:00
Daniel Imms 97e356fb83 Merge pull request #5562 from Tyriar/2117_3
Add tests for CSI Pm d, e, f, g, m
2025-12-31 05:24:41 -08:00
Daniel Imms 42872a84dd Add tests for CSI Pm d, e, f, g, m
Fixes #2117
2025-12-31 05:18:17 -08:00
Daniel Imms 4700e18833 Merge pull request #5561 from Tyriar/5558
Fix allowTransparency in lib and demo
2025-12-31 05:08:29 -08:00
Daniel Imms d143340b60 Fix allowTransparency in lib and demo
Fixes #5558
Fixes #5560
2025-12-31 05:00:33 -08:00
Daniel Imms a8f26bb758 Merge pull request #5559 from Tyriar/2117_2
Add tests for CSI `Ps K`, `? Ps K`, `Ps S`, `Ps T`, `Ps X`, `Ps Z`, `Ps ^`, `Ps \``
2025-12-30 07:06:37 -08:00
Daniel Imms da4243cf02 Don't skip new tests, implement CSI Ps ^ 2025-12-30 06:59:27 -08:00
Daniel Imms 54e66f3ac4 Add some more CSI tests 2025-12-30 06:48:28 -08:00
Daniel Imms 7d8ef60860 Merge pull request #5557 from Tyriar/playwright_update2
Update playwright test to latest
2025-12-30 06:47:24 -08:00
Daniel Imms 595c119404 Move skip into dom file 2025-12-30 06:05:46 -08:00
Daniel Imms 481431fbc1 Update playwright test to latest 2025-12-30 06:03:17 -08:00
Daniel Imms 5c2cd474ce Merge pull request #5556 from Tyriar/5555
Migrate evaluate calls to use terminal proxy
2025-12-30 05:43:14 -08:00
Daniel Imms 0b9a7cb856 Migrate MouseTracking and Terminal to playwright proxy
Fixes #5555
2025-12-30 05:37:09 -08:00
Daniel Imms a8a4455189 Migrate InputHandler.test.ts to playwright proxy
Part of #5555
2025-12-30 05:27:28 -08:00
Daniel Imms 11fbdb3db7 Merge pull request #5554 from Tyriar/2117
Add unit test for CSI Ps SP @ - SL
2025-12-30 05:10:44 -08:00
Daniel Imms 88ce0d40e7 Add unit test for CSI Ps SP @ - SL
Part of #2117
2025-12-30 05:04:20 -08:00
Daniel Imms 2785f11860 Merge pull request #5553 from Tyriar/1526
Save cursor and restore more modes
2025-12-30 04:55:51 -08:00
Daniel Imms 857c4529cd Save cursor and restore more modes
Fixes #1526
2025-12-30 04:49:54 -08:00
Daniel Imms d30c3e7204 Merge pull request #5552 from Tyriar/contrib
Rework contributing doc
2025-12-30 04:42:18 -08:00
Daniel Imms 982f5e15ff Merge pull request #5551 from Tyriar/702
Add dimensions API
2025-12-30 04:37:31 -08:00
Daniel Imms ec3e4f2af1 Rework contributing doc
Fixes #5550
2025-12-30 04:33:57 -08:00
Daniel Imms 13914ee01e Remove private use in ImageRenderer 2025-12-30 04:09:56 -08:00
Daniel Imms e27cc2c089 Remove private API access from tests/demo 2025-12-30 04:06:26 -08:00
Daniel Imms 214289aaab Add dimensions API
Fixes #702
2025-12-30 03:58:26 -08:00
Daniel Imms a5af434e3e Merge pull request #5549 from Tyriar/5548
Use OffscreenCanvas over DOM for WidthCache
2025-12-30 03:38:15 -08:00
Daniel Imms a513120348 Get dom/width cache tests passing in node 2025-12-30 03:32:34 -08:00
Daniel Imms ef978b370b Use OffscreenCanvas over DOM for WidthCache
Fixes #5548
2025-12-30 02:57:09 -08:00
Daniel Imms 00ad3eac40 Merge pull request #5547 from Tyriar/3433
Serialize scroll region
2025-12-29 07:59:49 -08:00
Daniel Imms 29d3eb2e52 Serialize scroll region
fixes #3433
2025-12-29 07:53:43 -08:00
Daniel Imms 8b594140e9 Merge pull request #5546 from Tyriar/progress
Add progress indicator custom glyphs (EE00-EE0B)
2025-12-29 07:37:53 -08:00
Daniel Imms 7cf48c1eb5 Add progress indicator custom glyphs (EE00-EE0B)
Fixes #5478
2025-12-29 07:31:52 -08:00
Daniel Imms 8e6b6e7464 Merge pull request #5545 from Tyriar/nf
Add nerd fonts test button
2025-12-29 06:39:13 -08:00
Daniel Imms 1b0554714b Add nerd fonts test button
Fixes #5544
2025-12-29 06:33:27 -08:00
Daniel Imms abdfa2628c Merge pull request #5543 from Tyriar/underline_inspect
Report underline attributes in demo cell inspector
2025-12-29 06:23:38 -08:00
Daniel Imms 5b4d794f7a Report underline attributes in demo cell inspector 2025-12-29 06:17:36 -08:00
Daniel Imms 9eb1da79e3 Merge pull request #5542 from Tyriar/4064
Make curly underline more vivid
2025-12-29 06:14:01 -08:00
Daniel Imms d4ba4c109b Make curly underline more vivid
Fixes #4064
2025-12-29 06:04:29 -08:00
Daniel Imms 1ad23d6081 Merge pull request #5539 from Tyriar/ris
Add RIS metadata comment
2025-12-29 05:06:07 -08:00
Daniel Imms 73c01cae89 Merge pull request #5541 from Tyriar/4114
Fix fit using correct dims on demo
2025-12-29 05:03:58 -08:00
Daniel Imms 5174a7310f Add RIS metadata comment
Fixes #5043
2025-12-29 05:00:13 -08:00
Daniel Imms 1d793c9054 Fix fit using correct dims on demo
This doesn't add the debounce suggested in the issue, but it does
comment about it.

Fixes #4114
2025-12-29 04:58:12 -08:00
Daniel Imms 1b7c61a6fc Merge pull request #5540 from Tyriar/4855
Fix decorations with height > 1
2025-12-29 04:20:51 -08:00
Daniel Imms 9780b44115 Fix decorations with height > 1
Fixes #4855
2025-12-29 04:14:56 -08:00
Daniel Imms 8d532a35c4 Merge pull request #5538 from Tyriar/4731_mobile_warn
Update viewport dims when dpr changes
2025-12-28 16:36:30 -08:00
Daniel Imms d6df8d7902 Update viewport dims when dpr changes
Fixes #4731
2025-12-28 16:30:44 -08:00
Daniel Imms 4b9566cef0 Merge pull request #5535 from Tyriar/5408_2
Fix firefox selection when in shadow dom take 2
2025-12-28 16:05:49 -08:00
Daniel Imms b30bb1e3b0 Fix stack overflow 2025-12-28 15:59:29 -08:00
Loïc Mangeonjean d67e73a4da fix(fit-addon): call getComputedStyle on the right window element 2025-12-28 15:55:21 -08:00
Loïc Mangeonjean e7434a932d fix: fix computed style computation on firefox inside a shadow dom 2025-12-28 15:55:21 -08:00
Daniel Imms cdb0497d0d Merge pull request #5534 from Tyriar/5184_sync_reset
Force reset to use sync render
2025-12-28 15:22:13 -08:00
Daniel Imms 2fb42c554e Force reset to use sync render
Fixes #5184
2025-12-28 15:16:23 -08:00
Daniel Imms 3632496c94 Merge pull request #5533 from Tyriar/5532
Fix addon build issue on macOS
2025-12-28 15:08:12 -08:00
Daniel Imms a6407fb3c6 Fix addon build issue on macOS
Fixes #5532
2025-12-28 15:02:16 -08:00
Daniel Imms 65aa28464c Merge pull request #5530 from Tyriar/4339
Fix double annoucne of too much output
2025-12-28 14:57:09 -08:00
Daniel Imms 2baad39ccf Fix double annoucne of too much output
Fixes #4339
2025-12-28 14:51:12 -08:00
Daniel Imms c975956f2a Merge pull request #5529 from Tyriar/worktree1
Force a sync render after resize occurs
2025-12-28 14:44:44 -08:00
Daniel Imms 4619a755b1 Force a sync render after resize occurs
Fixes #4922
Related microsoft/vscode#230120
2025-12-28 14:38:42 -08:00
Daniel Imms bc795d36c0 Merge pull request #5528 from Tyriar/4528
Serialize rich underline styles
2025-12-28 14:26:35 -08:00
Daniel Imms b989c5a568 Fix lint 2025-12-28 14:20:55 -08:00
Daniel Imms d66214b25e Serialize rich underline styles 2025-12-28 14:16:53 -08:00
Daniel Imms 6157f523a8 Merge pull request #5523 from Tyriar/4628
Add idle timeout which pauses cursor blink
2025-12-28 14:04:49 -08:00
Daniel Imms 3ab93ea083 Merge pull request #5526 from Tyriar/lint
Remove eol-last eslint rule
2025-12-28 14:04:18 -08:00
Daniel Imms 7ef4c55ca9 Merge pull request #5525 from Tyriar/demo_hover
Implement cell inspector in demo
2025-12-28 13:57:35 -08:00
Daniel Imms d393154271 Remove eol-last eslint rule
This one's just troublesome when working with generated code. It
doesn't make a difference in the end anyway.
2025-12-28 13:56:22 -08:00
Daniel Imms 2c32ef5963 Fix lint 2025-12-28 13:53:03 -08:00
Daniel Imms a730533790 Implement cell inspector in demo
Fixes #5524
2025-12-28 13:50:37 -08:00
Daniel Imms 8a5ffc1bb1 Tidy comments 2025-12-28 13:42:22 -08:00
Daniel Imms a966f7d591 Explain idle in cursorBlink option 2025-12-28 13:41:21 -08:00
Daniel Imms 4985c9f3f9 Use shared constant for idle timeout 2025-12-28 13:37:25 -08:00
Daniel Imms 2e1651badd Pull dom renderer blink management into class 2025-12-28 13:35:57 -08:00
Daniel Imms 7d1f74fb8b Use const enums for constants 2025-12-28 13:33:51 -08:00
Daniel Imms f8c1f5a32f Add idle timeout which pauses cursor blink
Fixes #4628
2025-12-28 13:30:58 -08:00
Daniel Imms 79944f7524 Merge pull request #5521 from Tyriar/5314
Add quirks setting to control DECSET/DECRST 12
2025-12-28 08:07:47 -08:00
Daniel Imms 52b6710b19 Hide quirks from demo for now 2025-12-28 08:01:56 -08:00
Daniel Imms 2a283a307c Fix lint 2025-12-28 07:56:31 -08:00
Daniel Imms ab4524873b Add test for DECSET quirks 2025-12-28 07:49:12 -08:00
Daniel Imms eb5f0d709a Merge pull request #5522 from Tyriar/5295
Note in API that reflowCursorLine will not move the cursor
2025-12-28 07:43:50 -08:00
Daniel Imms 8a8f262616 Note in API that reflowCursorLine will not move the cursor
Fixes #5295
2025-12-28 07:37:50 -08:00
Daniel Imms ca5a681363 Add quirks setting to control DECSET/DECRST 12
Fixes #5314
2025-12-28 07:34:55 -08:00
Daniel Imms fb10cd62c9 Merge pull request #5520 from Tyriar/4961
Ignore soft hyphen
2025-12-27 14:57:12 -08:00
Daniel Imms 28b62a5e0b Ignore soft hyphen
Fixes #4961
2025-12-27 14:51:07 -08:00
Daniel Imms bedf7e8326 Merge pull request #5519 from Tyriar/5444
Add onBeforeSearch and onAfterSearch APIs
2025-12-27 14:38:30 -08:00
Daniel Imms f50028abce Add onBeforeSearch and onAfterSearch APIs
Fixes #5444
2025-12-27 14:32:39 -08:00
Daniel Imms 7a4c9cadff Merge pull request #5518 from Tyriar/style
Demo: Improve style, remove headers from windows
2025-12-27 09:03:02 -08:00
Daniel Imms 01c1c477c3 Demo: Improve style, remove headers from windows 2025-12-27 08:57:26 -08:00
Daniel Imms 33daadb4ea Merge pull request #5516 from Tyriar/activeMatchBg
Ensure activeMatchBackground decoration is on top
2025-12-27 08:04:50 -08:00
Daniel Imms ef6013aac9 Merge pull request #5517 from Tyriar/demo_addons
Demo: Add addon tabs, shuffle order
2025-12-27 08:04:31 -08:00
Daniel Imms 82f24ad5a5 Demo: Add addon tabs, shuffle order 2025-12-27 06:47:17 -08:00
Daniel Imms 97c435862b Merge pull request #5439 from nya3jp/keydup
Fix key duplication on Android with physical keyboards
2025-12-27 06:38:28 -08:00
Daniel Imms 6488e839a1 Merge branch 'master' into keydup 2025-12-27 06:32:16 -08:00
Daniel Imms 5d3564bb38 Ensure activeMatchBackground decoration is on top
Fixes #5369
2025-12-27 06:30:02 -08:00
Daniel Imms 50985d30f9 Merge pull request #5515 from Tyriar/5457_ctrl+slash
Add ctrl+/ -> ^_ keybinding
2025-12-27 06:05:48 -08:00
Daniel Imms 733e48ec73 Add ctrl+/ -> ^_ keybinding
Fixes #5457
2025-12-27 05:57:43 -08:00
Daniel Imms 057fefd39e Merge pull request #5514 from Tyriar/demo6
Ensure types are checked in demo, fix errors, emit to out-demo
2025-12-27 05:40:41 -08:00
Daniel Imms 4d48f8624b Ensure types are checked in demo, fix errors, emit to out-demo
Part of #5509
2025-12-27 05:31:21 -08:00
Daniel Imms 995b491a7a Merge pull request #5513 from Tyriar/esbuild_warn
Set esbuild log level to warning
2025-12-27 05:23:44 -08:00
Daniel Imms 4fe118a8ff Set esbuild log level to warning
This suppresses a safe to ignore debug log in server task
2025-12-27 05:18:00 -08:00
Daniel Imms 45375c94e2 Merge pull request #5512 from Tyriar/demo_server
Move server.js to TypeScript
2025-12-27 05:12:14 -08:00
Daniel Imms f83760d027 Add esbuild server build task 2025-12-27 04:57:55 -08:00
Daniel Imms 1a1bdefc83 Move server to TS
Fixes #5509
2025-12-27 04:36:18 -08:00
Daniel Imms e28db9e8a9 Merge pull request #5511 from Tyriar/settings
Simplify vscode settings
2025-12-27 04:09:31 -08:00
Daniel Imms be5f510bfb Merge pull request #5510 from Tyriar/dev
Make default build task use a single terminal via concurrently
2025-12-27 04:07:16 -08:00
Daniel Imms ac6b8ebb10 Simplify vscode settings 2025-12-27 04:03:24 -08:00
Daniel Imms 1ada355048 Make default build task use a single terminal via concurrently 2025-12-27 03:41:47 -08:00
Daniel Imms 4ce3e3106b Merge pull request #5508 from Tyriar/demo4
Fix css not being used in demo
2025-12-27 03:15:58 -08:00
Daniel Imms 942aff6af7 Merge pull request #5507 from Tyriar/ts5.9
Upgrade to typescript 5.9.3
2025-12-27 03:14:38 -08:00
Daniel Imms 635b898dc0 Fix css not being used in demo 2025-12-27 03:09:39 -08:00
Daniel Imms e01d1f2f17 Upgrade to typescript 5.9.3 2025-12-27 03:06:14 -08:00
Daniel Imms bd16eeed90 Merge pull request #5506 from Tyriar/shared_compile
Fix type error in SharedRendererTests.ts
2025-12-27 03:01:27 -08:00
Daniel Imms df8f9fc519 Merge pull request #5505 from Tyriar/canvasExceptions
Remove unused skip canvas exceptions from playwright
2025-12-27 02:59:27 -08:00
Daniel Imms 373bf513f1 Fix type error in SharedRendererTests.ts 2025-12-27 02:55:43 -08:00
Daniel Imms 25a5497329 Remove unused skip canvas exceptions from playwright 2025-12-27 02:53:28 -08:00
Daniel Imms 4406043e6b Merge pull request #5504 from Tyriar/onRender_headless
Implement onRender in xterm-headless
2025-12-26 11:33:01 -08:00
Daniel Imms 82f961f76f Move new onRender behavior into headless Terminal impl 2025-12-26 11:27:38 -08:00
Daniel Imms a26471b9f3 Implement onRender in xterm-headless
This effectively enabled implementing an efficient renderer on top
of xterm-headless. See #5392
2025-12-26 11:19:59 -08:00
Daniel Imms ba989a6e6c Merge pull request #5503 from Tyriar/copilot_approve
Add some npm scripts that copilot can approve
2025-12-26 11:16:36 -08:00
Daniel Imms d4f725c649 Merge pull request #5502 from Tyriar/xtversion
Support reporting XTVERSION
2025-12-26 11:13:53 -08:00
Daniel Imms 79f5198b2d Add some npm scripts that copilot can approve 2025-12-26 11:10:53 -08:00
Daniel Imms d8a93b757a Support reporting XTVERSION
Fixes #5342
2025-12-26 11:06:55 -08:00
Daniel Imms 9453634f56 Merge pull request #5501 from Tyriar/demo3
Make clear css file is for index.html
2025-12-26 10:59:43 -08:00
Daniel Imms 10e12cf743 Make clear css file is for index.html
Follow up on #5500
2025-12-26 10:54:07 -08:00
Daniel Imms 79621a4e3f Merge pull request #5500 from Tyriar/demo2
More demo refactors
2025-12-26 10:51:49 -08:00
Daniel Imms 546c5e081d Inline simplified style to test.html 2025-12-26 10:46:27 -08:00
Daniel Imms e6ad59b1b1 Disable grid layout in test.html 2025-12-26 10:38:39 -08:00
Daniel Imms 8eab98b78d Change banner bg color 2025-12-26 04:41:05 -08:00
Daniel Imms 0ed6fd0db6 Fix order of init 2025-12-26 04:40:22 -08:00
Daniel Imms e3f87ab8a1 More move handlers into addons and test window 2025-12-26 04:36:51 -08:00
Daniel Imms 8119bb0a4a Simplify window interaction 2025-12-26 04:31:06 -08:00
Daniel Imms dc0d7c81d9 Add terminal and addon refs to all windows 2025-12-26 04:24:07 -08:00
Daniel Imms fa87ecdc4e Move more handlers into testWindow.ts 2025-12-26 04:15:13 -08:00
Daniel Imms b82e8f345f Move more handlers out, addon types into shared file 2025-12-26 04:09:24 -08:00
Daniel Imms 02e9e56eef Move more handlers into testWindow.ts 2025-12-26 04:04:08 -08:00
Daniel Imms 4fffb96f25 Move addCjk into window, move window interactions out of createTerminal 2025-12-26 03:51:01 -08:00
Daniel Imms 6fe5a124ca Merge pull request #5498 from Tyriar/demo
Improve demo
2025-12-26 03:29:03 -08:00
Daniel Imms f62043de91 Merge pull request #5497 from xtermjs/Tyriar-patch-1
Box Elements -> Block Elements
2025-12-25 04:27:12 -08:00
Daniel Imms 55e5ec6037 Box Elements -> Block Elements 2025-12-25 04:21:47 -08:00
Daniel Imms 7b2a5cb50f Merge pull request #5496 from xtermjs/Tyriar-patch-1
Replace range separator with hyphen
2025-12-25 04:05:17 -08:00
Daniel Imms 8740dcda4d Replace range separator with hyphen 2025-12-25 03:59:54 -08:00
Daniel Imms c046f5b4b1 More refactors 2025-12-25 03:41:47 -08:00
Daniel Imms 6bde923f07 Register the windows 2025-12-24 17:09:20 -08:00
Daniel Imms 40b2bbef34 Improve demo by making full screen, add control bar 2025-12-24 16:43:35 -08:00
Daniel Imms c04806fc92 Merge pull request #5495 from Tyriar/tyriar/git_branch
Support custom glyphs for Git Branch Symbols (PUA U+F5D0-U+F60D)
2025-12-24 16:42:45 -08:00
Daniel Imms 8b1306f46d More polish 2025-12-24 16:13:11 -08:00
Daniel Imms 6612153c44 Add alignment tests 2025-12-24 16:10:36 -08:00
Daniel Imms 2be529ce85 Polish definitions 2025-12-24 16:05:42 -08:00
Daniel Imms 96aa97ce11 Add extended symbols 2025-12-24 15:51:00 -08:00
Daniel Imms 98f4c3128c Stub second batch of glyphs 2025-12-24 15:35:46 -08:00
Daniel Imms ce1ab6fdc4 Correct order or curved glyphs 2025-12-24 15:31:49 -08:00
Daniel Imms e87e0f5443 Add git branch symbol alignment tests 2025-12-24 15:27:30 -08:00
Daniel Imms 4a8d8bd247 Partially implement git branch symbols
Part of #5477
2025-12-24 15:03:28 -08:00
Daniel Imms edf100a24b Merge pull request #5494 from Tyriar/tyriar/scale
Add char-based custom glyph scaling
2025-12-24 13:23:23 -08:00
Daniel Imms ed2d8b8fdc Merge branch 'master' into tyriar/scale 2025-12-24 12:36:32 -08:00
Daniel Imms faeefb53bf Merge pull request #5481 from BattlefieldDuck/fix/incorrect-theme-reference
Fix incorrect ITheme reference in comments
2025-12-24 12:29:40 -08:00
Daniel Imms 32ff1f2115 Merge pull request #5492 from Tyriar/sinon
Remove need for sinon
2025-12-24 12:25:41 -08:00
Daniel Imms acb59daf33 Fix lint 2025-12-24 12:20:12 -08:00
Daniel Imms a3d6bf6710 Merge pull request #5493 from Tyriar/tyriar/node
Upgrade missed parts from node 18 -> 22
2025-12-24 12:10:26 -08:00
Daniel Imms 5e5c623d8b Create test ts project in ligatures addon 2025-12-24 12:09:28 -08:00
Daniel Imms 168520f727 Upgrade missed parts from node 18 -> 22 2025-12-24 11:39:10 -08:00
Daniel Imms 23f61911e2 Remove need for sinon 2025-12-24 11:35:20 -08:00
Daniel Imms 491fa5586a Merge pull request #5491 from Tyriar/jsdom
Upgrade jsdom
2025-12-24 11:33:33 -08:00
Daniel Imms 2f549d74ae Merge pull request #5490 from Tyriar/lint
Update eslint and nyc
2025-12-24 11:26:35 -08:00
Daniel Imms 9df2430433 Upgrade jsdom 2025-12-24 11:26:19 -08:00
Daniel Imms ce9f6275ed Fix lint-api 2025-12-24 11:19:06 -08:00
Daniel Imms 9ca04799fe Merge remote-tracking branch 'origin/master' into lint 2025-12-24 11:16:01 -08:00
Daniel Imms 83757d1bf0 Polish arrow strokes 2025-12-24 11:13:58 -08:00
Daniel Imms 60fa9122a2 Remove old yarn rule and regen package lock 2025-12-24 10:46:59 -08:00
Daniel Imms a8d40a1edf Update eslint and nyc 2025-12-24 10:44:46 -08:00
Daniel Imms 78cc07f88a Split arrow parts into hybrid scaling 2025-12-24 10:30:34 -08:00
Daniel Imms 72d7c69a29 Add char-based custom glyph scaling
Draw calls can now be scaled by the char bounding box instead of the
cell bounding box. This means these character won't be stretched. Both
can also be used at the same like as is the case with 1FBBC RIGHT OPEN
SQUARED DOT.

Fixes #5488
2025-12-24 10:19:11 -08:00
Daniel Imms 0e0b445784 Merge pull request #5487 from Tyriar/tyriar/5480
Move customGlyphs option into webgl addon
2025-12-24 08:46:27 -08:00
Daniel Imms 5d24866042 Move customGlyphs option into webgl addon
Fixes #5480
2025-12-24 08:35:06 -08:00
Daniel Imms cbea462737 Merge pull request #5486 from Tyriar/tyriar/powerline_extra
Implement remaining powerline extra symbols
2025-12-24 08:23:39 -08:00
Daniel Imms a3ae94af1d Correct range 2025-12-24 08:18:19 -08:00
Daniel Imms 0e35521810 Add powerline alignment tests 2025-12-24 08:15:50 -08:00
Daniel Imms 40590fedfa Merge branch 'master' into tyriar/powerline_extra 2025-12-24 08:07:37 -08:00
Daniel Imms 1551d94de4 Implement remaining powerline extra symbols
- Uses fontforge + helper bin/convert_svg_to_custom_glyph.js
- Some of these are very large. They'll be much smaller after gzip but
  if this becomes a problem we can compile the svgs into a binary format

Fixes #5479
2025-12-24 08:06:20 -08:00
Daniel Imms d605803866 Merge pull request #5485 from Tyriar/tyriar/braille
Support custom gylphs for braille patterns (U+2800-U+28FF)
2025-12-24 07:09:40 -08:00
Daniel Imms a797852dfd Support custom gylphs for braille patterns (U+2800-U+28FF)
Fixes #5476
2025-12-24 07:00:53 -08:00
Daniel Imms d42f2b54ff Merge pull request #5484 from Tyriar/tyriar/fix_sextants
Fix sextant rendering
2025-12-24 06:43:57 -08:00
Daniel Imms 79084c3066 Fix sextant rendering
Broke in #5483
2025-12-24 06:38:21 -08:00
Daniel Imms b4f4946cca Merge pull request #5483 from Tyriar/glyph_cleanup
Consolidate custom glyph definition parts
2025-12-24 06:21:11 -08:00
Daniel Imms ed779b9062 Replace PATH_FUNCTION_WITH_WEIGHT with a generic strokeWidth prop 2025-12-24 06:14:41 -08:00
Daniel Imms 20fdfba16f Replace BLOCK_PATTERN_WITH_REGION with new clipPath 2025-12-24 04:33:32 -08:00
Daniel Imms 11dae84ff7 Support clip path on any draw definition 2025-12-24 04:25:59 -08:00
Daniel Imms 579573e77d Merge pull request #5482 from xtermjs/Tyriar-patch-1
Remove line ending editorconfig file
2025-12-24 04:24:04 -08:00
Daniel Imms 2114abe88c Support multiple glyphs with multiple draw calls 2025-12-24 04:19:14 -08:00
Daniel Imms d4fd96639f Remove line ending editorconfig file
This is normalized by git and ends up causing git status to show files that don't actually need to be committed after line ending normalization.
2025-12-24 04:18:31 -08:00
Daniel Imms 269d8c20ae Merge pull request #5475 from Tyriar/depc
Remove remnants of deprecated options
2025-12-24 04:03:49 -08:00
TatLead 49b49627f1 Fix incorrect ITheme reference in comments 2025-12-24 07:10:12 +08:00
Daniel Imms 663b94011a Remove remnants of deprecated options
Following up on #5462
2025-12-23 08:24:35 -08:00
Daniel Imms 54fcb1ea2f Merge pull request #5474 from Tyriar/demo_warn
Fix warnings in demo
2025-12-23 08:17:29 -08:00
Daniel Imms a9da3fbf05 Merge pull request #5473 from Tyriar/tyriar/5466_legacy_computing
Support entire Symbols for Legacy Computing unicode block (0x1FB00-0x1FBFF), add missing powerline symbol
2025-12-23 08:15:05 -08:00
Daniel Imms 091c3967f7 Fix warnings in demo 2025-12-23 08:12:12 -08:00
Daniel Imms 10d6ce3805 Update customGlyphs option 2025-12-23 08:09:46 -08:00
Daniel Imms eb285af11d Add diagonal alignment tests 2025-12-23 08:05:02 -08:00
Daniel Imms 0976935f87 Implement remaining Terminal graphic characters (1FBC0-1FBCA) 2025-12-23 07:42:57 -08:00
Daniel Imms 884b667f4e Implement stick figures 1FBC5-1FBC9 2025-12-23 07:04:19 -08:00
Daniel Imms b8843732bf Implement Terminal graphic characters (1FBCB-1FBCD) 2025-12-23 04:49:46 -08:00
Daniel Imms 44d8d2c873 Finish implementing Terminal graphic characters (1FBB9-1FBBC) 2025-12-23 04:28:45 -08:00
Daniel Imms 3d3abf9cf2 Implement folder 1FBB9-1FBBA 2025-12-23 04:22:19 -08:00
Daniel Imms 268f7af5db Implement running man 1FBB2 and 1FBB3 2025-12-23 04:15:41 -08:00
Daniel Imms 16b72dfd63 Mark powerline symbol range as reserved 2025-12-23 03:50:34 -08:00
Daniel Imms a0e658a18c Add column number powerline symbol 2025-12-23 03:42:41 -08:00
Daniel Imms ea6385f310 Comment alignment 2025-12-23 03:39:37 -08:00
Daniel Imms 02a9d2b047 Indicate reserved codepoints in demo 2025-12-23 03:30:02 -08:00
Daniel Imms 5c0c553936 Implement Arrows (1FBB4-1FBB8) 2025-12-22 19:46:35 -08:00
Daniel Imms eb743e0e19 Implement Terminal graphic character (1FBFA-1FBFA) 2025-12-22 19:15:00 -08:00
Daniel Imms 29d91fe264 Fill in remaining parts of legacy computing 2025-12-22 16:27:41 -08:00
Daniel Imms e8dcf46f39 Add PATH_NEGATIVE, simplify 1FBBD-1FBBF 2025-12-22 16:22:06 -08:00
Daniel Imms 66c1316f87 Implement ARROWHEAD-SHAPED POINTER 2025-12-22 15:43:53 -08:00
Daniel Imms e1803ed5a5 Implement Triangular shade characters (1FB9C-1FB9F) 2025-12-22 15:30:55 -08:00
Daniel Imms 1c3813f3d4 Implement Negative terminal graphic characters (1FBBD-1FBBF) 2025-12-22 15:28:51 -08:00
Daniel Imms e12aef9287 Implement Block elements (1FBCE-1FBCF) 2025-12-22 15:21:10 -08:00
Daniel Imms f5edba0b64 Improve docs on segmentedDigit 2025-12-22 15:18:42 -08:00
Daniel Imms 5a995e5595 Implement Geometric shapes (1FBE0-1FBEF) 2025-12-22 15:14:40 -08:00
Daniel Imms 2bae212f83 Implement Character cell diagonals (1FBD0-1FBDF) 2025-12-22 14:19:02 -08:00
Daniel Imms bde3680cd1 Implement 2 more parts 2025-12-22 14:15:26 -08:00
Daniel Imms 717399ceb6 Implement diagonal fill chars and light solid line with stroke 2025-12-22 14:04:51 -08:00
Daniel Imms 51121f1913 Stub legacy computing custom glyph ranges
Part of #5466
2025-12-22 13:42:38 -08:00
Daniel Imms 483acd3335 Merge pull request #5472 from Tyriar/tyriar/5466_refactor__unified
Custom glyph: Unify rendering function and move all definitions into a single ordered object
2025-12-22 13:31:36 -08:00
Daniel Imms 9dc8cecea3 Fix imports 2025-12-22 13:04:44 -08:00
Daniel Imms 89c2649504 Merge branch 'master' into tyriar/5466_refactor__unified 2025-12-22 12:57:03 -08:00
Daniel Imms 390076fc3f Order box drawing consistently 2025-12-22 12:56:16 -08:00
Daniel Imms a4edeed85d Add box drawing to custom glyph ranges test 2025-12-22 12:48:23 -08:00
Daniel Imms e61697fc86 Start organizing box drawing characters 2025-12-22 12:46:43 -08:00
Daniel Imms 9c019b44c3 unified -> customGlyph 2025-12-22 12:39:31 -08:00
Daniel Imms 5579d83f7d Move box drawing into unified 2025-12-22 12:39:14 -08:00
Daniel Imms 429065487a Move block elements and like into unified 2025-12-22 12:34:12 -08:00
Daniel Imms 219036cb0f Add powerline to glyph ranges test 2025-12-22 12:26:58 -08:00
Daniel Imms 3e80637145 Move powerline to unified 2025-12-22 12:14:50 -08:00
Daniel Imms 0af884d379 Correct shade rectangle rendering 2025-12-22 12:09:41 -08:00
Daniel Imms 5ae9ddf4b8 Add range to headers in ranges test 2025-12-22 12:06:59 -08:00
Daniel Imms 7ac068844c Fix warnings in unicodeTables.ts 2025-12-22 12:06:27 -08:00
Daniel Imms 5bf3d57d16 Move rectangular shade chars into unified 2025-12-22 12:05:47 -08:00
Daniel Imms 6461829411 Clean up symbols for legacy def 2025-12-22 11:48:45 -08:00
Daniel Imms efd6a52033 Support path without function 2025-12-22 11:46:53 -08:00
Daniel Imms 3d9e976cb3 Move main symbols for legacy computing into unified 2025-12-22 11:41:49 -08:00
Daniel Imms 415de67106 Start move to unified draw definition
Part of #5466
2025-12-22 11:35:41 -08:00
Daniel Imms c17c01086f Merge pull request #5471 from Tyriar/tyriar/5466_refactor
Refactor custom glyph to move into folder, split definition from raster
2025-12-22 11:34:43 -08:00
Daniel Imms ce41b175ee Remove cyclic dep 2025-12-22 11:11:50 -08:00
Daniel Imms 32b4547c5a Tweak symbol names 2025-12-22 11:09:02 -08:00
Daniel Imms b64543aa59 Make medium shade consistent, make all shades less heavy 2025-12-22 10:57:59 -08:00
Daniel Imms f049c42acc Add CustomGlyph to exports from rasterizer 2025-12-22 10:48:50 -08:00
Daniel Imms 28760cf482 Move custom glyphs into new folder 2025-12-22 10:47:35 -08:00
Daniel Imms 4982305b51 Split definitions and rasterizer 2025-12-22 10:46:53 -08:00
Daniel Imms 7fee40ddcd Merge pull request #5470 from Tyriar/tyriar/5466_shade
Custom glyph support for rectangular shade/combo chars
2025-12-22 10:41:54 -08:00
Daniel Imms 180183e876 Custom glyph support for rectangular shade/combo chars
Range: U+1FB8C-U+1FB94

Part of #5466
2025-12-22 10:29:22 -08:00
Daniel Imms 8308ede3f5 Merge pull request #5468 from Tyriar/5466_mosaic
Custom glyph support for mosaic terminal graphics chars
2025-12-22 10:27:53 -08:00
Daniel Imms a6db7b41f6 Suppress naming-convention for file as it conflicts with unicode props 2025-12-22 08:34:43 -08:00
Daniel Imms f11241d9de Custom glyph support for mosaic terminal graphics chars
Range: U+1FB3C-U+1FB6F

Part of #5466
2025-12-22 08:29:03 -08:00
Daniel Imms 937deb9f4b Merge pull request #5467 from Tyriar/5464_block_sextants
Custom glyph support for block sextants (U+1FB00-U+1FB3B)
2025-12-22 08:27:29 -08:00
Daniel Imms 6acc861b06 Lint 2025-12-22 07:54:47 -08:00
Daniel Imms 8f05b1854b Lint 2025-12-22 07:41:18 -08:00
Daniel Imms 0f461e7a3e Custom glyph support for block sextants (U+1FB00-U+1FB3B)
Fixes #5464
2025-12-22 07:33:43 -08:00
Daniel Imms 4237d82923 Merge pull request #5465 from Tyriar/5464_demo_helper
Add demo helper to print Symbols for Legacy Computing
2025-12-22 07:26:42 -08:00
Daniel Imms 187156764a Add demo helper to print Symbols for Legacy Computing
Range: 1FB00–1FBFF

Part of #5464
2025-12-22 07:16:22 -08:00
Daniel Imms f447274f43 Merge pull request #5463 from Tyriar/tyriar/v6
v6.0.0, bump addon versions, publish unicode graphemes
2025-12-22 05:48:49 -08:00
Daniel Imms f68d1e5d58 Skip sync output tests for now 2025-12-21 13:03:25 -08:00
Daniel Imms 30691e8b46 v6.0.0, bump addon versions, publish unicode graphemes
Part of #5147
2025-12-21 12:49:37 -08:00
Daniel Imms 5fa632518e Merge pull request #5462 from Tyriar/tyriar/deprecated
Remove deprecated windowsMode and fastScrollModifier
2025-12-21 05:06:56 -08:00
Daniel Imms 3571d1f51a Remove windowsMode in favor of windowsPty 2025-12-21 05:01:15 -08:00
Daniel Imms ebc0d83d8e Remove deprecated/unused fastScrollModifier 2025-12-21 04:58:45 -08:00
Daniel Imms d1c50c14e3 Merge pull request #5461 from Tyriar/tyriar/glob
Remove need for glob module
2025-12-20 08:44:33 -08:00
Daniel Imms 92b43bdf9b Remove only 2025-12-20 08:33:56 -08:00
Daniel Imms a9d3ca19f4 Merge pull request #5449 from iSuslov/exports/esm-headless-node
Configure "exports" field in package.json [headless]
2025-12-20 07:47:58 -08:00
Daniel Imms cb4d1ef2c0 Merge pull request #5453 from chrislloyd/feat/synchronized-output
Add synchronized output support (DEC mode 2026)
2025-12-20 07:44:32 -08:00
Daniel Imms 34fd0dcf53 Remove need for glob module 2025-12-20 07:44:24 -08:00
Daniel Imms 7335f15943 Merge pull request #5460 from Tyriar/tyriar/test-results
Git ignore test-results/
2025-12-20 07:37:43 -08:00
Daniel Imms 5e9cd6208f Git ignore test-results/ 2025-12-20 07:37:20 -08:00
Daniel Imms 6733e13a44 Invert initial assertion 2025-12-20 07:33:18 -08:00
Daniel Imms ad07f2cf76 Revert last frame request cache clearing 2025-12-20 07:23:20 -08:00
Daniel Imms 5396243bce Merge pull request #5450 from iSuslov/fix-esm-path-addon-unicode-graphemes
Fix wrong export path for ESM target in [addon-unicode-graphemes]
2025-12-20 07:20:08 -08:00
Daniel Imms 12c4f9536e Use underline cursor style to ensure cursor color isn't reported 2025-12-20 07:13:41 -08:00
Daniel Imms e9ba31f1b6 Explicitly set background color in playwright tests 2025-12-20 06:40:16 -08:00
Daniel Imms ed0ab5f9e4 Add missing property to headless API test assertion 2025-12-20 06:39:27 -08:00
Daniel Imms 0741f8be5c Merge pull request #5452 from iSuslov/upgrade-glob-version-fix-tsc-build
Fix tsc build - upgrade glob version
2025-12-20 06:37:06 -08:00
Daniel Imms 2edd121f1b Add node about serialize addon support 2025-12-20 06:17:31 -08:00
Daniel Imms 1eee5cdf18 Add synchronizedOutput mode to headless terminal object 2025-12-20 06:14:45 -08:00
Daniel Imms 20c1674d00 Update package-lock.json 2025-12-20 06:12:41 -08:00
Daniel Imms 32553fff06 Add synchronizedOutputMode to xterm-headless.d.ts 2025-12-20 06:05:36 -08:00
Daniel Imms 832aa54478 Fix colors and clear cached frames to pass tests 2025-12-20 06:04:11 -08:00
Daniel Imms 7f50b56ae4 Fix one playwright test 2025-12-10 08:44:13 -08:00
Daniel Imms bd361af105 Fix lint 2025-12-10 07:59:52 -08:00
Daniel Imms 9ea059de6f Move SynchronizedOutputHandler to bottom of file 2025-12-10 07:50:59 -08:00
Daniel Imms 7cdf3f9dd7 Prefer const enum so the number is inlined 2025-12-10 07:50:28 -08:00
Daniel Imms 7f81df5a3a Add BSU and ESU to VT tab in demo 2025-12-10 07:49:07 -08:00
Chris LloydandClaude af5dbfcd2c Add DECRQM support for synchronized output mode
Applications can now detect synchronized output support by querying
CSI ? 2026 $ p and receiving CSI ? 2026 ; <mode> $ y response.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-09 08:26:04 -08:00
Chris LloydandClaude 5d0eae8576 Fix spec URL to use master branch
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-09 08:23:37 -08:00
Chris LloydandClaude 6b63c5f260 Address PR review feedback for synchronized output
- Remove synchronizedOutputTimeout public API, hardcode 1s timeout
- Extract SynchronizedOutputHandler class for cleaner code
- Update spec URL to contour-terminal/vt-extensions
- Remove unnecessary comment in InputHandler
- Delete unit tests, add integration tests in SharedRendererTests
- Fix whitespace issue

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-09 07:58:31 -08:00
Chris LloydandClaude 7228e2e2dc Add synchronized output support (DEC mode 2026)
Implement synchronized output mode (CSI ? 2026 h/l) which allows
applications to batch terminal updates and render them atomically,
preventing screen tearing during rapid output.

Features:
- BSU (CSI ? 2026 h) pauses rendering, buffering row updates
- ESU (CSI ? 2026 l) flushes buffer and renders atomically
- Configurable timeout via synchronizedOutputTimeout option (default 5s)
- Exposed via terminal.modes.synchronizedOutputMode

Closes #3375

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-04 11:16:25 -08:00
Ivan Suslov a3e1ecf91b Fix tsc build - upgrade glob version 2025-12-04 07:28:40 -05:00
Ivan Suslov b41d3ef130 Fix ESM export path for addon-unicode-graphemes 2025-12-04 05:02:12 -05:00
Ivan Suslov 8338418471 Added exports field to headless package.json 2025-12-04 03:56:32 -05:00
Daniel Imms 46c510a76d Merge pull request #5413 from afska/patch-1
Adding EmuDevz to the "Real-world uses" list
2025-11-23 13:47:40 -08:00
Daniel Imms 80c3164070 Merge pull request #5446 from Tyriar/tyriar/npm
Migrate from yarn to npm, node 18 -> 22, use npm workspaces
2025-11-23 13:47:15 -08:00
Daniel Imms 236004ce6c Merge branch 'master' into tyriar/npm 2025-11-23 12:53:21 -08:00
Daniel Imms 741d9c3273 Remove install-addon calls 2025-11-23 10:00:21 -08:00
Daniel Imms bf4fb3ad64 Migrate to npm workspaces 2025-11-23 09:54:17 -08:00
Daniel Imms da4ab8a01e Remove peer dependency, generate lock files 2025-11-23 09:35:43 -08:00
Daniel Imms 92eddf8a61 Move to npm 2025-11-23 09:01:24 -08:00
Daniel Imms 637231e6e1 Merge pull request #5445 from Tyriar/tyriar/perf
Prefer performance.now() over Date.now()
2025-11-23 08:46:24 -08:00
Daniel Imms 6468a78237 Fix comment lint 2025-11-23 08:40:35 -08:00
Daniel Imms 98e234992b Prefer performance.now() over Date.now()
performance.now() is faster than Date.now() as there's no need to
consult the system clock. The more important part of this is that
performance.now() is monotonic and is guaranteed to never to backwards.
2025-11-23 08:32:35 -08:00
Daniel Imms e1023d7ec2 Merge pull request #5442 from Tyriar/tyriar/5441
Fix headless module path
2025-11-14 10:18:17 -08:00
Daniel Imms d186a3a115 Fix headless module path
Fixes #5441
2025-11-12 09:30:11 -08:00
Shuhei Takahashi d03416467d Fix key duplication on Android with physical keyboards
When typing rapidly on Android with physical keyboards, characters often
duplicate on the terminal. This is because physical keyboard events
always go through IME on Android, even for plain English characters, and
rapid typing causes a race condition in the composition helper.

This patch fixes the issue by making sure textarea changes are handled
correctly.
2025-11-07 12:33:59 +09:00
Daniel Imms 65a0e03b66 Merge pull request #5437 from xtermjs/anthonykim1/preventPageScrollWhenMouseOff
Prevent entire page from scrolling when scrolling in alt buffer with mouse event off
2025-11-02 03:54:46 -08:00
Anthony Kim 6ae020f51e add this.cancel(ev, true) when line is zero on wheel handler 2025-10-31 14:39:42 -07:00
Daniel Imms 6c79a46513 Merge pull request #5436 from xtermjs/merogge/serialize-range
add range to IHTMLSerializeOptions
2025-10-28 09:41:17 -07:00
meganrogge 4a62856153 fix test 2025-10-28 12:30:54 -04:00
meganrogge 5121867bbb rm line 2025-10-28 11:56:56 -04:00
meganrogge 5a467da137 pass in startLine, endLine, startCol 2025-10-28 11:46:41 -04:00
meganrogge 18bafaf9c9 fix indentation 2025-10-28 10:30:22 -04:00
meganrogge 5141a91bba add range to IHTMLSerializeOptions 2025-10-28 10:27:00 -04:00
Daniel Imms 9fce067266 Merge pull request #5425 from xtermjs/anthonykim1/parsingDataCharCode
Only show char codes for parsing data on trace
2025-10-22 19:08:47 -07:00
Daniel Imms 59241e467c Update src/common/InputHandler.ts 2025-10-22 19:02:14 -07:00
Anthony Kim 09e26980d3 I dont think we need () => in front of typeof data === 'string' 2025-10-22 16:23:35 -07:00
Anthony Kim c3bb36bad9 if trace, debug each AND remove array.from 2025-10-22 16:11:22 -07:00
Anthony Kim 2c57b3219b Only show char codes for parsing data on trace 2025-10-22 09:47:33 -07:00
Daniel Imms e9e8206467 Merge pull request #5423 from Tyriar/tyriar/resize5300
Clear selection on vertical resize
2025-10-19 01:21:28 -07:00
Daniel Imms 9a7517d3eb Clear selection on vertical resize
Fixes #5300
2025-10-19 01:15:30 -07:00
Daniel Imms 1bf90d9f47 Merge pull request #5422 from xtermjs/Tyriar-patch-1
Explain filtering unit tests by file
2025-10-18 13:49:38 -07:00
Daniel Imms 1527e89ad5 Explain filtering unit tests by file 2025-10-18 06:40:45 -07:00
Daniel Imms 95cc31566a Merge pull request #5421 from xtermjs/Tyriar-patch-1
Use Windows PowerShell instead of pwsh 7 in demo
2025-10-18 06:20:41 -07:00
Daniel Imms 7274e1bbea Use Windows PowerShell instead of pwsh 7 in demo
When pwsh 7 isn't installed it's hard to figure out what the problem is.
2025-10-18 06:15:19 -07:00
Daniel Imms a8fabcfa01 Merge pull request #5420 from xtermjs/anthonykim1/charCodesForVerbose
Only log character codes for trace log
2025-10-17 11:39:21 -07:00
Anthony Kim 67cf25989a only send codes, not the data when in trace 2025-10-17 11:26:34 -07:00
Anthony KimandDaniel Imms f87e574971 Update src/common/services/CoreService.ts
Co-authored-by: Daniel Imms <2193314+Tyriar@users.noreply.github.com>
2025-10-17 11:23:24 -07:00
Anthony Kim 5269462817 Only log character codes for verbose/trace 2025-10-17 10:12:10 -07:00
Anthony Kim f4baddaaf1 Merge pull request #5411 from xtermjs/anthonykim1/fixTeleportWhenAltScroll
Fix teleport when exiting out of alt buffer. v2
2025-10-17 09:20:23 -07:00
Rodrigo Alfonso 3aa836e5d7 Adding EmuDevz to the "Real-world uses" list 2025-10-10 03:24:12 -03:00
Anthony Kim 2a6e22e8f8 set this._latestYDisp = undefined onBufferActivate 2025-10-01 20:36:06 -07:00
Anthony Kim 32c3cb01a1 Make sure this._latestYDisp is up to date 2025-09-30 23:04:04 -07:00
Daniel Imms a1d8e96fdd Merge pull request #5406 from Tyriar/tyriar/search_refactor
Refactor search addon into multiple files, add unit tests
2025-09-26 18:24:44 +09:00
Daniel Imms c4780e54fd Merge branch 'master' into tyriar/search_refactor 2025-09-26 17:52:47 +09:00
Daniel Imms c56336b32b Fix unit tests 2025-09-26 17:49:20 +09:00
Daniel Imms d11f411c17 Bring back respecting of cached search term 2025-09-26 17:09:54 +09:00
Daniel Imms f03dfbb3ba Merge pull request #5407 from Tyriar/tyriar/bd09_copilot_instructions
Improve Copilot instructions and add test guidelines
2025-09-26 16:51:46 +09:00
Daniel Imms ffd6dc6ab7 Fix import issue 2025-09-19 09:06:53 -07:00
Daniel Imms 5b4b93acc6 Improve Copilot instructions and add test guidelines
- Enhanced copilot instructions with detailed architecture overview
- Added comprehensive development workflow documentation
- Documented addon development patterns and key examples
- Added project-specific conventions and TypeScript structure
- Included common patterns for parser integration and buffer access
- Added critical implementation details for rendering and events
- Created unit test instructions file with addon testing rules
- Specified preferences for assertions and self-documenting tests
2025-09-19 09:03:02 -07:00
Daniel Imms 2b329eb7b7 Search engine tests 2025-09-19 08:58:30 -07:00
Daniel Imms 9c5644ddc0 Add SearchEngine tests 2025-09-19 08:52:14 -07:00
Daniel Imms 398e87490d Add coding guidelines and lint-fix script
- Add coding guidelines section to copilot instructions
- Specify rule about avoiding comments in unit tests
- Add lint-fix npm script for auto-fixing ESLint issues
2025-09-18 13:27:12 -07:00
Daniel Imms a191370d54 refactor: remove unnecessary interface exports
- Remove export from IHighlight and IMultiHighlight interfaces
- Remove export from ISelectedDecoration interface
- Remove export from ISearchPosition interface
- Keep ISearchResult exported as it's used across multiple files
- Make ISelectedDecoration extend IDisposable for consistency
2025-09-18 13:26:43 -07:00
Daniel Imms bf9ce3bfaf Clean up SearchLineCache.test.ts comments
- Remove unnecessary inline comments from unit tests
- Add descriptive assertion messages where appropriate
- Follow coding guidelines for test comment usage
2025-09-18 13:21:07 -07:00
Daniel Imms ca7e8ca3b7 refactor(addon-search): extract components
- Extract SearchEngine for core search algorithms
- Extract DecorationManager for visual decorations
- Extract SearchState for state management
- Extract SearchResultTracker for result tracking
- Simplify SearchAddon by delegating to components
- Improve modularity and maintainability
2025-09-18 07:38:42 -07:00
Daniel Imms ab43a3bd22 Merge pull request #5405 from Tyriar/tyriar/b8d4_search_interface_refactor
Refactor search addon interfaces
2025-09-18 06:31:18 -07:00
Daniel Imms 32e58c71fe Refactor search addon interfaces
- Move ISearchAddonOptions and ISearchResultChangeEvent to types
- Make noScroll property required in IInternalSearchOptions
- Change exported interfaces to internal scope where appropriate
2025-09-18 06:25:12 -07:00
Daniel Imms b12a9393f1 Merge pull request #5404 from Tyriar/tyriar/1de3_timeout_cleanup
Replace timeout handling with disposableTimeout
2025-09-17 15:12:00 -07:00
Daniel Imms c27af4fa2e Remove thenable from more places 2025-09-17 15:06:33 -07:00
Daniel Imms 90bf20d9d8 Remove Thenable 2025-09-17 15:02:27 -07:00
Daniel Imms 8ffcbd98c4 Replace timeout handling with disposableTimeout
- Use disposableTimeout instead of manual timeout management
- Replace MutableDisposable pattern for SearchAddon highlight timeout
- Replace SearchLineCache timeout with MutableDisposable approach
- Clean up timeout disposal logic in both classes
2025-09-17 14:48:11 -07:00
Daniel Imms 15d90826f9 Merge pull request #5403 from Tyriar/tyriar/76c0_search_line_cache
Add SearchLineCache for improved search performance
2025-09-17 14:38:34 -07:00
Daniel Imms 304143a166 Add SearchLineCache for improved search performance
- Add SearchLineCache class to cache translated buffer lines
- Integrate caching into SearchAddon for line translation
- Improve search performance by avoiding repeated translation
- Add cache invalidation on terminal resize and writes
2025-09-17 14:28:03 -07:00
Daniel Imms 02b3704af0 Merge pull request #5402 from Tyriar/tyriar/9436_copilot_instructions
Update GitHub Copilot instructions with test details
2025-09-17 09:28:07 -07:00
Daniel Imms eb1330ca18 Merge pull request #5401 from Tyriar/tyriar/search-refactor
Refactor search addon types and internal handling
2025-09-17 09:22:07 -07:00
Daniel Imms 3a15c8080d Update GitHub Copilot instructions with test details
- Add comprehensive unit test examples with yarn test-unit
- Include integration test examples with yarn test-integration
- Document test filtering by file path and wildcard patterns
- Explain test runner frameworks (mocha and playwright)
2025-09-17 09:21:40 -07:00
Daniel Imms 6ac9f59ef4 Fix tests 2025-09-17 09:16:20 -07:00
Daniel Imms bffdc59901 Merge branch 'master' into tyriar/search-refactor 2025-09-17 08:53:54 -07:00
Daniel Imms 76a8a8e347 Refactor search addon types and internal handling
- Import search types from addon-search package
- Add IInternalSearchOptions interface for noScroll param
- Update _updateMatches to use internal search methods
- Pass internal options through search method chain
2025-09-17 08:52:38 -07:00
Daniel Imms e37aba5924 Merge pull request #5399 from Tyriar/tyriar/search-constants-refactor
Refactor search constants and improve documentation
2025-09-17 08:16:51 -07:00
Daniel Imms 5c0107462e Merge pull request #5379 from xtermjs/dependabot/npm_and_yarn/addons/addon-ligatures/form-data-4.0.4
Bump form-data from 4.0.0 to 4.0.4 in /addons/addon-ligatures
2025-09-17 08:13:12 -07:00
Daniel Imms 87a45acae7 Refactor search constants and improve documentation
- Convert standalone constants to Constants enum with detailed docs
- Fix spacing issue in SearchAddon class declaration
- Update all references to use Constants enum for consistency
2025-09-17 08:06:56 -07:00
Daniel Imms ab01a5fd65 Merge pull request #5398 from Tyriar/tyriar/search-api-typing
Add proper typing for search results change event
2025-09-17 07:54:29 -07:00
Daniel Imms 6e833e5d5d Add proper typing for search results change event
- Export ISearchResultChangeEvent interface in addon-search
- Use proper Event<T> typing instead of inline object
- Improve API consistency and type safety for consumers
2025-09-17 07:33:43 -07:00
Daniel Imms d7d7f1b023 Merge pull request #5391 from Tyriar/tyriar/mouse_events
Bring back partial wheel tracking
2025-09-09 18:42:38 -07:00
Anthony Kim 9c42fb348b ignore fastScrollModifier 2025-09-09 13:21:37 -07:00
Anthony Kim e4f222f6b0 Make test happy 2025-09-09 12:03:51 -07:00
Anthony Kim 38bee71630 More clear comments 2025-09-09 12:00:19 -07:00
Anthony Kim 1a6a807f68 Remove stale comment 2025-09-09 11:59:29 -07:00
Anthony Kim 03ce26c697 Questions, possible concern 2025-09-09 11:56:28 -07:00
Anthony Kim cf6b868dfb fastScrollModifier seems deprecated 2025-09-09 11:51:27 -07:00
Anthony Kim d862c82386 no stale comments 2025-09-08 21:14:20 -07:00
Anthony Kim 50df7a69e6 remove unused var 2025-09-08 21:06:37 -07:00
Anthony Kim 26ecba8de1 Fix sluggish mouse problem. Now similar to trackpad 2025-09-08 21:03:05 -07:00
Daniel Imms 0d1e6b7a5e Merge pull request #5390 from xtermjs/anthonykim1/fixTeleportAltBuffer
Fix scrollbar teleport after exiting alt buffer
2025-08-29 05:41:52 -07:00
Anthony Kim b104a5b953 Respect original formatting 2025-08-28 22:56:15 -07:00
Anthony Kim ebddd5d346 Move from CoreBrowerTerminal to CoreMouseService. Still slow though 2025-08-28 22:54:50 -07:00
Daniel Imms 5bd7b4aaec Bring back partial wheel tracking
Part of microsoft/vscode#224750
2025-08-28 10:28:19 -07:00
Anthony Kim a6fdf043ce Respect original formatting 2025-08-28 08:57:25 -07:00
Anthony Kim 287702610c More clean up after removing test 2025-08-28 08:54:01 -07:00
Anthony Kim 4d59eca5f9 Clean up 2025-08-28 08:52:46 -07:00
Anthony Kim 753ea81fc0 BufferService listen to onBufferActivate, remove tests 2025-08-28 08:46:27 -07:00
Anthony Kim 675b2c3a70 Improve test 2025-08-27 21:31:34 -07:00
Anthony Kim 86745e1320 Add some tests from copilot 2025-08-27 21:12:15 -07:00
Anthony Kim a3ab942ea1 Fix teleport issue when coming back from alt buffer 2025-08-27 20:45:52 -07:00
Daniel Imms e969832946 Merge pull request #5385 from anthonykim1/anthonykim1/fixWrappedTerminalFind
Fix terminal find when wrapped
2025-08-21 12:40:01 -07:00
Anthony Kim 95a9531ec1 Try to add tests 2025-08-20 22:49:03 -07:00
Daniel Imms 7637a2de19 Merge pull request #5386 from alexkozy/safe-guards
fix: ensure that currentRow can not go out of the range
2025-08-20 14:15:08 -07:00
Alexey Kozy a589be2826 fix: ensure that currentRow can not go out of the range 2025-08-20 13:47:40 -07:00
Anthony Kim b53096f4a2 Clean code 2025-08-20 10:55:47 -07:00
Anthony Kim 687d8277e7 Fix terminal find when wrapped 2025-08-20 10:53:40 -07:00
Daniel Imms 4c0cf27cfc Merge pull request #5334 from CodinGame/webgl-renderer-shadow-dom
Support shadow dom in webgl renderer
2025-07-31 08:32:44 -07:00
Loïc Mangeonjean 149527ddb8 refactor: apply requested style change 2025-07-31 17:08:30 +02:00
dependabot[bot] 9c3eb170d2 Bump form-data from 4.0.0 to 4.0.4 in /addons/addon-ligatures
Bumps [form-data](https://github.com/form-data/form-data) from 4.0.0 to 4.0.4.
- [Release notes](https://github.com/form-data/form-data/releases)
- [Changelog](https://github.com/form-data/form-data/blob/master/CHANGELOG.md)
- [Commits](https://github.com/form-data/form-data/compare/v4.0.0...v4.0.4)

---
updated-dependencies:
- dependency-name: form-data
  dependency-version: 4.0.4
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-21 23:07:47 +00:00
Daniel Imms c4b707e9ca Merge pull request #5362 from Tyriar/tyriar/steps2
Move copilot steps to correct folder
2025-06-21 11:01:00 -07:00
Daniel Imms 9d35d59fb3 Move copilot steps to correct folder 2025-06-21 11:00:33 -07:00
Daniel Imms 877b37d3cc Merge pull request #5359 from Tyriar/tyriar/copilot_steps
Add copilot setup steps
2025-06-21 10:55:11 -07:00
Daniel Imms f14f55c951 Add copilot setup steps 2025-06-21 10:54:46 -07:00
Daniel Imms 8a16a551e0 Merge pull request #5358 from Tyriar/tyriar/reflow_bounds
Revert "Merge pull request #5321 from Tyriar/5319"
2025-06-18 06:17:46 -07:00
Daniel Imms 2a5239578a Revert "Merge pull request #5321 from Tyriar/5319"
This reverts commit a260f7d288, reversing
changes made to 2042bb8502.
2025-06-18 06:09:53 -07:00
Daniel Imms d28eb696ec Merge pull request #5355 from Tyriar/tyriar/3634
Add note about reverse tabnapping
2025-06-13 08:41:38 -07:00
Daniel Imms 4c0c2559c3 Add note about reverse tabnapping
Fixes #3634
2025-06-13 08:19:31 -07:00
Daniel Imms ee0a3deb6a Merge pull request #5349 from xtermjs/copilot/fix-3382
Add comprehensive tests for alt+shift+letter keyboard combinations
2025-06-05 03:47:13 -07:00
copilot-swe-agent[bot]andTyriar 076f051bb5 Fix ESLint trailing spaces warning in Keyboard.test.ts
Co-authored-by: Tyriar <2193314+Tyriar@users.noreply.github.com>
2025-06-05 10:34:53 +00:00
copilot-swe-agent[bot]andTyriar 658c57546c Add comprehensive tests for alt+shift+letter keyboard combinations
Co-authored-by: Tyriar <2193314+Tyriar@users.noreply.github.com>
2025-06-05 10:24:34 +00:00
copilot-swe-agent[bot] 223a650e3e Initial plan for issue 2025-06-05 10:13:25 +00:00
Daniel Imms 4b6316e510 Merge pull request #5346 from xtermjs/copilot/fix-4538-2
Remove alt -> ctrl+arrow hack in favor of embedder-specific solutions
2025-05-31 06:59:41 -07:00
copilot-swe-agent[bot]andTyriar 41546230dc Remove alt -> ctrl+arrow hack and update tests
Co-authored-by: Tyriar <2193314+Tyriar@users.noreply.github.com>
2025-05-29 13:20:54 +00:00
copilot-swe-agent[bot] 8413c0f94b Initial plan for issue 2025-05-29 13:09:01 +00:00
Daniel Imms 8828b47cea Merge pull request #5345 from Tyriar/tyriar/instruct
Add instructions to help Copilot coding agent
2025-05-29 06:05:50 -07:00
Daniel Imms b0f300e61b Add instructions to help Copilot coding agent 2025-05-29 06:00:41 -07:00
Loïc Mangeonjean 3ad83dd70e chore: add webgl shadow dom test 2025-05-21 21:51:01 +02:00
Loïc Mangeonjean 496b53d84d fix: support shadow dom in webgl renderer 2025-05-20 16:31:17 +02:00
Daniel Imms e9c547c1c6 Merge pull request #5337 from Tyriar/tyriar/5336
Fix finding terms across wrapped lines
2025-05-08 11:47:46 -07:00
Daniel Imms 0307e2c586 Fix finding terms across wrapped lines
Fixes #5336
2025-05-08 11:40:17 -07:00
Daniel Imms e29c2360e7 Merge pull request #5335 from Tyriar/tyriar/5255
webgl: Ignore alpha channel when allowTransparency is false
2025-05-07 08:19:19 -07:00
Daniel Imms 2b38b65392 webgl: Ignore alpha channel when allowTransparency is false
Fixes #5255
Part of microsoft/vscode#204663
2025-05-07 08:05:22 -07:00
Daniel Imms baf12e7aa4 Merge pull request #5308 from alexemorris/patch-1
Update AccessibilityManager.ts to use textContent rather than innerText
2025-04-23 05:13:17 -07:00
Daniel Imms 064efd6876 Merge branch 'master' into patch-1 2025-04-23 05:03:38 -07:00
Daniel Imms f615ac58b3 Merge pull request #5329 from Tyriar/esbuild_250422
Update and pin esbuild
2025-04-22 03:11:47 -07:00
Daniel Imms 2af170dbc9 Merge pull request #5317 from tabarra/docs/registerMarker-tip
docs: added usage tip for registerMarker()
2025-04-22 03:03:48 -07:00
Daniel Imms 39ad2cf3fe Update and pin esbuild 2025-04-22 03:03:44 -07:00
Daniel Imms 21387ecdae Merge pull request #5322 from xtermjs/dependabot/npm_and_yarn/addons/addon-ligatures/axios-1.8.2
Bump axios from 1.7.4 to 1.8.2 in /addons/addon-ligatures
2025-04-22 02:56:22 -07:00
Daniel Imms 78fbae3293 Add additional note to main doc 2025-04-22 02:54:14 -07:00
Daniel Imms a8ead811bf Move clarification into write apis 2025-04-22 02:47:33 -07:00
Daniel Imms 9696dd0c2b Merge branch 'master' into docs/registerMarker-tip 2025-04-22 02:35:47 -07:00
Daniel Imms 5e9ffb4c56 Merge pull request #5328 from Tyriar/5327_scroll
Refresh viewport after clear or ED
2025-04-21 07:09:59 -07:00
Daniel Imms e9587cf68f Refresh viewport after clear or ED
Fixes #5327
2025-04-21 07:00:27 -07:00
Daniel Imms 1771825e81 Merge pull request #5224 from Blashaq/master
putty-style ED2 sequence handling as terminal option
2025-04-21 06:54:42 -07:00
Daniel Imms e5b838a01b scrollOnDisplayErase -> scrollOnEraseInDisplay 2025-04-21 06:39:15 -07:00
Daniel Imms cd23a21bbf Align jsdoc 2025-04-21 06:37:48 -07:00
Daniel Imms 42946d72ba More alphabetical fix 2025-04-21 06:37:17 -07:00
Daniel Imms 2c836ede76 Tweak API position/docs 2025-04-21 06:34:45 -07:00
Adrian Błasiak 26d29abef1 Merge branch 'master' into master 2025-04-15 11:03:04 +02:00
dependabot[bot] 14d0f14c82 Bump axios from 1.7.4 to 1.8.2 in /addons/addon-ligatures
Bumps [axios](https://github.com/axios/axios) from 1.7.4 to 1.8.2.
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v1.7.4...v1.8.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-14 17:44:13 +00:00
Daniel Imms a260f7d288 Merge pull request #5321 from Tyriar/5319
Reflow on resize using similar logic to conpty
2025-03-14 10:43:16 -07:00
Daniel Imms 16772e8d8b Update lock file 2025-03-14 10:36:22 -07:00
Daniel Imms e3e11b1a90 Reflow on resize using similar logic to conpty
This aligns reflowing much closer to how conpty does it. This was always
an issue but only became a big issue recently because conpty 1.22+ opts
to passthrough sequences rather than reprinting aggressively. This means
that the conpty buffer being in sync with the xterm.js buffer is more
important, otherwise the cursor will show up in a seemingly random
position.

The existing reflow appears to differ somewhat in conpty, like it seems
to reflow at the word level, not the character level like xterm.js, but
refining that closer if not worth the effort since conpty may end up
relying on the terminal's buffer in the future[1].

Fixes #5319
Fixes #3513
Related #4231
Related microsoft/vscode#241978

[1]: https://github.com/microsoft/terminal/blob/main/doc/specs/%2313000%20-%20In-process%20ConPTY.md
2025-03-14 10:26:32 -07:00
tabarra 8576581574 docs: added usage tip for registerMarker() 2025-03-02 09:28:50 -03:00
Daniel Imms 2042bb8502 Merge pull request #5313 from Tyriar/revert_4978
Revert "Merge pull request #5253 from akphi/patch-1"
2025-02-14 06:44:18 -08:00
Daniel Imms 18cd9b137f Revert "Merge pull request #5253 from akphi/patch-1"
This reverts commit 601efc3745, reversing
changes made to af4208dad0.
2025-02-14 06:35:24 -08:00
Alex Morris 1495e7134f Update AccessibilityManager.ts to use textContent rather than innerText
The innerText property requires layout and therefore is not very efficient. For more information, see http://www.kellegous.com/j/2013/02/27/innertext-vs-textcontent/.
2025-02-12 16:46:01 +00:00
Daniel Imms a7a941ce78 Merge pull request #5305 from Tyriar/tyriar/ctx_slow
Fix issue where listeners remain after WebglRenderer throws
2025-02-07 06:26:21 -08:00
Daniel Imms 1c98c52535 Bump download-artifact 2025-02-07 06:11:47 -08:00
Daniel Imms 9a3d1c6de1 Bump upload-artifact 2025-02-07 06:07:47 -08:00
Daniel Imms a8d43eb2ec Fix issue where listeners remain after WebglRenderer throws
See microsoft/vscode#239838
2025-02-07 05:58:28 -08:00
Daniel Imms d81b25c3bf Merge pull request #5291 from Tyriar/polish_2
Make ProgressAddon.onChange non undefined
2025-01-09 06:14:23 -08:00
Daniel Imms c95ec71e09 Make ProgressAddon.onChange non undefined
Fixes #5288
2025-01-09 06:03:34 -08:00
Daniel Imms 6188f764d1 Merge pull request #5290 from Tyriar/progress_polish
Progress polish
2025-01-09 05:41:58 -08:00
Daniel Imms dc9084b5ad Progress polish
Fixes #5287
Fixes #5288
Fixes #5289
2025-01-09 05:32:36 -08:00
Daniel Imms 330e91ef08 Merge pull request #5251 from jerch/progress-addon
progress-addon
2025-01-09 05:04:36 -08:00
Daniel Imms 9caff8d778 Add note in readme about full API 2025-01-09 04:58:02 -08:00
Jörg Breitbart 6f6ce33588 Merge branch 'master' into progress-addon 2025-01-09 02:32:26 +01:00
Jörg Breitbart 8101132352 rename types, remove rc import in d.ts 2025-01-09 02:05:36 +01:00
Jörg Breitbart 96a3be3ca8 polish docs 2025-01-09 01:20:25 +01:00
jerchandDaniel Imms 75e5cc19e0 Update addons/addon-progress/typings/addon-progress.d.ts
Co-authored-by: Daniel Imms <2193314+Tyriar@users.noreply.github.com>
2025-01-09 01:06:18 +01:00
Daniel Imms 0692d486f4 Merge pull request #5286 from Tyriar/webgl_only_from_shared
Move WebGL addon-specific code into addon
2025-01-08 11:01:15 -08:00
Daniel Imms cfc1071c73 Fix more relative imports 2025-01-08 10:54:57 -08:00
Daniel Imms 6bcd4f0551 Add missing relative dir to imports 2025-01-08 10:45:41 -08:00
Daniel Imms 71f50b841f Move webgl-specific types into addon 2025-01-08 10:39:06 -08:00
Daniel Imms d70f2a462c Move webgl-specific shared code into addon
Never got moved when canvas addon was removed
2025-01-08 10:35:39 -08:00
Daniel Imms dce403afd9 Merge pull request #5218 from xtermjs/dependabot/npm_and_yarn/cross-spawn-7.0.6
Bump cross-spawn from 7.0.3 to 7.0.6
2025-01-08 09:38:14 -08:00
Daniel Imms ef4a3fd933 Merge pull request #5212 from LeoDog896/patch-1
chore(addons/fit): fix typo
2025-01-08 09:34:07 -08:00
Daniel Imms c21f32d012 Merge pull request #5282 from database-client-lib/fix-unexpected-composition
Fixed CapsLock triggering input twice in MacOS.
2025-01-08 09:34:03 -08:00
Daniel Imms 66780c03d4 Merge pull request #5196 from ceccopierangiolieugenio/add-pyTermTk-sandbox-project
Add pyTermTk HTML5 Exporter project to "Real-world uses"
2025-01-08 09:33:30 -08:00
Daniel Imms 6b53ab4b61 Merge pull request #5252 from jtbandes/patch-1
Fix ReferenceError in publish.js
2025-01-08 09:33:19 -08:00
Daniel Imms 334ca07eae Merge branch 'master' into add-pyTermTk-sandbox-project 2025-01-08 09:33:03 -08:00
Daniel Imms 7e01dc1358 Merge pull request #5219 from jackieoft/master
Add new site to Real-world uses
2025-01-08 09:32:13 -08:00
Daniel Imms 84f14cab58 Merge branch 'master' into master 2025-01-08 09:32:01 -08:00
Daniel Imms 85992928a7 Merge pull request #5247 from mathiscode/patch-1
Update README.md - add ecmaOS as Use Case
2025-01-08 09:30:57 -08:00
Daniel Imms 582a027df1 Merge branch 'master' into master 2025-01-08 09:30:29 -08:00
Daniel Imms db71de2858 Merge branch 'master' into dependabot/npm_and_yarn/cross-spawn-7.0.6 2025-01-08 09:28:50 -08:00
Daniel Imms c1446d6385 Merge branch 'master' into patch-1 2025-01-08 09:28:02 -08:00
Daniel Imms 0151805864 Merge pull request #5285 from Tyriar/5284_detailed_liga
Support detailed ligatures and variants
2025-01-08 06:40:30 -08:00
Daniel Imms 408d0995dd Support detailed ligatures and variants
Fixes #5284
2025-01-08 06:26:15 -08:00
Jörg Breitbart 6a232765f4 borrow emitter ctor from xterm 2025-01-07 21:22:40 +01:00
Jörg Breitbart f516346e09 fix docs 2025-01-07 20:36:02 +01:00
Jörg Breitbart c207edd49c make linter happy 2025-01-07 19:25:47 +01:00
Jörg Breitbart 828603cfab fix integration test 2025-01-07 19:24:14 +01:00
Jörg Breitbart d7cebc312e use event from xterm.js instead of own impl 2025-01-07 19:21:51 +01:00
Jörg Breitbart 5badbd3a6f import ProgressState type to d.ts 2025-01-07 18:22:27 +01:00
Jörg Breitbart a1e85a4eb5 Merge branch 'master' into progress-addon 2025-01-07 16:49:09 +01:00
cweijan 0433fb8a4b Fixed CapsLock triggering input twice in MacOS. 2025-01-07 22:57:40 +08:00
Daniel Imms 9e206415b8 Merge pull request #5279 from AnouarTouati/fixing-#5270
Fixes: #5270 regex case-sensitive should behave like monaco
2025-01-07 06:45:03 -08:00
Daniel Imms ffc61258f9 Merge pull request #5278 from Tyriar/liga_texture_limit
Handle glyph widths up to the maximum device texture size
2025-01-07 06:43:52 -08:00
Daniel Imms a095c100ce Merge pull request #5280 from Tyriar/monospace
Remove courier fonts from defaults
2025-01-07 06:40:22 -08:00
Daniel Imms 3c790ffbc5 Merge branch 'master' into fixing-#5270 2025-01-07 06:34:33 -08:00
Daniel Imms 04c1782f2f Merge branch 'master' into liga_texture_limit 2025-01-07 06:34:25 -08:00
Daniel Imms 1efd52af30 Merge branch 'master' into monospace 2025-01-07 06:34:15 -08:00
Daniel Imms cc78831262 Merge pull request #5281 from Tyriar/pw_dep
Pin Ubuntu 22 to fix playwright GH Actions issue for now
2025-01-07 06:34:04 -08:00
Daniel Imms a7b98de833 Handle overflowing to new row 2025-01-07 06:28:59 -08:00
Daniel Imms 2c11eae732 Pin ubuntu to v22 2025-01-07 06:02:30 -08:00
Daniel Imms bb7393b1cb . 2025-01-07 06:00:38 -08:00
Daniel Imms 23e3905529 . 2025-01-07 05:55:39 -08:00
Daniel Imms 2774a27500 Try fix playwright dependency problem 2025-01-07 05:53:03 -08:00
Daniel Imms 763d2e773a Match coding style of repo 2025-01-07 05:38:11 -08:00
Daniel Imms 38d340d176 Remove courier fonts from defaults
Fixes #5156
2025-01-07 05:17:36 -08:00
Anouar Touati baf5cf45c3 fixes: #5270 regex case-sensitive should behave like monaco 2025-01-06 16:43:03 -05:00
Daniel Imms ff9a3a1067 Merge branch 'master' into liga_texture_limit 2025-01-06 11:53:36 -08:00
Daniel Imms 7541cf2fd7 Add todo 2025-01-06 11:52:20 -08:00
Daniel Imms e882f4a46a Prototype of adding a large atlas page for overflow glyphs
Part of #5246
2025-01-06 11:50:08 -08:00
Daniel Imms 33a5457407 Merge pull request #5277 from Tyriar/ligature_selection__cursor
Disable ligatures when cursor is in range
2025-01-06 11:27:44 -08:00
Daniel Imms 112cfbeac8 Merge branch 'master' into ligature_selection__cursor 2025-01-06 11:20:24 -08:00
Daniel Imms d1b01c58f8 Merge pull request #5276 from Tyriar/ligature_selection
Fix selection rendering on ligatures in both renderers
2025-01-06 11:17:25 -08:00
Daniel Imms 9912e7b331 Disable ligatures when cursor is in range
Fixes #5230
2025-01-06 11:08:57 -08:00
Daniel Imms c7a1e0aea3 Handle selections in the middle of ligatures too 2025-01-06 11:03:10 -08:00
Daniel Imms 5bfb130f03 Apply same approach for rendering joined selection in webgl 2025-01-06 10:52:04 -08:00
Daniel Imms 0521378e70 Fix ligature selection in DOM renderer
Part of #5231
2025-01-06 10:50:13 -08:00
Daniel Imms ad08a2e15b Render selection under ligatures for webgl
Part of #5231
2025-01-06 10:15:16 -08:00
Daniel Imms a61fff6a93 Merge pull request #5275 from Tyriar/liga-test
Demo test button for common ligatures
2025-01-06 10:08:50 -08:00
Daniel Imms cd5438c2c5 Add = and - long ligatures too 2025-01-06 10:01:09 -08:00
Daniel Imms ef67b42fbd Demo test button for common ligatures
Part of #5231
2025-01-06 09:59:00 -08:00
Daniel Imms 601efc3745 Merge pull request #5253 from akphi/patch-1
bug: properly render the terminal when open() is called again
2025-01-06 09:27:09 -08:00
An Phi 62656b8375 Merge branch 'master' into patch-1 2025-01-06 10:28:27 -05:00
Daniel Imms af4208dad0 Merge pull request #5199 from asemdreibati/di-enhancement
Store the DI decorator's id in specific property (#5131).
2025-01-06 06:04:40 -08:00
Asem Dreibati db925ad65d Merge branch 'master' into di-enhancement 2024-12-23 16:06:48 +03:00
Daniel Imms ce095b3593 Merge pull request #5249 from jtbandes/fix-click-not-delivered
Fix click event bug caused by DomRenderer replaceChildren behavior
2024-12-20 12:41:52 -08:00
Daniel Imms 5230e5aad0 Merge pull request #5265 from Tyriar/tyriar/3293
Revert to cursor options after DECSCUSR 0
2024-12-20 10:53:53 -08:00
Daniel Imms d44f7818c4 Fix test to assert new behavior 2024-12-20 10:43:38 -08:00
Daniel Imms f824083324 Correct @vt docs 2024-12-20 10:37:48 -08:00
Daniel Imms 82598e94a9 Fix default param DECSCUSR handling 2024-12-20 10:36:32 -08:00
Daniel Imms b579649a39 Revert to cursor options after DECSCUSR 0
Fixes #3293
2024-12-20 10:34:05 -08:00
Daniel Imms fb2e96e1b0 Add test button for DECSCUSR
Part of #3293
2024-12-20 10:21:19 -08:00
Daniel Imms d5a2fed679 Merge pull request #5263 from Tyriar/5256
Make textarea readonly when disableStdin is set
2024-12-20 09:09:53 -08:00
Daniel Imms 318f3dc378 Merge pull request #5262 from Tyriar/tyriar/5241_2
Blend cursorAccent with background too
2024-12-20 09:06:36 -08:00
Daniel Imms 18c9eb1960 Remove .only 2024-12-20 09:01:30 -08:00
Daniel Imms da22b0308e Make textarea readonly when disableStdin is set
Fixes #5256
2024-12-20 09:00:05 -08:00
Daniel Imms 3e998387fe Blend cursorAccent with background too
Fixes #5241
2024-12-20 08:51:17 -08:00
Daniel Imms 5018a07f3c Merge pull request #5260 from Tyriar/5241
Blend cursor with background to support alpha in webgl
2024-12-20 07:46:40 -08:00
Daniel Imms b74ec267e2 Blend cursor with background to support alpha in webgl
Fixes #5241
2024-12-20 07:37:12 -08:00
An Phi d12b825ed4 Merge branch 'master' into patch-1 2024-12-18 01:28:45 -05:00
An Phi 9f9bb3c11c bug: properly render the terminal when open() is called again 2024-12-18 01:26:47 -05:00
Jacob Bandes-Storch 9824230caf Fix ReferenceError in publish.js 2024-12-17 12:20:55 -08:00
Jörg Breitbart 94ca05c8d6 fix handler example in readme 2024-12-15 20:01:05 +01:00
Jörg Breitbart 569e854a03 add to workflow bundle 2024-12-15 19:32:47 +01:00
Jörg Breitbart f0fd3683d5 add to publish script 2024-12-15 19:28:19 +01:00
Jörg Breitbart 80c57221ef fix esbuild script 2024-12-15 19:18:46 +01:00
Jörg Breitbart 6bb2210a91 initial commit 2024-12-15 18:57:03 +01:00
Jacob Bandes-Storch 1978399259 Fix click event bug caused by DomRenderer replaceChildren behavior 2024-12-13 13:57:17 -08:00
Jay Mathis bc7288a389 Update README.md - add ecmaOS as Use Case 2024-12-12 21:40:46 -06:00
Daniel Imms e826f6b69f Merge pull request #5234 from jtbandes/reflow-cursor-line
Add reflowCursorLine option
2024-12-12 06:36:34 -08:00
Daniel Imms 0d67393072 Fix lint 2024-12-12 06:30:17 -08:00
Daniel Imms 65899a9b50 Update src/common/buffer/Buffer.ts 2024-12-12 06:25:36 -08:00
Daniel Imms d7364faeee Update src/common/buffer/BufferReflow.ts 2024-12-12 06:25:31 -08:00
Jacob Bandes-Storch ae2622e5ce Add reflowCursorLine option 2024-12-05 18:03:27 -08:00
blashaq 904ddbc884 code style fix 2024-11-23 16:59:30 +00:00
blashaq 92c21a6950 + scrollOnDisplayErase option 2024-11-23 10:58:19 +00:00
Adrian Błasiakandjerch 780f9a31ff Update src/common/InputHandler.ts
Co-authored-by: jerch <jerch@rockborn.de>
2024-11-21 22:46:06 +01:00
blashaq 9aba07f8f8 putty-style ED2 sequence handling as terminal option 2024-11-21 18:16:19 +00:00
JackieL db621d7294 Update README.md 2024-11-19 14:59:53 +08:00
JackieL 9c66ad1139 Update README.md 2024-11-19 14:58:32 +08:00
dependabot[bot] 8d23cbc051 Bump cross-spawn from 7.0.3 to 7.0.6
Bumps [cross-spawn](https://github.com/moxystudio/node-cross-spawn) from 7.0.3 to 7.0.6.
- [Changelog](https://github.com/moxystudio/node-cross-spawn/blob/master/CHANGELOG.md)
- [Commits](https://github.com/moxystudio/node-cross-spawn/compare/v7.0.3...v7.0.6)

---
updated-dependencies:
- dependency-name: cross-spawn
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-18 22:41:31 +00:00
Tristan F.-R. 9e1d5ba7db chore(addons/fit): fix typo
Reprepresents -> Represents
2024-11-11 18:57:03 -08:00
Daniel Imms 41e8ae3959 Merge pull request #5209 from Tyriar/5206
Ensure last ligature cell is updated
2024-11-04 10:20:34 -08:00
Daniel Imms 7fab293cd6 Ensure last ligature cell is updated
Fixes #3288
Fixes #5206
Part of microsoft/vscode#233005
2024-11-04 10:13:34 -08:00
Daniel Imms b3f3cf1216 Merge pull request #5208 from Tyriar/5207
Set liga font feature when ligatures is enabled
2024-11-04 09:54:45 -08:00
Daniel Imms 77885ed63d Set liga font feature when ligatures is enabled
Fixes #5207
2024-11-04 09:47:26 -08:00
Daniel Imms 3d9b1073d0 Merge pull request #5203 from xtermjs/Tyriar-patch-1
Don't hide build folders
2024-11-01 07:52:47 -07:00
Daniel Imms aca9c89ece Merge pull request #5202 from Tyriar/ligatures_resolve
Only resolve ligatures externals in commonjs environment
2024-11-01 07:52:28 -07:00
Daniel Imms 3948482dc7 Also remove associated comment 2024-11-01 07:20:10 -07:00
Daniel Imms 6684c406e8 Don't hide build folders
This was getting too annoying needing to comment out these lines
2024-11-01 07:19:33 -07:00
Daniel Imms 952ac61761 Only resolve ligatures externals in commonjs environment
Part of microsoft/vscode#34103
2024-11-01 07:18:27 -07:00
Asem Dreibati a1b438cea6 Store the DI decorator's id in specific property (#5131). 2024-10-29 10:31:57 +03:00
Eugenio Parodi 932b65ef67 Add pyTermTk HTML5 Exporter project to Real-world uses 2024-10-22 14:46:43 +01:00
Daniel Imms f701d073d1 Merge pull request #5182 from jerch/fix_5181
fix #5181
2024-10-14 05:28:58 -07:00
Jörg Breitbart 085a275038 fix promise return, reject on missing family name, doc polish 2024-10-06 23:53:30 +02:00
Jörg Breitbart 9c74c81608 modified version of 5623ba6 2024-10-05 14:36:22 +02:00
Jörg Breitbart 687126970d revert 5623ba6 2024-10-05 14:24:25 +02:00
Jörg Breitbart 39bd429835 partially revert last commit 2024-10-05 14:07:49 +02:00
Jörg Breitbart 104c63d7e0 cleanup files 2024-10-04 17:52:45 +02:00
Jörg Breitbart 70c9861c39 Merge branch 'master' into webfonts-addon 2024-10-04 17:51:18 +02:00
Jörg Breitbart 8faf097e83 fix flaky tests 2024-10-04 17:12:00 +02:00
Jörg Breitbart d3298935b7 Merge remote-tracking branch 'upstream/master' into fix_5181 2024-10-04 16:56:30 +02:00
Jörg Breitbart f9cf9b9782 fix #5181 2024-10-04 16:55:13 +02:00
Daniel Imms a5fc111d62 Merge pull request #5180 from Tyriar/5179
Forward integration test exit code to running proc
2024-10-04 07:51:17 -07:00
jerch 5623ba6b63 slow down web links tests 2024-10-04 16:29:58 +02:00
jerch 3d9668f8fa fix dispose error in Terminal.test.ts 2024-10-04 15:50:36 +02:00
Jörg Breitbart c93623cb3b CI tests 2024-10-04 14:24:36 +02:00
Jörg Breitbart b0389a2ed1 future proof demo 2024-10-04 05:31:47 +02:00
Daniel Imms 621c702ff8 Forward integration test exit code to running proc
Fixes #5179
2024-10-03 20:10:10 -07:00
Jörg Breitbart 7907f13774 add license notes for fonts 2024-10-04 04:14:10 +02:00
Jörg Breitbart 956c7cbcba demo tests 2024-10-04 04:00:20 +02:00
Jörg Breitbart 23bef99132 cleanup API 2024-10-04 02:06:02 +02:00
Jörg Breitbart b104bcd144 remove await remnants 2024-10-04 00:42:38 +02:00
Jörg Breitbart 809b9aa1ad fix test runner 2024-10-04 00:15:49 +02:00
Jörg Breitbart 9a536f758d fix integration test runner 2024-10-03 22:23:31 +02:00
Jörg Breitbart 15a63bae22 change to global replacement 2024-10-03 22:10:40 +02:00
Jörg Breitbart 41640d9402 make linter happy 2024-10-03 22:07:10 +02:00
Jörg Breitbart 15664de472 initial 2024-10-03 21:50:34 +02:00
Daniel Imms 9186c724c4 Merge pull request #5171 from Tyriar/links
Fix links sometimes not activating
2024-09-27 13:08:14 -07:00
Daniel Imms f764c0ac91 Fix links sometimes not activating
This was a very long standing bug that was hard to nail down a repro case. Turns
out this was because at some point the current link can get cleared and re-evaluated,
I think because of a different fix we made. That caused the object check to fail.
So the fix there is to check link equality, not js object equality.

Additionally I found we don't dispose of old linkifiers when open is called
multiple times.

See microsoft/vscode#230010
2024-09-27 12:58:34 -07:00
Daniel Imms f56675f7e5 Merge pull request #5127 from jeanp413/fix-vscode-224750
Fix scroll dimensions when switching buffers
2024-09-20 10:36:05 -07:00
Daniel Imms aa94fd6cfe Merge pull request #5136 from esimkowitz/patch-1
Fix description for WebglAddon class in @xterm/addon-webgl
2024-09-20 10:27:23 -07:00
Daniel Imms e011513b15 Merge pull request #5166 from xtermjs/dependabot/npm_and_yarn/express-4.20.0
Bump express from 4.19.2 to 4.20.0
2024-09-20 09:53:19 -07:00
Daniel Imms 5c10617981 Merge pull request #5165 from xtermjs/dependabot/npm_and_yarn/addons/addon-ligatures/axios-1.7.4
Bump axios from 1.6.0 to 1.7.4 in /addons/addon-ligatures
2024-09-20 09:18:08 -07:00
dependabot[bot] c7f5ec4064 Bump express from 4.19.2 to 4.20.0
Bumps [express](https://github.com/expressjs/express) from 4.19.2 to 4.20.0.
- [Release notes](https://github.com/expressjs/express/releases)
- [Changelog](https://github.com/expressjs/express/blob/master/History.md)
- [Commits](https://github.com/expressjs/express/compare/4.19.2...4.20.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-20 16:10:45 +00:00
Daniel Imms 7642f55a1c Merge pull request #5141 from xtermjs/dependabot/npm_and_yarn/webpack-5.94.0
Bump webpack from 5.88.2 to 5.94.0
2024-09-20 09:10:02 -07:00
Daniel Imms 3d9d526977 Merge pull request #5140 from fuglaro/master
Add Filet Cloud to Real-world uses
2024-09-20 09:09:39 -07:00
Daniel Imms 4e9233d150 Merge pull request #5151 from PerBothner/unicode-graphemes-webpack-fix
Fix 'npm run package' in addon-unicode-graphemes
2024-09-20 09:08:20 -07:00
dependabot[bot] ac79c18090 Bump axios from 1.6.0 to 1.7.4 in /addons/addon-ligatures
Bumps [axios](https://github.com/axios/axios) from 1.6.0 to 1.7.4.
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v1.6.0...v1.7.4)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-20 16:06:45 +00:00
Daniel Imms 08221f17d6 Merge pull request #5157 from xtermjs/dependabot/npm_and_yarn/addons/addon-ligatures/path-to-regexp-1.9.0
Bump path-to-regexp from 1.7.0 to 1.9.0 in /addons/addon-ligatures
2024-09-20 09:06:22 -07:00
Daniel Imms d910cfd696 Merge pull request #5148 from xtermjs/dependabot/npm_and_yarn/micromatch-4.0.8
Bump micromatch from 4.0.5 to 4.0.8
2024-09-20 09:06:16 -07:00
dependabot[bot] 0bf10e525e Bump path-to-regexp from 1.7.0 to 1.9.0 in /addons/addon-ligatures
Bumps [path-to-regexp](https://github.com/pillarjs/path-to-regexp) from 1.7.0 to 1.9.0.
- [Release notes](https://github.com/pillarjs/path-to-regexp/releases)
- [Changelog](https://github.com/pillarjs/path-to-regexp/blob/master/History.md)
- [Commits](https://github.com/pillarjs/path-to-regexp/compare/v1.7.0...v1.9.0)

---
updated-dependencies:
- dependency-name: path-to-regexp
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-11 21:24:00 +00:00
Per Bothner 7cc921693a Fix 'npm run package' in addon-unicode-graphemes 2024-09-07 21:02:20 -07:00
dependabot[bot] ee3901f5da Bump micromatch from 4.0.5 to 4.0.8
Bumps [micromatch](https://github.com/micromatch/micromatch) from 4.0.5 to 4.0.8.
- [Release notes](https://github.com/micromatch/micromatch/releases)
- [Changelog](https://github.com/micromatch/micromatch/blob/master/CHANGELOG.md)
- [Commits](https://github.com/micromatch/micromatch/compare/4.0.5...4.0.8)

---
updated-dependencies:
- dependency-name: micromatch
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-04 18:10:30 +00:00
Evan Simkowitz 8bd4e10643 Merge branch 'master' into patch-1 2024-08-31 10:14:39 -07:00
Daniel Imms 44f9fa39ae Merge pull request #5143 from Tyriar/5135
Publish commit and set peerDependencies to @xterm/xterm beta when publishing
2024-08-31 08:22:44 -07:00
Daniel Imms fd0bb58379 Merge branch 'master' into 5135 2024-08-31 07:27:23 -07:00
Daniel Imms 13d078ecc7 Only set peerDependencies if the xterm version is beta 2024-08-31 07:26:12 -07:00
Daniel Imms 9a9ccbe29c Improve logging in publish script 2024-08-31 07:24:50 -07:00
Daniel Imms 7ebbb86b50 Set peer dependencies and commit in published package.json
The commit will allow us to link published releases to the exact code
version.

Fixes #5135
2024-08-31 07:18:52 -07:00
Daniel Imms 6b54599ca7 Get publish script working on Windows/node 20
Part of #5135
2024-08-31 06:59:34 -07:00
Evan Simkowitz 1f60a244c2 Merge branch 'master' into patch-1 2024-08-30 20:26:32 -07:00
dependabot[bot] 54789266b6 Bump webpack from 5.88.2 to 5.94.0
Bumps [webpack](https://github.com/webpack/webpack) from 5.88.2 to 5.94.0.
- [Release notes](https://github.com/webpack/webpack/releases)
- [Commits](https://github.com/webpack/webpack/compare/v5.88.2...v5.94.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-30 18:03:03 +00:00
John van Leeuwen 2d8c6be264 Add Filet Cloud to real-world uses 2024-08-30 02:43:47 +01:00
jerch 1b48cd04b8 Merge pull request #5139 from lusingander/image-addon-iip-jpeg
Fix image addon not being able to render some jpeg images in IIP
2024-08-28 16:28:30 +02:00
Kyosuke Fujimoto b307adb081 Fix IIPMetrics jpg detection 2024-08-28 23:16:00 +09:00
Kyosuke Fujimoto a07d97a9d3 Add test for jpg without APP segment to IIPMetrics 2024-08-28 16:21:34 +09:00
Evan Simkowitz 4914c4322c Merge branch 'master' into patch-1 2024-08-24 15:14:53 -07:00
Daniel Imms f186475ec9 Merge pull request #5130 from Tyriar/tyriar/5128
Remove xterm- prefix from addon mjs output
2024-08-23 09:29:29 -07:00
Evan Simkowitz be84a02c8d Fix description for WebglAddon class in @xterm/addon-webgl 2024-08-22 19:49:31 -07:00
Daniel Imms fcad91a682 Remove xterm- prefix from addon mjs output
Fixes #5128
2024-08-15 05:56:24 -07:00
jeanp413 ad38b1a166 Fix https://github.com/microsoft/vscode/issues/224750 2024-08-14 07:45:37 -05:00
Daniel Imms c6a50d5a1a Merge pull request #5124 from Tyriar/tyriar/node-pty
Update node-pty, use conpty.dll
2024-08-08 06:52:20 -07:00
Daniel Imms dd54ef13bc Update node-pty, use conpty.dll 2024-08-08 05:54:56 -07:00
Daniel Imms c2aee5b6c1 Merge pull request #5122 from Tyriar/tyriar/main_document
Use main document to create viewport element
2024-08-05 12:05:43 -07:00
Daniel Imms abe0640331 Use main document to create viewport element
See microsoft/vscode#224555
2024-08-05 11:50:36 -07:00
Daniel Imms cb3bd463a5 Merge pull request #5106 from Tyriar/tyriar/lifecycle
Move to vs/base disposables
2024-07-15 10:11:12 -07:00
Daniel Imms 56ec86242a Fix test, fix all unit tests running on mac/linux 2024-07-15 10:01:55 -07:00
Daniel Imms 2a6cd8230c Merge remote-tracking branch 'upstream/master' into tyriar/lifecycle 2024-07-15 09:52:09 -07:00
Daniel Imms b192ba2304 Merge pull request #5107 from Tyriar/tyriar/overview_ruler
Add top/bottom border overview ruler options
2024-07-15 09:22:50 -07:00
Daniel Imms bf41eb4204 Add top/bottom border overview ruler options 2024-07-15 09:14:49 -07:00
Daniel Imms 8a2a7ac6db Resolve vs in image addon 2024-07-14 16:25:01 -07:00
Daniel Imms 93adffa819 Fix lint 2024-07-14 16:24:12 -07:00
Daniel Imms 144bdb5dd0 Move over to vs disposables 2024-07-14 16:19:20 -07:00
Daniel Imms aee936264b Move to MutableDisposable in vs 2024-07-14 16:09:53 -07:00
Daniel Imms 866bc9eb90 Move to dom.ts addDisposableListener 2024-07-14 16:07:39 -07:00
Daniel Imms a7952ff36c Merge pull request #5105 from Tyriar/canvas
Remove the canvas renderer
2024-07-14 10:54:54 -07:00
Daniel Imms 9712057b91 Remove the canvas renderer
Fixes #4779
2024-07-14 10:48:39 -07:00
Daniel Imms 9d8bba7a18 Merge pull request #5103 from tisilent/remove-unread
remove _workCell in InputHandler.
2024-07-14 09:21:15 -07:00
Daniel Imms 060ce7ad28 Merge pull request #5104 from Tyriar/tyriar/events
Remove EventEmitter in favor of vs/base/common/event's Emitter
2024-07-14 09:20:11 -07:00
Daniel Imms 180fd3c9ac Resolve vs in unicode11 2024-07-14 09:11:33 -07:00
Daniel Imms bd700fe515 Allow refreshing all rows without creating object 2024-07-14 09:05:18 -07:00
Daniel Imms b6b19babc1 Fix benchmark npm script 2024-07-14 09:00:29 -07:00
Daniel Imms 618954b5d6 Import right path in addons 2024-07-14 08:56:54 -07:00
Daniel Imms 03c2b2f68e Fix addon resolve path 2024-07-14 08:52:15 -07:00
Daniel Imms ae8f2dd61a Remove Events.ts in favor of new Event.forward 2024-07-14 08:50:37 -07:00
Daniel Imms 460a1370d7 Add forward to Event 2024-07-14 08:47:59 -07:00
Daniel Imms 68e0eb714d Add vs resolve to addons 2024-07-14 08:47:03 -07:00
Daniel Imms 10b97f4856 Add vs alias to headless webpack config 2024-07-14 08:34:26 -07:00
Daniel Imms 26c73b8ead Add vs project to headless tsconfig 2024-07-14 08:30:06 -07:00
Daniel Imms e267f05141 Move forwardEvent to Events.ts 2024-07-14 08:20:18 -07:00
Daniel Imms 9c7e2ea98e Remove IEvent and runAndSubscribe 2024-07-14 08:17:16 -07:00
Daniel Imms 9141b525a6 Migrate rest of EventEmitter to base/ Emitter 2024-07-14 08:06:48 -07:00
Daniel Imms 90ff6f3d06 Migrate most EventEmitter to base/ Emitter 2024-07-14 07:41:56 -07:00
Daniel Imms 9187cd3d0a Add load test for long lines 2024-07-14 07:41:38 -07:00
谢佳龙 d0d759155b remove _workCell. 2024-07-12 16:41:10 +08:00
Daniel Imms 618e47204d Merge pull request #5102 from Tyriar/tyriar/dec
Ensure decorations are refreshed when origin of scroll event is buffe…
2024-07-11 11:48:37 -07:00
Daniel Imms e9fd5c70ba Ensure decorations are refreshed when origin of scroll event is buffer service
Fixes #5101
2024-07-11 11:48:00 -07:00
Daniel Imms cbcd195cad Merge pull request #5099 from Tyriar/tyriar/overflow
Remove overflow to allow decorations outside terminal
2024-07-11 11:16:28 -07:00
Daniel Imms 55303647c1 Remove overflow to allow decorations outside terminal
This is needed in the demo to show the red test decoration and in vscode for
shell integration decorations/quick fixes
2024-07-11 11:15:58 -07:00
Daniel Imms f3bfe80995 Merge pull request #5096 from Tyriar/tyriar/esbuild4
Integrate base/ platform from VS Code and adopt scroll bar
2024-07-11 09:42:44 -07:00
Daniel Imms 90a1da28bd Scroll immediately to bottom when scrollOnUserInput is enabled
See microsoft/vscode#211199
2024-07-11 09:36:22 -07:00
Daniel Imms 73ee951807 Resolve more todos 2024-07-11 08:43:54 -07:00
Daniel Imms 0f373512d2 Fix ICoreMouseService injecting 2024-07-11 08:37:48 -07:00
Daniel Imms 721d483cc3 Implement zero scrollback+no mouse event wheel handling 2024-07-11 08:35:42 -07:00
Daniel Imms 2da5eca384 Fix mouse events 2024-07-11 08:29:29 -07:00
Daniel Imms a644b5d068 Clean up dealing with animation frame 2024-07-10 13:50:07 -07:00
Daniel Imms d532a15bf2 Include all files, not just entry root folder 2024-07-10 13:46:04 -07:00
Daniel Imms 31e0af745d Fix out-esbuild build 2024-07-10 13:42:14 -07:00
Daniel Imms 444c07fd81 Replace monaco- with xterm- in code/css files 2024-07-10 13:33:43 -07:00
Daniel Imms f155d67c45 Replace monaco- with xterm- in the update script 2024-07-10 13:32:30 -07:00
Daniel Imms 59a8f0cf8e _getMutableOptions -> _getChangeOptions 2024-07-10 12:47:47 -07:00
Daniel Imms 1096b7f17f Create shared file for viewport constants 2024-07-10 12:40:02 -07:00
Daniel Imms d6f7133fa6 Fix api lint 2024-07-10 12:36:08 -07:00
Daniel Imms 55c627572c Support smooth scroll 2024-07-10 12:32:55 -07:00
Daniel Imms 2b8ccb5e51 Deprecate fast scroll modifier as it's no longer possible
Not possible via the component we import. We could add this but I
doubt it was being used anyway.
2024-07-10 08:30:32 -07:00
Daniel Imms d1d25a5bf7 Polish all files (excl. CoreBrowserTerminal/Viewport) 2024-07-10 08:08:02 -07:00
Daniel Imms ade5591d60 Const enum over magic number 2024-07-10 07:58:29 -07:00
Daniel Imms eca0800b9d Fix api lint 2024-07-10 07:55:22 -07:00
Daniel Imms 884141cd8f Support theming of scroll bar slider 2024-07-10 07:54:39 -07:00
Daniel Imms c7f80986c8 Inline some functions 2024-07-10 07:33:53 -07:00
Daniel Imms 9b26bb4436 Support theming of overview ruler border 2024-07-10 07:30:38 -07:00
Daniel Imms 75bf192af5 Have scroll bar bg react to theme change 2024-07-10 07:03:51 -07:00
Daniel Imms 416f12fcc3 Remove ScrollSource
This isn't used anymore, it was used earlier to workaround recursion between
the viewport and the terminal objects
2024-07-10 07:01:44 -07:00
Daniel Imms c2b90c6dae Remove missing demo webpack npm script 2024-07-10 06:58:45 -07:00
Daniel Imms 8b83f44a46 Tidy up esbuild bundle configs 2024-07-10 06:56:56 -07:00
Daniel Imms 522b39be86 Hide more folders from explorer 2024-07-10 06:55:55 -07:00
Daniel Imms 1b0078ad20 Exclude vs project from coverage 2024-07-10 06:42:50 -07:00
Daniel Imms 345b6906f9 Remove unused demo webpack config 2024-07-10 06:29:32 -07:00
Daniel Imms c3c787eaa7 Resolve vs imports in webpacked bundle 2024-07-10 06:29:05 -07:00
Daniel Imms 1dada9f256 Fix scroll lines API 2024-07-10 06:27:47 -07:00
Daniel Imms d904f2fce5 Generate source maps so errors report ts files 2024-07-10 06:26:39 -07:00
Daniel Imms 27195b003a Patch out unsafe usage of navigator and window in node envs 2024-07-10 06:22:16 -07:00
Daniel Imms b401865957 More clean up, doc updating vs/base 2024-07-09 13:53:16 -07:00
Daniel Imms 381200bce1 Fix fit addon to get the scroll bar working in the demo 2024-07-09 13:34:51 -07:00
Daniel Imms c33ed46161 Delete unused files 2024-07-09 13:29:20 -07:00
Daniel Imms 86c875f7e9 Helper script for finding unused files 2024-07-09 13:08:57 -07:00
Daniel Imms 99db197472 . 2024-07-09 11:59:22 -07:00
Daniel Imms 4789c24e1e Get esbuild working and adopt vs scrollbar 2024-07-09 11:19:15 -07:00
Daniel Imms c30bffc60c Set up vs project 2024-07-09 10:52:09 -07:00
Daniel Imms 190e1f014e Add git ignore for vs 2024-07-09 10:41:54 -07:00
Daniel Imms 995ccc72f8 Merge pull request #5094 from Tyriar/images
Add build flow images and move logo into new images folder
2024-07-09 08:30:29 -07:00
Daniel Imms 5eb592f8c0 Merge pull request #5095 from Tyriar/procfile
Remove procfile
2024-07-09 08:30:07 -07:00
Daniel Imms cb9b96dbbb Copy right file when publishing headless 2024-07-09 08:21:42 -07:00
Daniel Imms 6382067e5b Remove procfile
No one uses this afaik and it's been stale and broken for a while anyway
2024-07-09 08:19:59 -07:00
Daniel Imms 844315f05a Add build flow images and move logo into new images folder 2024-07-09 08:17:32 -07:00
Daniel Imms c5d352bbe1 Merge pull request #5093 from Tyriar/tyriar/test_naming
Align npm and test script names
2024-07-07 09:45:11 -07:00
Daniel Imms aa6907335a Align npm and test script names 2024-07-07 09:35:30 -07:00
Daniel Imms d5921cd21b Merge pull request #5092 from Tyriar/tyriar/esbuild3
Add support for ESM via esbuild
2024-07-07 09:29:23 -07:00
Daniel Imms f341d0a3e9 Fix formatting of tasks.json 2024-07-07 09:21:55 -07:00
Daniel Imms ab8453c773 Simplify zip job 2024-07-07 09:21:17 -07:00
Daniel Imms 2fc8acf81e Publish addon mjs files 2024-07-07 09:15:25 -07:00
Daniel Imms 89e315ab2b Hide build output from vscode explorer 2024-07-07 09:12:28 -07:00
Daniel Imms faed3e7c80 Move playwright tests to use project references 2024-07-07 09:06:26 -07:00
Daniel Imms 215315b377 Esbuild package headless 2024-07-07 08:51:38 -07:00
Daniel Imms b6a9c24c7d Generate addon playwright suites via list of addons 2024-07-07 08:35:17 -07:00
Daniel Imms ff01995691 Improve testing instructions 2024-07-07 08:32:19 -07:00
Daniel Imms 62c5747e3f Use out-test for integration tests 2024-07-05 14:05:24 -07:00
Daniel Imms c337fa8dc5 Upload lib artifacts 2024-07-05 13:41:12 -07:00
Daniel Imms 1911d19cf8 Build demo with esbuild 2024-07-05 12:37:10 -07:00
Daniel Imms 4d45215622 Run right esbuild script 2024-07-05 12:34:17 -07:00
Daniel Imms 804a2b7693 Zip up right folders, run esbuild in CI 2024-07-05 12:32:21 -07:00
Daniel Imms 992fa015a4 Move on to all ESM imports in the demo 2024-07-05 12:12:03 -07:00
Daniel Imms 208d6e96f8 Use esm image addon in demo 2024-07-05 12:00:05 -07:00
Daniel Imms 93bf18c5b0 Sourcemaps on prod build 2024-07-05 11:51:47 -07:00
Daniel Imms 057abba08b mjs and js 2024-07-05 11:48:42 -07:00
Daniel Imms 40c5fbf0cb Don't build test folders
These are for integration tests which need tsc sources anyway
2024-07-05 10:30:47 -07:00
Daniel Imms 24813f56ed How to run tests 2024-07-05 09:35:03 -07:00
Daniel Imms 6135757349 Remove unwanted npm scripts 2024-07-05 09:16:47 -07:00
Daniel Imms c9baa366ab Fix image esbuild 2024-07-05 09:16:14 -07:00
Daniel Imms be49bb6263 Move integration tests onto esbuild sources (except image) 2024-07-05 09:12:30 -07:00
Daniel Imms 9c3694af5a Move unit tests onto esbuild sources 2024-07-05 09:10:20 -07:00
Daniel Imms ba3ba899a5 Clean up 2024-07-05 08:55:01 -07:00
Daniel Imms 1f11c7e46c Get sources building with esbuild 2024-07-05 08:46:15 -07:00
Daniel Imms 5b5e262dff Merge pull request #5089 from Tyriar/tyriar/dts
Remove src dts files and correct exports
2024-07-05 08:20:03 -07:00
Daniel Imms a7b248ea9b Fix flakes in fit addon integration tests 2024-07-05 08:13:03 -07:00
Daniel Imms 039cd02482 Merge pull request #5091 from Tyriar/tyriar/windows_server
Ignore EPIPE exception on Windows when reloading demo client
2024-07-05 08:02:57 -07:00
Daniel Imms 7b3927a810 Merge pull request #5090 from Tyriar/tyriar/demo_warn
Suppress unrecognized option warning in demo
2024-07-05 07:53:38 -07:00
Daniel Imms 4a9e52ee44 Ignore EPIPE exception on Windows when reloading demo client
Whenever the demo is reloaded on Windows the demo server crashes. This just
ignores the error as I tried logging and adding try/catch around all
interactions with the websocket and couldn't narrow it down.
2024-07-05 07:53:33 -07:00
Daniel Imms 8f8c94a734 Suppress unrecognized option warning in demo 2024-07-05 07:14:17 -07:00
Daniel Imms 7b2e8a6ce1 Remove src dts files and correct exports
Esbuild doesn't appear to play nice with dts files and when they are regular ts
files they require the exports
2024-07-05 06:51:00 -07:00
Daniel Imms 8927d5c50f Merge pull request #5088 from Tyriar/tyriar/dec
Sync DOM decoration styles when line height/width changes
2024-07-04 08:45:58 -07:00
Daniel Imms 132553f521 Sync DOM decoration styles when line height/width changes
See microsoft/vscode#219979
2024-07-04 08:37:48 -07:00
Daniel Imms 795839cbe1 Merge pull request #5085 from Tyriar/addon_tests
Run other integration tests in CI
2024-07-03 08:51:45 -07:00
Daniel Imms a1b2a1c5ee \n -> \r\n on non-Windows 2024-07-03 08:44:49 -07:00
Daniel Imms cae6fa0561 Merge pull request #5086 from xtermjs/dependabot/npm_and_yarn/ws-8.17.1
Bump ws from 7.5.9 to 8.17.1
2024-07-03 08:23:15 -07:00
Daniel Imms 224409d7d6 Use fixed dimensions on image test 2024-07-03 08:17:19 -07:00
dependabot[bot] f0d2cb8d05 Bump ws from 7.5.9 to 8.17.1
Bumps [ws](https://github.com/websockets/ws) from 7.5.9 to 8.17.1.
- [Release notes](https://github.com/websockets/ws/releases)
- [Commits](https://github.com/websockets/ws/compare/7.5.9...8.17.1)

---
updated-dependencies:
- dependency-name: ws
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-03 15:17:03 +00:00
Daniel Imms aee480afd3 Merge pull request #5070 from xtermjs/dependabot/npm_and_yarn/braces-3.0.3
Bump braces from 3.0.2 to 3.0.3
2024-07-03 08:16:31 -07:00
Daniel Imms fbd430af6e Merge pull request #5052 from renatoliveira/master
Update README.md
2024-07-03 08:14:35 -07:00
Daniel Imms a884f91b22 Merge pull request #5059 from myarmolinsky/add-balena-real-world-use
Add `balena` to real world uses
2024-07-03 08:12:43 -07:00
Daniel Imms 46a3676634 Merge branch 'master' into add-balena-real-world-use 2024-07-03 08:12:10 -07:00
Daniel Imms 46a1221fc6 Merge pull request #5056 from Cainier/master
Add app OpenSFTP to README.md
2024-07-03 08:11:47 -07:00
Daniel Imms b6ea2c4a99 Merge branch 'master' into master 2024-07-03 08:11:39 -07:00
Daniel Imms d0782ad6b1 Merge pull request #5049 from info24/master
Add eva pro to  README.md
2024-07-03 08:10:57 -07:00
Daniel Imms 11a909453f Merge pull request #5071 from xtermjs/dependabot/npm_and_yarn/ws-8.17.1
Bump ws from 8.13.0 to 8.17.1
2024-07-03 08:10:29 -07:00
Daniel Imms fec21b6e8e Merge branch 'master' into dependabot/npm_and_yarn/braces-3.0.3 2024-07-03 08:10:01 -07:00
Daniel Imms 11b9eb4a1c Use fixed dimensions on search test 2024-07-03 08:09:15 -07:00
Daniel Imms 2b735a0159 Remove space from project names 2024-07-03 05:15:41 -07:00
Daniel Imms 135c038167 Add server start script to all addons with integration tests 2024-07-03 05:07:09 -07:00
Daniel Imms 7489ddc4f7 Run other integration tests in CI 2024-07-03 05:03:18 -07:00
dependabot[bot] a9fd92eba6 Bump ws from 8.13.0 to 8.17.1
Bumps [ws](https://github.com/websockets/ws) from 8.13.0 to 8.17.1.
- [Release notes](https://github.com/websockets/ws/releases)
- [Commits](https://github.com/websockets/ws/compare/8.13.0...8.17.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-03 11:39:42 +00:00
Daniel Imms bc5074116e Merge pull request #5083 from Tyriar/tyriar/ts
typescript@5.5
2024-07-03 04:39:15 -07:00
Daniel Imms b9fbbfbb8e Merge pull request #5082 from Tyriar/tyriar/eslint_warnings
Fix .eslintrc.json warnings, move deprecated to stylistic/ts
2024-07-03 04:38:08 -07:00
Daniel Imms 26c072f45c Add missing plugin include 2024-07-03 04:32:22 -07:00
Daniel Imms b7988bf00a Upgrade playwright tsconfigs to use ESNext
This ends up outputing the required object destructuring that's needed by
playwright, see microsoft/playwright#8798
2024-07-03 04:31:34 -07:00
Daniel Imms cca051041f Also migrate semi rule, alphabetize 2024-07-03 04:21:41 -07:00
Daniel Imms 3c84cfe610 typescript@5.5 2024-07-03 04:17:11 -07:00
Daniel Imms e2c5d91a2d Fix .eslintrc.json warnings, move deprecated to stylistic/ts 2024-07-03 04:15:38 -07:00
Daniel Imms 7970eb3b3b Merge pull request #5081 from Tyriar/cleanup
Simplify GH action names and remove test/api project
2024-07-03 03:22:01 -07:00
Daniel Imms f44d68ef4a Remain test/api project 2024-07-03 03:10:58 -07:00
Daniel Imms 54a20c511b Simplify GH action names 2024-07-03 03:10:51 -07:00
Daniel Imms 147b542c9d Merge pull request #5080 from Tyriar/tyriar/more_playwright2
Migrate remaining API tests to playwright and remove API test infrastructure
2024-07-03 03:08:37 -07:00
Daniel Imms 0f37152a9b Remove API test CI jobs 2024-07-03 03:01:25 -07:00
Daniel Imms efd4c5b340 Remove API test infrastructure 2024-07-03 02:57:05 -07:00
Daniel Imms 580bb082b5 Migrate image api tests to playwright 2024-07-03 02:56:09 -07:00
Daniel Imms 779ec7fad8 Migrate web-links api tests to playwright 2024-07-03 02:41:19 -07:00
Daniel Imms 89f3353a83 Migrate unicode11 api tests to playwright 2024-07-03 02:37:06 -07:00
Daniel Imms ab2cb9a809 Migrate unicode-graphemes api tests to playwright 2024-07-03 02:33:17 -07:00
Daniel Imms c9ad9cbf0e Migrate serialize api tests to playwright 2024-07-03 02:30:27 -07:00
Daniel Imms 6047093325 Merge pull request #5079 from Tyriar/more_playwright
Migrate more API tests to playwright
2024-07-02 14:57:46 -07:00
Daniel Imms 713f765731 Migrate attach api tests to playwright 2024-07-02 14:52:47 -07:00
Daniel Imms 4295dfaf20 Migrate clipboard api tests to playwright (only chromium) 2024-07-02 14:49:36 -07:00
Daniel Imms 57bac91904 Migrate fit api tests to playwright 2024-07-02 14:25:31 -07:00
Daniel Imms 958ae9b0ce Migrate search api tests to playwright 2024-07-02 14:08:34 -07:00
Daniel Imms 50032e2820 Merge pull request #5078 from Tyriar/shell_tests
Get tests working on node that requires shell arg
2024-07-02 13:01:52 -07:00
Daniel Imms daa2440a40 Fix playwright project names 2024-07-02 11:42:39 -07:00
Daniel Imms 645b698f37 Reduce coverage threshold 2024-07-02 11:39:54 -07:00
Daniel Imms 4b6c8f1fe4 Get tests working on node that requires shell arg
These silently fail on Windows after a recent change in node.js
2024-07-02 11:04:04 -07:00
dependabot[bot] 9a7941c004 Bump braces from 3.0.2 to 3.0.3
Bumps [braces](https://github.com/micromatch/braces) from 3.0.2 to 3.0.3.
- [Changelog](https://github.com/micromatch/braces/blob/master/CHANGELOG.md)
- [Commits](https://github.com/micromatch/braces/compare/3.0.2...3.0.3)

---
updated-dependencies:
- dependency-name: braces
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-16 09:56:44 +00:00
Daniel Imms f77a763858 Merge pull request #5067 from Tyriar/base64
Correct addon-clipboard ctor types
2024-06-04 06:34:18 -07:00
Daniel Imms 2b716cd436 Correct addon-clipboard ctor types 2024-06-04 06:24:59 -07:00
Daniel Imms a3024a98fe Merge pull request #4879 from JasonXJ/a11y-char-width2
Align character position in a11y tree with the actual rendering
2024-06-04 05:38:31 -07:00
Daniel Imms 12e5841959 Fix lint 2024-06-04 05:30:54 -07:00
Daniel Imms 599cbc49f7 Merge branch 'master' into a11y-char-width2 2024-06-04 05:29:06 -07:00
Daniel Imms c73793fa11 Merge pull request #5066 from Tyriar/tyriar/vscode_213174
Speed up clearing all markers, mass event listener dispose
2024-06-03 11:49:48 -07:00
Daniel Imms dd106ae9b8 Speed up clearing all markers, mass event listener dispose
Part of microsoft/vscode#213174
2024-06-03 11:43:31 -07:00
Daniel Imms baa4e1b0e8 Merge pull request #5054 from joelhockey/master
Add powerline git, LN, lock symbols
2024-06-03 07:38:53 -07:00
myarmolinsky f02d68b36d Add balena to real world uses 2024-05-24 07:47:45 -04:00
Cainier 80d2ee78e7 Update README.md
Add "OpenSFTP" to Real-world uses
2024-05-15 16:07:12 +08:00
Joel Hockey d52f09de3c Add powerline git, LN, lock symbols
Symbols taken from https://github.com/powerline/fontpatcher/blob/develop/fonts/powerline-symbols.sfd

The original symbols use height and widith of approx 1060x2048, so I
modified to be roughly 1000x1000 via:

fontforge -lang=ff -c 'Open($1); SelectAll(); UnlinkReference(); Scale(100, 50); Move(0, 300); Export("svg/%n-%e.svg");' powerline-symbols.sfd

Then I edited by hand to round values to approx 2 significant digits,
and then used a script to convert relative SVG commands such as 'l',
'c', 's' to use only 'M', 'L' and 'C' and used expected format with
comma separators rather than spaces, and divide all values by 1000 to
map into a 1x1 space.
2024-05-12 17:56:38 -07:00
Renato Oliveira 3c017f2291 Update README.md
Removes Katacoda from the real-world uses. That platform has been shutdown by O'Reilly.
2024-05-07 16:10:32 -03:00
info24 9b1396fb32 Add eva pro to README.md 2024-04-26 23:10:03 +08:00
Daniel Imms 2edc65baaa Merge pull request #4220 from aymanbagabas/osc52
Add support to ANSI OSC52
2024-04-22 02:40:23 -07:00
Daniel Imms 6ed1f42fbe Merge branch 'master' into osc52 2024-04-22 02:04:33 -07:00
Daniel Imms 826c057047 Merge pull request #5003 from josiahhudson/master
Support semi-colons in OSC 8 hyperlink URIs
2024-04-22 01:41:16 -07:00
Daniel Imms 1709d44697 Merge pull request #5020 from wavetermdev/escape-html
Escape Unsafe HTML Characters in addon-serialize
2024-04-21 08:50:50 -07:00
Daniel Imms d77b41ed25 Add tests to cover OSC 8 hyperlinks 2024-04-21 08:38:12 -07:00
Daniel Imms 0e77f839ff Use indexOf(';') approach 2024-04-21 08:30:45 -07:00
Daniel Imms 487278ac79 Merge branch 'master' into master 2024-04-21 08:26:25 -07:00
Daniel Imms 499afa1e30 Merge pull request #4936 from SimonSiefke/fix/memory-leak
fix: memory leak in CoreBrowserService
2024-04-21 08:10:24 -07:00
Daniel Imms af8a663f70 Merge branch 'master' into fix/memory-leak 2024-04-21 08:06:05 -07:00
Daniel Imms 6be1c3fdf2 Merge pull request #4940 from octoclonius/patch-1
Update global object fix
2024-04-21 08:04:02 -07:00
Daniel Imms f1bb8f48c5 Merge pull request #5010 from xtermjs/dependabot/npm_and_yarn/addons/addon-ligatures/follow-redirects-1.15.6
Bump follow-redirects from 1.15.3 to 1.15.6 in /addons/addon-ligatures
2024-04-21 08:01:25 -07:00
Daniel Imms 7938dfcc17 Merge pull request #5018 from xtermjs/dependabot/npm_and_yarn/express-4.19.2
Bump express from 4.18.2 to 4.19.2
2024-04-21 07:59:38 -07:00
Daniel Imms ce3e0852d8 Merge branch 'master' into patch-1 2024-04-21 07:59:25 -07:00
Daniel Imms ca269907dc Merge branch 'master' into dependabot/npm_and_yarn/addons/addon-ligatures/follow-redirects-1.15.6 2024-04-21 07:56:27 -07:00
Daniel Imms bca4e717b8 Merge branch 'master' into dependabot/npm_and_yarn/express-4.19.2 2024-04-21 07:54:45 -07:00
jerch cbb3472780 Merge pull request #4963 from unional/patch-1
Update xterm.d.ts
2024-04-21 16:48:39 +02:00
dependabot[bot] b0c55d1bbf Bump follow-redirects from 1.15.3 to 1.15.6 in /addons/addon-ligatures
Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.15.3 to 1.15.6.
- [Release notes](https://github.com/follow-redirects/follow-redirects/releases)
- [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.15.3...v1.15.6)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-21 14:47:44 +00:00
dependabot[bot] f1e0737c5e Bump express from 4.18.2 to 4.19.2
Bumps [express](https://github.com/expressjs/express) from 4.18.2 to 4.19.2.
- [Release notes](https://github.com/expressjs/express/releases)
- [Changelog](https://github.com/expressjs/express/blob/master/History.md)
- [Commits](https://github.com/expressjs/express/compare/4.18.2...4.19.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-21 14:47:44 +00:00
Daniel Imms 650ecb0640 Merge branch 'master' into patch-1 2024-04-21 07:45:41 -07:00
Daniel Imms 75491f4152 Remove trailing whitespace 2024-04-21 07:42:17 -07:00
Daniel Imms 770c17318e Merge pull request #4933 from tisilent/fix-#4917
Check after updating the SelectionRenderModel
2024-04-21 07:42:15 -07:00
Daniel Imms c52b4c3ed5 Merge branch 'master' into patch-1 2024-04-21 07:41:48 -07:00
Daniel Imms a7df139c46 Merge branch 'master' into fix-#4917 2024-04-21 07:38:10 -07:00
Daniel Imms bada1d28d6 Merge pull request #4865 from silamon/devcontainer-update
Update devcontainer
2024-04-21 07:37:18 -07:00
Daniel Imms e4bbbcf1fb Merge pull request #4989 from gonzolively/master
adding Wave Terminal to the README
2024-04-21 07:35:35 -07:00
Daniel Imms 292042e813 Merge pull request #4984 from tisilent/fix-#4983
Clear timer when dispose
2024-04-21 07:33:52 -07:00
Ayman Bagabas 3cedfbe9bf Merge branch 'master' into osc52 2024-04-21 15:32:08 +03:00
Daniel Imms 0d799ba69b Merge pull request #5038 from Tyriar/tyriar/4911
Speed up insertion and deletion of values in SortedList by batching and deferring to idle task
2024-04-20 11:20:52 -07:00
Daniel Imms 7b66da2706 Merge branch 'master' into tyriar/4911 2024-04-20 11:16:31 -07:00
Daniel Imms 6b4bf428de Fix tests 2024-04-20 11:15:14 -07:00
Daniel Imms a4a7ce843c Reduce gc pressure in api verify integers 2024-04-20 11:08:28 -07:00
Daniel Imms 73fe7015b7 Use sort on object 2024-04-20 11:00:41 -07:00
Daniel Imms ef1152a8fb Doc 2024-04-20 10:59:17 -07:00
Daniel Imms 71dddbc8b9 Move from set to array 2024-04-20 10:57:39 -07:00
Daniel Imms 45fd3c900f Only flush when needed 2024-04-20 10:52:43 -07:00
Daniel Imms 69c4966804 Consistent naming 2024-04-20 10:48:55 -07:00
Daniel Imms 3af11c2b1f Optimize SortedList.insert by batching to idle task 2024-04-20 10:47:40 -07:00
Daniel Imms dc541d5495 Optimize SortedList.delete by batching to idle task 2024-04-20 10:32:30 -07:00
Daniel Imms 5c8a9084e3 Add mass decoration test button
Part of #4911
2024-04-20 08:41:22 -07:00
Ayman Bagabas 24b04e9243 Merge branch 'master' into osc52 2024-04-20 11:24:58 -04:00
Ayman Bagabas 0562f32af6 Apply suggestions 2024-04-20 11:20:16 -04:00
Daniel Imms 21d7f784d1 Merge pull request #5037 from Tyriar/tyriar/5036
Prevent smooth scroll from running more than once per frame
2024-04-20 08:19:17 -07:00
Daniel Imms 16db227119 Prevent smooth scroll from running more than once per frame
Fixes #5036
2024-04-20 08:11:59 -07:00
Ayman Bagabas 9ebfb11ac8 Fix tests 2024-04-19 10:52:38 +03:00
Ayman Bagabas 98b0e16984 Merge branch 'master' into osc52 2024-04-19 10:09:21 +03:00
Ayman Bagabas 87169ee3ac fix: tests 2024-04-19 10:09:08 +03:00
Daniel Imms 47a46870b6 Merge pull request #5034 from Tyriar/tyriar/update_cursor_still
Expose onWriteParsed on API
2024-04-18 16:31:19 -07:00
Daniel Imms 8458bb4c4f Expose onWriteParsed on API
Inconsistency with headless API
2024-04-18 16:27:48 -07:00
Ayman Bagabas 5238461f3f Update ClipboardAddon.ts 2024-04-18 23:17:17 +03:00
Ayman Bagabas 1f7db053cd Merge branch 'master' into osc52 2024-04-18 13:10:23 +03:00
Ayman Bagabas 8a9cb37273 Tidy 2024-04-18 13:09:27 +03:00
Daniel Imms d2b8e6c125 Merge pull request #5027 from Tyriar/4916
Finish OSC hyperlinks when the second param is only whitespace
2024-04-12 16:52:45 -07:00
Daniel Imms e845abc784 Finish OSC hyperlinks when the second param is only whitespace
Fixes #4916
2024-04-12 16:47:59 -07:00
Ayman Bagabas 70dd76f954 Merge branch 'master' into osc52 2024-04-08 09:36:19 -07:00
Daniel Imms 887e5a6cba Merge pull request #5024 from ksqsf/master
Fix duplicate input for some IMEs
2024-04-08 05:53:56 -07:00
Ayman Bagabas acc0a2ef78 Support passing playwright browser options 2024-04-08 12:53:17 +03:00
Ayman Bagabas 6ac75d88c4 Fix demo tsconfig addon name 2024-04-08 12:46:37 +03:00
Ayman Bagabas 00514d41b6 Undo format 2024-04-08 12:45:55 +03:00
ksqsf 52e8a75e9f Fix duplicate input for some IMEs
fixes #5023
2024-04-08 13:42:55 +08:00
Ayman Bagabas ce9e92f8e5 Fix OSC52 sequence response 2024-04-08 05:52:21 +03:00
Ayman Bagabas a709187007 Update addon-clipboard readme 2024-04-08 04:51:55 +03:00
Ayman Bagabas fad97bc4d5 Clean up 2024-04-08 04:40:17 +03:00
Ayman Bagabas 94648eb4e9 Update addon-clipboard 2024-04-08 02:47:26 +03:00
Ayman Bagabas 805cabafe8 Merge remote-tracking branch 'upstream/master' into osc52 2024-04-07 23:22:01 +03:00
sawka 431045619e match capitalization of HTML with the rest of the file 2024-03-29 10:26:44 -07:00
sawka 55e34cb174 fix unit test (test terminal only has 10 cols) 2024-03-29 00:44:02 -07:00
sawka cdb45c743f escape special html characters in addon-serialize 2024-03-29 00:30:29 -07:00
Simon Lamon 9886d60be0 Merge branch 'master' into devcontainer-update 2024-03-22 19:06:35 +00:00
Josiah Hudson b40e58bc77 Fix https://github.com/xtermjs/xterm.js/issues/4944 by only splitting on the first ";" in InputHandler.setHyperlink(). 2024-03-15 17:40:21 -04:00
Knox Lively 553b9f261a adding Wave Terminal to the README 2024-03-06 23:56:04 -07:00
tisilent fe22671a6e Clear timer when dispose 2024-03-06 17:03:25 +08:00
Homa Wong 88bfc4ad9c add termios link 2024-02-19 17:18:52 -08:00
Homa Wong 9107ee905a Update xterm.d.ts
Fix doc typo
2024-02-16 18:08:58 -08:00
octoclonius b5ca41d658 Merge branch 'master' into patch-1 2024-02-04 15:55:14 -05:00
octoclonius d83f442cff Update webpack.config.headless.js 2024-01-13 19:57:38 -06:00
octoclonius 67e8e60a69 Update webpack.config.headless.js 2024-01-13 19:55:18 -06:00
octoclonius d5827ec77d Update webpack.config.headless.js 2024-01-13 19:54:27 -06:00
octoclonius c0044dc4a5 Update webpack.config.js global object fix 2024-01-13 13:30:10 -06:00
octoclonius 8696cf8789 Update webpack.config.js global object fix 2024-01-13 13:29:59 -06:00
octoclonius 693ec1b2bc Update webpack.config.js global object fix 2024-01-13 13:29:48 -06:00
octoclonius 330d7b3100 Update webpack.config.js global object fix 2024-01-13 13:29:33 -06:00
octoclonius d8f96ab9c7 Update webpack.config.js global object fix
`this` might be okay, I'm not actually sure, but xterm.js uses `globalThis` and so I think consistency is better maybe?
2024-01-13 13:29:20 -06:00
octoclonius 072cd029e4 Update webpack.config.js global object fix 2024-01-13 13:28:21 -06:00
octoclonius 46baff8459 Update webpack.config.js global object fix 2024-01-13 13:28:05 -06:00
octoclonius 8d6fff7000 Update webpack.config.js global object fix 2024-01-13 13:27:44 -06:00
octoclonius f84fff5f87 Update global object fix 2024-01-13 13:24:15 -06:00
octoclonius 1a67241f67 Update global object fix 2024-01-13 13:23:57 -06:00
octoclonius 7f7de14e6f Add comma 2024-01-13 13:23:33 -06:00
octoclonius 815758529d Update global object fix 2024-01-13 13:21:30 -06:00
Simon Siefke 819254e540 fix formatting 2024-01-04 22:33:39 +01:00
Simon Siefke f486989697 fix formatting 2024-01-04 22:32:59 +01:00
Simon Siefke 9e98b631da use disposable for text area listeners 2024-01-04 22:29:12 +01:00
Simon Siefke b0667aacca fix: memory leak in CoreBrowserService 2024-01-04 22:05:23 +01:00
tisilent 750f3179de move test 2024-01-01 22:48:39 +08:00
tisilent b1d9b4751d Check after updating the SelectionRenderModel 2024-01-01 22:25:54 +08:00
Jason Lin 26dc69446e Align character position in a11y tree with the actual rendering
This helps screen readers to draw selection outline at the correct
position.
2023-11-10 21:21:50 +11:00
Jason Lin a0da287b9c Use the right document in AccessibilityManager.ts 2023-11-09 16:57:58 +11:00
Simon Lamon 5c1d9b28cd Update yarn lock 2023-11-04 13:10:36 +00:00
Simon Lamon 0aeeebdd64 Devcontainer update 2023-11-04 13:05:42 +00:00
Daniel Imms bdcba308df Upload clipboard addon artifacts 2023-11-03 12:44:19 -07:00
Daniel Imms aed94e5317 xterm-addon-clipboard to scoped module 2023-11-03 11:45:21 -07:00
Daniel Imms a4e6713823 Merge remote-tracking branch 'upstream/master' into pr/aymanbagabas/4220 2023-11-03 11:35:23 -07:00
Ayman Bagabas 96fcfbded1 Merge branch 'master' into osc52 2023-10-20 13:47:45 -07:00
Ayman Bagabas 2a8ddcd508 Merge branch 'master' into osc52 2023-10-04 11:30:55 -07:00
Ayman Bagabas d245e90185 Add playwright terminal test 2023-09-21 12:09:30 -04:00
Ayman Bagabas 092aeaeb6f Fix rename clipboard selection for clarity 2023-09-21 10:33:44 -04:00
Ayman Bagabas b22762b472 Fix tsconfigs, IDisposable, and add comments 2023-09-21 10:26:46 -04:00
Ayman Bagabas db5bfc7525 Add support to ANSI OSC52
Add support to ANSI OSC52 sequence to manipulate selection and clipboard
data. The sequence specs supports multiple clipboard selections but we
only support the common ones, system and primary clipboard selections.

This adds a new event listener to the common terminal module
`onClipboard` to allow external implementations to hook into it.

The addon uses the browser Clipboard API to read/write from and to the
clipboard. The default `ClipboardProvider` uses the browser Clipboard
API. This means it only supports read/write to and from the system
clipboard.

Reference: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Operating-System-Commands
Fixes: xtermjs#3260
Signed-off-by: Ayman Bagabas <ayman.bagabas@gmail.com>
2023-09-20 17:46:52 -04:00
455 changed files with 52114 additions and 15133 deletions
+5 -3
View File
@@ -1,13 +1,15 @@
{
"name": "xterm.js",
"image": "mcr.microsoft.com/devcontainers/typescript-node:0-18-buster",
"image": "mcr.microsoft.com/devcontainers/typescript-node:22-bookworm",
"features": {
"ghcr.io/devcontainers/features/node:1": {} // yarn
"ghcr.io/devcontainers/features/node:1": {
"version": 22
} // yarn
},
"forwardPorts": [
3000
],
"postCreateCommand": "yarn install",
"postCreateCommand": "npm install && npm run setup",
"customizations": {
"vscode": {
"extensions": [
-1
View File
@@ -5,7 +5,6 @@ indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
end_of_line = lf
[*.{j,t}s]
max_line_length = 100
-264
View File
@@ -1,264 +0,0 @@
{
"env": {
"browser": true,
"es2021": true,
"node": true
},
"parser": "@typescript-eslint/parser",
"parserOptions": {
"project": [
"demo/tsconfig.json",
"src/browser/tsconfig.json",
"src/common/tsconfig.json",
"src/headless/tsconfig.json",
"test/api/tsconfig.json",
"test/benchmark/tsconfig.json",
"test/playwright/tsconfig.json",
"addons/addon-attach/src/tsconfig.json",
"addons/addon-attach/test/tsconfig.json",
"addons/addon-canvas/src/tsconfig.json",
"addons/addon-canvas/test/tsconfig.json",
"addons/addon-fit/src/tsconfig.json",
"addons/addon-fit/test/tsconfig.json",
"addons/addon-image/src/tsconfig.json",
"addons/addon-image/test/tsconfig.json",
"addons/addon-ligatures/src/tsconfig.json",
"addons/addon-search/src/tsconfig.json",
"addons/addon-search/test/tsconfig.json",
"addons/addon-serialize/src/tsconfig.json",
"addons/addon-serialize/test/tsconfig.json",
"addons/addon-serialize/benchmark/tsconfig.json",
"addons/addon-unicode11/src/tsconfig.json",
"addons/addon-unicode11/test/tsconfig.json",
"addons/addon-unicode-graphemes/src/tsconfig.json",
"addons/addon-unicode-graphemes/test/tsconfig.json",
"addons/addon-unicode-graphemes/benchmark/tsconfig.json",
"addons/addon-web-links/src/tsconfig.json",
"addons/addon-web-links/test/tsconfig.json",
"addons/addon-webgl/src/tsconfig.json",
"addons/addon-webgl/test/tsconfig.json"
],
"sourceType": "module"
},
"ignorePatterns": [
"addons/*/src/third-party/*.ts",
"out/*",
"out-test/*",
"**/inwasm-sdks/*",
"**/typings/*.d.ts",
"**/node_modules",
"**/*.js"
],
"plugins": [
"@typescript-eslint",
"jsdoc"
],
"rules": {
"no-extra-semi": "error",
"@typescript-eslint/array-type": [
"warn",
{
"default": "array",
"readonly": "generic"
}
],
"@typescript-eslint/consistent-type-assertions": "warn",
"@typescript-eslint/consistent-type-definitions": "warn",
"@typescript-eslint/explicit-function-return-type": [
"warn",
{
"allowExpressions": true
}
],
"@typescript-eslint/explicit-member-accessibility": [
"warn",
{
"accessibility": "explicit",
"overrides": {
"constructors": "off"
}
}
],
"@typescript-eslint/indent": [
"warn",
2
],
"@typescript-eslint/member-delimiter-style": [
"warn",
{
"multiline": {
"delimiter": "semi",
"requireLast": true
},
"singleline": {
"delimiter": "comma",
"requireLast": false
}
}
],
"@typescript-eslint/naming-convention": [
"warn",
{ "selector": "default", "format": ["camelCase"],
"filter": {
"regex": "^[a-z]",
"match": true
}
},
// variableLike
{ "selector": "variable", "format": ["camelCase", "UPPER_CASE"] },
{ "selector": "variable", "filter": "^I.+Service$", "format": ["PascalCase"], "prefix": ["I"] },
// memberLike
{ "selector": "memberLike", "modifiers": ["private"], "format": ["camelCase"], "leadingUnderscore": "require" },
{ "selector": "memberLike", "modifiers": ["protected"], "format": ["camelCase"], "leadingUnderscore": "require" },
{ "selector": "enumMember", "format": ["UPPER_CASE"] },
// memberLike - Allow enum-like objects to use UPPER_CASE
{ "selector": "property", "modifiers": ["public"], "format": ["camelCase", "UPPER_CASE"],
"filter": {
"regex": "^[a-z]",
"match": true
}
},
// restrict on* naming for events only
{ "selector": "method", "modifiers": ["public"], "format": ["camelCase", "UPPER_CASE"], "custom": {
"regex": "^on[A-Z].+",
"match": false
} },
{ "selector": "method", "modifiers": ["private"], "format": ["camelCase"], "leadingUnderscore": "require", "custom": {
"regex": "^on[A-Z].+",
"match": false
} },
{ "selector": "method", "modifiers": ["protected"], "format": ["camelCase"], "leadingUnderscore": "require", "custom": {
"regex": "^on[A-Z].+",
"match": false
} },
// typeLike
{ "selector": "typeLike", "format": ["PascalCase"] },
{ "selector": "interface", "format": ["PascalCase"], "prefix": ["I"] }
],
"@typescript-eslint/no-confusing-void-expression": [
"warn",
{ "ignoreArrowShorthand": true }
],
"@typescript-eslint/no-useless-constructor": "warn",
"@typescript-eslint/prefer-namespace-keyword": "warn",
"@typescript-eslint/type-annotation-spacing": "warn",
"@typescript-eslint/quotes": [
"warn",
"single",
{ "allowTemplateLiterals": true }
],
"@typescript-eslint/semi": [
"warn",
"always"
],
"comma-dangle": [
"warn",
{
"objects": "never",
"arrays": "never",
"functions": "never"
}
],
"curly": [
"warn",
"multi-line"
],
"eol-last": "warn",
"eqeqeq": [
"warn",
"always"
],
"jsdoc/check-alignment": 1,
"jsdoc/check-param-names": 1,
"jsdoc/no-multi-asterisks": 1,
"keyword-spacing": "warn",
"max-len": [
"warn",
{
"code": 1000, // Don't enforce for code
"comments": 100,
"ignoreTrailingComments": true,
"ignoreUrls": true,
"ignorePattern": "^ *((?<vt_comment>(//|\\*) @vt)|(?<table_comment>\\* \\| )|(?<commented_code>// ))"
}
],
"new-parens": "warn",
"no-duplicate-imports": "warn",
"no-else-return": [
"warn",
{
"allowElseIf": false
}
],
"no-eval": "warn",
"no-irregular-whitespace": "warn",
"no-restricted-imports": [
"warn",
{
"patterns": [
".*\\/out\\/.*"
]
}
],
"no-restricted-syntax": [
"warn",
{
"selector": "CallExpression[callee.name='requestAnimationFrame']",
"message": "The global requestAnimationFrame() should be avoided, call it on the parent window from ICoreBrowserService."
},
{
"selector": "CallExpression[callee.name='cancelAnimationFrame']",
"message": "The global cancelAnimationFrame() should be avoided, call it on the parent window from ICoreBrowserService."
},
{
"selector": "CallExpression > MemberExpression[object.name='window'][property.name='requestAnimationFrame']",
"message": "window.requestAnimationFrame() should be avoided, call it on the parent window from ICoreBrowserService."
},
{
"selector": "CallExpression > MemberExpression[object.name='window'][property.name='cancelAnimationFrame']",
"message": "window.cancelAnimationFrame() should be avoided, call it on the parent window from ICoreBrowserService."
},
{
"selector": "MemberExpression[object.name='window'][property.name='devicePixelRatio']",
"message": "window.devicePixelRatio should be avoided, get it from ICoreBrowserService."
}
],
"no-trailing-spaces": "warn",
"no-unsafe-finally": "warn",
"no-unused-vars": ["warn", {
"vars": "all",
"args": "none"
}],
"no-var": "warn",
"one-var": [
"warn",
"never"
],
"object-curly-spacing": [
"warn",
"always"
],
"prefer-const": "warn",
"spaced-comment": [
"warn",
"always",
{
"markers": ["/"],
"exceptions": ["-"]
}
]
},
"overrides": [
{
"files": [
"**/*.api.ts",
"**/*.test.ts"
],
"rules": {
"object-curly-spacing": "off",
"max-len": "off",
"no-unused-vars": "off"
}
}
]
}
-104
View File
@@ -1,104 +0,0 @@
{
"env": {
"browser": true,
"es2021": true,
"node": true
},
"parser": "@typescript-eslint/parser",
"plugins": [
"@typescript-eslint",
"jsdoc"
],
"rules": {
"no-extra-semi": "error",
"@typescript-eslint/array-type": [
"warn",
{
"default": "array",
"readonly": "generic"
}
],
"@typescript-eslint/explicit-function-return-type": [
"warn",
{
"allowExpressions": true
}
],
"@typescript-eslint/indent": [
"warn",
2
],
"@typescript-eslint/member-delimiter-style": [
"warn",
{
"multiline": {
"delimiter": "semi",
"requireLast": true
},
"singleline": {
"delimiter": "comma",
"requireLast": false
}
}
],
"@typescript-eslint/naming-convention": [
"warn",
{ "selector": "typeLike", "format": ["PascalCase"] },
{ "selector": "interface", "format": ["PascalCase"], "prefix": ["I"] }
],
"@typescript-eslint/prefer-namespace-keyword": "warn",
"@typescript-eslint/type-annotation-spacing": "warn",
"@typescript-eslint/quotes": [
"warn",
"single",
{ "allowTemplateLiterals": true }
],
"@typescript-eslint/semi": [
"warn",
"always"
],
"comma-dangle": [
"warn",
{
"objects": "never",
"arrays": "never",
"functions": "never"
}
],
"curly": [
"warn",
"multi-line"
],
"eol-last": "warn",
"eqeqeq": [
"warn",
"always"
],
"jsdoc/check-alignment": 1,
"jsdoc/check-param-names": 1,
"keyword-spacing": "warn",
"max-len": [
"warn",
{
"code": 1000, // Don't enforce for code
"comments": 80,
"ignoreUrls": true,
"ignorePattern": "^ *(?<ps_description>\\* Ps=)"
}
],
"no-irregular-whitespace": "warn",
"no-trailing-spaces": "warn",
"object-curly-spacing": [
"warn",
"always"
],
"spaced-comment": [
"warn",
"always",
{
"markers": ["/"],
"exceptions": ["-"]
}
]
}
}
+93
View File
@@ -0,0 +1,93 @@
# xterm.js Copilot Instructions
## Architecture Overview
**Core Structure**: xterm.js is a multi-target terminal emulator with three main distributions:
- `src/browser/`: Full-featured browser terminal with DOM rendering
- `src/headless/`: Server-side terminal for Node.js (no DOM)
- `src/common/`: Shared core logic (parsing, buffer management, terminal state)
**Key Classes**:
- `Terminal` (browser/headless): Public API wrapper
- `CoreTerminal` (common): Core terminal logic and state
- `CoreBrowserTerminal` (browser): Browser-specific terminal implementation
## Development Workflow
**Build System**:
```bash
npm run build && npm run esbuild # Build all TypeScript and bundle
```
**Testing**:
- Unit tests: `npm run test-unit` (Mocha)
- Unit tests filtering to file: `npm run test-unit -- **/fileName.ts
- Per-addon unit tests: `npm run test-unit addons/addon-image/out-esbuild/*.test.js`
- Integration tests: `npm run test-integration` (Playwright across Chrome/Firefox/WebKit)
- Integration tests by file: `npm run test-integration -- test/playwright/InputHandler.test.ts`. Never use grep to filter tests, it doesn't work
- Integration tests by addon: `npm run test-integration --suite=addon-search`. Suites always follow the format `addon-<something>`
- Lint changes: `npm run lint-changes` to lint only changed files, `npm run lint-changes-fix` to fix them
## Addon Development Pattern
All addons follow this structure:
```typescript
export class MyAddon implements ITerminalAddon {
activate(terminal: Terminal): void {
// Called when loaded via terminal.loadAddon()
// Register handlers, access terminal APIs
}
dispose(): void {
// Cleanup when addon is disposed
}
}
```
**Key Examples**:
- `addons/addon-fit/`: Terminal sizing
- `addons/addon-webgl/`: GPU-accelerated rendering
- `addons/addon-search/`: Text search functionality
## Project-Specific Conventions
**TypeScript Project Structure**: Uses TypeScript project references (`tsconfig.all.json`) for incremental builds across browser/headless/addons.
**API Design**:
- Browser and headless terminals share the same public API
- Proposed APIs require `allowProposedApi: true` option
- Constructor-only options (cols, rows) cannot be changed after instantiation
**Testing Utilities**: Use `TestUtils.ts` helpers:
- `openTerminal(ctx, options)` for setup
- `pollFor(page, fn, expectedValue)` for async assertions
- `writeSync(page, data)` for terminal input
## Common Patterns
**Parser Integration**: Register custom escape sequence handlers:
```typescript
terminal.parser.registerCsiHandler('m', params => {
// Handle SGR sequences
return true; // Handled
});
```
**Buffer Access**: Read terminal content via buffer API:
```typescript
const line = terminal.buffer.active.getLine(0);
const cell = line?.getCell(0);
```
**Events**: All terminals emit standard events (onData, onResize, onRender) plus platform-specific ones.
## Critical Implementation Details
- Terminal rendering uses either DOM or WebGL renderers
- Buffer lines are immutable; create new instances for modifications
- Character width handling supports Unicode 11+ and grapheme clustering
- Mouse events translate web events to terminal protocols (X10, VT200, etc.)
- Color theming supports both palette and true color modes
## Writing unit tests
- Unit tests live alongside the source code file of the thing it's testing with a .test.ts suffix.
@@ -0,0 +1,8 @@
---
applyTo: '**/*.test.ts'
---
When writing unit tests follow these rules:
- When writing unit tests for addons, always create a real xterm.js instance instead of mocking it.
- Prefer `assert.ok` over `assert.notStrictEqual` when checking something is undefined or not.
- Avoid comments as most tests should be self-documenting.
+98 -139
View File
@@ -12,44 +12,64 @@ jobs:
timeout-minutes: 10
steps:
- uses: actions/checkout@v3
- name: Use Node.js 18.x
- name: Use Node.js 22.x
uses: actions/setup-node@v3
with:
node-version: 18.x
cache: 'yarn'
node-version: 22.x
cache: 'npm'
- name: Install dependencies
run: yarn --frozen-lockfile
- name: Build
run: yarn setup
run: npm ci
- name: Setup and run tsc
run: npm run setup
- name: Esbuild
run: npm run esbuild
- name: Zip artifacts
run: |
zip -r compressed-build \
./lib/* \
./out/* \
./out-test/* \
./out-*/* \
./addons/addon-attach/lib/* \
./addons/addon-attach/out/* \
./addons/addon-attach/out-test/* \
./addons/addon-canvas/out/* \
./addons/addon-canvas/out-test/* \
./addons/addon-attach/out-*/* \
./addons/addon-clipboard/lib/* \
./addons/addon-clipboard/out/* \
./addons/addon-clipboard/out-*/* \
./addons/addon-fit/lib/* \
./addons/addon-fit/out/* \
./addons/addon-fit/out-test/* \
./addons/addon-fit/out-*/* \
./addons/addon-image/lib/* \
./addons/addon-image/out/* \
./addons/addon-image/out-test/* \
./addons/addon-image/out-*/* \
./addons/addon-ligatures/lib/* \
./addons/addon-ligatures/out/* \
./addons/addon-ligatures/out-test/* \
./addons/addon-ligatures/out-*/* \
./addons/addon-progress/lib/* \
./addons/addon-progress/out/* \
./addons/addon-progress/out-*/* \
./addons/addon-search/lib/* \
./addons/addon-search/out/* \
./addons/addon-search/out-test/* \
./addons/addon-search/out-*/* \
./addons/addon-serialize/lib/* \
./addons/addon-serialize/out/* \
./addons/addon-serialize/out-test/* \
./addons/addon-serialize/out-*/* \
./addons/addon-unicode11/lib/* \
./addons/addon-unicode11/out/* \
./addons/addon-unicode11/out-test/* \
./addons/addon-unicode11/out-*/* \
./addons/addon-unicode-graphemes/lib/* \
./addons/addon-unicode-graphemes/out/* \
./addons/addon-unicode-graphemes/out-test/* \
./addons/addon-unicode-graphemes/out-*/* \
./addons/addon-web-links/lib/* \
./addons/addon-web-links/out/* \
./addons/addon-web-links/out-test/* \
./addons/addon-web-links/out-*/* \
./addons/addon-web-fonts/lib/* \
./addons/addon-web-fonts/out/* \
./addons/addon-web-fonts/out-*/* \
./addons/addon-webgl/lib/* \
./addons/addon-webgl/out/* \
./addons/addon-webgl/out-test/*
./addons/addon-webgl/out-*st/*
- name: Upload artifacts
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: build-artifacts
path: compressed-build.zip
@@ -60,21 +80,20 @@ jobs:
timeout-minutes: 10
steps:
- uses: actions/checkout@v3
- name: Use Node.js 18.x
- name: Use Node.js 22.x
uses: actions/setup-node@v3
with:
node-version: 18.x
cache: 'yarn'
node-version: 22.x
cache: 'npm'
- name: Install dependencies
run: |
yarn --frozen-lockfile
yarn install-addons
npm ci
- name: Lint code
env:
NODE_OPTIONS: --max_old_space_size=4096
run: yarn lint
run: npm run lint
- name: Lint API
run: yarn lint-api
run: npm run lint-api
test-unit-coverage:
needs: build
@@ -82,16 +101,15 @@ jobs:
timeout-minutes: 10
steps:
- uses: actions/checkout@v3
- name: Use Node.js 18.x
- name: Use Node.js 22.x
uses: actions/setup-node@v3
with:
node-version: 18.x
cache: 'yarn'
node-version: 22.x
cache: 'npm'
- name: Install dependencies
run: |
yarn --frozen-lockfile
yarn install-addons
- uses: actions/download-artifact@v3
npm ci
- uses: actions/download-artifact@v4
with:
name: build-artifacts
- name: Unzip artifacts
@@ -105,16 +123,16 @@ jobs:
ls -R
- name: Unit test coverage
run: |
yarn test-unit-coverage --forbid-only
npm run test-unit-coverage --forbid-only
EXIT_CODE=$?
./node_modules/.bin/nyc report --reporter=cobertura
exit $EXIT_CODE
test-unit-parallel:
test-unit:
timeout-minutes: 20
strategy:
matrix:
node-version: [18]
node-version: [22]
runs-on: [ubuntu, macos, windows]
runs-on: ${{ matrix.runs-on }}-latest
steps:
@@ -123,17 +141,16 @@ jobs:
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}.x
cache: 'yarn'
cache: 'npm'
- name: Install dependencies
run: |
yarn --frozen-lockfile
yarn install-addons
npm ci
- name: Wait for build job
uses: NathanFirmo/wait-for-other-job@v1.1.1
with:
token: ${{ secrets.GITHUB_TOKEN }}
job: build
- uses: actions/download-artifact@v3
- uses: actions/download-artifact@v4
with:
name: build-artifacts
- name: Unzip artifacts
@@ -146,68 +163,26 @@ jobs:
fi
ls -R
- name: Unit tests
run: yarn test-unit --forbid-only
run: npm run test-unit --forbid-only
test-api-parallel:
test-integration:
timeout-minutes: 20
strategy:
matrix:
node-version: [18] # just one as integration tests are about testing in browser
runs-on: [ubuntu] # macos is flaky
browser: [chromium, firefox]
runs-on: ${{ matrix.runs-on }}-latest
steps:
- uses: actions/checkout@v3
- name: Use Node.js ${{ matrix.node-version }}.x
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}.x
cache: 'yarn'
- name: Install dependencies
run: |
yarn --frozen-lockfile
yarn install-addons
- name: Install playwright
run: npx playwright install
- name: Wait for build job
uses: NathanFirmo/wait-for-other-job@v1.1.1
with:
token: ${{ secrets.GITHUB_TOKEN }}
job: build
- uses: actions/download-artifact@v3
with:
name: build-artifacts
- name: Unzip artifacts
shell: bash
run: |
if [ "$RUNNER_OS" == "Windows" ]; then
pwsh -Command "7z x compressed-build.zip -aoa -o${{ github.workspace }}"
else
unzip -o compressed-build.zip
fi
ls -R
- name: Integration tests (${{ matrix.browser }})
run: yarn test-api-${{ matrix.browser }} --headless --forbid-only
test-playwright-parallel:
timeout-minutes: 20
strategy:
matrix:
node-version: [18] # just one as integration tests are about testing in browser
runs-on: [ubuntu] # macos is flaky
node-version: [22] # just one as integration tests are about testing in browser
runs-on: [ubuntu-22.04] # macos is flaky
browser: [chromium, firefox, webkit]
runs-on: ${{ matrix.runs-on }}-latest
runs-on: ${{ matrix.runs-on }}
steps:
- uses: actions/checkout@v3
- name: Use Node.js ${{ matrix.node-version }}.x
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}.x
cache: 'yarn'
cache: 'npm'
- name: Install dependencies
run: |
yarn --frozen-lockfile
yarn install-addons
npm ci
- name: Install playwright
run: npx playwright install --with-deps ${{ matrix.browser }}
- name: Wait for build job
@@ -215,7 +190,7 @@ jobs:
with:
token: ${{ secrets.GITHUB_TOKEN }}
job: build
- uses: actions/download-artifact@v3
- uses: actions/download-artifact@v4
with:
name: build-artifacts
- name: Unzip artifacts
@@ -227,72 +202,56 @@ jobs:
unzip -o compressed-build.zip
fi
ls -R
- name: Build demo
run: yarn build-demo
- name: Build demo client
run: npm run esbuild-demo-client
- name: Build demo server
run: npm run esbuild-demo-server
- name: Integration tests (core) # Tests use 50% workers to reduce flakiness
run: yarn test-playwright-${{ matrix.browser }} --workers=50% --forbid-only --suite=core
- name: Integration tests (addon-canvas)
run: yarn test-playwright-${{ matrix.browser }} --workers=50% --forbid-only --suite=addon-canvas
run: npm run test-integration-${{ matrix.browser }} --workers=50% --forbid-only --suite=core
- name: Integration tests (addon-attach)
run: npm run test-integration-${{ matrix.browser }} --workers=50% --forbid-only --suite=addon-attach
- name: Integration tests (addon-clipboard)
run: npm run test-integration-${{ matrix.browser }} --workers=50% --forbid-only --suite=addon-clipboard
- name: Integration tests (addon-fit)
run: npm run test-integration-${{ matrix.browser }} --workers=50% --forbid-only --suite=addon-fit
- name: Integration tests (addon-image)
run: npm run test-integration-${{ matrix.browser }} --workers=50% --forbid-only --suite=addon-image
- name: Integration tests (addon-progress)
run: npm run test-integration-${{ matrix.browser }} --workers=50% --forbid-only --suite=addon-progress
- name: Integration tests (addon-search)
run: npm run test-integration-${{ matrix.browser }} --workers=50% --forbid-only --suite=addon-search
- name: Integration tests (addon-serialize)
run: npm run test-integration-${{ matrix.browser }} --workers=50% --forbid-only --suite=addon-serialize
- name: Integration tests (addon-unicode-graphemes)
run: npm run test-integration-${{ matrix.browser }} --workers=50% --forbid-only --suite=addon-unicode-graphemes
- name: Integration tests (addon-unicode11)
run: npm run test-integration-${{ matrix.browser }} --workers=50% --forbid-only --suite=addon-unicode11
- name: Integration tests (addon-web-fonts)
run: npm run test-integration-${{ matrix.browser }} --workers=50% --forbid-only --suite=addon-web-fonts
- name: Integration tests (addon-web-links)
run: npm run test-integration-${{ matrix.browser }} --workers=50% --forbid-only --suite=addon-web-links
- name: Integration tests (addon-webgl)
run: yarn test-playwright-${{ matrix.browser }} --workers=50% --forbid-only --suite=addon-webgl
test-api:
needs: build
timeout-minutes: 20
strategy:
matrix:
node-version: [18] # just one as integration tests are about testing in browser
runs-on: [windows] # macos is flaky
browser: [chromium, firefox]
runs-on: ${{ matrix.runs-on }}-latest
steps:
- uses: actions/checkout@v3
- name: Use Node.js ${{ matrix.node-version }}.x
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}.x
cache: 'yarn'
- name: Install dependencies
run: |
yarn --frozen-lockfile
yarn install-addons
- name: Install playwright
run: npx playwright install
- uses: actions/download-artifact@v3
with:
name: build-artifacts
- name: Unzip artifacts
shell: bash
run: |
if [ "$RUNNER_OS" == "Windows" ]; then
pwsh -Command "7z x compressed-build.zip -aoa -o${{ github.workspace }}"
else
unzip -o compressed-build.zip
fi
ls -R
- name: Integration tests (${{ matrix.browser }})
run: yarn test-api-${{ matrix.browser }} --headless --forbid-only
run: npm run test-integration-${{ matrix.browser }} --workers=50% --forbid-only --suite=addon-webgl
release-dry-run:
needs: build
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [18]
node-version: [22]
steps:
- uses: actions/checkout@v3
- name: Use Node.js ${{ matrix.node-version }}.x
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}.x
cache: 'yarn'
cache: 'npm'
- name: Install dependencies
run: |
yarn --frozen-lockfile
yarn install-addons
npm ci
- name: Install playwright
run: npx playwright install
- uses: actions/download-artifact@v3
- uses: actions/download-artifact@v4
with:
name: build-artifacts
- name: Unzip artifacts
@@ -306,7 +265,7 @@ jobs:
ls -R
- name: Package headless
run: |
yarn package-headless
npm run package-headless
node ./bin/package_headless.js
- name: Publish to npm (dry run)
run: node ./bin/publish.js --dry
+32
View File
@@ -0,0 +1,32 @@
name: "Copilot Setup Steps"
on:
workflow_dispatch:
push:
paths:
- .github/workflows/copilot-setup-steps.yml
pull_request:
paths:
- .github/workflows/copilot-setup-steps.yml
jobs:
copilot-setup-steps:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Use Node.js 22.x
uses: actions/setup-node@v3
with:
node-version: 22.x
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Setup and run tsc
run: npm run setup
- name: Esbuild
run: npm run esbuild
+6 -6
View File
@@ -11,18 +11,18 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Use Node.js 18.x
- name: Use Node.js 22.x
uses: actions/setup-node@v3
with:
node-version: 18.x
cache: 'yarn'
node-version: 22.x
cache: 'npm'
- name: Install dependencies
run: yarn --frozen-lockfile
run: npm ci
- name: Build
run: yarn setup
run: npm run setup
- name: Package headless
run: |
yarn package-headless
npm run package-headless
node ./bin/package_headless.js
- name: Publish to npm
env:
+5 -1
View File
@@ -3,7 +3,10 @@ node_modules/
.lock-wscript
lib/
out/
out-demo/
out-test/
out-esbuild/
out-esbuild-test/
.nyc_output/
Makefile.gyp
*.Makefile
@@ -16,7 +19,8 @@ npm-debug.log
.env
build/
.DS_Store
package-lock.json
yarn.lock
test-results/
# Keep bundled code out of Git
dist/
-7
View File
@@ -1,7 +0,0 @@
Antonis Kalipetis <akalipetis@sourcelair.com>
Antonis Kalipetis <akalipetis@sourcelair.com> <akalipetis@gmail.com>
Daniel Imms <daimms@microsoft.com> <tyriar@tyriar.com>
Paris Kasidiaris <paris@sourcelair.com>
Paris Kasidiaris <pariskasidiaris@gmail.com>
Thanasis Daglis <thanasis@sourcelair.com>
Thanasis Daglis <thanasis@sourcelair.com> <ath.daglis@gmail.com>
-1
View File
@@ -1 +0,0 @@
NODE_PATH=./out
-5
View File
@@ -1,5 +0,0 @@
require:
- source-map-support/register
reporter: spec
color: true
check-leaks: true
+3
View File
@@ -17,6 +17,9 @@
!lib/**/*.js
!lib/**/*.js.map
!lib/**/*.mjs
!lib/**/*.mjs.map
!lib/**/*.css
# Whitelist - src/
-1
View File
@@ -1 +0,0 @@
package-lock=false
+1 -1
View File
@@ -1 +1 @@
16
22
+5
View File
@@ -0,0 +1,5 @@
{
"recommendations": [
"dbaeumer.vscode-eslint"
]
}
+1 -1
View File
@@ -61,7 +61,7 @@
"runtimeExecutable": "npm",
"runtimeArgs": ["start"],
"stopOnEntry": true,
"runtimeVersion": "16",
"runtimeVersion": "22",
"serverReadyAction": {
"action": "openExternally",
"pattern": "App listening to (http://.*?:[0-9]+)"
+1 -13
View File
@@ -1,16 +1,4 @@
{
"files.associations": {
".eslintrc.json.typings": "jsonc"
},
"typescript.preferences.importModuleSpecifier": "non-relative",
"typescript.preferences.quoteStyle": "single",
"mochaExplorer.envPath": ".mocha.env",
"mochaExplorer.files": [
"out/**/*.test.js",
"addons/**/out/*.test.js"
],
"mochaExplorer.watch": [
"out/**/*.js",
"addons/**/out/*.js"
]
"typescript.preferences.quoteStyle": "single"
}
+81 -53
View File
@@ -8,6 +8,87 @@
"showReuseMessage": true
},
"tasks": [
// Compound tasks
{
"label": "dev",
"detail": "Runs all tasks required to run the demo in a single terminal using concurrently. This does not support problem matching.",
"type": "npm",
"script": "dev",
"isBackground": true,
"group": {
"kind": "build",
"isDefault": true
}
},
{
"label": "dev (separate terminals)",
"detail": "Runs all tasks required to run the demo in separate terminals. This does support problem matching.",
"dependsOn": ["demo-server", "tsc", "esbuild", "esbuild-demo-client", "esbuild-demo-server"],
"group": "build"
},
// Demo
{
"label": "demo-server",
"type": "npm",
"script": "start",
"group": "build",
"isBackground": true,
"problemMatcher": [],
"presentation": {
"group": "xterm-demo"
}
},
// Build
{
"label": "tsc",
"type": "npm",
"script": "tsc-watch",
"group": "build",
"isBackground": true,
"problemMatcher": "$tsc-watch",
"presentation": {
"group": "xterm-build"
}
},
{
"label": "esbuild",
"type": "npm",
"script": "esbuild-watch",
"group": "build",
"isBackground": true,
"problemMatcher": "$esbuild-watch",
"presentation": {
"group": "xterm-build"
}
},
{
"label": "esbuild-demo-client",
"type": "npm",
"script": "esbuild-demo-client-watch",
"dependsOn": ["esbuild", "tsc"],
"group": "build",
"isBackground": true,
"problemMatcher": "$esbuild-watch",
"presentation": {
"group": "xterm-demo"
}
},
{
"label": "esbuild-demo-server",
"type": "npm",
"script": "esbuild-demo-server-watch",
"dependsOn": ["esbuild", "tsc"],
"group": "build",
"isBackground": true,
"problemMatcher": "$esbuild-watch",
"presentation": {
"group": "xterm-demo"
}
},
// Test
{
"type": "npm",
"script": "test",
@@ -16,59 +97,6 @@
"isDefault": true
},
"problemMatcher": []
},
{
"label": "watch",
"type": "npm",
"script": "watch",
"group": "build",
"isBackground": true,
"problemMatcher": "$tsc-watch",
"presentation": {
"group": "vscode"
}
},
{
"label": "start",
"type": "npm",
"script": "start",
"group": "build",
"isBackground": true,
"problemMatcher": [],
"presentation": {
"group": "vscode"
}
},
{
"label": "Start demo",
"dependsOn": ["start", "watch"],
"group": {
"kind": "build",
"isDefault": true
},
"isBackground": true,
"problemMatcher": {
"owner": "typescript",
"fileLocation": [
"relative",
"${workspaceFolder}"
],
"pattern": [
{
"regexp": "^([^\\\\s].*)\\\\((\\\\d+,\\\\d+)\\\\):\\\\s*(.*)$",
"file": 1,
"location": 2,
"message": 3
}
],
"background": {
"beginsPattern": "assets by",
"endsPattern": "webpack \\d+\\.\\d+\\.\\d+ compiled successfully"
}
},
"presentation": {
"group": "vscode"
}
}
]
}
+35 -47
View File
@@ -1,62 +1,50 @@
# How to contribute to xterm.js
- [Opening issues for bug reports or feature requests](#opening-issues)
- [Contributing code](#contributing-code)
- [Opening issues](#opening-issues)
- [Answering discussion questions](#answering-discussion-questions)
## Contributing code
You can find issues to work on by looking at issues labeled with [help wanted](https://github.com/xtermjs/xterm.js/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22) or [good first issue](https://github.com/xtermjs/xterm.js/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22). It's a good idea to comment on the issue saying that you're looking into it, just in case someone else comes along and you duplicate work. Once you have your issue, here are the steps to contribute:
- Fork [xterm.js](https://github.com/xtermjs/xterm.js/) ([how to fork a repo](https://help.github.com/articles/fork-a-repo)).
- Get the [xterm.js demo](https://github.com/xtermjs/xterm.js/wiki/Contributing#running-the-demo) running.
- Make the fix and verify it works in the demo. Be sure to follow thew general code style of the rest of the project.
- If your changes are easy to test or likely to regress in the future, add tests. These could be unit or integration (playwright) tests.
- Submit a pull request ([how to create a pull request](https://help.github.com/articles/fork-a-repo)).
> ![TIP]
> Don't put more than one feature or fix in a single pull request. The smaller pull requests are the easier they are to review and merge.
By contributing code to xterm.js you:
- Agree to license the contributed code under xterm.js' [MIT license](LICENSE).
- Confirm that you have the right to contribute and license the code in question. This means that either you hold all rights on the code, or the rights holder has explicitly granted the right to use it like this, through a compatible open source license or through a direct agreement with you.
## Opening issues
The preferred way to report bugs or request features is to use
[GitHub issues](http://github.com/sourcelair/xterm.js/issues). Before
opening an issue, read these pointers.
[GitHub issues](http://github.com/xtermjs/xterm.js/issues).
### Opening issues effectively
### Creating great issues
- Include information about **the browser in which the problem occurred**. Even
if you tested several browsers, and the problem occurred in all of them,
mention this fact in the bug report. Also include browser version numbers and
the operating system that you're on.
- Include information about **the browser in which the problem occurred** or the terminal being used. If you tested several browsers and the problem occurred in all of them, mention this fact in the bug report. Also include browser version numbers and the operating system that you're on.
- Include the version of xterm.js being used, preferably either with the latest `beta` tagged release on npm or reproducing in the demo on the `master` branch.
- Mention precisely what went wrong. What did you expect to happen? What happened instead? Describe the exact steps a maintainer has to take to make the problem occur.
- If the problem can not be reproduced in the [demo of xterm.js](https://github.com/xtermjs/xterm.js/wiki/Contributing#running-the-demo), provide an HTML document that demonstrates the problem.
- Be polite and follow [the code of conduct](https://github.com/xtermjs/xterm.js?tab=coc-ov-file#readme).
- Mention which release of xterm.js you're using. Preferably, try also with
the current HEAD of the master branch, to ensure the problem has not already been
fixed.
### Issue triaging philosophy
- Mention precisely what went wrong. What did you expect to happen? What happened instead? Describe the
exact steps a maintainer has to take to make the problem occur.
It's pretty common for maintainers of large open source projects to suffer from burnout, especially when needing to triage a large number of incoming issues instead of actually building things. Here are some of the steps we take to try mitigate this:
- If the problem can not be reproduced in the [demo of xterm.js](https://github.com/xtermjs/xterm.js/wiki/Contributing#running-the-demo), please provide an HTML document that demonstrates the problem.
- Be polite. Issues with an indignant or belligerent tone tend to be moved to the
bottom of the pile.
- Support questions live in [GH discussions](https://github.com/xtermjs/xterm.js/discussions), issues may be transfered there without further comment and core maintainers may or may not participate in discussions.
- Issues are strictly for well defined features or bugs that are _actionable_.
- Sometimes features are out of scope. A common example of this is a niche feature that the pricipal implementation ([VS Code](https://code.visualstudio.com/)) won't leverage and therefore would be difficult to maintain and likely suffer from bitrot. The reporter may not agree with this, but you could always create an addon if that works or maintain your own fork if it comes to that.
- If a feature does not have a clear way forward or needs more discussion it may be closed ro moved to a discussion.
- If a bug is not easily reproducible it may be closed or moved to a discussion. Generally issues that are labeled are something we want to do or has actionable steps to look into further.
## Answering discussion questions
Issues are only meant to track (likely) feature requests and bugs. We use [GitHub Discussions](https://github.com/xtermjs/xterm.js/discussions) for general Q&A as well as discussing possible features. If you want to help out, many questions get asked over at the [discussions page](https://github.com/xtermjs/xterm.js/discussions) which could use an expert as the core team is often stretched thin.
## Contributing code
You can find issues to work on by looking at the [help wanted](https://github.com/xtermjs/xterm.js/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22) or [good first issue](https://github.com/xtermjs/xterm.js/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) issues. It's a good idea to comment on the issue saying that you're taking it, just in case someone else comes along and you duplicate work. Once you have your issue, here are the steps to contribute:
- Fork [xterm.js](https://github.com/sourcelair/xterm.js/)
([how to fork a repo](https://help.github.com/articles/fork-a-repo)).
- Get the [xterm.js demo](https://github.com/xtermjs/xterm.js/wiki/Contributing#running-the-demo) running.
- Make your changes.
- If your changes are easy to test or likely to regress, add tests. Tests go into `test`, directory.
- Follow the general code style of the rest of the project (see below).
- Submit a pull request
([how to create a pull request](https://help.github.com/articles/fork-a-repo)).
Don't put more than one feature/fix in a single pull request.
By contributing code to xterm.js you:
- Agree to license the contributed code under xterm.js' [MIT
license](LICENSE).
- Confirm that you have the right to contribute and license the code
in question. (Either you hold all rights on the code, or the rights
holder has explicitly granted the right to use it like this,
through a compatible open source license or through a direct
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.
Issues are only meant to track (likely) feature requests and bugs. We use [GitHub Discussions](https://github.com/xtermjs/xterm.js/discussions) for general Q&A as well as discussing possible features. If you want to help out, many questions get asked over at the [discussions page](https://github.com/xtermjs/xterm.js/discussions) which could use an expert as the core team is often stretched thin.
-2
View File
@@ -1,2 +0,0 @@
web: npm start
webpack: npm run watch
+37 -30
View File
@@ -1,27 +1,27 @@
# [![xterm.js logo](logo-full.png)](https://xtermjs.org)
# [![xterm.js](images/logo-full.png)](https://xtermjs.org)
Xterm.js is a front-end component written in TypeScript that lets applications bring fully-featured terminals to their users in the browser. It's used by popular projects such as VS Code, Hyper and Theia.
Xterm.js is a frontend component that enables applications to bring fully-featured terminals to their users in the browser. It's used by popular projects such as [VS Code](https://code.visualstudio.com/) (and its forks), [Tabby](https://tabby.sh/) and [Hyper](https://hyper.is/).
## Features
- **Terminal apps just work**: Xterm.js works with most terminal apps such as `bash`, `vim`, and `tmux`, including support for curses-based apps and mouse events.
- **Performant**: Xterm.js is *really* fast, it even includes a GPU-accelerated renderer.
- **Performant**: Xterm.js is *really* fast and includes an optional GPU-accelerated renderer.
- **Rich Unicode support**: Supports CJK, emojis, and IMEs.
- **Self-contained**: Requires zero dependencies to work.
- **Accessible**: Screen reader and minimum contrast ratio support can be turned on.
- **And much more**: Links, theming, addons, well documented API, etc.
- **Self-contained**: The core library has zero dependencies.
- **Accessible**: Screen reader mode and minimum contrast ratio support can be turned on.
- **And much more**: Links, theming, custom glyphs, addons, well documented API, etc.
## What xterm.js is not
- Xterm.js is not a terminal application that you can download and use on your computer.
- Xterm.js is not `bash`. Xterm.js can be connected to processes like `bash` and let you interact with them (provide input, receive output).
- Xterm.js is not `bash`. Xterm.js can be connected to processes like `bash` and let you interact with them (provide input, receive output) through a library like [node-pty](https://github.com/microsoft/node-pty).
## Getting Started
First, you need to install the module, we ship exclusively through [npm](https://www.npmjs.com/), so you need that installed and then add xterm.js as a dependency by running:
First, you need to install the module. We ship exclusively through [npm](https://www.npmjs.com), so you need that installed and then add [@xterm/xterm](https://www.npmjs.com/package/@xterm/xterm) as a dependency by running:
```bash
npm install @xterm/xterm
npm install --save @xterm/xterm
```
To start using xterm.js on your browser, add the `xterm.js` and `xterm.css` to the head of your HTML page. Then create a `<div id="terminal"></div>` onto which xterm can attach itself. Finally, instantiate the `Terminal` object and then call the `open` function with the DOM object of the `div`.
@@ -54,12 +54,10 @@ import { Terminal } from '@xterm/xterm';
### Addons
⚠️ *This section describes the new addon format introduced in v3.14.0, see [here](https://github.com/xtermjs/xterm.js/blob/3.14.2/README.md#addons) for the instructions on the old format*
Addons are separate modules that extend the `Terminal` by building on the [xterm.js API](https://github.com/xtermjs/xterm.js/blob/master/typings/xterm.d.ts). To use an addon, you first need to install it in your project:
```bash
npm i -S @xterm/addon-web-links
npm install --save @xterm/addon-web-links
```
Then import the addon, instantiate it and call `Terminal.loadAddon`:
@@ -77,58 +75,58 @@ terminal.loadAddon(new WebLinksAddon());
The xterm.js team maintains the following addons, but anyone can build them:
- [`@xterm/addon-attach`](https://github.com/xtermjs/xterm.js/tree/master/addons/addon-attach): Attaches to a server running a process via a websocket
- [`@xterm/addon-canvas`](https://github.com/xtermjs/xterm.js/tree/master/addons/addon-canvas): Renders xterm.js using a `canvas` element's 2d context
- [`@xterm/addon-clipboard`](https://github.com/xtermjs/xterm.js/tree/master/addons/addon-clipboard): Access the browser's clipboard
- [`@xterm/addon-fit`](https://github.com/xtermjs/xterm.js/tree/master/addons/addon-fit): Fits the terminal to the containing element
- [`@xterm/addon-image`](https://github.com/xtermjs/xterm.js/tree/master/addons/addon-image): Adds image support
- [`@xterm/addon-ligatures`](https://github.com/xtermjs/xterm.js/tree/master/addons/addon-ligatures): Enables rendering of ligatures
- [`@xterm/addon-progress`](https://github.com/xtermjs/xterm.js/tree/master/addons/addon-progress): Adds support for the progress API (`OSC 9;4`)
- [`@xterm/addon-search`](https://github.com/xtermjs/xterm.js/tree/master/addons/addon-search): Adds search functionality
- [`@xterm/addon-serialize`](https://github.com/xtermjs/xterm.js/tree/master/addons/addon-serialize): Serializes the terminal's buffer to a VT sequences or HTML
- [`@xterm/addon-serialize`](https://github.com/xtermjs/xterm.js/tree/master/addons/addon-serialize): Serializes the terminal's buffer to VT sequences or HTML
- [`@xterm/addon-unicode-graphemes`](https://github.com/xtermjs/xterm.js/tree/master/addons/addon-unicode-graphemes): Enhanced unicode support including grapheme clustering (experimental)
- [`@xterm/addon-unicode11`](https://github.com/xtermjs/xterm.js/tree/master/addons/addon-unicode11): Updates character widths to their unicode11 values
- [`@xterm/addon-web-fonts`](https://github.com/xtermjs/xterm.js/tree/master/addons/addon-web-fonts): Easily integrate web fonts
- [`@xterm/addon-web-links`](https://github.com/xtermjs/xterm.js/tree/master/addons/addon-web-links): Adds web link detection and interaction
- [`@xterm/addon-webgl`](https://github.com/xtermjs/xterm.js/tree/master/addons/addon-webgl): Renders xterm.js using a `canvas` element's webgl2 context
## Browser Support
Since xterm.js is typically implemented as a developer tool, only modern browsers are supported officially. Specifically the latest versions of *Chrome*, *Edge*, *Firefox*, and *Safari*.
Xterm.js works seamlessly in [Electron](https://electronjs.org/) apps and may even work on earlier versions of the browsers. These are the versions we strive to keep working.
Since xterm.js is typically implemented as a developer tool, generally only modern evergreen browsers are supported officially. Specifically the latest versions of *Chrome*, *Edge*, *Firefox*, and *Safari*. Xterm.js also works seamlessly in [Electron](https://electronjs.org/) apps and may even work on earlier versions of the browsers. These are the versions we strive to keep working.
### Node.js Support
We also publish [`xterm-headless`](https://www.npmjs.com/package/xterm-headless) which is a stripped down version of xterm.js that runs in Node.js. An example use case for this is to keep track of a terminal's state where the process is running and using the serialize addon so it can get all state restored upon reconnection.
We also publish [`xterm-headless`](https://www.npmjs.com/package/xterm-headless) which is a stripped down version of xterm.js that runs headless in Node.js. An example use case for this is to keep track of a terminal's state where the process is running and using the [serialize addon](https://www.npmjs.com/package/@xterm/addon-serialize) so it can get all state restored upon reconnection.
## API
The full API for xterm.js is contained within the [TypeScript declaration file](https://github.com/xtermjs/xterm.js/blob/master/typings/xterm.d.ts), use the branch/tag picker in GitHub (`w`) to navigate to the correct version of the API.
Note that some APIs are marked *experimental*, these are added to enable experimentation with new ideas without committing to support it like a normal [semver](https://semver.org/) API. Note that these APIs can change radically between versions, so be sure to read release notes if you plan on using experimental APIs.
Some APIs may be marked with *experimental*, these are added to enable experimentation with new ideas without committing to support it like a normal [semver](https://semver.org/) API. Note that these APIs can change radically between versions, so be sure to read release notes if you plan on using experimental APIs.
## Releases
Xterm.js follows a monthly release cycle roughly.
All current and past releases are available on this repo's [Releases page](https://github.com/sourcelair/xterm.js/releases), you can view the [high-level roadmap on the wiki](https://github.com/xtermjs/xterm.js/wiki/Roadmap) and see what we're working on now by looking through [Milestones](https://github.com/sourcelair/xterm.js/milestones).
Stable releases are done on an as needed basis. All current and past releases are available on this repo's [releases page](https://github.com/sourcelair/xterm.js/releases), you can see what's planned for upcoming releases looking through the repository [milestones](https://github.com/sourcelair/xterm.js/milestones).
### Beta builds
Our CI releases beta builds to npm for every change that goes into master. Install the latest beta build with:
Beta releases are continuously published off the `master` branch. Install the latest beta build with:
```bash
npm install -S @xterm/xterm@beta
npm install --save @xterm/xterm@beta
```
These should generally be stable, but some bugs may slip in. We recommend using the beta build primarily to test out new features and to verify bug fixes.
The principal implementation (VS Code) typically uses the latest or near the latest beta build. Generally they are quite stable but can potentially contain bugs or breaking changes. If stability is very important we recommend using the beta build primarily to test out new features and to verify bug fixes, unless you're tracking what's landing and are comfortable taking that risk.
## Contributing
You can read the [guide on the wiki](https://github.com/xtermjs/xterm.js/wiki/Contributing) to learn how to contribute and set up xterm.js for development.
Read [CONTRIBUTING.md](https://github.com/xtermjs/xterm.js/blob/master/CONTRIBUTING.md) to learn how to contribute to the project.
## Real-world uses
Xterm.js is used in several world-class applications to provide great terminal experiences.
Xterm.js is used in many world-class applications to provide great terminal experiences.
- [**SourceLair**](https://www.sourcelair.com/): In-browser IDE that provides its users with fully-featured Linux terminals based on xterm.js.
- [**Microsoft Visual Studio Code**](http://code.visualstudio.com/): Modern, versatile, and powerful open source code editor that provides an integrated terminal based on xterm.js.
- [**ttyd**](https://github.com/tsl0922/ttyd): A command-line tool for sharing terminal over the web, with fully-featured terminal emulation based on xterm.js.
- [**Katacoda**](https://www.katacoda.com/): Katacoda is an Interactive Learning Platform for software developers, covering the latest Cloud Native technologies.
- [**Eclipse Che**](http://www.eclipse.org/che): Developer workspace server, cloud IDE, and Eclipse next-generation IDE.
- [**Codenvy**](http://www.codenvy.com): Cloud workspaces for development teams.
- [**CoderPad**](https://coderpad.io): Online interviewing platform for programmers. Run code in many programming languages, with results displayed by xterm.js.
@@ -222,14 +220,23 @@ Xterm.js is used in several world-class applications to provide great terminal e
- [**Cloudtutor.io**](https://cloudtutor.io): innovative online learning platform that offers users access to an interactive lab.
- [**Helix Editor Playground**](https://github.com/tomgroenwoldt/helix-editor-playground): Online playground for the terminal based helix editor.
- [**Coder**](https://github.com/coder/coder): Self-Hosted Remote Development Environments
- [**Wave Terminal**](https://waveterm.dev): An open-source, ai-native, terminal built for seamless workflows.
- [**eva**](https://github.com/info24/eva): Eva is a web application for SSH remote login, developed in Go.
- [**OpenSFTP**](https://opensftp.com): Super beautiful SSH and SFTP integrated workspace client.
- [**balena**](https://www.balena.io/): Balena is a full-stack solution for developing, deploying, updating, and troubleshooting IoT Edge devices. We use xterm.js to manage & debug devices on [balenaCloud](https://www.balena.io/cloud).
- [**Filet Cloud**](https://github.com/fuglaro/filet-cloud): The lean and powerful personal cloud ⛅.
- [**pyTermTk**](https://github.com/ceccopierangiolieugenio/pyTermTk): Python Terminal Toolkit - a Spiced Up Cross Compatible TUI Library 🌶️, use xterm.js for the [HTML5 exporter](https://ceccopierangiolieugenio.github.io/pyTermTk/sandbox/sandbox.html).
- [**ecmaOS**](https://ecmaos.sh): A kernel and suite of applications tying modern web technologies into a browser-based operating system.
- [**LabEx**](https://labex.io): Interactive learning platform with hands-on labs and xterm.js-based online terminals, focused on learn-by-doing approach.
- [**EmuDevz**](https://afska.github.io/emudevz): A free coding game where players learn how to build an emulator from scratch.
- [And much more...](https://github.com/xtermjs/xterm.js/network/dependents?package_id=UGFja2FnZS0xNjYzMjc4OQ%3D%3D)
Do you use xterm.js in your application as well? Please [open a Pull Request](https://github.com/sourcelair/xterm.js/pulls) to include it here. We would love to have it on our list. Note: Please add any new contributions to the end of the list only.
Do you use xterm.js in your application as well? Please [open a Pull Request](https://github.com/sourcelair/xterm.js/pulls) to include it here. We would love to have it on our list. Please add any new contributions to the end of the list.
## License Agreement
If you contribute code to this project, you implicitly allow your code to be distributed under the MIT license. You are also implicitly verifying that all code is your original work.
Copyright (c) 2017-2022, [The xterm.js authors](https://github.com/xtermjs/xterm.js/graphs/contributors) (MIT License)<br>
Copyright (c) 2017-2026, [The xterm.js authors](https://github.com/xtermjs/xterm.js/graphs/contributors) (MIT License)<br>
Copyright (c) 2014-2017, SourceLair, Private Company ([www.sourcelair.com](https://www.sourcelair.com/home)) (MIT License)<br>
Copyright (c) 2012-2013, Christopher Jeffrey (MIT License)
+3
View File
@@ -8,6 +8,9 @@
!lib/**/*.js
!lib/**/*.js.map
!lib/**/*.mjs
!lib/**/*.mjs.map
!lib/**/*.css
# Whitelist - src/
+4 -5
View File
@@ -1,11 +1,12 @@
{
"name": "@xterm/addon-attach",
"version": "0.11.0",
"version": "0.12.0",
"author": {
"name": "The xterm.js authors",
"url": "https://xtermjs.org/"
},
"main": "lib/addon-attach.js",
"module": "lib/addon-attach.mjs",
"types": "typings/addon-attach.d.ts",
"repository": "https://github.com/xtermjs/xterm.js/tree/master/addons/addon-attach",
"license": "MIT",
@@ -18,9 +19,7 @@
"build": "../../node_modules/.bin/tsc -p .",
"prepackage": "npm run build",
"package": "../../node_modules/.bin/webpack",
"prepublishOnly": "npm run package"
},
"peerDependencies": {
"@xterm/xterm": "^5.0.0"
"prepublishOnly": "npm run package",
"start": "node ../../demo/start"
}
}
@@ -1,50 +0,0 @@
/**
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
* @license MIT
*/
import WebSocket = require('ws');
import { openTerminal, pollFor, launchBrowser } from '../../../out-test/api/TestUtils';
import { Browser, Page } from '@playwright/test';
const APP = 'http://127.0.0.1:3001/test';
let browser: Browser;
let page: Page;
const width = 800;
const height = 600;
describe('AttachAddon', () => {
before(async function(): Promise<any> {
browser = await launchBrowser();
page = await (await browser.newContext()).newPage();
await page.setViewportSize({ width, height });
});
after(async () => {
await browser.close();
});
beforeEach(async () => await page.goto(APP));
it('string', async function(): Promise<any> {
await openTerminal(page);
const port = 8080;
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}')))`);
await pollFor(page, `window.term.buffer.active.getLine(0).translateToString(true)`, 'foo');
server.close();
});
it('utf8', async function(): Promise<any> {
await openTerminal(page);
const port = 8080;
const server = new WebSocket.Server({ port });
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}')))`);
await pollFor(page, `window.term.buffer.active.getLine(0).translateToString(true)`, 'foo');
server.close();
});
});
@@ -0,0 +1,42 @@
/**
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
* @license MIT
*/
import WebSocket = require('ws');
import test from '@playwright/test';
import { ITestContext, createTestContext, openTerminal, pollFor, timeout } from '../../../test/playwright/TestUtils';
let ctx: ITestContext;
test.beforeAll(async ({ browser }) => {
ctx = await createTestContext(browser);
await openTerminal(ctx);
});
test.afterAll(async () => await ctx.page.close());
test.describe('Search Tests', () => {
test.beforeEach(async () => {
await ctx.proxy.reset();
});
test('string', async function(): Promise<any> {
const port = 8080;
const server = new WebSocket.Server({ port });
server.on('connection', socket => socket.send('foo'));
await ctx.page.evaluate(`window.term.loadAddon(new window.AttachAddon(new WebSocket('ws://localhost:${port}')))`);
await pollFor(ctx.page, `window.term.buffer.active.getLine(0).translateToString(true)`, 'foo');
server.close();
});
test('utf8', async function(): Promise<any> {
const port = 8080;
const server = new WebSocket.Server({ port });
const data = new Uint8Array([102, 111, 111]);
server.on('connection', socket => socket.send(data));
await ctx.page.evaluate(`window.term.loadAddon(new window.AttachAddon(new WebSocket('ws://localhost:${port}')))`);
await pollFor(ctx.page, `window.term.buffer.active.getLine(0).translateToString(true)`, 'foo');
server.close();
});
});
@@ -5,14 +5,14 @@ const config: PlaywrightTestConfig = {
timeout: 10000,
projects: [
{
name: 'Chrome Stable',
name: 'ChromeStable',
use: {
browserName: 'chromium',
channel: 'chrome'
}
},
{
name: 'Firefox Stable',
name: 'FirefoxStable',
use: {
browserName: 'firefox'
}
@@ -26,7 +26,7 @@ const config: PlaywrightTestConfig = {
],
reporter: 'list',
webServer: {
command: 'npm run start-server-only',
command: 'npm run start',
port: 3000,
timeout: 120000,
reuseExistingServer: !process.env.CI
+23 -5
View File
@@ -1,23 +1,41 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "es2021",
"target": "ESNext",
"lib": [
"es2015"
"es2021",
],
"rootDir": ".",
"outDir": "../out-test",
"sourceMap": true,
"removeComments": true,
"baseUrl": ".",
"paths": {
"common/*": [
"../../../src/common/*"
],
"browser/*": [
"../../../src/browser/*"
]
},
"strict": true,
"types": [
"../../../node_modules/@types/mocha",
"../../../node_modules/@types/node",
"../../../out-test/api/TestUtils"
"../../../node_modules/@types/node"
]
},
"include": [
"./**/*",
"../../../typings/xterm.d.ts"
],
"references": [
{
"path": "../../../src/common"
},
{
"path": "../../../src/browser"
},
{
"path": "../../../test/playwright"
}
]
}
+3 -1
View File
@@ -25,7 +25,9 @@ module.exports = {
filename: mainFile,
path: path.resolve('./lib'),
library: addonName,
libraryTarget: 'umd'
libraryTarget: 'umd',
// Force usage of globalThis instead of global / self. (This is cross-env compatible)
globalObject: 'globalThis',
},
mode: 'production'
};
-2
View File
@@ -1,2 +0,0 @@
lib
node_modules
-28
View File
@@ -1,28 +0,0 @@
## @xterm/addon-canvas
An addon for [xterm.js](https://github.com/xtermjs/xterm.js) that enables a canvas-based renderer using a 2d context to draw. This addon requires xterm.js v5+.
The purpose of this addon is to be used as a fallback for the [webgl addon](https://www.npmjs.com/package/@xterm/addon-webgl) when better performance is desired over the default DOM renderer, but WebGL2 isn't supported or performant for some reason.
### Install
```bash
npm install --save @xterm/addon-canvas
```
### Usage
```ts
import { Terminal } from '@xterm/xterm';
import { CanvasAddon } from '@xterm/addon-canvas';
const terminal = new Terminal();
terminal.open(element);
terminal.loadAddon(new CanvasAddon());
```
See the full [API](https://github.com/xtermjs/xterm.js/blob/master/addons/addon-canvas/typings/addon-canvas.d.ts) for more advanced usage.
### See also
- [@xterm/addon-webgl](https://www.npmjs.com/package/@xterm/addon-webgl) A renderer for xterm.js that uses WebGL
-511
View File
@@ -1,511 +0,0 @@
/**
* Copyright (c) 2017 The xterm.js authors. All rights reserved.
* @license MIT
*/
import { ReadonlyColorSet } from 'browser/Types';
import { CellColorResolver } from 'browser/renderer/shared/CellColorResolver';
import { acquireTextureAtlas } from 'browser/renderer/shared/CharAtlasCache';
import { TEXT_BASELINE } from 'browser/renderer/shared/Constants';
import { tryDrawCustomChar } from 'browser/renderer/shared/CustomGlyphs';
import { allowRescaling, throwIfFalsy } from 'browser/renderer/shared/RendererUtils';
import { createSelectionRenderModel } from 'browser/renderer/shared/SelectionRenderModel';
import { IRasterizedGlyph, IRenderDimensions, ISelectionRenderModel, ITextureAtlas } from 'browser/renderer/shared/Types';
import { ICoreBrowserService, IThemeService } from 'browser/services/Services';
import { EventEmitter, forwardEvent } from 'common/EventEmitter';
import { Disposable, MutableDisposable, toDisposable } from 'common/Lifecycle';
import { isSafari } from 'common/Platform';
import { ICellData } from 'common/Types';
import { CellData } from 'common/buffer/CellData';
import { WHITESPACE_CELL_CODE } from 'common/buffer/Constants';
import { IBufferService, IDecorationService, IOptionsService } from 'common/services/Services';
import { Terminal } from '@xterm/xterm';
import { IRenderLayer } from './Types';
export abstract class BaseRenderLayer extends Disposable implements IRenderLayer {
private _canvas: HTMLCanvasElement;
protected _ctx!: CanvasRenderingContext2D;
private _deviceCharWidth: number = 0;
private _deviceCharHeight: number = 0;
private _deviceCellWidth: number = 0;
private _deviceCellHeight: number = 0;
private _deviceCharLeft: number = 0;
private _deviceCharTop: number = 0;
protected _selectionModel: ISelectionRenderModel = createSelectionRenderModel();
private _cellColorResolver: CellColorResolver;
private _bitmapGenerator: (BitmapGenerator | undefined)[] = [];
protected _charAtlas!: ITextureAtlas;
protected _charAtlasDisposable = this.register(new MutableDisposable());
public get canvas(): HTMLCanvasElement { return this._canvas; }
public get cacheCanvas(): HTMLCanvasElement { return this._charAtlas?.pages[0].canvas!; }
private readonly _onAddTextureAtlasCanvas = this.register(new EventEmitter<HTMLCanvasElement>());
public readonly onAddTextureAtlasCanvas = this._onAddTextureAtlasCanvas.event;
constructor(
private readonly _terminal: Terminal,
private _container: HTMLElement,
id: string,
zIndex: number,
private _alpha: boolean,
protected readonly _themeService: IThemeService,
protected readonly _bufferService: IBufferService,
protected readonly _optionsService: IOptionsService,
protected readonly _decorationService: IDecorationService,
protected readonly _coreBrowserService: ICoreBrowserService
) {
super();
this._cellColorResolver = new CellColorResolver(this._terminal, this._optionsService, this._selectionModel, this._decorationService, this._coreBrowserService, this._themeService);
this._canvas = this._coreBrowserService.mainDocument.createElement('canvas');
this._canvas.classList.add(`xterm-${id}-layer`);
this._canvas.style.zIndex = zIndex.toString();
this._initCanvas();
this._container.appendChild(this._canvas);
this._refreshCharAtlas(this._themeService.colors);
this.register(this._themeService.onChangeColors(e => {
this._refreshCharAtlas(e);
this.reset();
// Trigger selection changed as it's handled separately to regular rendering
this.handleSelectionChanged(this._selectionModel.selectionStart, this._selectionModel.selectionEnd, this._selectionModel.columnSelectMode);
}));
this.register(toDisposable(() => {
this._canvas.remove();
}));
}
private _initCanvas(): void {
this._ctx = throwIfFalsy(this._canvas.getContext('2d', { alpha: this._alpha }));
// Draw the background if this is an opaque layer
if (!this._alpha) {
this._clearAll();
}
}
public handleBlur(): void {}
public handleFocus(): void {}
public handleCursorMove(): void {}
public handleGridChanged(startRow: number, endRow: number): void {}
public handleSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean = false): void {
this._selectionModel.update((this._terminal as any)._core, start, end, columnSelectMode);
}
protected _setTransparency(alpha: boolean): void {
// Do nothing when alpha doesn't change
if (alpha === this._alpha) {
return;
}
// Create new canvas and replace old one
const oldCanvas = this._canvas;
this._alpha = alpha;
// Cloning preserves properties
this._canvas = this._canvas.cloneNode() as HTMLCanvasElement;
this._initCanvas();
this._container.replaceChild(this._canvas, oldCanvas);
// Regenerate char atlas and force a full redraw
this._refreshCharAtlas(this._themeService.colors);
this.handleGridChanged(0, this._bufferService.rows - 1);
}
/**
* Refreshes the char atlas, aquiring a new one if necessary.
* @param colorSet The color set to use for the char atlas.
*/
private _refreshCharAtlas(colorSet: ReadonlyColorSet): void {
if (this._deviceCharWidth <= 0 && this._deviceCharHeight <= 0) {
return;
}
this._charAtlas = acquireTextureAtlas(this._terminal, this._optionsService.rawOptions, colorSet, this._deviceCellWidth, this._deviceCellHeight, this._deviceCharWidth, this._deviceCharHeight, this._coreBrowserService.dpr);
this._charAtlasDisposable.value = forwardEvent(this._charAtlas.onAddTextureAtlasCanvas, this._onAddTextureAtlasCanvas);
this._charAtlas.warmUp();
for (let i = 0; i < this._charAtlas.pages.length; i++) {
this._bitmapGenerator[i] = new BitmapGenerator(this._charAtlas.pages[i].canvas);
}
}
public resize(dim: IRenderDimensions): void {
this._deviceCellWidth = dim.device.cell.width;
this._deviceCellHeight = dim.device.cell.height;
this._deviceCharWidth = dim.device.char.width;
this._deviceCharHeight = dim.device.char.height;
this._deviceCharLeft = dim.device.char.left;
this._deviceCharTop = dim.device.char.top;
this._canvas.width = dim.device.canvas.width;
this._canvas.height = dim.device.canvas.height;
this._canvas.style.width = `${dim.css.canvas.width}px`;
this._canvas.style.height = `${dim.css.canvas.height}px`;
// Draw the background if this is an opaque layer
if (!this._alpha) {
this._clearAll();
}
this._refreshCharAtlas(this._themeService.colors);
}
public abstract reset(): void;
public clearTextureAtlas(): void {
this._charAtlas?.clearTexture();
}
/**
* Fills 1+ cells completely. This uses the existing fillStyle on the context.
* @param x The column to start at.
* @param y The row to start at
* @param width The number of columns to fill.
* @param height The number of rows to fill.
*/
protected _fillCells(x: number, y: number, width: number, height: number): void {
this._ctx.fillRect(
x * this._deviceCellWidth,
y * this._deviceCellHeight,
width * this._deviceCellWidth,
height * this._deviceCellHeight);
}
/**
* Fills a 1px line (2px on HDPI) at the middle of the cell. This uses the
* existing fillStyle on the context.
* @param x The column to fill.
* @param y The row to fill.
*/
protected _fillMiddleLineAtCells(x: number, y: number, width: number = 1): void {
const cellOffset = Math.ceil(this._deviceCellHeight * 0.5);
this._ctx.fillRect(
x * this._deviceCellWidth,
(y + 1) * this._deviceCellHeight - cellOffset - this._coreBrowserService.dpr,
width * this._deviceCellWidth,
this._coreBrowserService.dpr);
}
/**
* Fills a 1px line (2px on HDPI) at the bottom of the cell. This uses the
* existing fillStyle on the context.
* @param x The column to fill.
* @param y The row to fill.
*/
protected _fillBottomLineAtCells(x: number, y: number, width: number = 1, pixelOffset: number = 0): void {
this._ctx.fillRect(
x * this._deviceCellWidth,
(y + 1) * this._deviceCellHeight + pixelOffset - this._coreBrowserService.dpr - 1 /* Ensure it's drawn within the cell */,
width * this._deviceCellWidth,
this._coreBrowserService.dpr);
}
protected _curlyUnderlineAtCell(x: number, y: number, width: number = 1): void {
this._ctx.save();
this._ctx.beginPath();
this._ctx.strokeStyle = this._ctx.fillStyle;
const lineWidth = this._coreBrowserService.dpr;
this._ctx.lineWidth = lineWidth;
for (let xOffset = 0; xOffset < width; xOffset++) {
const xLeft = (x + xOffset) * this._deviceCellWidth;
const xMid = (x + xOffset + 0.5) * this._deviceCellWidth;
const xRight = (x + xOffset + 1) * this._deviceCellWidth;
const yMid = (y + 1) * this._deviceCellHeight - lineWidth - 1;
const yMidBot = yMid - lineWidth;
const yMidTop = yMid + lineWidth;
this._ctx.moveTo(xLeft, yMid);
this._ctx.bezierCurveTo(
xLeft, yMidBot,
xMid, yMidBot,
xMid, yMid
);
this._ctx.bezierCurveTo(
xMid, yMidTop,
xRight, yMidTop,
xRight, yMid
);
}
this._ctx.stroke();
this._ctx.restore();
}
protected _dottedUnderlineAtCell(x: number, y: number, width: number = 1): void {
this._ctx.save();
this._ctx.beginPath();
this._ctx.strokeStyle = this._ctx.fillStyle;
const lineWidth = this._coreBrowserService.dpr;
this._ctx.lineWidth = lineWidth;
this._ctx.setLineDash([lineWidth * 2, lineWidth]);
const xLeft = x * this._deviceCellWidth;
const yMid = (y + 1) * this._deviceCellHeight - lineWidth - 1;
this._ctx.moveTo(xLeft, yMid);
for (let xOffset = 0; xOffset < width; xOffset++) {
// const xLeft = x * this._deviceCellWidth;
const xRight = (x + width + xOffset) * this._deviceCellWidth;
this._ctx.lineTo(xRight, yMid);
}
this._ctx.stroke();
this._ctx.closePath();
this._ctx.restore();
}
protected _dashedUnderlineAtCell(x: number, y: number, width: number = 1): void {
this._ctx.save();
this._ctx.beginPath();
this._ctx.strokeStyle = this._ctx.fillStyle;
const lineWidth = this._coreBrowserService.dpr;
this._ctx.lineWidth = lineWidth;
this._ctx.setLineDash([lineWidth * 4, lineWidth * 3]);
const xLeft = x * this._deviceCellWidth;
const xRight = (x + width) * this._deviceCellWidth;
const yMid = (y + 1) * this._deviceCellHeight - lineWidth - 1;
this._ctx.moveTo(xLeft, yMid);
this._ctx.lineTo(xRight, yMid);
this._ctx.stroke();
this._ctx.closePath();
this._ctx.restore();
}
/**
* Fills a 1px line (2px on HDPI) at the left of the cell. This uses the
* existing fillStyle on the context.
* @param x The column to fill.
* @param y The row to fill.
*/
protected _fillLeftLineAtCell(x: number, y: number, width: number): void {
this._ctx.fillRect(
x * this._deviceCellWidth,
y * this._deviceCellHeight,
this._coreBrowserService.dpr * width,
this._deviceCellHeight);
}
/**
* Strokes a 1px rectangle (2px on HDPI) around a cell. This uses the existing
* strokeStyle on the context.
* @param x The column to fill.
* @param y The row to fill.
*/
protected _strokeRectAtCell(x: number, y: number, width: number, height: number): void {
const lineWidth = this._coreBrowserService.dpr;
this._ctx.lineWidth = lineWidth;
this._ctx.strokeRect(
x * this._deviceCellWidth + lineWidth / 2,
y * this._deviceCellHeight + (lineWidth / 2),
width * this._deviceCellWidth - lineWidth,
(height * this._deviceCellHeight) - lineWidth);
}
/**
* Clears the entire canvas.
*/
protected _clearAll(): void {
if (this._alpha) {
this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height);
} else {
this._ctx.fillStyle = this._themeService.colors.background.css;
this._ctx.fillRect(0, 0, this._canvas.width, this._canvas.height);
}
}
/**
* Clears 1+ cells completely.
* @param x The column to start at.
* @param y The row to start at.
* @param width The number of columns to clear.
* @param height The number of rows to clear.
*/
protected _clearCells(x: number, y: number, width: number, height: number): void {
if (this._alpha) {
this._ctx.clearRect(
x * this._deviceCellWidth,
y * this._deviceCellHeight,
width * this._deviceCellWidth,
height * this._deviceCellHeight);
} else {
this._ctx.fillStyle = this._themeService.colors.background.css;
this._ctx.fillRect(
x * this._deviceCellWidth,
y * this._deviceCellHeight,
width * this._deviceCellWidth,
height * this._deviceCellHeight);
}
}
/**
* Draws a truecolor character at the cell. The character will be clipped to
* ensure that it fits with the cell, including the cell to the right if it's
* a wide character. This uses the existing fillStyle on the context.
* @param cell The cell data for the character to draw.
* @param x The column to draw at.
* @param y The row to draw at.
*/
protected _fillCharTrueColor(cell: CellData, x: number, y: number): void {
this._ctx.font = this._getFont(false, false);
this._ctx.textBaseline = TEXT_BASELINE;
this._clipRow(y);
// Draw custom characters if applicable
let drawSuccess = false;
if (this._optionsService.rawOptions.customGlyphs !== false) {
drawSuccess = tryDrawCustomChar(this._ctx, cell.getChars(), x * this._deviceCellWidth, y * this._deviceCellHeight, this._deviceCellWidth, this._deviceCellHeight, this._optionsService.rawOptions.fontSize, this._coreBrowserService.dpr);
}
// Draw the character
if (!drawSuccess) {
this._ctx.fillText(
cell.getChars(),
x * this._deviceCellWidth + this._deviceCharLeft,
y * this._deviceCellHeight + this._deviceCharTop + this._deviceCharHeight);
}
}
/**
* Draws one or more characters at a cell. If possible this will draw using
* the character atlas to reduce draw time.
*/
protected _drawChars(cell: ICellData, x: number, y: number): void {
const chars = cell.getChars();
const code = cell.getCode();
const width = cell.getWidth();
this._cellColorResolver.resolve(cell, x, this._bufferService.buffer.ydisp + y, this._deviceCellWidth);
if (!this._charAtlas) {
return;
}
let glyph: IRasterizedGlyph;
if (chars && chars.length > 1) {
glyph = this._charAtlas.getRasterizedGlyphCombinedChar(chars, this._cellColorResolver.result.bg, this._cellColorResolver.result.fg, this._cellColorResolver.result.ext, true);
} else {
glyph = this._charAtlas.getRasterizedGlyph(cell.getCode() || WHITESPACE_CELL_CODE, this._cellColorResolver.result.bg, this._cellColorResolver.result.fg, this._cellColorResolver.result.ext, true);
}
if (!glyph.size.x || !glyph.size.y) {
return;
}
this._ctx.save();
this._clipRow(y);
// Draw the image, use the bitmap if it's available
// HACK: If the canvas doesn't match, delete the generator. It's not clear how this happens but
// something is wrong with either the lifecycle of _bitmapGenerator or the page canvases are
// swapped out unexpectedly
if (this._bitmapGenerator[glyph.texturePage] && this._charAtlas.pages[glyph.texturePage].canvas !== this._bitmapGenerator[glyph.texturePage]!.canvas) {
this._bitmapGenerator[glyph.texturePage]?.bitmap?.close();
delete this._bitmapGenerator[glyph.texturePage];
}
if (this._charAtlas.pages[glyph.texturePage].version !== this._bitmapGenerator[glyph.texturePage]?.version) {
if (!this._bitmapGenerator[glyph.texturePage]) {
this._bitmapGenerator[glyph.texturePage] = new BitmapGenerator(this._charAtlas.pages[glyph.texturePage].canvas);
}
this._bitmapGenerator[glyph.texturePage]!.refresh();
this._bitmapGenerator[glyph.texturePage]!.version = this._charAtlas.pages[glyph.texturePage].version;
}
// Reduce scale horizontally for wide glyphs printed in cells that would overlap with the
// following cell (ie. the width is not 2).
let renderWidth = glyph.size.x;
if (this._optionsService.rawOptions.rescaleOverlappingGlyphs) {
if (allowRescaling(code, width, glyph.size.x, this._deviceCellWidth)) {
renderWidth = this._deviceCellWidth - 1; // - 1 to improve readability
}
}
this._ctx.drawImage(
this._bitmapGenerator[glyph.texturePage]?.bitmap || this._charAtlas!.pages[glyph.texturePage].canvas,
glyph.texturePosition.x,
glyph.texturePosition.y,
glyph.size.x,
glyph.size.y,
x * this._deviceCellWidth + this._deviceCharLeft - glyph.offset.x,
y * this._deviceCellHeight + this._deviceCharTop - glyph.offset.y,
renderWidth,
glyph.size.y
);
this._ctx.restore();
}
/**
* Clips a row to ensure no pixels will be drawn outside the cells in the row.
* @param y The row to clip.
*/
private _clipRow(y: number): void {
this._ctx.beginPath();
this._ctx.rect(
0,
y * this._deviceCellHeight,
this._bufferService.cols * this._deviceCellWidth,
this._deviceCellHeight);
this._ctx.clip();
}
/**
* Gets the current font.
* @param isBold If we should use the bold fontWeight.
*/
protected _getFont(isBold: boolean, isItalic: boolean): string {
const fontWeight = isBold ? this._optionsService.rawOptions.fontWeightBold : this._optionsService.rawOptions.fontWeight;
const fontStyle = isItalic ? 'italic' : '';
return `${fontStyle} ${fontWeight} ${this._optionsService.rawOptions.fontSize * this._coreBrowserService.dpr}px ${this._optionsService.rawOptions.fontFamily}`;
}
}
/**
* The number of milliseconds to wait before generating the ImageBitmap, this is to debounce/batch
* the operation as window.createImageBitmap is asynchronous.
*/
const GLYPH_BITMAP_COMMIT_DELAY = 100;
const enum BitmapGeneratorState {
IDLE = 0,
GENERATING = 1,
GENERATING_INVALID = 2
}
class BitmapGenerator {
private _state: BitmapGeneratorState = BitmapGeneratorState.IDLE;
private _commitTimeout: number | undefined = undefined;
private _bitmap: ImageBitmap | undefined = undefined;
public get bitmap(): ImageBitmap | undefined { return this._bitmap; }
public version: number = -1;
constructor(public readonly canvas: HTMLCanvasElement) {
}
public refresh(): void {
// Clear the bitmap immediately as it's stale
this._bitmap?.close();
this._bitmap = undefined;
// Disable ImageBitmaps on Safari because of https://bugs.webkit.org/show_bug.cgi?id=149990
if (isSafari) {
return;
}
if (this._commitTimeout === undefined) {
this._commitTimeout = window.setTimeout(() => this._generate(), GLYPH_BITMAP_COMMIT_DELAY);
}
if (this._state === BitmapGeneratorState.GENERATING) {
this._state = BitmapGeneratorState.GENERATING_INVALID;
}
}
private _generate(): void {
if (this._state === BitmapGeneratorState.IDLE) {
this._bitmap?.close();
this._bitmap = undefined;
this._state = BitmapGeneratorState.GENERATING;
window.createImageBitmap(this.canvas).then(bitmap => {
if (this._state === BitmapGeneratorState.GENERATING_INVALID) {
this.refresh();
} else {
this._bitmap = bitmap;
}
this._state = BitmapGeneratorState.IDLE;
});
if (this._commitTimeout) {
this._commitTimeout = undefined;
}
}
}
}
-73
View File
@@ -1,73 +0,0 @@
/**
* Copyright (c) 2022 The xterm.js authors. All rights reserved.
* @license MIT
*/
import type { ITerminalAddon, Terminal } from '@xterm/xterm';
import type { CanvasAddon as ICanvasApi } from '@xterm/addon-canvas';
import { ICharacterJoinerService, ICharSizeService, ICoreBrowserService, IRenderService, IThemeService } from 'browser/services/Services';
import { ITerminal } from 'browser/Types';
import { EventEmitter, forwardEvent } from 'common/EventEmitter';
import { Disposable, toDisposable } from 'common/Lifecycle';
import { setTraceLogger } from 'common/services/LogService';
import { IBufferService, IDecorationService, ILogService } from 'common/services/Services';
import { CanvasRenderer } from './CanvasRenderer';
export class CanvasAddon extends Disposable implements ITerminalAddon , ICanvasApi {
private _terminal?: Terminal;
private _renderer?: CanvasRenderer;
private readonly _onChangeTextureAtlas = this.register(new EventEmitter<HTMLCanvasElement>());
public readonly onChangeTextureAtlas = this._onChangeTextureAtlas.event;
private readonly _onAddTextureAtlasCanvas = this.register(new EventEmitter<HTMLCanvasElement>());
public readonly onAddTextureAtlasCanvas = this._onAddTextureAtlasCanvas.event;
public get textureAtlas(): HTMLCanvasElement | undefined {
return this._renderer?.textureAtlas;
}
public activate(terminal: Terminal): void {
const core = (terminal as any)._core as ITerminal;
if (!terminal.element) {
this.register(core.onWillOpen(() => this.activate(terminal)));
return;
}
this._terminal = terminal;
const coreService = core.coreService;
const optionsService = core.optionsService;
const screenElement = core.screenElement!;
const linkifier = core.linkifier!;
const unsafeCore = core as any;
const bufferService: IBufferService = unsafeCore._bufferService;
const renderService: IRenderService = unsafeCore._renderService;
const characterJoinerService: ICharacterJoinerService = unsafeCore._characterJoinerService;
const charSizeService: ICharSizeService = unsafeCore._charSizeService;
const coreBrowserService: ICoreBrowserService = unsafeCore._coreBrowserService;
const decorationService: IDecorationService = unsafeCore._decorationService;
const logService: ILogService = unsafeCore._logService;
const themeService: IThemeService = unsafeCore._themeService;
// Set trace logger just in case it hasn't been yet which could happen when the addon is
// bundled separately to the core module
setTraceLogger(logService);
this._renderer = new CanvasRenderer(terminal, screenElement, linkifier, bufferService, charSizeService, optionsService, characterJoinerService, coreService, coreBrowserService, decorationService, themeService);
this.register(forwardEvent(this._renderer.onChangeTextureAtlas, this._onChangeTextureAtlas));
this.register(forwardEvent(this._renderer.onAddTextureAtlasCanvas, this._onAddTextureAtlasCanvas));
renderService.setRenderer(this._renderer);
renderService.handleResize(bufferService.cols, bufferService.rows);
this.register(toDisposable(() => {
renderService.setRenderer((this._terminal as any)._core._createRenderer());
renderService.handleResize(terminal.cols, terminal.rows);
this._renderer?.dispose();
this._renderer = undefined;
}));
}
public clearTextureAtlas(): void {
this._renderer?.clearTextureAtlas();
}
}
-191
View File
@@ -1,191 +0,0 @@
/**
* Copyright (c) 2017 The xterm.js authors. All rights reserved.
* @license MIT
*/
import { ILinkifier2 } from 'browser/Types';
import { removeTerminalFromCache } from 'browser/renderer/shared/CharAtlasCache';
import { observeDevicePixelDimensions } from 'browser/renderer/shared/DevicePixelObserver';
import { createRenderDimensions } from 'browser/renderer/shared/RendererUtils';
import { IRenderDimensions, IRenderer, IRequestRedrawEvent } from 'browser/renderer/shared/Types';
import { ICharSizeService, ICharacterJoinerService, ICoreBrowserService, IThemeService } from 'browser/services/Services';
import { EventEmitter, forwardEvent } from 'common/EventEmitter';
import { Disposable, MutableDisposable, toDisposable } from 'common/Lifecycle';
import { IBufferService, ICoreService, IDecorationService, IOptionsService } from 'common/services/Services';
import { Terminal } from '@xterm/xterm';
import { CursorRenderLayer } from './CursorRenderLayer';
import { LinkRenderLayer } from './LinkRenderLayer';
import { SelectionRenderLayer } from './SelectionRenderLayer';
import { TextRenderLayer } from './TextRenderLayer';
import { IRenderLayer } from './Types';
export class CanvasRenderer extends Disposable implements IRenderer {
private _renderLayers: IRenderLayer[];
private _devicePixelRatio: number;
private _observerDisposable = this.register(new MutableDisposable());
public dimensions: IRenderDimensions;
private readonly _onRequestRedraw = this.register(new EventEmitter<IRequestRedrawEvent>());
public readonly onRequestRedraw = this._onRequestRedraw.event;
private readonly _onChangeTextureAtlas = this.register(new EventEmitter<HTMLCanvasElement>());
public readonly onChangeTextureAtlas = this._onChangeTextureAtlas.event;
private readonly _onAddTextureAtlasCanvas = this.register(new EventEmitter<HTMLCanvasElement>());
public readonly onAddTextureAtlasCanvas = this._onAddTextureAtlasCanvas.event;
constructor(
private readonly _terminal: Terminal,
private readonly _screenElement: HTMLElement,
linkifier2: ILinkifier2,
private readonly _bufferService: IBufferService,
private readonly _charSizeService: ICharSizeService,
private readonly _optionsService: IOptionsService,
characterJoinerService: ICharacterJoinerService,
coreService: ICoreService,
private readonly _coreBrowserService: ICoreBrowserService,
decorationService: IDecorationService,
private readonly _themeService: IThemeService
) {
super();
const allowTransparency = this._optionsService.rawOptions.allowTransparency;
this._renderLayers = [
new TextRenderLayer(this._terminal, this._screenElement, 0, allowTransparency, this._bufferService, this._optionsService, characterJoinerService, decorationService, this._coreBrowserService, _themeService),
new SelectionRenderLayer(this._terminal, this._screenElement, 1, this._bufferService, this._coreBrowserService, decorationService, this._optionsService, _themeService),
new LinkRenderLayer(this._terminal, this._screenElement, 2, linkifier2, this._bufferService, this._optionsService, decorationService, this._coreBrowserService, _themeService),
new CursorRenderLayer(this._terminal, this._screenElement, 3, this._onRequestRedraw, this._bufferService, this._optionsService, coreService, this._coreBrowserService, decorationService, _themeService)
];
for (const layer of this._renderLayers) {
forwardEvent(layer.onAddTextureAtlasCanvas, this._onAddTextureAtlasCanvas);
}
this.dimensions = createRenderDimensions();
this._devicePixelRatio = this._coreBrowserService.dpr;
this._updateDimensions();
this._observerDisposable.value = observeDevicePixelDimensions(this._renderLayers[0].canvas, this._coreBrowserService.window, (w, h) => this._setCanvasDevicePixelDimensions(w, h));
this.register(this._coreBrowserService.onWindowChange(w => {
this._observerDisposable.value = observeDevicePixelDimensions(this._renderLayers[0].canvas, w, (w, h) => this._setCanvasDevicePixelDimensions(w, h));
}));
this.register(toDisposable(() => {
for (const l of this._renderLayers) {
l.dispose();
}
removeTerminalFromCache(this._terminal);
}));
}
public get textureAtlas(): HTMLCanvasElement | undefined {
return this._renderLayers[0].cacheCanvas;
}
public handleDevicePixelRatioChange(): void {
// If the device pixel ratio changed, the char atlas needs to be regenerated
// and the terminal needs to refreshed
if (this._devicePixelRatio !== this._coreBrowserService.dpr) {
this._devicePixelRatio = this._coreBrowserService.dpr;
this.handleResize(this._bufferService.cols, this._bufferService.rows);
}
}
public handleResize(cols: number, rows: number): void {
// Update character and canvas dimensions
this._updateDimensions();
// Resize all render layers
for (const l of this._renderLayers) {
l.resize(this.dimensions);
}
// Resize the screen
this._screenElement.style.width = `${this.dimensions.css.canvas.width}px`;
this._screenElement.style.height = `${this.dimensions.css.canvas.height}px`;
}
public handleCharSizeChanged(): void {
this.handleResize(this._bufferService.cols, this._bufferService.rows);
}
public handleBlur(): void {
this._runOperation(l => l.handleBlur());
}
public handleFocus(): void {
this._runOperation(l => l.handleFocus());
}
public handleSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean = false): void {
this._runOperation(l => l.handleSelectionChanged(start, end, columnSelectMode));
// Selection foreground requires a full re-render
if (this._themeService.colors.selectionForeground) {
this._onRequestRedraw.fire({ start: 0, end: this._bufferService.rows - 1 });
}
}
public handleCursorMove(): void {
this._runOperation(l => l.handleCursorMove());
}
public clear(): void {
this._runOperation(l => l.reset());
}
private _runOperation(operation: (layer: IRenderLayer) => void): void {
for (const l of this._renderLayers) {
operation(l);
}
}
/**
* Performs the refresh loop callback, calling refresh only if a refresh is
* necessary before queueing up the next one.
*/
public renderRows(start: number, end: number): void {
for (const l of this._renderLayers) {
l.handleGridChanged(start, end);
}
}
public clearTextureAtlas(): void {
for (const layer of this._renderLayers) {
layer.clearTextureAtlas();
}
}
/**
* Recalculates the character and canvas dimensions.
*/
private _updateDimensions(): void {
if (!this._charSizeService.hasValidSize) {
return;
}
// See the WebGL renderer for an explanation of this section.
const dpr = this._coreBrowserService.dpr;
this.dimensions.device.char.width = Math.floor(this._charSizeService.width * dpr);
this.dimensions.device.char.height = Math.ceil(this._charSizeService.height * dpr);
this.dimensions.device.cell.height = Math.floor(this.dimensions.device.char.height * this._optionsService.rawOptions.lineHeight);
this.dimensions.device.char.top = this._optionsService.rawOptions.lineHeight === 1 ? 0 : Math.round((this.dimensions.device.cell.height - this.dimensions.device.char.height) / 2);
this.dimensions.device.cell.width = this.dimensions.device.char.width + Math.round(this._optionsService.rawOptions.letterSpacing);
this.dimensions.device.char.left = Math.floor(this._optionsService.rawOptions.letterSpacing / 2);
this.dimensions.device.canvas.height = this._bufferService.rows * this.dimensions.device.cell.height;
this.dimensions.device.canvas.width = this._bufferService.cols * this.dimensions.device.cell.width;
this.dimensions.css.canvas.height = Math.round(this.dimensions.device.canvas.height / dpr);
this.dimensions.css.canvas.width = Math.round(this.dimensions.device.canvas.width / dpr);
this.dimensions.css.cell.height = this.dimensions.css.canvas.height / this._bufferService.rows;
this.dimensions.css.cell.width = this.dimensions.css.canvas.width / this._bufferService.cols;
}
private _setCanvasDevicePixelDimensions(width: number, height: number): void {
this.dimensions.device.canvas.height = height;
this.dimensions.device.canvas.width = width;
// Resize all render layers
for (const l of this._renderLayers) {
l.resize(this.dimensions);
}
this._requestRedrawViewport();
}
private _requestRedrawViewport(): void {
this._onRequestRedraw.fire({ start: 0, end: this._bufferService.rows - 1 });
}
}
@@ -1,233 +0,0 @@
/**
* Copyright (c) 2017 The xterm.js authors. All rights reserved.
* @license MIT
*/
import { CursorBlinkStateManager } from 'browser/renderer/shared/CursorBlinkStateManager';
import { IRenderDimensions, IRequestRedrawEvent } from 'browser/renderer/shared/Types';
import { ICoreBrowserService, IThemeService } from 'browser/services/Services';
import { IEventEmitter } from 'common/EventEmitter';
import { MutableDisposable } from 'common/Lifecycle';
import { isFirefox } from 'common/Platform';
import { ICellData } from 'common/Types';
import { CellData } from 'common/buffer/CellData';
import { IBufferService, ICoreService, IDecorationService, IOptionsService } from 'common/services/Services';
import { Terminal } from '@xterm/xterm';
import { BaseRenderLayer } from './BaseRenderLayer';
interface ICursorState {
x: number;
y: number;
isFocused: boolean;
style: string;
width: number;
}
export class CursorRenderLayer extends BaseRenderLayer {
private _state: ICursorState;
private _cursorRenderers: {[key: string]: (x: number, y: number, cell: ICellData) => void};
private _cursorBlinkStateManager: MutableDisposable<CursorBlinkStateManager> = this.register(new MutableDisposable());
private _cell: ICellData = new CellData();
constructor(
terminal: Terminal,
container: HTMLElement,
zIndex: number,
private readonly _onRequestRedraw: IEventEmitter<IRequestRedrawEvent>,
bufferService: IBufferService,
optionsService: IOptionsService,
private readonly _coreService: ICoreService,
coreBrowserService: ICoreBrowserService,
decorationService: IDecorationService,
themeService: IThemeService
) {
super(terminal, container, 'cursor', zIndex, true, themeService, bufferService, optionsService, decorationService, coreBrowserService);
this._state = {
x: 0,
y: 0,
isFocused: false,
style: '',
width: 0
};
this._cursorRenderers = {
'bar': this._renderBarCursor.bind(this),
'block': this._renderBlockCursor.bind(this),
'underline': this._renderUnderlineCursor.bind(this),
'outline': this._renderOutlineCursor.bind(this)
};
this.register(optionsService.onOptionChange(() => this._handleOptionsChanged()));
this._handleOptionsChanged();
}
public resize(dim: IRenderDimensions): void {
super.resize(dim);
// Resizing the canvas discards the contents of the canvas so clear state
this._state = {
x: 0,
y: 0,
isFocused: false,
style: '',
width: 0
};
}
public reset(): void {
this._clearCursor();
this._cursorBlinkStateManager.value?.restartBlinkAnimation();
this._handleOptionsChanged();
}
public handleBlur(): void {
this._cursorBlinkStateManager.value?.pause();
this._onRequestRedraw.fire({ start: this._bufferService.buffer.y, end: this._bufferService.buffer.y });
}
public handleFocus(): void {
this._cursorBlinkStateManager.value?.resume();
this._onRequestRedraw.fire({ start: this._bufferService.buffer.y, end: this._bufferService.buffer.y });
}
private _handleOptionsChanged(): void {
if (this._optionsService.rawOptions.cursorBlink) {
if (!this._cursorBlinkStateManager.value) {
this._cursorBlinkStateManager.value = new CursorBlinkStateManager(() => this._render(true), this._coreBrowserService);
}
} else {
this._cursorBlinkStateManager.clear();
}
// Request a refresh from the terminal as management of rendering is being
// moved back to the terminal
this._onRequestRedraw.fire({ start: this._bufferService.buffer.y, end: this._bufferService.buffer.y });
}
public handleCursorMove(): void {
this._cursorBlinkStateManager.value?.restartBlinkAnimation();
}
public handleGridChanged(startRow: number, endRow: number): void {
if (!this._cursorBlinkStateManager.value || this._cursorBlinkStateManager.value.isPaused) {
this._render(false);
} else {
this._cursorBlinkStateManager.value.restartBlinkAnimation();
}
}
private _render(triggeredByAnimationFrame: boolean): void {
// Don't draw the cursor if it's hidden
if (!this._coreService.isCursorInitialized || this._coreService.isCursorHidden) {
this._clearCursor();
return;
}
const cursorY = this._bufferService.buffer.ybase + this._bufferService.buffer.y;
const viewportRelativeCursorY = cursorY - this._bufferService.buffer.ydisp;
// Don't draw the cursor if it's off-screen
if (viewportRelativeCursorY < 0 || viewportRelativeCursorY >= this._bufferService.rows) {
this._clearCursor();
return;
}
// in case cursor.x == cols adjust visual cursor to cols - 1
const cursorX = Math.min(this._bufferService.buffer.x, this._bufferService.cols - 1);
this._bufferService.buffer.lines.get(cursorY)!.loadCell(cursorX, this._cell);
if (this._cell.content === undefined) {
return;
}
if (!this._coreBrowserService.isFocused) {
this._clearCursor();
this._ctx.save();
this._ctx.fillStyle = this._themeService.colors.cursor.css;
const cursorStyle = this._optionsService.rawOptions.cursorStyle;
const cursorInactiveStyle = this._optionsService.rawOptions.cursorInactiveStyle;
if (cursorInactiveStyle && cursorInactiveStyle !== 'none') {
this._cursorRenderers[cursorInactiveStyle](cursorX, viewportRelativeCursorY, this._cell);
}
this._ctx.restore();
this._state.x = cursorX;
this._state.y = viewportRelativeCursorY;
this._state.isFocused = false;
this._state.style = cursorStyle;
this._state.width = this._cell.getWidth();
return;
}
// Don't draw the cursor if it's blinking
if (this._cursorBlinkStateManager.value && !this._cursorBlinkStateManager.value.isCursorVisible) {
this._clearCursor();
return;
}
if (this._state) {
// The cursor is already in the correct spot, don't redraw
if (this._state.x === cursorX &&
this._state.y === viewportRelativeCursorY &&
this._state.isFocused === this._coreBrowserService.isFocused &&
this._state.style === this._optionsService.rawOptions.cursorStyle &&
this._state.width === this._cell.getWidth()) {
return;
}
this._clearCursor();
}
this._ctx.save();
this._cursorRenderers[this._optionsService.rawOptions.cursorStyle || 'block'](cursorX, viewportRelativeCursorY, this._cell);
this._ctx.restore();
this._state.x = cursorX;
this._state.y = viewportRelativeCursorY;
this._state.isFocused = false;
this._state.style = this._optionsService.rawOptions.cursorStyle;
this._state.width = this._cell.getWidth();
}
private _clearCursor(): void {
if (this._state) {
// Avoid potential rounding errors when browser is Firefox (#4487) or device pixel ratio is
// less than 1
if (isFirefox || this._coreBrowserService.dpr < 1) {
this._clearAll();
} else {
this._clearCells(this._state.x, this._state.y, this._state.width, 1);
}
this._state = {
x: 0,
y: 0,
isFocused: false,
style: '',
width: 0
};
}
}
private _renderBarCursor(x: number, y: number, cell: ICellData): void {
this._ctx.save();
this._ctx.fillStyle = this._themeService.colors.cursor.css;
this._fillLeftLineAtCell(x, y, this._optionsService.rawOptions.cursorWidth);
this._ctx.restore();
}
private _renderBlockCursor(x: number, y: number, cell: ICellData): void {
this._ctx.save();
this._ctx.fillStyle = this._themeService.colors.cursor.css;
this._fillCells(x, y, cell.getWidth(), 1);
this._ctx.fillStyle = this._themeService.colors.cursorAccent.css;
this._fillCharTrueColor(cell, x, y);
this._ctx.restore();
}
private _renderUnderlineCursor(x: number, y: number, cell: ICellData): void {
this._ctx.save();
this._ctx.fillStyle = this._themeService.colors.cursor.css;
this._fillBottomLineAtCells(x, y);
this._ctx.restore();
}
private _renderOutlineCursor(x: number, y: number, cell: ICellData): void {
this._ctx.save();
this._ctx.strokeStyle = this._themeService.colors.cursor.css;
this._strokeRectAtCell(x, y, cell.getWidth(), 1);
this._ctx.restore();
}
}
-62
View File
@@ -1,62 +0,0 @@
/**
* Copyright (c) 2017 The xterm.js authors. All rights reserved.
* @license MIT
*/
import { assert } from 'chai';
import { GridCache } from './GridCache';
describe('GridCache', () => {
let grid: GridCache<number>;
beforeEach(() => {
grid = new GridCache<number>();
});
describe('constructor', () => {
it('should create an empty cache', () => {
assert.equal(grid.cache.length, 0);
});
});
describe('resize', () => {
it('should fill all new elements with null', () => {
grid.resize(2, 2);
assert.equal(grid.cache.length, 2);
assert.equal(grid.cache[0].length, 2);
assert.equal(grid.cache[0][0], null);
assert.equal(grid.cache[0][1], null);
assert.equal(grid.cache[1].length, 2);
assert.equal(grid.cache[1][0], null);
assert.equal(grid.cache[1][1], null);
grid.resize(3, 2);
assert.equal(grid.cache.length, 3);
assert.equal(grid.cache[2][0], null);
assert.equal(grid.cache[2][1], null);
});
it('should remove rows/cols from the cache when reduced', () => {
grid.resize(2, 2);
grid.resize(1, 1);
assert.equal(grid.cache.length, 1);
assert.equal(grid.cache[0].length, 1);
});
it('should not touch existing cache entries if they fit in the new cache', () => {
grid.resize(1, 1);
assert.equal(grid.cache[0][0], null);
grid.cache[0][0] = 1;
grid.resize(2, 1);
assert.equal(grid.cache[0][0], 1);
});
});
describe('clear', () => {
it('should make all entries null', () => {
grid.resize(1, 1);
grid.cache[0][0] = 1;
grid.clear();
assert.equal(grid.cache[0][0], null);
});
});
});
-33
View File
@@ -1,33 +0,0 @@
/**
* Copyright (c) 2017 The xterm.js authors. All rights reserved.
* @license MIT
*/
export class GridCache<T> {
public cache: (T | undefined)[][];
public constructor() {
this.cache = [];
}
public resize(width: number, height: number): void {
for (let x = 0; x < width; x++) {
if (this.cache.length <= x) {
this.cache.push([]);
}
for (let y = this.cache[x].length; y < height; y++) {
this.cache[x].push(undefined);
}
this.cache[x].length = height;
}
this.cache.length = width;
}
public clear(): void {
for (let x = 0; x < this.cache.length; x++) {
for (let y = 0; y < this.cache[x].length; y++) {
this.cache[x][y] = undefined;
}
}
}
}
@@ -1,84 +0,0 @@
/**
* Copyright (c) 2017 The xterm.js authors. All rights reserved.
* @license MIT
*/
import { ILinkifier2, ILinkifierEvent } from 'browser/Types';
import { is256Color } from 'browser/renderer/shared/CharAtlasUtils';
import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/shared/Constants';
import { IRenderDimensions } from 'browser/renderer/shared/Types';
import { ICoreBrowserService, IThemeService } from 'browser/services/Services';
import { IBufferService, IDecorationService, IOptionsService } from 'common/services/Services';
import { Terminal } from '@xterm/xterm';
import { BaseRenderLayer } from './BaseRenderLayer';
export class LinkRenderLayer extends BaseRenderLayer {
private _state: ILinkifierEvent | undefined;
constructor(
terminal: Terminal,
container: HTMLElement,
zIndex: number,
linkifier2: ILinkifier2,
bufferService: IBufferService,
optionsService: IOptionsService,
decorationService: IDecorationService,
coreBrowserService: ICoreBrowserService,
themeService: IThemeService
) {
super(terminal, container, 'link', zIndex, true, themeService, bufferService, optionsService, decorationService, coreBrowserService);
this.register(linkifier2.onShowLinkUnderline(e => this._handleShowLinkUnderline(e)));
this.register(linkifier2.onHideLinkUnderline(e => this._handleHideLinkUnderline(e)));
}
public resize(dim: IRenderDimensions): void {
super.resize(dim);
// Resizing the canvas discards the contents of the canvas so clear state
this._state = undefined;
}
public reset(): void {
this._clearCurrentLink();
}
private _clearCurrentLink(): void {
if (this._state) {
this._clearCells(this._state.x1, this._state.y1, this._state.cols - this._state.x1, 1);
const middleRowCount = this._state.y2 - this._state.y1 - 1;
if (middleRowCount > 0) {
this._clearCells(0, this._state.y1 + 1, this._state.cols, middleRowCount);
}
this._clearCells(0, this._state.y2, this._state.x2, 1);
this._state = undefined;
}
}
private _handleShowLinkUnderline(e: ILinkifierEvent): void {
if (e.fg === INVERTED_DEFAULT_COLOR) {
this._ctx.fillStyle = this._themeService.colors.background.css;
} else if (e.fg && is256Color(e.fg)) {
// 256 color support
this._ctx.fillStyle = this._themeService.colors.ansi[e.fg].css;
} else {
this._ctx.fillStyle = this._themeService.colors.foreground.css;
}
if (e.y1 === e.y2) {
// Single line link
this._fillBottomLineAtCells(e.x1, e.y1, e.x2 - e.x1);
} else {
// Multi-line link
this._fillBottomLineAtCells(e.x1, e.y1, e.cols - e.x1);
for (let y = e.y1 + 1; y < e.y2; y++) {
this._fillBottomLineAtCells(0, y, e.cols);
}
this._fillBottomLineAtCells(0, e.y2, e.x2);
}
this._state = e;
}
private _handleHideLinkUnderline(e: ILinkifierEvent): void {
this._clearCurrentLink();
}
}
@@ -1,152 +0,0 @@
/**
* Copyright (c) 2017 The xterm.js authors. All rights reserved.
* @license MIT
*/
import { IRenderDimensions } from 'browser/renderer/shared/Types';
import { BaseRenderLayer } from './BaseRenderLayer';
import { IBufferService, IDecorationService, IOptionsService } from 'common/services/Services';
import { ICoreBrowserService, IThemeService } from 'browser/services/Services';
import { Terminal } from '@xterm/xterm';
interface ISelectionState {
start?: [number, number];
end?: [number, number];
columnSelectMode?: boolean;
ydisp?: number;
}
export class SelectionRenderLayer extends BaseRenderLayer {
private _state!: ISelectionState;
constructor(
terminal: Terminal,
container: HTMLElement,
zIndex: number,
bufferService: IBufferService,
coreBrowserService: ICoreBrowserService,
decorationService: IDecorationService,
optionsService: IOptionsService,
themeService: IThemeService
) {
super(terminal, container, 'selection', zIndex, true, themeService, bufferService, optionsService, decorationService, coreBrowserService);
this._clearState();
}
private _clearState(): void {
this._state = {
start: undefined,
end: undefined,
columnSelectMode: undefined,
ydisp: undefined
};
}
public resize(dim: IRenderDimensions): void {
super.resize(dim);
// On resize use the base render layer's cached selection values since resize clears _state
// inside reset.
if (this._selectionModel.selectionStart && this._selectionModel.selectionEnd) {
this._clearState();
this._redrawSelection(this._selectionModel.selectionStart, this._selectionModel.selectionEnd, this._selectionModel.columnSelectMode);
}
}
public reset(): void {
if (this._state.start && this._state.end) {
this._clearState();
this._clearAll();
}
}
public handleBlur(): void {
this.reset();
this._redrawSelection(this._selectionModel.selectionStart, this._selectionModel.selectionEnd, this._selectionModel.columnSelectMode);
}
public handleFocus(): void {
this.reset();
this._redrawSelection(this._selectionModel.selectionStart, this._selectionModel.selectionEnd, this._selectionModel.columnSelectMode);
}
public handleSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void {
super.handleSelectionChanged(start, end, columnSelectMode);
this._redrawSelection(start, end, columnSelectMode);
}
private _redrawSelection(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void {
// Selection has not changed
if (!this._didStateChange(start, end, columnSelectMode, this._bufferService.buffer.ydisp)) {
return;
}
// Remove all selections
this._clearAll();
// Selection does not exist
if (!start || !end) {
this._clearState();
return;
}
// Translate from buffer position to viewport position
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._bufferService.rows - 1);
// No need to draw the selection
if (viewportCappedStartRow >= this._bufferService.rows || viewportCappedEndRow < 0) {
this._state.ydisp = this._bufferService.buffer.ydisp;
return;
}
this._ctx.fillStyle = (this._coreBrowserService.isFocused
? this._themeService.colors.selectionBackgroundTransparent
: this._themeService.colors.selectionInactiveBackgroundTransparent).css;
if (columnSelectMode) {
const startCol = start[0];
const width = end[0] - startCol;
const height = viewportCappedEndRow - viewportCappedStartRow + 1;
this._fillCells(startCol, viewportCappedStartRow, width, height);
} else {
// Draw first row
const startCol = viewportStartRow === viewportCappedStartRow ? start[0] : 0;
const startRowEndCol = viewportCappedStartRow === viewportEndRow ? end[0] : this._bufferService.cols;
this._fillCells(startCol, viewportCappedStartRow, startRowEndCol - startCol, 1);
// Draw middle rows
const middleRowsCount = Math.max(viewportCappedEndRow - viewportCappedStartRow - 1, 0);
this._fillCells(0, viewportCappedStartRow + 1, this._bufferService.cols, middleRowsCount);
// Draw final row
if (viewportCappedStartRow !== viewportCappedEndRow) {
// Only draw viewportEndRow if it's not the same as viewportStartRow
const endCol = viewportEndRow === viewportCappedEndRow ? end[0] : this._bufferService.cols;
this._fillCells(0, viewportCappedEndRow, endCol, 1);
}
}
// Save state for next render
this._state.start = [start[0], start[1]];
this._state.end = [end[0], end[1]];
this._state.columnSelectMode = columnSelectMode;
this._state.ydisp = this._bufferService.buffer.ydisp;
}
private _didStateChange(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean, ydisp: number): boolean {
return !this._areCoordinatesEqual(start, this._state.start) ||
!this._areCoordinatesEqual(end, this._state.end) ||
columnSelectMode !== this._state.columnSelectMode ||
ydisp !== this._state.ydisp;
}
private _areCoordinatesEqual(coord1: [number, number] | undefined, coord2: [number, number] | undefined): boolean {
if (!coord1 || !coord2) {
return false;
}
return coord1[0] === coord2[0] && coord1[1] === coord2[1];
}
}
-292
View File
@@ -1,292 +0,0 @@
/**
* Copyright (c) 2017 The xterm.js authors. All rights reserved.
* @license MIT
*/
import { IRenderDimensions } from 'browser/renderer/shared/Types';
import { JoinedCellData } from 'browser/services/CharacterJoinerService';
import { ICharacterJoinerService, ICoreBrowserService, IThemeService } from 'browser/services/Services';
import { CharData, ICellData } from 'common/Types';
import { AttributeData } from 'common/buffer/AttributeData';
import { CellData } from 'common/buffer/CellData';
import { Content, NULL_CELL_CODE } from 'common/buffer/Constants';
import { IBufferService, IDecorationService, IOptionsService } from 'common/services/Services';
import { Terminal } from '@xterm/xterm';
import { BaseRenderLayer } from './BaseRenderLayer';
import { GridCache } from './GridCache';
/**
* This CharData looks like a null character, which will forc a clear and render
* when the character changes (a regular space ' ' character may not as it's
* drawn state is a cleared cell).
*/
// const OVERLAP_OWNED_CHAR_DATA: CharData = [null, '', 0, -1];
export class TextRenderLayer extends BaseRenderLayer {
private _state: GridCache<CharData>;
private _characterWidth: number = 0;
private _characterFont: string = '';
private _characterOverlapCache: { [key: string]: boolean } = {};
private _workCell = new CellData();
constructor(
terminal: Terminal,
container: HTMLElement,
zIndex: number,
alpha: boolean,
bufferService: IBufferService,
optionsService: IOptionsService,
private readonly _characterJoinerService: ICharacterJoinerService,
decorationService: IDecorationService,
coreBrowserService: ICoreBrowserService,
themeService: IThemeService
) {
super(terminal, container, 'text', zIndex, alpha, themeService, bufferService, optionsService, decorationService, coreBrowserService);
this._state = new GridCache<CharData>();
this.register(optionsService.onSpecificOptionChange('allowTransparency', value => this._setTransparency(value)));
}
public resize(dim: IRenderDimensions): void {
super.resize(dim);
// Clear the character width cache if the font or width has changed
const terminalFont = this._getFont(false, false);
if (this._characterWidth !== dim.device.char.width || this._characterFont !== terminalFont) {
this._characterWidth = dim.device.char.width;
this._characterFont = terminalFont;
this._characterOverlapCache = {};
}
// Resizing the canvas discards the contents of the canvas so clear state
this._state.clear();
this._state.resize(this._bufferService.cols, this._bufferService.rows);
}
public reset(): void {
this._state.clear();
this._clearAll();
}
private _forEachCell(
firstRow: number,
lastRow: number,
callback: (
cell: ICellData,
x: number,
y: number
) => void
): void {
for (let y = firstRow; y <= lastRow; y++) {
const row = y + this._bufferService.buffer.ydisp;
const line = this._bufferService.buffer.lines.get(row);
const joinedRanges = this._characterJoinerService.getJoinedCharacters(row);
for (let x = 0; x < this._bufferService.cols; x++) {
line!.loadCell(x, this._workCell);
let cell = this._workCell;
// If true, indicates that the current character(s) to draw were joined.
let isJoined = false;
let lastCharX = x;
// The character to the left is a wide character, drawing is owned by
// the char at x-1
if (cell.getWidth() === 0) {
continue;
}
// exit early for NULL and SP
// NOTE: commented out due to #4120 (needs a more clever patch to keep things performant)
// const code = cell.getCode();
// if (code === 0 || code === 32) {
// continue;
// }
// Process any joined character ranges as needed. Because of how the
// ranges are produced, we know that they are valid for the characters
// and attributes of our input.
if (joinedRanges.length > 0 && x === joinedRanges[0][0]) {
isJoined = true;
const range = joinedRanges.shift()!;
// We already know the exact start and end column of the joined range,
// so we get the string and width representing it directly
cell = new JoinedCellData(
this._workCell,
line!.translateToString(true, range[0], range[1]),
range[1] - range[0]
);
// Skip over the cells occupied by this range in the loop
lastCharX = range[1] - 1;
}
// If the character is an overlapping char and the character to the
// right is a space, take ownership of the cell to the right. We skip
// this check for joined characters because their rendering likely won't
// yield the same result as rendering the last character individually.
if (!isJoined && this._isOverlapping(cell)) {
// If the character is overlapping, we want to force a re-render on every
// frame. This is specifically to work around the case where two
// overlaping chars `a` and `b` are adjacent, the cursor is moved to b and a
// space is added. Without this, the first half of `b` would never
// get removed, and `a` would not re-render because it thinks it's
// already in the correct state.
// this._state.cache[x][y] = OVERLAP_OWNED_CHAR_DATA;
if (lastCharX < line!.length - 1 && line!.getCodePoint(lastCharX + 1) === NULL_CELL_CODE) {
// patch width to 2
cell.content &= ~Content.WIDTH_MASK;
cell.content |= 2 << Content.WIDTH_SHIFT;
// this._clearChar(x + 1, y);
// The overlapping char's char data will force a clear and render when the
// overlapping char is no longer to the left of the character and also when
// the space changes to another character.
// this._state.cache[x + 1][y] = OVERLAP_OWNED_CHAR_DATA;
}
}
callback(
cell,
x,
y
);
x = lastCharX;
}
}
}
/**
* Draws the background for a specified range of columns. Tries to batch adjacent cells of the
* same color together to reduce draw calls.
*/
private _drawBackground(firstRow: number, lastRow: number): void {
const ctx = this._ctx;
const cols = this._bufferService.cols;
let startX: number = 0;
let startY: number = 0;
let prevFillStyle: string | null = null;
ctx.save();
this._forEachCell(firstRow, lastRow, (cell, x, y) => {
// libvte and xterm both draw the background (but not foreground) of invisible characters,
// so we should too.
let nextFillStyle = null; // null represents default background color
if (cell.isInverse()) {
if (cell.isFgDefault()) {
nextFillStyle = this._themeService.colors.foreground.css;
} else if (cell.isFgRGB()) {
nextFillStyle = `rgb(${AttributeData.toColorRGB(cell.getFgColor()).join(',')})`;
} else {
nextFillStyle = this._themeService.colors.ansi[cell.getFgColor()].css;
}
} else if (cell.isBgRGB()) {
nextFillStyle = `rgb(${AttributeData.toColorRGB(cell.getBgColor()).join(',')})`;
} else if (cell.isBgPalette()) {
nextFillStyle = this._themeService.colors.ansi[cell.getBgColor()].css;
}
// Get any decoration foreground/background overrides, this must be fetched before the early
// exist but applied after inverse
let isTop = false;
this._decorationService.forEachDecorationAtCell(x, this._bufferService.buffer.ydisp + y, undefined, d => {
if (d.options.layer !== 'top' && isTop) {
return;
}
if (d.backgroundColorRGB) {
nextFillStyle = d.backgroundColorRGB.css;
}
isTop = d.options.layer === 'top';
});
if (prevFillStyle === null) {
// This is either the first iteration, or the default background was set. Either way, we
// don't need to draw anything.
startX = x;
startY = y;
}
if (y !== startY) {
// our row changed, draw the previous row
ctx.fillStyle = prevFillStyle || '';
this._fillCells(startX, startY, cols - startX, 1);
startX = x;
startY = y;
} else if (prevFillStyle !== nextFillStyle) {
// our color changed, draw the previous characters in this row
ctx.fillStyle = prevFillStyle || '';
this._fillCells(startX, startY, x - startX, 1);
startX = x;
startY = y;
}
prevFillStyle = nextFillStyle;
});
// flush the last color we encountered
if (prevFillStyle !== null) {
ctx.fillStyle = prevFillStyle;
this._fillCells(startX, startY, cols - startX, 1);
}
ctx.restore();
}
private _drawForeground(firstRow: number, lastRow: number): void {
this._forEachCell(firstRow, lastRow, (cell, x, y) => this._drawChars(cell, x, y));
}
public handleGridChanged(firstRow: number, lastRow: number): void {
// Resize has not been called yet
if (this._state.cache.length === 0) {
return;
}
if (this._charAtlas) {
this._charAtlas.beginFrame();
}
this._clearCells(0, firstRow, this._bufferService.cols, lastRow - firstRow + 1);
this._drawBackground(firstRow, lastRow);
this._drawForeground(firstRow, lastRow);
}
/**
* Whether a character is overlapping to the next cell.
*/
private _isOverlapping(cell: ICellData): boolean {
// Only single cell characters can be overlapping, rendering issues can
// occur without this check
if (cell.getWidth() !== 1) {
return false;
}
// We assume that any ascii character will not overlap
if (cell.getCode() < 256) {
return false;
}
const chars = cell.getChars();
// Deliver from cache if available
if (this._characterOverlapCache.hasOwnProperty(chars)) {
return this._characterOverlapCache[chars];
}
// Setup the font
this._ctx.save();
this._ctx.font = this._characterFont;
// Measure the width of the character, but Math.floor it
// because that is what the renderer does when it calculates
// the character dimensions we are comparing against
const overlaps = Math.floor(this._ctx.measureText(chars).width) > this._characterWidth;
// Restore the original context
this._ctx.restore();
// Cache and return
this._characterOverlapCache[chars] = overlaps;
return overlaps;
}
}
-86
View File
@@ -1,86 +0,0 @@
/**
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
* @license MIT
*/
import { IDisposable } from 'common/Types';
import { IEvent } from 'common/EventEmitter';
import { IRenderDimensions } from 'browser/renderer/shared/Types';
export interface IRequestRedrawEvent {
start: number;
end: number;
}
/**
* Note that IRenderer implementations should emit the refresh event after
* rendering rows to the screen.
*/
export interface IRenderer extends IDisposable {
readonly dimensions: IRenderDimensions;
/**
* Fires when the renderer is requesting to be redrawn on the next animation
* frame but is _not_ a result of content changing (eg. selection changes).
*/
readonly onRequestRedraw: IEvent<IRequestRedrawEvent>;
handleDevicePixelRatioChange(): void;
handleResize(cols: number, rows: number): void;
handleCharSizeChanged(): void;
handleBlur(): void;
handleFocus(): void;
handleSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void;
handleCursorMove(): void;
handleOptionsChanged(): void;
clear(): void;
renderRows(start: number, end: number): void;
clearTextureAtlas?(): void;
}
export interface IRenderLayer extends IDisposable {
readonly canvas: HTMLCanvasElement;
readonly cacheCanvas: HTMLCanvasElement;
readonly onAddTextureAtlasCanvas: IEvent<HTMLCanvasElement>;
/**
* Called when the terminal loses focus.
*/
handleBlur(): void;
/**
* Called when the terminal gets focus.
*/
handleFocus(): void;
/**
* Called when the cursor is moved.
*/
handleCursorMove(): void;
/**
* Called when the data in the grid has changed (or needs to be rendered
* again).
*/
handleGridChanged(startRow: number, endRow: number): void;
/**
* Calls when the selection changes.
*/
handleSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void;
/**
* Resize the render layer.
*/
resize(dim: IRenderDimensions): void;
/**
* Clear the state of the render layer.
*/
reset(): void;
/**
* Clears the texture atlas.
*/
clearTextureAtlas(): void;
}
@@ -1,37 +0,0 @@
/**
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
* @license MIT
*/
import test from '@playwright/test';
import { ISharedRendererTestContext, injectSharedRendererTests, injectSharedRendererTestsStandalone } from '../../../out-test/playwright/SharedRendererTests';
import { ITestContext, createTestContext, openTerminal } from '../../../out-test/playwright/TestUtils';
let ctx: ITestContext;
const ctxWrapper: ISharedRendererTestContext = {
value: undefined,
skipCanvasExceptions: true
} as any;
test.beforeAll(async ({ browser }) => {
ctx = await createTestContext(browser);
await openTerminal(ctx);
ctxWrapper.value = ctx;
await ctx.page.evaluate(`
window.addon = new window.CanvasAddon(true);
window.term.loadAddon(window.addon);
`);
});
test.afterAll(async () => await ctx.page.close());
test.describe('Canvas Renderer Integration Tests', () => {
// HACK: The tests fail for an unknown reason
test.skip(({ browserName }) => browserName === 'webkit');
injectSharedRendererTests(ctxWrapper);
injectSharedRendererTestsStandalone(ctxWrapper, async () => {
await ctx.page.evaluate(`
window.addon = new window.CanvasAddon(true);
window.term.loadAddon(window.addon);
`);
});
});
-43
View File
@@ -1,43 +0,0 @@
/**
* Copyright (c) 2017 The xterm.js authors. All rights reserved.
* @license MIT
*/
import { Terminal, ITerminalAddon, IEvent } from '@xterm/xterm';
declare module '@xterm/addon-canvas' {
/**
* An xterm.js addon that provides search functionality.
*/
export class CanvasAddon implements ITerminalAddon {
public textureAtlas?: HTMLCanvasElement;
/**
* An event that is fired when the texture atlas of the renderer changes.
*/
public readonly onChangeTextureAtlas: IEvent<HTMLCanvasElement>;
/**
* An event that is fired when the a new page is added to the texture atlas.
*/
public readonly onAddTextureAtlasCanvas: IEvent<HTMLCanvasElement>;
constructor();
/**
* Activates the addon.
* @param terminal The terminal the addon is being loaded in.
*/
public activate(terminal: Terminal): void;
/**
* Disposes the addon.
*/
public dispose(): void;
/**
* Clears the terminal's texture atlas and triggers a redraw.
*/
public clearTextureAtlas(): void;
}
}
+2
View File
@@ -0,0 +1,2 @@
lib
node_modules
@@ -8,6 +8,9 @@
!lib/**/*.js
!lib/**/*.js.map
!lib/**/*.mjs
!lib/**/*.mjs.map
!lib/**/*.css
# Whitelist - src/
@@ -1,4 +1,4 @@
Copyright (c) 2017, The xterm.js authors (https://github.com/xtermjs/xterm.js)
Copyright (c) 2023, The xterm.js authors (https://github.com/xtermjs/xterm.js)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
+53
View File
@@ -0,0 +1,53 @@
## @xterm/addon-clipboard
An addon for [xterm.js](https://github.com/xtermjs/xterm.js) that enables
accessing the system clipboard. This addon requires xterm.js v4+.
### Install
```bash
npm install --save @xterm/addon-clipboard
```
### Usage
```ts
import { Terminal } from 'xterm';
import { ClipboardAddon } from '@xterm/addon-clipboard';
const terminal = new Terminal();
const clipboardAddon = new ClipboardAddon();
terminal.loadAddon(clipboardAddon);
```
To use a custom clipboard provider
```ts
import { Terminal } from '@xterm/xterm';
import { ClipboardAddon, IClipboardProvider, ClipboardSelectionType } from '@xterm/addon-clipboard';
function b64Encode(data: string): string {
// Base64 encode impl
}
function b64Decode(data: string): string {
// Base64 decode impl
}
class MyCustomClipboardProvider implements IClipboardProvider {
private _data: string
public readText(selection: ClipboardSelectionType): Promise<string> {
return Promise.resolve(b64Encode(this._data));
}
public writeText(selection: ClipboardSelectionType, data: string): Promise<void> {
this._data = b64Decode(data);
return Promise.resolve();
}
}
const terminal = new Terminal();
const clipboardAddon = new ClipboardAddon(new MyCustomClipboardProvider());
terminal.loadAddon(clipboardAddon);
```
See the full [API](https://github.com/xtermjs/xterm.js/blob/master/addons/addon-clipboard/typings/addon-clipboard.d.ts) for more advanced usage.
@@ -1,17 +1,17 @@
{
"name": "@xterm/addon-canvas",
"version": "0.7.0",
"name": "@xterm/addon-clipboard",
"version": "0.2.0",
"author": {
"name": "The xterm.js authors",
"url": "https://xtermjs.org/"
},
"main": "lib/addon-canvas.js",
"types": "typings/addon-canvas.d.ts",
"repository": "https://github.com/xtermjs/xterm.js/tree/master/addons/addon-canvas",
"main": "lib/addon-clipboard.js",
"module": "lib/addon-clipboard.mjs",
"types": "typings/addon-clipboard.d.ts",
"repository": "https://github.com/xtermjs/xterm.js/tree/master/addons/addon-clipboard",
"license": "MIT",
"keywords": [
"terminal",
"canvas",
"xterm",
"xterm.js"
],
@@ -20,9 +20,9 @@
"prepackage": "npm run build",
"package": "../../node_modules/.bin/webpack",
"prepublishOnly": "npm run package",
"start-server-only": "node ../../demo/start-server-only"
"start": "node ../../demo/start"
},
"peerDependencies": {
"@xterm/xterm": "^5.0.0"
"dependencies": {
"js-base64": "^3.7.5"
}
}
@@ -0,0 +1,99 @@
/**
* Copyright (c) 2023 The xterm.js authors. All rights reserved.
* @license MIT
*/
import type { IDisposable, ITerminalAddon, Terminal } from '@xterm/xterm';
import { type IClipboardProvider, ClipboardSelectionType, type IBase64 } from '@xterm/addon-clipboard';
import { Base64 as JSBase64 } from 'js-base64';
export class ClipboardAddon implements ITerminalAddon {
private _terminal?: Terminal;
private _disposable?: IDisposable;
constructor(
private _base64: IBase64 = new Base64(),
private _provider: IClipboardProvider = new BrowserClipboardProvider()
) {}
public activate(terminal: Terminal): void {
this._terminal = terminal;
this._disposable = terminal.parser.registerOscHandler(52, data => this._setOrReportClipboard(data));
}
public dispose(): void {
return this._disposable?.dispose();
}
private _readText(sel: ClipboardSelectionType, data: string): void {
const b64 = this._base64.encodeText(data);
this._terminal?.input(`\x1b]52;${sel};${b64}\x07`, false);
}
private _setOrReportClipboard(data: string): boolean | Promise<boolean> {
const args = data.split(';');
if (args.length < 2) {
return true;
}
const pc = args[0] as ClipboardSelectionType;
const pd = args[1];
if (pd === '?') {
const text = this._provider.readText(pc);
// Report clipboard
if (text instanceof Promise) {
return text.then((data) => {
this._readText(pc, data);
return true;
});
}
this._readText(pc, text);
return true;
}
// Clear clipboard if text is not a base64 encoded string.
let text = '';
try {
text = this._base64.decodeText(pd);
} catch {}
const result = this._provider.writeText(pc, text);
if (result instanceof Promise) {
return result.then(() => true);
}
return true;
}
}
export class BrowserClipboardProvider implements IClipboardProvider {
public async readText(selection: ClipboardSelectionType): Promise<string> {
if (selection !== 'c') {
return Promise.resolve('');
}
return navigator.clipboard.readText();
}
public async writeText(selection: ClipboardSelectionType, text: string): Promise<void> {
if (selection !== 'c') {
return Promise.resolve();
}
return navigator.clipboard.writeText(text);
}
}
export class Base64 implements IBase64 {
public encodeText(data: string): string {
return JSBase64.encode(data);
}
public decodeText(data: string): string {
const text = JSBase64.decode(data);
if (!JSBase64.isValid(data) || JSBase64.encode(text) !== data) {
return '';
}
return text;
}
}
+35
View File
@@ -0,0 +1,35 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "es2021",
"lib": [
"dom",
"es2015"
],
"rootDir": ".",
"outDir": "../out",
"sourceMap": true,
"removeComments": true,
"strict": true,
"types": [
"../../../node_modules/@types/mocha"
],
"paths": {
"browser/*": [
"../../../src/browser/*"
],
"@xterm/addon-clipboard": [
"../typings/addon-clipboard.d.ts"
]
}
},
"include": [
"./**/*",
"../../../typings/xterm.d.ts"
],
"references": [
{
"path": "../../../src/browser"
}
]
}
@@ -0,0 +1,78 @@
/**
* Copyright (c) 2023 The xterm.js authors. All rights reserved.
* @license MIT
*/
import test from '@playwright/test';
import { deepEqual, ok, strictEqual } from 'assert';
import { ITestContext, createTestContext, launchBrowser, openTerminal, timeout } from '../../../test/playwright/TestUtils';
let ctx: ITestContext;
test.beforeAll(async ({ browser }, testInfo) => {
ctx = await createTestContext(browser);
await openTerminal(ctx);
});
test.afterAll(async () => {
await ctx.page.close();
});
test.describe('ClipboardAddon', () => {
test.beforeEach(async ({}, testInfo) => {
// DEBT: This test doesn't work since the migration to @playwright/test
if (ctx.browser.browserType().name() !== 'chromium') {
testInfo.skip();
return;
}
if (ctx.browser.browserType().name() === 'chromium') {
// Enable clipboard access in chromium without user gesture
await ctx.page.context().grantPermissions(['clipboard-read', 'clipboard-write']);
}
await ctx.page.evaluate(`
window.term.reset()
window.clipboard?.dispose();
window.clipboard = new ClipboardAddon();
window.term.loadAddon(window.clipboard);
`);
});
test.beforeEach(async () => {
await ctx.proxy.reset();
});
const testDataEncoded = 'aGVsbG8gd29ybGQ=';
const testDataDecoded = 'hello world';
test.describe('write data', async function (): Promise<any> {
test('simple string', async () => {
await ctx.proxy.write(`\x1b]52;c;${testDataEncoded}\x07`);
deepEqual(await ctx.page.evaluate(() => window.navigator.clipboard.readText()), testDataDecoded);
});
test('invalid base64 string', async () => {
await ctx.proxy.write(`\x1b]52;c;${testDataEncoded}invalid\x07`);
deepEqual(await ctx.page.evaluate(() => window.navigator.clipboard.readText()), '');
});
test('empty string', async () => {
await ctx.proxy.write(`\x1b]52;c;${testDataEncoded}\x07`);
await ctx.proxy.write(`\x1b]52;c;\x07`);
deepEqual(await ctx.page.evaluate(() => window.navigator.clipboard.readText()), '');
});
});
test.describe('read data', async function (): Promise<any> {
test('simple string', async () => {
await ctx.page.evaluate(`
window.data = [];
window.term.onData(e => data.push(e));
`);
await ctx.page.evaluate(() => window.navigator.clipboard.writeText('hello world'));
await ctx.proxy.write(`\x1b]52;c;?\x07`);
deepEqual(await ctx.page.evaluate('window.data'), [`\x1b]52;c;${testDataEncoded}\x07`]);
});
test('clear clipboard', async () => {
await ctx.proxy.write(`\x1b]52;c;!\x07`);
await ctx.proxy.write(`\x1b]52;c;?\x07`);
deepEqual(await ctx.page.evaluate(() => window.navigator.clipboard.readText()), '');
});
});
});
@@ -0,0 +1,35 @@
import { PlaywrightTestConfig } from '@playwright/test';
const config: PlaywrightTestConfig = {
testDir: '.',
timeout: 10000,
projects: [
{
name: 'ChromeStable',
use: {
browserName: 'chromium',
channel: 'chrome'
}
},
{
name: 'FirefoxStable',
use: {
browserName: 'firefox'
}
},
{
name: 'WebKit',
use: {
browserName: 'webkit'
}
}
],
reporter: 'list',
webServer: {
command: 'npm run start',
port: 3000,
timeout: 120000,
reuseExistingServer: !process.env.CI
}
};
export default config;
@@ -1,9 +1,9 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "es2015",
"target": "ESNext",
"lib": [
"es6",
"es2021",
],
"rootDir": ".",
"outDir": "../out-test",
@@ -20,10 +20,7 @@
},
"strict": true,
"types": [
"../../../node_modules/@types/node",
"../../../node_modules/@lunapaint/png-codec",
"../../../out-test/playwright/TestUtils",
"../../../out-test/playwright/SharedRendererTests"
"../../../node_modules/@types/node"
]
},
"include": [
@@ -36,6 +33,9 @@
},
{
"path": "../../../src/browser"
},
{
"path": "../../../test/playwright"
}
]
}
+113
View File
@@ -0,0 +1,113 @@
/**
* Copyright (c) 2023 The xterm.js authors. All rights reserved.
* @license MIT
*/
import { Terminal, ITerminalAddon } from '@xterm/xterm';
declare module '@xterm/addon-clipboard' {
/**
* An xterm.js addon that enables accessing the system clipboard from
* xterm.js.
*/
export class ClipboardAddon implements ITerminalAddon {
/**
* Creates a new clipboard addon.
* @param base64 An optional base64 encoder/decoder.
* @param provider An optional clipboard provider.
*/
constructor(base64?: IBase64, provider?: IClipboardProvider);
/**
* Activates the addon
* @param terminal The terminal the addon is being loaded in.
*/
public activate(terminal: Terminal): void;
/**
* Disposes the addon.
*/
public dispose(): void
}
/**
* Clipboard selection type. This is used to specify which selection buffer to
* read or write to.
* - SYSTEM `c`: The system clipboard.
* - PRIMARY `p`: The primary clipboard. This is provided for compatibility
* with Linux X11.
*/
export const enum ClipboardSelectionType {
SYSTEM = 'c',
PRIMARY = 'p',
}
export interface IBase64 {
/**
* Converts a utf-8 string to a base64 string.
* @param data The utf-8 string to convert to base64 string.
*/
encodeText(data: string): string;
/**
* Converts a base64 string to a utf-8 string.
* @param data The base64 string to convert to utf-8 string.
* @throws An error if the input is not valid base64.
*/
decodeText(data: string): string;
}
/**
* A default Base64 encoding and decoding type.
**/
export class Base64 implements IBase64 {
/**
* Converts a utf-8 string to a base64 string.
* @param data The utf-8 string to convert to base64 string.
*/
public encodeText(data: string): string;
/**
* Converts a base64 string to a utf-8 string.
* @param data The base64 string to convert to utf-8 string.
* @throws An error if the input is not valid base64.
*/
public decodeText(data: string): string;
}
export interface IClipboardProvider {
/**
* Gets the clipboard content.
* @param selection The clipboard selection to read.
* @returns A promise that resolves with clipboard selection data.
*/
readText(selection: ClipboardSelectionType): string | Promise<string>;
/**
* Sets the clipboard content.
* @param selection The clipboard selection to set.
* @param data The clipboard text to write.
*/
writeText(selection: ClipboardSelectionType, text: string): void | Promise<void>;
}
/**
* The clipboard provider interface that enables xterm.js to access the system clipboard.
*/
export class BrowserClipboardProvider implements IClipboardProvider{
/**
* Reads text from the clipboard.
* @param selection The selection type to read from.
* @returns A promise that resolves with the text from the clipboard.
*/
public readText(selection: ClipboardSelectionType): Promise<string>;
/**
* Writes text to the clipboard.
* @param selection The selection type to write to.
* @param data The text to write to the clipboard.
* @returns A promise that resolves when the text has been written to the clipboard.
*/
public writeText(selection: ClipboardSelectionType, data: string): Promise<void>;
}
}
+31
View File
@@ -0,0 +1,31 @@
/**
* Copyright (c) 2023 The xterm.js authors. All rights reserved.
* @license MIT
*/
const path = require('path');
const addonName = 'ClipboardAddon';
const mainFile = 'addon-clipboard.js';
module.exports = {
entry: `./out/${addonName}.js`,
devtool: 'source-map',
module: {
rules: [
{
test: /\.js$/,
use: ["source-map-loader"],
enforce: "pre",
exclude: /node_modules/
}
]
},
output: {
filename: mainFile,
path: path.resolve('./lib'),
library: addonName,
libraryTarget: 'umd'
},
mode: 'production'
};
+3
View File
@@ -8,6 +8,9 @@
!lib/**/*.js
!lib/**/*.js.map
!lib/**/*.mjs
!lib/**/*.mjs.map
!lib/**/*.css
# Whitelist - src/
+4 -5
View File
@@ -1,11 +1,12 @@
{
"name": "@xterm/addon-fit",
"version": "0.10.0",
"version": "0.11.0",
"author": {
"name": "The xterm.js authors",
"url": "https://xtermjs.org/"
},
"main": "lib/addon-fit.js",
"module": "lib/addon-fit.mjs",
"types": "typings/addon-fit.d.ts",
"repository": "https://github.com/xtermjs/xterm.js/tree/master/addons/addon-fit",
"license": "MIT",
@@ -18,9 +19,7 @@
"build": "../../node_modules/.bin/tsc -p .",
"prepackage": "npm run build",
"package": "../../node_modules/.bin/webpack",
"prepublishOnly": "npm run package"
},
"peerDependencies": {
"@xterm/xterm": "^5.0.0"
"prepublishOnly": "npm run package",
"start": "node ../../demo/start"
}
}
+20 -14
View File
@@ -3,9 +3,9 @@
* @license MIT
*/
import type { Terminal, ITerminalAddon } from '@xterm/xterm';
import type { Terminal, ITerminalAddon, IRenderDimensions } from '@xterm/xterm';
import type { FitAddon as IFitApi } from '@xterm/addon-fit';
import { IRenderDimensions } from 'browser/renderer/shared/Types';
import { ViewportConstants } from 'browser/shared/Constants';
interface ITerminalDimensions {
/**
@@ -22,6 +22,17 @@ interface ITerminalDimensions {
const MINIMUM_COLS = 2;
const MINIMUM_ROWS = 1;
function getWindow(e: Node): Window {
if (e?.ownerDocument?.defaultView) {
return e.ownerDocument.defaultView;
}
return window;
}
function _getComputedStyle(el: HTMLElement): CSSStyleDeclaration {
return getWindow(el).getComputedStyle(el, null);
}
export class FitAddon implements ITerminalAddon , IFitApi {
private _terminal: Terminal | undefined;
@@ -37,12 +48,8 @@ export class FitAddon implements ITerminalAddon , IFitApi {
return;
}
// TODO: Remove reliance on private API
const core = (this._terminal as any)._core;
// Force a full render
if (this._terminal.rows !== dims.rows || this._terminal.cols !== dims.cols) {
core._renderService.clear();
this._terminal.resize(dims.cols, dims.rows);
}
}
@@ -56,21 +63,20 @@ export class FitAddon implements ITerminalAddon , IFitApi {
return undefined;
}
// TODO: Remove reliance on private API
const core = (this._terminal as any)._core;
const dims: IRenderDimensions = core._renderService.dimensions;
const dims: IRenderDimensions | undefined = this._terminal.dimensions;
if (dims.css.cell.width === 0 || dims.css.cell.height === 0) {
if (!dims || dims.css.cell.width === 0 || dims.css.cell.height === 0) {
return undefined;
}
const scrollbarWidth = this._terminal.options.scrollback === 0 ?
0 : core.viewport.scrollBarWidth;
const scrollbarWidth = (this._terminal.options.scrollback === 0
? 0
: (this._terminal.options.overviewRuler?.width || ViewportConstants.DEFAULT_SCROLL_BAR_WIDTH));
const parentElementStyle = window.getComputedStyle(this._terminal.element.parentElement);
const parentElementStyle = _getComputedStyle(this._terminal.element.parentElement);
const parentElementHeight = parseInt(parentElementStyle.getPropertyValue('height'));
const parentElementWidth = Math.max(0, parseInt(parentElementStyle.getPropertyValue('width')));
const elementStyle = window.getComputedStyle(this._terminal.element);
const elementStyle = _getComputedStyle(this._terminal.element);
const elementPadding = {
top: parseInt(elementStyle.getPropertyValue('padding-top')),
bottom: parseInt(elementStyle.getPropertyValue('padding-bottom')),
-135
View File
@@ -1,135 +0,0 @@
/**
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
* @license MIT
*/
import { assert } from 'chai';
import { openTerminal, launchBrowser, timeout } from '../../../out-test/api/TestUtils';
import { Browser, Page } from '@playwright/test';
const APP = 'http://127.0.0.1:3001/test';
let browser: Browser;
let page: Page;
const width = 1024;
const height = 768;
describe('FitAddon', () => {
before(async function(): Promise<any> {
browser = await launchBrowser();
page = await (await browser.newContext()).newPage();
await page.setViewportSize({ width, height });
await page.goto(APP);
});
beforeEach(async function(): Promise<any> {
await page.evaluate(`document.querySelector('#terminal-container').style.display=''`);
await openTerminal(page);
});
after(async () => {
await browser.close();
});
afterEach(async function(): Promise<any> {
await page.evaluate(`window.term.dispose()`);
});
it('no terminal', async function(): Promise<any> {
await page.evaluate(`window.fit = new FitAddon();`);
assert.equal(await page.evaluate(`window.fit.proposeDimensions()`), undefined);
});
describe('proposeDimensions', () => {
afterEach(() => unloadFit());
it('default', async function(): Promise<any> {
await loadFit();
const dimensions: {cols: number, rows: number} = await page.evaluate(`window.fit.proposeDimensions()`);
assert.isAbove(dimensions.cols, 85);
assert.isBelow(dimensions.cols, 88);
assert.isAbove(dimensions.rows, 24);
assert.isBelow(dimensions.rows, 29);
});
it('width', async function(): Promise<any> {
await loadFit(1008);
const dimensions: {cols: number, rows: number} = await page.evaluate(`window.fit.proposeDimensions()`);
assert.isAbove(dimensions.cols, 108);
assert.isBelow(dimensions.cols, 111);
assert.isAbove(dimensions.rows, 24);
assert.isBelow(dimensions.rows, 29);
});
it('small', async function(): Promise<any> {
await loadFit(1, 1);
assert.deepEqual(await page.evaluate(`window.fit.proposeDimensions()`), {
cols: 2,
rows: 1
});
});
it('hidden', async function(): Promise<any> {
await page.evaluate(`window.term.dispose()`);
await page.evaluate(`document.querySelector('#terminal-container').style.display='none'`);
await page.evaluate(`window.term = new Terminal()`);
await page.evaluate(`window.term.open(document.querySelector('#terminal-container'))`);
await loadFit();
const dimensions: { cols: number, rows: number } | undefined = await page.evaluate(`window.fit.proposeDimensions()`);
// The value of dims will be undefined if the char measure strategy falls back to the DOM
// method, so only assert if it's not undefined.
if (dimensions) {
assert.isAbove(dimensions.cols, 85);
assert.isBelow(dimensions.cols, 88);
assert.isAbove(dimensions.rows, 24);
assert.isBelow(dimensions.rows, 29);
}
});
});
describe('fit', () => {
afterEach(() => unloadFit());
it('default', async function(): Promise<any> {
await loadFit();
await page.evaluate(`window.fit.fit()`);
const cols: number = await page.evaluate(`window.term.cols`);
const rows: number = await page.evaluate(`window.term.rows`);
assert.isAbove(cols, 85);
assert.isBelow(cols, 88);
assert.isAbove(rows, 24);
assert.isBelow(rows, 29);
});
it('width', async function(): Promise<any> {
await loadFit(1008);
await page.evaluate(`window.fit.fit()`);
const cols: number = await page.evaluate(`window.term.cols`);
const rows: number = await page.evaluate(`window.term.rows`);
assert.isAbove(cols, 108);
assert.isBelow(cols, 111);
assert.isAbove(rows, 24);
assert.isBelow(rows, 29);
});
it('small', async function(): Promise<any> {
await loadFit(1, 1);
await page.evaluate(`window.fit.fit()`);
assert.equal(await page.evaluate(`window.term.cols`), 2);
assert.equal(await page.evaluate(`window.term.rows`), 1);
});
});
});
async function loadFit(width: number = 800, height: number = 450): Promise<void> {
await page.evaluate(`
window.fit = new FitAddon();
window.term.loadAddon(window.fit);
document.querySelector('#terminal-container').style.width='${width}px';
document.querySelector('#terminal-container').style.height='${height}px';
`);
}
async function unloadFit(): Promise<void> {
await page.evaluate(`window.fit.dispose();`);
}
+133
View File
@@ -0,0 +1,133 @@
/**
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
* @license MIT
*/
import test from '@playwright/test';
import { deepEqual, ok, strictEqual } from 'assert';
import { ITestContext, createTestContext, openTerminal, timeout } from '../../../test/playwright/TestUtils';
let ctx: ITestContext;
test.beforeAll(async ({ browser }) => {
ctx = await createTestContext(browser);
ctx.page.setViewportSize({ width: 1024, height: 768 });
await openTerminal(ctx);
});
test.afterAll(async () => await ctx.page.close());
test.describe('FitAddon', () => {
test.beforeEach(async function(): Promise<any> {
await ctx.page.evaluate(`document.querySelector('#terminal-container').style.display=''`);
await ctx.page.evaluate(`
window.term.reset()
window.fit?.dispose();
window.fit = new FitAddon();
window.term.loadAddon(window.fit);
`);
});
// test.beforeEach(async function(): Promise<any> {
// await ctx.page.evaluate(`document.querySelector('#terminal-container').style.display=''`);
// await openTerminal(page);
// });
// after(async () => {
// await browser.close();
// });
// afterEach(async function(): Promise<any> {
// await ctx.proxy.dispose();
// });
test('no terminal', async function(): Promise<any> {
await ctx.page.evaluate(`window.fit2 = new FitAddon();`);
strictEqual(await ctx.page.evaluate(`window.fit2.proposeDimensions()`), undefined);
await ctx.page.evaluate(`window.fit2.dispose();`);
});
test.describe('proposeDimensions', () => {
test('default', async function(): Promise<any> {
await setDimensions();
const dimensions: {cols: number, rows: number} = await ctx.page.evaluate(`window.fit.proposeDimensions()`);
ok(dimensions.cols > 85);
ok(dimensions.cols < 88);
ok(dimensions.rows > 24);
ok(dimensions.rows < 29);
});
test('width', async function(): Promise<any> {
await setDimensions(1008);
const dimensions: {cols: number, rows: number} = await ctx.page.evaluate(`window.fit.proposeDimensions()`);
ok(dimensions.cols > 108);
ok(dimensions.cols < 111);
ok(dimensions.rows > 24);
ok(dimensions.rows < 29);
});
test('small', async function(): Promise<any> {
await setDimensions(1, 1);
deepEqual(await ctx.page.evaluate(`window.fit.proposeDimensions()`), {
cols: 2,
rows: 1
});
});
test('hidden', async function(): Promise<any> {
await ctx.proxy.dispose();
await ctx.page.evaluate(`document.querySelector('#terminal-container').style.display='none'`);
await ctx.page.evaluate(`window.term = new Terminal()`);
await ctx.page.evaluate(`window.term.open(document.querySelector('#terminal-container'))`);
await setDimensions();
const dimensions: { cols: number, rows: number } | undefined = await ctx.page.evaluate(`window.fit.proposeDimensions()`);
// The value of dims will be undefined if the char measure strategy falls back to the DOM
// method, so only assert if it's not undefined.
if (dimensions) {
ok(dimensions.cols > 85);
ok(dimensions.cols < 88);
ok(dimensions.rows > 24);
ok(dimensions.rows < 29);
}
});
});
test.describe('fit', () => {
test('default', async function(): Promise<any> {
await setDimensions();
await ctx.page.evaluate(`window.fit.fit()`);
const cols: number = await ctx.proxy.cols;
const rows: number = await ctx.proxy.rows;
ok(cols > 85);
ok(cols < 88);
ok(rows > 24);
ok(rows < 29);
});
test('width', async function(): Promise<any> {
await setDimensions(1008);
await ctx.page.evaluate(`window.fit.fit()`);
const cols: number = await ctx.proxy.cols;
const rows: number = await ctx.proxy.rows;
ok(cols > 108);
ok(cols < 111);
ok(rows > 24);
ok(rows < 29);
});
test('small', async function(): Promise<any> {
await setDimensions(1, 1);
await ctx.page.evaluate(`window.fit.fit()`);
strictEqual(await ctx.proxy.cols, 2);
strictEqual(await ctx.proxy.rows, 1);
});
});
});
async function setDimensions(width: number = 800, height: number = 450): Promise<void> {
await ctx.page.evaluate(`
document.querySelector('#terminal-container').style.width='${width}px';
document.querySelector('#terminal-container').style.height='${height}px';
document.querySelector('#terminal-container').style.display='';
`);
// HACK: Await a short period as hiding #terminal-container can mess with other tests
await timeout(500);
}
@@ -0,0 +1,35 @@
import { PlaywrightTestConfig } from '@playwright/test';
const config: PlaywrightTestConfig = {
testDir: '.',
timeout: 10000,
projects: [
{
name: 'ChromeStable',
use: {
browserName: 'chromium',
channel: 'chrome'
}
},
{
name: 'FirefoxStable',
use: {
browserName: 'firefox'
}
},
{
name: 'WebKit',
use: {
browserName: 'webkit'
}
}
],
reporter: 'list',
webServer: {
command: 'npm run start',
port: 3000,
timeout: 120000,
reuseExistingServer: !process.env.CI
}
};
export default config;
+23 -5
View File
@@ -1,23 +1,41 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "es2021",
"target": "ESNext",
"lib": [
"es2015"
"es2021",
],
"rootDir": ".",
"outDir": "../out-test",
"sourceMap": true,
"removeComments": true,
"baseUrl": ".",
"paths": {
"common/*": [
"../../../src/common/*"
],
"browser/*": [
"../../../src/browser/*"
]
},
"strict": true,
"types": [
"../../../node_modules/@types/mocha",
"../../../node_modules/@types/node",
"../../../out-test/api/TestUtils"
"../../../node_modules/@types/node"
]
},
"include": [
"./**/*",
"../../../typings/xterm.d.ts"
],
"references": [
{
"path": "../../../src/common"
},
{
"path": "../../../src/browser"
},
{
"path": "../../../test/playwright"
}
]
}
+1 -1
View File
@@ -39,7 +39,7 @@ declare module '@xterm/addon-fit' {
}
/**
* Reprepresents the dimensions of a terminal.
* Represents the dimensions of a terminal.
*/
export interface ITerminalDimensions {
/**
+3 -1
View File
@@ -25,7 +25,9 @@ module.exports = {
filename: mainFile,
path: path.resolve('./lib'),
library: addonName,
libraryTarget: 'umd'
libraryTarget: 'umd',
// Force usage of globalThis instead of global / self. (This is cross-env compatible)
globalObject: 'globalThis',
},
mode: 'production'
};
-1
View File
@@ -17,7 +17,6 @@ npm-debug.log
.env
build/
.DS_Store
package-lock.json
yarn.lock
# Keep bundled code out of Git
+2
View File
@@ -6,5 +6,7 @@ tsconfig.json
webpack.config.js
test
out-test
out-esbuild
out-esbuild-test
inwasm-sdks
inwasm-builds
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

+4 -5
View File
@@ -1,11 +1,12 @@
{
"name": "@xterm/addon-image",
"version": "0.8.0",
"version": "0.9.0",
"author": {
"name": "The xterm.js authors",
"url": "https://xtermjs.org/"
},
"main": "lib/addon-image.js",
"module": "lib/addon-image.mjs",
"types": "typings/addon-image.d.ts",
"repository": "https://github.com/xtermjs/xterm.js/tree/master/addons/addon-image",
"license": "MIT",
@@ -19,10 +20,8 @@
"scripts": {
"prepackage": "../../node_modules/.bin/tsc -p .",
"package": "../../node_modules/.bin/webpack",
"prepublishOnly": "npm run package"
},
"peerDependencies": {
"@xterm/xterm": "^5.2.0"
"prepublishOnly": "npm run package",
"start": "node ../../demo/start"
},
"devDependencies": {
"sixel": "^0.16.0",
+1 -1
View File
@@ -105,7 +105,7 @@ export class IIPHandler implements IOscHandler, IResetHandler {
return true;
}
const blob = new Blob([this._dec.data8], { type: this._metrics.mime });
const blob = new Blob([new Uint8Array(this._dec.data8)], { type: this._metrics.mime });
this._dec.release();
if (!window.createImageBitmap) {
+1 -1
View File
@@ -176,7 +176,7 @@ export class HeaderParser {
try {
const v = this._buffer.slice(0, pos);
this.fields[this._key] = DECODERS[this._key] ? DECODERS[this._key](v) : v;
} catch (e) {
} catch {
return false;
}
return true;
@@ -25,6 +25,7 @@ const TEST_IMAGES: [string, IMetrics][] = [
['w3c_home_gray.png', { mime: 'image/png', width: 72, height: 48 }],
['w3c_home.jpg', { mime: 'image/jpeg', width: 72, height: 48 }],
['w3c_home.png', { mime: 'image/png', width: 72, height: 48 }],
['w3c_home_noexif.jpg', { mime: 'image/jpeg', width: 72, height: 48 }],
['spinfox.png', { mime: 'image/png', width: 148, height: 148 }],
['iphone_hdr_YES.jpg', { mime: 'image/jpeg', width: 3264, height: 2448 }],
['nikon-e950.jpg', { mime: 'image/jpeg', width: 800, height: 600 }],
+2 -7
View File
@@ -32,13 +32,8 @@ export function imageType(d: Uint8Array): IMetrics {
height: d[20] << 24 | d[21] << 16 | d[22] << 8 | d[23]
};
}
// JPEG: FF D8 FF E0 xx xx JFIF or FF D8 FF E1 xx xx Exif 00 00
if ((d32[0] === 0xE0FFD8FF || d32[0] === 0xE1FFD8FF)
&& (
(d[6] === 0x4a && d[7] === 0x46 && d[8] === 0x49 && d[9] === 0x46)
|| (d[6] === 0x45 && d[7] === 0x78 && d[8] === 0x69 && d[9] === 0x66)
)
) {
// JPEG: FF D8 FF
if (d[0] === 0xFF && d[1] === 0xD8 && d[2] === 0xFF) {
const [width, height] = jpgSize(d);
return { mime: 'image/jpeg', width, height };
}
+4 -5
View File
@@ -6,8 +6,7 @@
import { toRGBA8888 } from 'sixel/lib/Colors';
import { IDisposable } from '@xterm/xterm';
import { ICellSize, ITerminalExt, IImageSpec, IRenderDimensions, IRenderService } from './Types';
import { Disposable, MutableDisposable, toDisposable } from 'common/Lifecycle';
import { Disposable, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle';
const PLACEHOLDER_LENGTH = 4096;
const PLACEHOLDER_HEIGHT = 24;
@@ -23,7 +22,7 @@ export class ImageRenderer extends Disposable implements IDisposable {
private _ctx: CanvasRenderingContext2D | null | undefined;
private _placeholder: HTMLCanvasElement | undefined;
private _placeholderBitmap: ImageBitmap | undefined;
private _optionsRefresh = this.register(new MutableDisposable());
private _optionsRefresh = this._register(new MutableDisposable());
private _oldOpen: ((parent: HTMLElement) => void) | undefined;
private _renderService: IRenderService | undefined;
private _oldSetRenderer: ((renderer: any) => void) | undefined;
@@ -85,7 +84,7 @@ export class ImageRenderer extends Disposable implements IDisposable {
this._renderService?.refreshRows(0, this._terminal.rows);
}
});
this.register(toDisposable(() => {
this._register(toDisposable(() => {
this.removeLayerFromDom();
if (this._terminal._core && this._oldOpen) {
this._terminal._core.open = this._oldOpen;
@@ -125,7 +124,7 @@ export class ImageRenderer extends Disposable implements IDisposable {
* Forwarded from internal render service.
*/
public get dimensions(): IRenderDimensions | undefined {
return this._renderService?.dimensions;
return this._terminal.dimensions;
}
/**
+1 -1
View File
@@ -99,7 +99,7 @@ export class SixelHandler implements IDcsHandler, IResetHandler {
}
const canvas = ImageRenderer.createCanvas(undefined, width, height);
canvas.getContext('2d')?.putImageData(new ImageData(this._dec.data8, width, height), 0, 0);
canvas.getContext('2d')?.putImageData(new ImageData(this._dec.data8 as Uint8ClampedArray<ArrayBuffer>, width, height), 0, 0);
if (this._dec.memoryUsage > MEM_PERMA_LIMIT) {
this._dec.release();
}
+3 -1
View File
@@ -15,6 +15,7 @@
"paths": {
"browser/*": [ "../../../src/browser/*" ],
"common/*": [ "../../../src/common/*" ],
"vs/*": [ "../../../src/vs/*" ],
"@xterm/addon-image": [ "../typings/addon-image.d.ts" ]
}
},
@@ -24,6 +25,7 @@
],
"references": [
{ "path": "../../../src/browser" },
{ "path": "../../../src/common" }
{ "path": "../../../src/common" },
{ "path": "../../../src/vs" }
]
}
-321
View File
@@ -1,321 +0,0 @@
/**
* Copyright (c) 2020 The xterm.js authors. All rights reserved.
* @license MIT
*/
import { assert } from 'chai';
import { openTerminal, launchBrowser, pollFor } from '../../../out-test/api/TestUtils';
import { Browser, Page } from '@playwright/test';
import { IImageAddonOptions } from '../src/Types';
import { FINALIZER, introducer, sixelEncode } from 'sixel';
import { readFileSync } from 'fs';
const APP = 'http://127.0.0.1:3001/test';
let browser: Browser;
let page: Page;
const width = 800;
const height = 600;
// eslint-disable-next-line
declare const ImageAddon: {
new(options?: Partial<IImageAddonOptions>): any;
};
interface ITestData {
width: number;
height: number;
bytes: Uint8Array;
palette: number[];
sixel: string;
}
interface IDimensions {
cellWidth: number;
cellHeight: number;
width: number;
height: number;
}
// image: 640 x 80, 512 color
const TESTDATA: ITestData = (() => {
const data8 = readFileSync('./addons/addon-image/fixture/palette.blob');
const data32 = new Uint32Array(data8.buffer);
const palette = new Set<number>();
for (let i = 0; i < data32.length; ++i) palette.add(data32[i]);
const sixel = sixelEncode(data8, 640, 80, [...palette]);
return {
width: 640,
height: 80,
bytes: data8,
palette: [...palette],
sixel
};
})();
const SIXEL_SEQ_0 = introducer(0) + TESTDATA.sixel + FINALIZER;
// const SIXEL_SEQ_1 = introducer(1) + TESTDATA.sixel + FINALIZER;
// const SIXEL_SEQ_2 = introducer(2) + TESTDATA.sixel + FINALIZER;
// NOTE: the data is loaded as string for easier transport through playwright
const TESTDATA_IIP: [string, [number, number]][] = [
[readFileSync('./addons/addon-image/fixture/iip/palette.iip', { encoding: 'utf-8' }), [640, 80]],
[readFileSync('./addons/addon-image/fixture/iip/spinfox.iip', { encoding: 'utf-8' }), [148, 148]],
[readFileSync('./addons/addon-image/fixture/iip/w3c_gif.iip', { encoding: 'utf-8' }), [72, 48]],
[readFileSync('./addons/addon-image/fixture/iip/w3c_jpg.iip', { encoding: 'utf-8' }), [72, 48]],
[readFileSync('./addons/addon-image/fixture/iip/w3c_png.iip', { encoding: 'utf-8' }), [72, 48]]
];
describe('ImageAddon', () => {
before(async () => {
browser = await launchBrowser();
page = await (await browser.newContext()).newPage();
await page.setViewportSize({ width, height });
});
after(async () => {
await browser.close();
});
beforeEach(async () => {
await page.goto(APP);
await openTerminal(page);
await page.evaluate(opts => {
(window as any).imageAddon = new ImageAddon(opts.opts);
(window as any).term.loadAddon((window as any).imageAddon);
}, { opts: { sixelPaletteLimit: 512 } });
});
it('test for private accessors', async () => {
// terminal privates
const accessors = [
'_core',
'_core._renderService',
'_core._inputHandler',
'_core._inputHandler._parser',
'_core._inputHandler._curAttrData',
'_core._inputHandler._dirtyRowTracker',
'_core._themeService.colors',
'_core._coreBrowserService'
];
for (const prop of accessors) {
assert.equal(
await page.evaluate('(() => { const v = window.term.' + prop + '; return v !== undefined && v !== null; })()'),
true, `problem at ${prop}`
);
}
// bufferline privates
assert.equal(await page.evaluate('window.term._core.buffer.lines.get(0)._data instanceof Uint32Array'), true);
assert.equal(await page.evaluate('window.term._core.buffer.lines.get(0)._extendedAttrs instanceof Object'), true);
// inputhandler privates
assert.equal(await page.evaluate('window.term._core._inputHandler._curAttrData.constructor.name'), 'AttributeData');
assert.equal(await page.evaluate('window.term._core._inputHandler._parser.constructor.name'), 'EscapeSequenceParser');
});
describe('ctor options', () => {
it('empty settings should load defaults', async () => {
const DEFAULT_OPTIONS: IImageAddonOptions = {
enableSizeReports: true,
pixelLimit: 16777216,
sixelSupport: true,
sixelScrolling: true,
sixelPaletteLimit: 512, // set to 512 to get example image working
sixelSizeLimit: 25000000,
storageLimit: 128,
showPlaceholder: true,
iipSupport: true,
iipSizeLimit: 20000000
};
assert.deepEqual(await page.evaluate(`window.imageAddon._opts`), DEFAULT_OPTIONS);
});
it('custom settings should overload defaults', async () => {
const customSettings: IImageAddonOptions = {
enableSizeReports: false,
pixelLimit: 5,
sixelSupport: false,
sixelScrolling: false,
sixelPaletteLimit: 1024,
sixelSizeLimit: 1000,
storageLimit: 10,
showPlaceholder: false,
iipSupport: false,
iipSizeLimit: 1000
};
await page.evaluate(opts => {
(window as any).imageAddonCustom = new ImageAddon(opts.opts);
(window as any).term.loadAddon((window as any).imageAddonCustom);
}, { opts: customSettings });
assert.deepEqual(await page.evaluate(`window.imageAddonCustom._opts`), customSettings);
});
});
describe('scrolling & cursor modes', () => {
it('testdata default (scrolling with VT240 cursor pos)', async () => {
const dim = await getDimensions();
await writeToTerminal(SIXEL_SEQ_0);
assert.deepEqual(await getCursor(), [0, Math.floor(TESTDATA.height/dim.cellHeight)]);
// moved to right by 10 cells
await writeToTerminal('#'.repeat(10) + SIXEL_SEQ_0);
assert.deepEqual(await getCursor(), [10, Math.floor(TESTDATA.height/dim.cellHeight) * 2]);
});
it('write testdata noScrolling', async () => {
await writeToTerminal('\x1b[?80h' + SIXEL_SEQ_0);
assert.deepEqual(await getCursor(), [0, 0]);
// second draw does not change anything
await writeToTerminal(SIXEL_SEQ_0);
assert.deepEqual(await getCursor(), [0, 0]);
});
it('testdata cursor always at VT240 pos', async () => {
const dim = await getDimensions();
// offset 0
await writeToTerminal(SIXEL_SEQ_0);
assert.deepEqual(await getCursor(), [0, Math.floor(TESTDATA.height/dim.cellHeight)]);
// moved to right by 10 cells
await writeToTerminal('#'.repeat(10) + SIXEL_SEQ_0);
assert.deepEqual(await getCursor(), [10, Math.floor(TESTDATA.height/dim.cellHeight) * 2]);
// moved by 30 cells (+10 prev)
await writeToTerminal('#'.repeat(30) + SIXEL_SEQ_0);
assert.deepEqual(await getCursor(), [10 + 30, Math.floor(TESTDATA.height/dim.cellHeight) * 3]);
});
});
describe('image lifecycle & eviction', () => {
it('delete image once scrolled off', async () => {
await writeToTerminal(SIXEL_SEQ_0);
pollFor(page, 'window.imageAddon._storage._images.size', 1);
// scroll to scrollback + rows - 1
await page.evaluate(
scrollback => new Promise(res => (window as any).term.write('\n'.repeat(scrollback), res)),
(await getScrollbackPlusRows() - 1)
);
// wait here, as we have to make sure, that eviction did not yet occur
await new Promise(r => setTimeout(r, 100));
pollFor(page, 'window.imageAddon._storage._images.size', 1);
// scroll one further should delete the image
await page.evaluate(() => new Promise(res => (window as any).term.write('\n', res)));
pollFor(page, 'window.imageAddon._storage._images.size', 0);
});
it('get storageUsage', async () => {
assert.equal(await page.evaluate('imageAddon.storageUsage'), 0);
await writeToTerminal(SIXEL_SEQ_0);
assert.closeTo(await page.evaluate('imageAddon.storageUsage'), 640 * 80 * 4 / 1000000, 0.05);
});
it('get/set storageLimit', async () => {
assert.equal(await page.evaluate('imageAddon.storageLimit'), 128);
assert.equal(await page.evaluate('imageAddon.storageLimit = 1'), 1);
assert.equal(await page.evaluate('imageAddon.storageLimit'), 1);
});
it('remove images by storage limit pressure', async () => {
assert.equal(await page.evaluate('imageAddon.storageLimit = 1'), 1);
// never go beyond storage limit
await writeToTerminal(SIXEL_SEQ_0);
await writeToTerminal(SIXEL_SEQ_0);
await writeToTerminal(SIXEL_SEQ_0);
await writeToTerminal(SIXEL_SEQ_0);
await new Promise(r => setTimeout(r, 50));
const usage = await page.evaluate('imageAddon.storageUsage');
await writeToTerminal(SIXEL_SEQ_0);
await writeToTerminal(SIXEL_SEQ_0);
await writeToTerminal(SIXEL_SEQ_0);
await writeToTerminal(SIXEL_SEQ_0);
await new Promise(r => setTimeout(r, 50));
assert.equal(await page.evaluate('imageAddon.storageUsage'), usage);
assert.equal(usage as number < 1, true);
});
it('set storageLimit removes images synchronously', async () => {
await writeToTerminal(SIXEL_SEQ_0 + SIXEL_SEQ_0 + SIXEL_SEQ_0);
const usage: number = await page.evaluate('imageAddon.storageUsage');
const newUsage: number = await page.evaluate('imageAddon.storageLimit = 0.5; imageAddon.storageUsage');
assert.equal(newUsage < usage, true);
assert.equal(newUsage < 0.5, true);
});
it('clear alternate images on buffer change', async () => {
assert.equal(await page.evaluate('imageAddon.storageUsage'), 0);
await writeToTerminal('\x1b[?1049h' + SIXEL_SEQ_0);
assert.closeTo(await page.evaluate('imageAddon.storageUsage'), 640 * 80 * 4 / 1000000, 0.05);
await writeToTerminal('\x1b[?1049l');
assert.equal(await page.evaluate('imageAddon.storageUsage'), 0);
});
it('evict tiles by in-place overwrites (only full overwrite tested)', async () => {
await new Promise(r => setTimeout(r, 50));
await writeToTerminal('\x1b[H' + SIXEL_SEQ_0 + '\x1b[100;100H');
let usage = await page.evaluate('imageAddon.storageUsage');
while (usage === 0) {
await new Promise(r => setTimeout(r, 50));
usage = await page.evaluate('imageAddon.storageUsage');
}
await writeToTerminal('\x1b[H' + SIXEL_SEQ_0 + '\x1b[100;100H');
await new Promise(r => setTimeout(r, 200)); // wait some time and re-check
assert.equal(await page.evaluate('imageAddon.storageUsage'), usage);
});
it('manual eviction on alternate buffer must not miss images', async () => {
await writeToTerminal('\x1b[?1049h');
await writeToTerminal(SIXEL_SEQ_0 + SIXEL_SEQ_0 + SIXEL_SEQ_0 + SIXEL_SEQ_0 + SIXEL_SEQ_0 + SIXEL_SEQ_0);
await new Promise(r => setTimeout(r, 50));
const usage: number = await page.evaluate('imageAddon.storageUsage');
await writeToTerminal(SIXEL_SEQ_0 + SIXEL_SEQ_0 + SIXEL_SEQ_0 + SIXEL_SEQ_0 + SIXEL_SEQ_0 + SIXEL_SEQ_0);
await writeToTerminal(SIXEL_SEQ_0 + SIXEL_SEQ_0 + SIXEL_SEQ_0 + SIXEL_SEQ_0 + SIXEL_SEQ_0 + SIXEL_SEQ_0);
await new Promise(r => setTimeout(r, 50));
const newUsage: number = await page.evaluate('imageAddon.storageUsage');
assert.equal(newUsage, usage);
});
});
describe('IIP support - testimages', () => {
it('palette.png', async () => {
await writeToTerminal(TESTDATA_IIP[0][0]);
assert.deepEqual(await getOrigSize(1), TESTDATA_IIP[0][1]);
});
it('spinfox.png', async () => {
await writeToTerminal(TESTDATA_IIP[1][0]);
assert.deepEqual(await getOrigSize(1), TESTDATA_IIP[1][1]);
});
it('w3c gif', async () => {
await writeToTerminal(TESTDATA_IIP[2][0]);
assert.deepEqual(await getOrigSize(1), TESTDATA_IIP[2][1]);
});
it('w3c jpeg', async () => {
await writeToTerminal(TESTDATA_IIP[3][0]);
assert.deepEqual(await getOrigSize(1), TESTDATA_IIP[3][1]);
});
it('w3c png', async () => {
await writeToTerminal(TESTDATA_IIP[4][0]);
assert.deepEqual(await getOrigSize(1), TESTDATA_IIP[4][1]);
});
});
});
/**
* terminal access helpers.
*/
async function getDimensions(): Promise<IDimensions> {
const dimensions: any = await page.evaluate(`term._core._renderService.dimensions`);
return {
cellWidth: Math.round(dimensions.css.cell.width),
cellHeight: Math.round(dimensions.css.cell.height),
width: Math.round(dimensions.css.canvas.width),
height: Math.round(dimensions.css.canvas.height)
};
}
async function getCursor(): Promise<[number, number]> {
return page.evaluate('[window.term.buffer.active.cursorX, window.term.buffer.active.cursorY]');
}
async function getImageStorageLength(): Promise<number> {
return page.evaluate('window.imageAddon._storage._images.size');
}
async function getScrollbackPlusRows(): Promise<number> {
return page.evaluate('window.term.options.scrollback + window.term.rows');
}
async function writeToTerminal(d: string): Promise<any> {
return page.evaluate(data => new Promise(res => (window as any).term.write(data, res)), d);
}
async function getOrigSize(id: number): Promise<[number, number]> {
return page.evaluate<any>(`[
window.imageAddon._storage._images.get(${id}).orig.width,
window.imageAddon._storage._images.get(${id}).orig.height
]`);
}
+329
View File
@@ -0,0 +1,329 @@
/**
* Copyright (c) 2020 The xterm.js authors. All rights reserved.
* @license MIT
*/
import test from '@playwright/test';
import { readFileSync } from 'fs';
import { FINALIZER, introducer, sixelEncode } from 'sixel';
import { ITestContext, createTestContext, openTerminal, pollFor, timeout } from '../../../test/playwright/TestUtils';
import { deepStrictEqual, ok, strictEqual } from 'assert';
/**
* Plugin ctor options.
*/
export interface IImageAddonOptions {
enableSizeReports: boolean;
pixelLimit: number;
storageLimit: number;
showPlaceholder: boolean;
sixelSupport: boolean;
sixelScrolling: boolean;
sixelPaletteLimit: number;
sixelSizeLimit: number;
iipSupport: boolean;
iipSizeLimit: number;
}
// eslint-disable-next-line
declare const ImageAddon: {
new(options?: Partial<IImageAddonOptions>): any;
};
interface ITestData {
width: number;
height: number;
bytes: Uint8Array;
palette: number[];
sixel: string;
}
interface IDimensions {
cellWidth: number;
cellHeight: number;
width: number;
height: number;
}
// image: 640 x 80, 512 color
const TESTDATA: ITestData = (() => {
const data8 = readFileSync('./addons/addon-image/fixture/palette.blob');
const data32 = new Uint32Array(data8.buffer);
const palette = new Set<number>();
for (let i = 0; i < data32.length; ++i) palette.add(data32[i]);
const sixel = sixelEncode(data8, 640, 80, [...palette]);
return {
width: 640,
height: 80,
bytes: data8,
palette: [...palette],
sixel
};
})();
const SIXEL_SEQ_0 = introducer(0) + TESTDATA.sixel + FINALIZER;
// const SIXEL_SEQ_1 = introducer(1) + TESTDATA.sixel + FINALIZER;
// const SIXEL_SEQ_2 = introducer(2) + TESTDATA.sixel + FINALIZER;
// NOTE: the data is loaded as string for easier transport through playwright
const TESTDATA_IIP: [string, [number, number]][] = [
[readFileSync('./addons/addon-image/fixture/iip/palette.iip', { encoding: 'utf-8' }), [640, 80]],
[readFileSync('./addons/addon-image/fixture/iip/spinfox.iip', { encoding: 'utf-8' }), [148, 148]],
[readFileSync('./addons/addon-image/fixture/iip/w3c_gif.iip', { encoding: 'utf-8' }), [72, 48]],
[readFileSync('./addons/addon-image/fixture/iip/w3c_jpg.iip', { encoding: 'utf-8' }), [72, 48]],
[readFileSync('./addons/addon-image/fixture/iip/w3c_png.iip', { encoding: 'utf-8' }), [72, 48]]
];
let ctx: ITestContext;
test.beforeAll(async ({ browser }) => {
ctx = await createTestContext(browser);
await openTerminal(ctx, { cols: 80, rows: 24 });
});
test.afterAll(async () => await ctx.page.close());
test.describe('ImageAddon', () => {
test.beforeEach(async ({}, testInfo) => {
// DEBT: This test never worked on webkit
if (ctx.browser.browserType().name() === 'webkit') {
testInfo.skip();
return;
}
await ctx.page.evaluate(`
window.term.reset()
window.imageAddon?.dispose();
window.imageAddon = new ImageAddon({ sixelPaletteLimit: 512 });
window.term.loadAddon(window.imageAddon);
`);
});
test('test for private accessors', async () => {
// terminal privates
const accessors = [
'_core',
'_core._renderService',
'_core._inputHandler',
'_core._inputHandler._parser',
'_core._inputHandler._curAttrData',
'_core._inputHandler._dirtyRowTracker',
'_core._themeService.colors',
'_core._coreBrowserService'
];
for (const prop of accessors) {
strictEqual(
await ctx.page.evaluate('(() => { const v = window.term.' + prop + '; return v !== undefined && v !== null; })()'),
true, `problem at ${prop}`
);
}
// bufferline privates
strictEqual(await ctx.page.evaluate('window.term._core.buffer.lines.get(0)._data instanceof Uint32Array'), true);
strictEqual(await ctx.page.evaluate('window.term._core.buffer.lines.get(0)._extendedAttrs instanceof Object'), true);
// inputhandler privates
strictEqual(await ctx.page.evaluate('window.term._core._inputHandler._curAttrData.constructor.name'), '_AttributeData');
strictEqual(await ctx.page.evaluate('window.term._core._inputHandler._parser.constructor.name'), 'EscapeSequenceParser');
});
test.describe('ctor options', () => {
test('empty settings should load defaults', async () => {
const DEFAULT_OPTIONS: IImageAddonOptions = {
enableSizeReports: true,
pixelLimit: 16777216,
sixelSupport: true,
sixelScrolling: true,
sixelPaletteLimit: 512, // set to 512 to get example image working
sixelSizeLimit: 25000000,
storageLimit: 128,
showPlaceholder: true,
iipSupport: true,
iipSizeLimit: 20000000
};
deepStrictEqual(await ctx.page.evaluate(`window.imageAddon._opts`), DEFAULT_OPTIONS);
});
test('custom settings should overload defaults', async () => {
const customSettings: IImageAddonOptions = {
enableSizeReports: false,
pixelLimit: 5,
sixelSupport: false,
sixelScrolling: false,
sixelPaletteLimit: 1024,
sixelSizeLimit: 1000,
storageLimit: 10,
showPlaceholder: false,
iipSupport: false,
iipSizeLimit: 1000
};
await ctx.page.evaluate(opts => {
(window as any).imageAddonCustom = new ImageAddon(opts.opts);
(window as any).term.loadAddon((window as any).imageAddonCustom);
}, { opts: customSettings });
deepStrictEqual(await ctx.page.evaluate(`window.imageAddonCustom._opts`), customSettings);
});
});
test.describe('scrolling & cursor modes', () => {
test('testdata default (scrolling with VT240 cursor pos)', async () => {
const dim = await getDimensions();
await ctx.proxy.write(SIXEL_SEQ_0);
deepStrictEqual(await getCursor(), [0, Math.floor(TESTDATA.height/dim.cellHeight)]);
// moved to right by 10 cells
await ctx.proxy.write('#'.repeat(10) + SIXEL_SEQ_0);
deepStrictEqual(await getCursor(), [10, Math.floor(TESTDATA.height/dim.cellHeight) * 2]);
});
test('write testdata noScrolling', async () => {
await ctx.proxy.write('\x1b[?80h' + SIXEL_SEQ_0);
deepStrictEqual(await getCursor(), [0, 0]);
// second draw does not change anything
await ctx.proxy.write(SIXEL_SEQ_0);
deepStrictEqual(await getCursor(), [0, 0]);
});
test('testdata cursor always at VT240 pos', async () => {
const dim = await getDimensions();
// offset 0
await ctx.proxy.write(SIXEL_SEQ_0);
deepStrictEqual(await getCursor(), [0, Math.floor(TESTDATA.height/dim.cellHeight)]);
// moved to right by 10 cells
await ctx.proxy.write('#'.repeat(10) + SIXEL_SEQ_0);
deepStrictEqual(await getCursor(), [10, Math.floor(TESTDATA.height/dim.cellHeight) * 2]);
// moved by 30 cells (+10 prev)
await ctx.proxy.write('#'.repeat(30) + SIXEL_SEQ_0);
deepStrictEqual(await getCursor(), [10 + 30, Math.floor(TESTDATA.height/dim.cellHeight) * 3]);
});
});
test.describe('image lifecycle & eviction', () => {
test('delete image once scrolled off', async () => {
await ctx.proxy.write(SIXEL_SEQ_0);
pollFor(ctx.page, 'window.imageAddon._storage._images.size', 1);
// scroll to scrollback + rows - 1
await ctx.page.evaluate(
scrollback => new Promise(res => (window as any).term.write('\n'.repeat(scrollback), res)),
(await getScrollbackPlusRows() - 1)
);
// wait here, as we have to make sure, that eviction did not yet occur
await timeout(100);
pollFor(ctx.page, 'window.imageAddon._storage._images.size', 1);
// scroll one further should delete the image
await ctx.page.evaluate(() => new Promise(res => (window as any).term.write('\n', res)));
pollFor(ctx.page, 'window.imageAddon._storage._images.size', 0);
});
test('get storageUsage', async () => {
strictEqual(await ctx.page.evaluate('window.imageAddon.storageUsage'), 0);
await ctx.proxy.write(SIXEL_SEQ_0);
ok(Math.abs((await ctx.page.evaluate<number>('window.imageAddon.storageUsage')) - 640 * 80 * 4 / 1000000) < 0.05);
});
test('get/set storageLimit', async () => {
strictEqual(await ctx.page.evaluate('window.imageAddon.storageLimit'), 128);
strictEqual(await ctx.page.evaluate('window.imageAddon.storageLimit = 1'), 1);
strictEqual(await ctx.page.evaluate('window.imageAddon.storageLimit'), 1);
});
test('remove images by storage limit pressure', async () => {
strictEqual(await ctx.page.evaluate('window.imageAddon.storageLimit = 1'), 1);
// never go beyond storage limit
await ctx.proxy.write(SIXEL_SEQ_0);
await ctx.proxy.write(SIXEL_SEQ_0);
await ctx.proxy.write(SIXEL_SEQ_0);
await ctx.proxy.write(SIXEL_SEQ_0);
await timeout(100);
const usage = await ctx.page.evaluate('window.imageAddon.storageUsage');
await ctx.proxy.write(SIXEL_SEQ_0);
await ctx.proxy.write(SIXEL_SEQ_0);
await ctx.proxy.write(SIXEL_SEQ_0);
await ctx.proxy.write(SIXEL_SEQ_0);
await timeout(100);
strictEqual(await ctx.page.evaluate('window.imageAddon.storageUsage'), usage);
strictEqual(usage as number < 1, true);
});
test('set storageLimit removes images synchronously', async () => {
await ctx.proxy.write(SIXEL_SEQ_0 + SIXEL_SEQ_0 + SIXEL_SEQ_0);
const usage: number = await ctx.page.evaluate('window.imageAddon.storageUsage');
const newUsage: number = await ctx.page.evaluate('window.imageAddon.storageLimit = 0.5; window.imageAddon.storageUsage');
strictEqual(newUsage < usage, true);
strictEqual(newUsage < 0.5, true);
});
test('clear alternate images on buffer change', async () => {
strictEqual(await ctx.page.evaluate('window.imageAddon.storageUsage'), 0);
await ctx.proxy.write('\x1b[?1049h' + SIXEL_SEQ_0);
ok(Math.abs((await ctx.page.evaluate<number>('window.imageAddon.storageUsage')) - 640 * 80 * 4 / 1000000) < 0.05);
await ctx.proxy.write('\x1b[?1049l');
strictEqual(await ctx.page.evaluate('window.imageAddon.storageUsage'), 0);
});
test('evict tiles by in-place overwrites (only full overwrite tested)', async () => {
await timeout(50);
await ctx.proxy.write('\x1b[H' + SIXEL_SEQ_0 + '\x1b[100;100H');
await timeout(50);
let usage = await ctx.page.evaluate('window.imageAddon.storageUsage');
while (usage === 0) {
await timeout(50);
usage = await ctx.page.evaluate('window.imageAddon.storageUsage');
}
await ctx.proxy.write('\x1b[H' + SIXEL_SEQ_0 + '\x1b[100;100H');
await timeout(200); // wait some time and re-check
strictEqual(await ctx.page.evaluate('window.imageAddon.storageUsage'), usage);
});
test('manual eviction on alternate buffer must not miss images', async () => {
await ctx.proxy.write('\x1b[?1049h');
await ctx.proxy.write(SIXEL_SEQ_0 + SIXEL_SEQ_0 + SIXEL_SEQ_0 + SIXEL_SEQ_0 + SIXEL_SEQ_0 + SIXEL_SEQ_0);
await timeout(100);
const usage: number = await ctx.page.evaluate('window.imageAddon.storageUsage');
await ctx.proxy.write(SIXEL_SEQ_0 + SIXEL_SEQ_0 + SIXEL_SEQ_0 + SIXEL_SEQ_0 + SIXEL_SEQ_0 + SIXEL_SEQ_0);
await ctx.proxy.write(SIXEL_SEQ_0 + SIXEL_SEQ_0 + SIXEL_SEQ_0 + SIXEL_SEQ_0 + SIXEL_SEQ_0 + SIXEL_SEQ_0);
await timeout(100);
const newUsage: number = await ctx.page.evaluate('window.imageAddon.storageUsage');
strictEqual(newUsage, usage);
});
});
test.describe('IIP support - testimages', () => {
test('palette.png', async () => {
await ctx.proxy.write(TESTDATA_IIP[0][0]);
deepStrictEqual(await getOrigSize(1), TESTDATA_IIP[0][1]);
});
test('spinfox.png', async () => {
await ctx.proxy.write(TESTDATA_IIP[1][0]);
deepStrictEqual(await getOrigSize(1), TESTDATA_IIP[1][1]);
});
test('w3c gif', async () => {
await ctx.proxy.write(TESTDATA_IIP[2][0]);
deepStrictEqual(await getOrigSize(1), TESTDATA_IIP[2][1]);
});
test('w3c jpeg', async () => {
await ctx.proxy.write(TESTDATA_IIP[3][0]);
deepStrictEqual(await getOrigSize(1), TESTDATA_IIP[3][1]);
});
test('w3c png', async () => {
await ctx.proxy.write(TESTDATA_IIP[4][0]);
deepStrictEqual(await getOrigSize(1), TESTDATA_IIP[4][1]);
});
});
});
/**
* terminal access helpers.
*/
async function getDimensions(): Promise<IDimensions> {
const dimensions: any = await ctx.page.evaluate(`term.dimensions`);
return {
cellWidth: Math.round(dimensions.css.cell.width),
cellHeight: Math.round(dimensions.css.cell.height),
width: Math.round(dimensions.css.canvas.width),
height: Math.round(dimensions.css.canvas.height)
};
}
async function getCursor(): Promise<[number, number]> {
return ctx.page.evaluate('[window.term.buffer.active.cursorX, window.term.buffer.active.cursorY]');
}
async function getImageStorageLength(): Promise<number> {
return ctx.page.evaluate('window.imageAddon._storage._images.size');
}
async function getScrollbackPlusRows(): Promise<number> {
return ctx.page.evaluate('window.term.options.scrollback + window.term.rows');
}
async function getOrigSize(id: number): Promise<[number, number]> {
return ctx.page.evaluate<any>(`[
window.imageAddon._storage._images.get(${id}).orig.width,
window.imageAddon._storage._images.get(${id}).orig.height
]`);
}
@@ -0,0 +1,35 @@
import { PlaywrightTestConfig } from '@playwright/test';
const config: PlaywrightTestConfig = {
testDir: '.',
timeout: 10000,
projects: [
{
name: 'ChromeStable',
use: {
browserName: 'chromium',
channel: 'chrome'
}
},
{
name: 'FirefoxStable',
use: {
browserName: 'firefox'
}
},
{
name: 'WebKit',
use: {
browserName: 'webkit'
}
}
],
reporter: 'list',
webServer: {
command: 'npm run start',
port: 3000,
timeout: 120000,
reuseExistingServer: !process.env.CI
}
};
export default config;
+21 -9
View File
@@ -1,21 +1,26 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "es2017",
"target": "ESNext",
"lib": [
"es2021",
],
"rootDir": ".",
"outDir": "../out-test",
"sourceMap": true,
"removeComments": true,
"strict": true,
"baseUrl": ".",
"paths": {
"browser/*": [ "../../../src/browser/*" ],
"common/*": [ "../../../src/common/*" ]
"common/*": [
"../../../src/common/*"
],
"browser/*": [
"../../../src/browser/*"
]
},
"strict": true,
"types": [
"../../../node_modules/@types/mocha",
"../../../node_modules/@types/node",
"../../../out-test/api/TestUtils"
"../../../node_modules/@types/node"
]
},
"include": [
@@ -23,7 +28,14 @@
"../../../typings/xterm.d.ts"
],
"references": [
{ "path": "../../../src/browser" },
{ "path": "../../../src/common" }
{
"path": "../../../src/common"
},
{
"path": "../../../src/browser"
},
{
"path": "../../../test/playwright"
}
]
}
+5 -2
View File
@@ -26,14 +26,17 @@ const addon = {
extensions: [ '.js' ],
alias: {
common: path.resolve('../../out/common'),
browser: path.resolve('../../out/browser')
browser: path.resolve('../../out/browser'),
vs: path.resolve('../../out/vs')
}
},
output: {
filename: mainFile,
path: path.resolve('./lib'),
library: addonName,
libraryTarget: 'umd'
libraryTarget: 'umd',
// Force usage of globalThis instead of global / self. (This is cross-env compatible)
globalObject: 'globalThis',
},
mode: 'production'
};
-2
View File
@@ -3,11 +3,9 @@ node_modules/
coverage/
lib/
fonts/
.env
.vscode/
*.swp
*.tgz
npm-debug.log*
yarn-error.log*
+3
View File
@@ -8,6 +8,9 @@
!lib/**/*.js
!lib/**/*.js.map
!lib/**/*.mjs
!lib/**/*.mjs.map
!lib/**/*.css
# Whitelist - src/
+27 -3
View File
@@ -1,6 +1,30 @@
MIT License
Copyright (c) 2018
Copyright (c) 2019, The xterm.js authors (https://github.com/xtermjs/xterm.js)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
---
The code that analyzes font ligatures is forked from https://github.com/princjef/font-ligatures with this license:
MIT License
Copyright (c) 2018 Jeffrey Principe
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -1,78 +0,0 @@
/**
* Copyright (c) 2018 The xterm.js authors. All rights reserved.
* @license MIT
*/
const fs = require('fs');
const path = require('path');
const util = require('util');
const axios = require('axios').default;
const mkdirp = require('mkdirp');
const yauzl = require('yauzl');
const urls = {
fira: 'https://github.com/tonsky/FiraCode/raw/d42e7276fa925e5f82748f3ec9ea429736611b48/distr/otf/FiraCode-Regular.otf',
iosevka: 'https://github.com/be5invis/Iosevka/releases/download/v1.14.3/01-iosevka-1.14.3.zip'
};
const writeFile = util.promisify(fs.writeFile);
const fontsFolder = path.join(__dirname, '../fonts');
async function download() {
await mkdirp(fontsFolder);
try {
await downloadFiraCode();
await downloadIosevka();
console.log('Loaded all fonts for testing')
} catch (e) {
console.warn('Fonts failed to download, ligature tests will not work', e);
}
}
async function downloadFiraCode() {
const file = path.join(fontsFolder, 'firaCode.otf');
if (await util.promisify(fs.exists)(file)) {
console.log('Fira Code already loaded');
} else {
console.log('Downloading Fira Code...');
await writeFile(
file,
(await axios.get(urls.fira, { responseType: 'arraybuffer' })).data
);
}
}
async function downloadIosevka() {
const file = path.join(fontsFolder, 'iosevka.ttf');
if (await util.promisify(fs.exists)(file)) {
console.log('Iosevka already loaded');
} else {
console.log('Downloading Iosevka...');
const iosevkaContents = (await axios.get(urls.iosevka, { responseType: 'arraybuffer' })).data;
const iosevkaZipfile = await util.promisify(yauzl.fromBuffer)(iosevkaContents);
await new Promise((resolve, reject) => {
iosevkaZipfile.on('entry', entry => {
if (entry.fileName === 'ttf/iosevka-regular.ttf') {
iosevkaZipfile.openReadStream(entry, (err, stream) => {
if (err) {
return reject(err);
}
const writeStream = fs.createWriteStream(file);
stream.pipe(writeStream);
writeStream.on('close', () => resolve());
});
}
});
});
}
}
download();
process.on('unhandledRejection', e => {
console.error(e);
process.exit(1);
});
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+7 -9
View File
@@ -1,19 +1,19 @@
{
"name": "@xterm/addon-ligatures",
"version": "0.9.0",
"version": "0.10.0",
"description": "Add support for programming ligatures to xterm.js",
"author": {
"name": "The xterm.js authors",
"url": "https://xtermjs.org/"
},
"main": "lib/addon-ligatures.js",
"module": "lib/addon-ligatures.mjs",
"types": "typings/addon-ligatures.d.ts",
"repository": "https://github.com/xtermjs/xterm.js/tree/master/addons/addon-ligatures",
"engines": {
"node": ">8.0.0"
},
"scripts": {
"prepare": "node bin/download-fonts.js",
"build": "tsc -p src",
"watch": "tsc -w -p src",
"prepackage": "npm run build",
@@ -31,17 +31,15 @@
],
"license": "MIT",
"dependencies": {
"font-finder": "^1.1.0",
"font-ligatures": "^1.4.1"
"lru-cache": "^6.0.0",
"opentype.js": "^0.8.0"
},
"devDependencies": {
"@types/sinon": "^5.0.1",
"@types/lru-cache": "^5.1.0",
"@types/opentype.js": "^0.7.0",
"axios": "^1.6.0",
"font-finder": "^1.1.0",
"mkdirp": "0.5.5",
"sinon": "6.3.5",
"yauzl": "^2.10.0"
},
"peerDependencies": {
"@xterm/xterm": "^5.0.0"
}
}
+14 -4
View File
@@ -15,24 +15,31 @@ export interface ITerminalAddon {
export class LigaturesAddon implements ITerminalAddon , ILigaturesApi {
private readonly _fallbackLigatures: string[];
private readonly _fontFeatureSettings?: string;
private _terminal: Terminal | undefined;
private _characterJoinerId: number | undefined;
constructor(options?: Partial<ILigatureOptions>) {
// Source: calt set from https://github.com/be5invis/Iosevka?tab=readme-ov-file#ligations
this._fallbackLigatures = (options?.fallbackLigatures || [
'<--', '<---', '<<-', '<-', '->', '->>', '-->', '--->',
'<==', '<===', '<<=', '<=', '=>', '=>>', '==>', '===>', '>=', '>>=',
'<->', '<-->', '<--->', '<---->', '<=>', '<==>', '<===>', '<====>', '-------->',
'<~~', '<~', '~>', '~~>', '::', ':::', '==', '!=', '===', '!==',
':=', ':-', ':+', '<*', '<*>', '*>', '<|', '<|>', '|>', '+:', '-:', '=:', ':>',
'++', '+++', '<!--', '<!---', '<***>'
'<->', '<-->', '<--->', '<---->', '<=>', '<==>', '<===>', '<====>', '::', ':::',
'<~~', '</', '</>', '/>', '~~>', '==', '!=', '/=', '~=', '<>', '===', '!==', '!===',
'<:', ':=', '*=', '*+', '<*', '<*>', '*>', '<|', '<|>', '|>', '+*', '=*', '=:', ':>',
'/*', '*/', '+++', '<!--', '<!---'
]).sort((a, b) => b.length - a.length);
this._fontFeatureSettings = options?.fontFeatureSettings;
}
public activate(terminal: Terminal): void {
if (!terminal.element) {
throw new Error('Cannot activate LigaturesAddon before open is called');
}
this._terminal = terminal;
this._characterJoinerId = enableLigatures(terminal, this._fallbackLigatures);
terminal.element.style.fontFeatureSettings = this._fontFeatureSettings ?? '"calt" on';
}
public dispose(): void {
@@ -40,5 +47,8 @@ export class LigaturesAddon implements ITerminalAddon , ILigaturesApi {
this._terminal?.deregisterCharacterJoiner(this._characterJoinerId);
this._characterJoinerId = undefined;
}
if (this._terminal?.element) {
this._terminal.element.style.fontFeatureSettings = '';
}
}
}
@@ -5,4 +5,5 @@
export interface ILigatureOptions {
fallbackLigatures: string[];
fontFeatureSettings: string;
}
+1 -1
View File
@@ -3,7 +3,7 @@
* @license MIT
*/
import { Font, loadBuffer } from 'font-ligatures';
import { Font, loadBuffer } from './fontLigatures/index';
import parse from './parse';
@@ -0,0 +1,40 @@
import { ILookupTree, IFlattenedLookupTree, ILookupTreeEntry, IFlattenedLookupTreeEntry } from './types';
export default function flatten(tree: ILookupTree, visited: Map<ILookupTreeEntry, IFlattenedLookupTreeEntry> = new Map()): IFlattenedLookupTree {
const result: IFlattenedLookupTree = {};
for (const [glyphId, entry] of Object.entries(tree.individual)) {
result[glyphId] = flattenEntry(entry, visited);
}
for (const { range, entry } of tree.range) {
const flattened = flattenEntry(entry, visited);
for (let glyphId = range[0]; glyphId < range[1]; glyphId++) {
result[glyphId] = flattened;
}
}
return result;
}
function flattenEntry(entry: ILookupTreeEntry, visited: Map<ILookupTreeEntry, IFlattenedLookupTreeEntry>): IFlattenedLookupTreeEntry {
if (visited.has(entry)) {
return visited.get(entry)!;
}
const result: IFlattenedLookupTreeEntry = {};
visited.set(entry, result);
if (entry.forward) {
result.forward = flatten(entry.forward, visited);
}
if (entry.reverse) {
result.reverse = flatten(entry.reverse, visited);
}
if (entry.lookup) {
result.lookup = entry.lookup;
}
return result;
}
@@ -0,0 +1,358 @@
/**
* Copyright (c) 2018 The xterm.js authors. All rights reserved.
* @license MIT
*/
import * as path from 'path';
import * as fs from 'fs';
import { assert } from 'chai';
import { loadBuffer } from './index';
interface IFont {
findLigatures(text: string): { outputGlyphs: number[], contextRanges: [number, number][] };
findLigatureRanges(text: string): [number, number][];
}
interface ITestCase {
font: string;
input: string;
glyphs: number[];
ranges: [number, number][];
}
const fira = (input: string, glyphs: number[], ranges: [number, number][]): ITestCase =>
({ font: 'Fira Code', input, glyphs, ranges });
const iosevka = (input: string, glyphs: number[], ranges: [number, number][]): ITestCase =>
({ font: 'Iosevka', input, glyphs, ranges });
const monoid = (input: string, glyphs: number[], ranges: [number, number][]): ITestCase =>
({ font: 'Monoid', input, glyphs, ranges });
const ubuntu = (input: string, glyphs: number[], ranges: [number, number][]): ITestCase =>
({ font: 'Ubuntu Mono', input, glyphs, ranges });
const firaCases: ITestCase[] = [
fira('abc', [133, 145, 146], []),
fira('.=', [1614, 1081], [[0, 2]]),
fira('..=', [1614, 1614, 1083], [[0, 3]]),
fira('.-', [1614, 1080], [[0, 2]]),
fira(':=', [1614, 1055], [[0, 2]]),
fira('=:=', [1614, 1614, 1483], [[0, 3]]),
fira('=!=', [1614, 1614, 1484], [[0, 3]]),
fira('__', [1614, 1099], [[0, 2]]),
fira('==', [1614, 1485], [[0, 2]]),
fira('!=', [1614, 1058], [[0, 2]]),
fira('===', [1614, 1614, 1486], [[0, 3]]),
fira('!==', [1614, 1614, 1059], [[0, 3]]),
fira('=/=', [1614, 1614, 1491], [[0, 3]]),
fira('<-<', [1614, 1614, 1513], [[0, 3]]),
fira('<<-', [1614, 1614, 1522], [[0, 3]]),
fira('<--', [1614, 1614, 1511], [[0, 3]]),
fira('<-', [1614, 1510], [[0, 2]]),
fira('<->', [1614, 1614, 1512], [[0, 3]]),
fira('->', [1614, 1064], [[0, 2]]),
fira('-->', [1614, 1614, 1063], [[0, 3]]),
fira('->>', [1614, 1614, 1065], [[0, 3]]),
fira('>->', [1614, 1614, 1493], [[0, 3]]),
fira('<=<', [1614, 1614, 1519], [[0, 3]]),
fira('<<=', [1614, 1614, 1523], [[0, 3]]),
fira('<==', [1614, 1614, 1517], [[0, 3]]),
fira('<=>', [1614, 1614, 1518], [[0, 3]]),
fira('=>', [1614, 1488], [[0, 2]]),
fira('==>', [1614, 1614, 1487], [[0, 3]]),
fira('=>>', [1614, 1614, 1489], [[0, 3]]),
fira('>=>', [1614, 1614, 1495], [[0, 3]]),
fira('>>=', [1614, 1614, 1498], [[0, 3]]),
fira('>>-', [1614, 1614, 1497], [[0, 3]]),
fira('>-', [1614, 1492], [[0, 2]]),
fira('<~>', [1614, 1614, 1526], [[0, 3]]),
fira('-<', [1614, 1066], [[0, 2]]),
fira('-<<', [1614, 1614, 1067], [[0, 3]]),
fira('=<<', [1614, 1614, 1490], [[0, 3]]),
fira('<~~', [1614, 1614, 1527], [[0, 3]]),
fira('<~', [1614, 1525], [[0, 2]]),
fira('~~', [1614, 1534], [[0, 2]]),
fira('~>', [1614, 1533], [[0, 2]]),
fira('~~>', [1614, 1614, 1535], [[0, 3]]),
fira('<<<', [1614, 1614, 1524], [[0, 3]]),
fira('<<', [1614, 1521], [[0, 2]]),
fira('<=', [1614, 1516], [[0, 2]]),
fira('<>', [1614, 1520], [[0, 2]]),
fira('>=', [1614, 1494], [[0, 2]]),
fira('>>', [1614, 1496], [[0, 2]]),
fira('>>>', [1614, 1614, 1499], [[0, 3]]),
fira('{.', [1001, 977], [[0, 2]]),
fira('{|', [1614, 1049], [[0, 2]]),
fira('[|', [1614, 1050], [[0, 2]]),
fira('<:', [1614, 1506], [[0, 2]]),
fira(':>', [1614, 1056], [[0, 2]]),
fira('|]', [1614, 1474], [[0, 2]]),
fira('|}', [1614, 1473], [[0, 2]]),
fira('.}', [977, 1002], [[0, 2]]),
fira('<|||', [1614, 1614, 1614, 1504], [[0, 4]]),
fira('<||', [1614, 1614, 1503], [[0, 3]]),
fira('<|', [1614, 1502], [[0, 2]]),
fira('<|>', [1614, 1614, 1505], [[0, 3]]),
fira('|>', [1614, 1477], [[0, 2]]),
fira('||>', [1614, 1614, 1472], [[0, 3]]),
fira('|||>', [1614, 1614, 1614, 1470], [[0, 4]]),
fira('<$', [1614, 1507], [[0, 2]]),
fira('<$>', [1614, 1614, 1508], [[0, 3]]),
fira('$>', [1614, 1479], [[0, 2]]),
fira('<+', [1614, 1514], [[0, 2]]),
fira('<+>', [1614, 1614, 1515], [[0, 3]]),
fira('+>', [1614, 1482], [[0, 2]]),
fira('<*', [1614, 1500], [[0, 2]]),
fira('<*>', [1614, 1614, 1501], [[0, 3]]),
fira('*>', [1614, 1047], [[0, 2]]),
fira('/*', [1614, 1092], [[0, 2]]),
fira('*/', [1614, 1048], [[0, 2]]),
fira('///', [1614, 1614, 1097], [[0, 3]]),
fira('//', [1614, 1096], [[0, 2]]),
fira('</', [1614, 1528], [[0, 2]]),
fira('<!--', [1614, 1614, 1614, 1509], [[0, 4]]),
fira('</>', [1614, 1614, 1529], [[0, 3]]),
fira('/>', [1614, 1095], [[0, 2]]),
fira('0xff', [895, 270, 166, 166], [[0, 3]]),
fira('10x10', [896, 895, 270, 896, 895], [[1, 4]]),
fira('9:45', [904, 998, 899, 900], [[0, 2]]),
fira('[:]', [1003, 998, 1004], [[0, 2]]),
fira(';;', [1614, 1091], [[0, 2]]),
fira('::', [1614, 1052], [[0, 2]]),
fira(':::', [1614, 1614, 1053], [[0, 3]]),
fira('..', [1614, 1082], [[0, 2]]),
fira('...', [1614, 1614, 1085], [[0, 3]]),
fira('..<', [1614, 1614, 1084], [[0, 3]]),
fira('!!', [1614, 1057], [[0, 2]]),
fira('??', [1614, 1090], [[0, 2]]),
fira('%%', [1614, 1536], [[0, 2]]),
fira('&&', [1614, 1468], [[0, 2]]),
fira('||', [1614, 1469], [[0, 2]]),
fira('?.', [1614, 1089], [[0, 2]]),
fira('?:', [1614, 1087], [[0, 2]]),
fira('++', [1614, 1480], [[0, 2]]),
fira('+++', [1614, 1614, 1481], [[0, 3]]),
fira('--', [1614, 1061], [[0, 2]]),
fira('---', [1614, 1614, 1062], [[0, 3]]),
fira('**', [1614, 1045], [[0, 2]]),
fira('***', [1614, 1614, 1046], [[0, 3]]),
fira('~=', [1614, 1532], [[0, 2]]),
fira('~-', [1614, 1531], [[0, 2]]),
fira('www', [1614, 1614, 271], [[0, 3]]),
fira('-~', [1614, 1068], [[0, 2]]),
fira('~@', [1614, 1530], [[0, 2]]),
fira('^=', [1614, 1478], [[0, 2]]),
fira('?=', [1614, 1088], [[0, 2]]),
fira('/=', [1614, 1093], [[0, 2]]),
fira('/==', [1614, 1614, 1094], [[0, 3]]),
fira('-|', [1614, 1060], [[0, 2]]),
fira('_|_', [1614, 1614, 1098], [[0, 3]]),
fira('|-', [1614, 1475], [[0, 2]]),
fira('|=', [1614, 1476], [[0, 2]]),
fira('||=', [1614, 1614, 1471], [[0, 3]]),
fira('#!', [1614, 1071], [[0, 2]]),
fira('#=', [1614, 1075], [[0, 2]]),
fira('##', [1614, 1072], [[0, 2]]),
fira('###', [1614, 1614, 1073], [[0, 3]]),
fira('####', [1614, 1614, 1614, 1074], [[0, 4]]),
fira('#{', [1614, 1069], [[0, 2]]),
fira('#[', [1614, 1070], [[0, 2]]),
fira(']#', [1614, 1051], [[0, 2]]),
fira('#(', [1614, 1076], [[0, 2]]),
fira('#?', [1614, 1077], [[0, 2]]),
fira('#_', [1614, 1078], [[0, 2]]),
fira('#_(', [1614, 1614, 1079], [[0, 3]]),
fira('::=', [1614, 1614, 1054], [[0, 3]]),
fira('.?', [1614, 1086], [[0, 2]]),
fira('===>', [1614, 1614, 1486, 1148], [[0, 4]])
];
const iosevkaCases: ITestCase[] = [
iosevka('<-', [31, 3127], [[0, 2]]),
iosevka('<--', [31, 3129, 3139], [[0, 3]]),
iosevka('<---', [31, 3129, 3150, 3139], [[0, 4]]),
iosevka('<-----', [31, 3129, 3150, 3139, 3151, 3151], [[0, 6]]),
iosevka('->', [3126, 33], [[0, 2]]),
iosevka('-->', [3140, 3128, 33], [[0, 3]]),
iosevka('--->', [3140, 3150, 3128, 33], [[0, 4]]),
iosevka('----->', [3153, 3153, 3140, 3150, 3128, 33], [[0, 6]]),
iosevka('<->', [31, 3149, 33], [[0, 3]]),
iosevka('<-->', [31, 3129, 3128, 33], [[0, 4]]),
iosevka('<--->', [31, 3129, 3150, 3128, 33], [[0, 5]]),
iosevka('<----->', [31, 3129, 3150, 3150, 3150, 3128, 33], [[0, 7]]),
iosevka('<=', [3094, 3095], [[0, 2]]),
iosevka('<==', [31, 3158, 3168], [[0, 3]]),
iosevka('<===', [31, 3158, 3179, 3168], [[0, 4]]),
iosevka('<=====', [31, 3158, 3179, 3168, 3180, 3180], [[0, 6]]),
iosevka('=>', [3155, 33], [[0, 2]]),
iosevka('==>', [3169, 3157, 33], [[0, 3]]),
iosevka('===>', [3169, 3179, 3157, 33], [[0, 4]]),
iosevka('=====>', [3182, 3182, 3169, 3179, 3157, 33], [[0, 6]]),
iosevka('<=>', [31, 3178, 33], [[0, 3]]),
iosevka('<==>', [31, 3158, 3157, 33], [[0, 4]]),
iosevka('<===>', [31, 3158, 3179, 3157, 33], [[0, 5]]),
iosevka('<=====>', [31, 3158, 3179, 3179, 3179, 3157, 33], [[0, 7]]),
iosevka('<!--', [31, 3184, 3130, 3139], [[0, 4]]),
iosevka('<!---', [31, 3184, 3130, 3150, 3139], [[0, 5]]),
iosevka('<!-----', [31, 3184, 3130, 3150, 3139, 3151, 3151], [[0, 7]]),
iosevka('a:b', [68, 29, 69], []),
iosevka('a::b', [68, 3012, 3013, 69], [[1, 3]]),
iosevka('a:::b', [68, 3012, 3010, 3013, 69], [[1, 4]]),
iosevka(':=', [3011, 32], [[0, 2]]),
iosevka(':-', [3011, 16], [[0, 2]]),
iosevka(':+', [3011, 14], [[0, 2]]),
iosevka('=:', [32, 3011], [[0, 2]]),
iosevka('-:', [16, 3011], [[0, 2]]),
iosevka('+:', [14, 3011], [[0, 2]]),
iosevka('<*', [31, 3023], [[0, 2]]),
iosevka('*>', [3023, 33], [[0, 2]]),
iosevka('<*>', [31, 3023, 33], [[1, 3]]),
iosevka('<**>', [31, 3023, 3023, 33], [[1, 4]]),
iosevka('<****>', [31, 3023, 3023, 3023, 3023, 33], [[0, 3], [3, 6]]),
iosevka('==', [3083, 3084], [[0, 2]]),
iosevka('!=', [3098, 3084], [[0, 2]]),
iosevka('===', [3083, 3085, 3084], [[0, 3]]),
iosevka('!==', [3099, 3085, 3084], [[0, 3]]),
iosevka('====', [3083, 3085, 3085, 3084], [[0, 4]]),
iosevka('!===', [3100, 3085, 3085, 3084], [[0, 4]])
];
const monoidCases: ITestCase[] = [
monoid('<!--', [775, 775, 775, 643], [[0, 4]]),
monoid('-->', [779, 779, 628], [[0, 3]]),
monoid('<--', [776, 776, 627], [[0, 3]]),
monoid('->>', [780, 780, 626], [[0, 3]]),
monoid('<<-', [777, 777, 625], [[0, 3]]),
monoid('->', [781, 623], [[0, 2]]),
monoid('<-', [778, 624], [[0, 2]]),
monoid('=>', [793, 666], [[0, 2]]),
monoid('<=>', [785, 785, 760], [[0, 3]]),
monoid('<==>', [786, 786, 786, 771], [[0, 4]]),
monoid('==>', [787, 787, 672], [[0, 3]]),
monoid('<==', [788, 788, 671], [[0, 3]]),
monoid('>>=', [791, 791, 758], [[0, 3]]),
monoid('=<<', [792, 792, 759], [[0, 3]]),
monoid('--', [667, 667], [[0, 2]]),
monoid(':=', [29, 761], [[0, 2]]),
monoid('=:=', [789, 789, 665], [[0, 3]]),
monoid('==', [794, 641], [[0, 2]]),
monoid('!==', [782, 782, 646], [[0, 3]]),
monoid('!=', [783, 629], [[0, 2]]),
monoid('<=', [790, 630], [[0, 2]]),
monoid('>=', [792, 631], [[0, 2]]),
monoid('//', [621, 664], [[0, 2]]),
monoid('/**', [18, 753, 753], [[0, 3]]),
monoid('/*', [18, 753], [[0, 2]]),
monoid('*/', [754, 18], [[0, 2]]),
monoid('&&', [633, 775], [[0, 2]]),
monoid('.&', [17, 755], [[0, 2]]),
monoid('||', [634, 635], [[0, 2]]),
monoid('!!', [769, 770], [[0, 2]]),
monoid('::', [772, 773], [[0, 2]]),
monoid('>>', [637, 638], [[0, 2]]),
monoid('<<', [639, 640], [[0, 2]]),
monoid('¯\\_(ツ)_/¯', [113, 765, 66, 767, 613, 768, 66, 766, 113], [[0, 3], [3, 6], [6, 9]]),
monoid('__', [763, 764], [[0, 2]])
];
const ubuntuCases: ITestCase[] = [
ubuntu('==>', [32, 32, 33], [])
];
const fontPaths: Record<string, string> = {
'Fira Code': path.join(__dirname, '../../fonts/FiraCode-Regular.otf'),
'Iosevka': path.join(__dirname, '../../fonts/iosevka-regular.ttf'),
'Monoid': path.join(__dirname, '../../fonts/Monoid-Regular.ttf'),
'Ubuntu Mono': path.join(__dirname, '../../fonts/UbuntuMono-Regular.ttf')
};
const fontCache: Map<string, IFont> = new Map();
function loadFont(fontName: string): IFont {
let font = fontCache.get(fontName);
if (!font) {
const fontPath = fontPaths[fontName];
const buffer = fs.readFileSync(fontPath);
font = loadBuffer(buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength));
fontCache.set(fontName, font);
}
return font;
}
describe('addon-ligatures - index', () => {
describe('findLigatures', () => {
for (const { font: fontName, input, glyphs, ranges } of [...firaCases, ...iosevkaCases, ...monoidCases, ...ubuntuCases]) {
it(`${fontName}: '${input}'`, () => {
const font = loadFont(fontName);
const result = font.findLigatures(input);
assert.deepEqual(result.outputGlyphs, glyphs);
assert.deepEqual(result.contextRanges, ranges);
});
}
});
describe('findLigatureRanges', () => {
for (const { font: fontName, input, ranges } of [...firaCases, ...iosevkaCases, ...monoidCases, ...ubuntuCases]) {
it(`${fontName}: '${input}'`, () => {
const font = loadFont(fontName);
const result = font.findLigatureRanges(input);
assert.deepEqual(result, ranges);
});
}
});
describe('caching', () => {
it('findLigatures caches successive calls correctly', () => {
const fontPath = fontPaths['Fira Code'];
const buffer = fs.readFileSync(fontPath);
const font = loadBuffer(buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength), { cacheSize: 100 });
const result1 = font.findLigatures('in --> out');
const result2 = font.findLigatures('in --> out');
assert.deepEqual(result1, result2);
});
it('findLigatureRanges caches successive calls correctly', () => {
const fontPath = fontPaths['Fira Code'];
const buffer = fs.readFileSync(fontPath);
const font = loadBuffer(buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength), { cacheSize: 100 });
const result1 = font.findLigatureRanges('in --> out');
const result2 = font.findLigatureRanges('in --> out');
assert.deepEqual(result1, result2);
});
it('caches calls to findLigatures after findLigatureRanges correctly', () => {
const fontPath = fontPaths['Fira Code'];
const buffer = fs.readFileSync(fontPath);
const uncached = loadBuffer(buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength));
const uncachedResult1 = uncached.findLigatureRanges('in --> out');
const uncachedResult2 = uncached.findLigatures('in --> out');
const font = loadBuffer(buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength), { cacheSize: 100 });
const result1 = font.findLigatureRanges('in --> out');
const result2 = font.findLigatures('in --> out');
assert.deepEqual(result1, uncachedResult1);
assert.deepEqual(result2, uncachedResult2);
assert.deepEqual(result1, result2.contextRanges);
});
it('caches calls to findLigatureRanges after findLigatures correctly', () => {
const fontPath = fontPaths['Fira Code'];
const buffer = fs.readFileSync(fontPath);
const uncached = loadBuffer(buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength));
const uncachedResult1 = uncached.findLigatures('in --> out');
const uncachedResult2 = uncached.findLigatureRanges('in --> out');
const font = loadBuffer(buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength), { cacheSize: 100 });
const result1 = font.findLigatures('in --> out');
const result2 = font.findLigatureRanges('in --> out');
assert.deepEqual(result1, uncachedResult1);
assert.deepEqual(result2, uncachedResult2);
assert.deepEqual(result1.contextRanges, result2);
});
});
});
@@ -0,0 +1,262 @@
import * as opentype from 'opentype.js';
import LRUCache = require('lru-cache');
import { IFont, ILigatureData, IFlattenedLookupTree, ILookupTree, IOptions } from './types';
import mergeTrees from './merge';
import walkTree from './walk';
import mergeRange from './mergeRange';
import buildTreeGsubType6Format1 from './processors/6-1';
import buildTreeGsubType6Format2 from './processors/6-2';
import buildTreeGsubType6Format3 from './processors/6-3';
import buildTreeGsubType8Format1 from './processors/8-1';
import flatten from './flatten';
class FontImpl implements IFont {
private _font: opentype.Font;
private _lookupTrees: { tree: IFlattenedLookupTree, processForward: boolean }[] = [];
private _glyphLookups: { [glyphId: string]: number[] } = {};
private _cache?: LRUCache<string, ILigatureData | [number, number][]>;
constructor(font: opentype.Font, options: Required<IOptions>) {
this._font = font;
if (options.cacheSize > 0) {
this._cache = new LRUCache({
max: options.cacheSize,
length: ((val: ILigatureData | [number, number][], key: string) => key.length) as any
});
}
const caltFeatures = this._font.tables.gsub && this._font.tables.gsub.features.filter((f: { tag: string }) => f.tag === 'calt') || [];
const lookupIndices: number[] = caltFeatures
.reduce((acc: number[], val: { feature: { lookupListIndexes: number[] } }) => [...acc, ...val.feature.lookupListIndexes], []);
const allLookups = this._font.tables.gsub && this._font.tables.gsub.lookups || [];
const lookupGroups = allLookups.filter((l: unknown, i: number) => lookupIndices.some(idx => idx === i));
for (const [index, lookup] of lookupGroups.entries()) {
const trees: ILookupTree[] = [];
switch (lookup.lookupType) {
case 6:
for (const [index, table] of lookup.subtables.entries()) {
switch (table.substFormat) {
case 1:
trees.push(buildTreeGsubType6Format1(table, allLookups, index));
break;
case 2:
trees.push(buildTreeGsubType6Format2(table, allLookups, index));
break;
case 3:
trees.push(buildTreeGsubType6Format3(table, allLookups, index));
break;
}
}
break;
case 8:
for (const [index, table] of lookup.subtables.entries()) {
trees.push(buildTreeGsubType8Format1(table, index));
}
break;
}
const tree = flatten(mergeTrees(trees));
this._lookupTrees.push({
tree,
processForward: lookup.lookupType !== 8
});
for (const glyphId of Object.keys(tree)) {
if (!this._glyphLookups[glyphId]) {
this._glyphLookups[glyphId] = [];
}
this._glyphLookups[glyphId].push(index);
}
}
}
public findLigatures(text: string): ILigatureData {
const cached = this._cache && this._cache.get(text);
if (cached && !Array.isArray(cached)) {
return cached;
}
const glyphIds: number[] = [];
for (const char of text) {
glyphIds.push(this._font.charToGlyphIndex(char));
}
// If there are no lookup groups, there's no point looking for
// replacements. This gives us a minor performance boost for fonts with
// no ligatures
if (this._lookupTrees.length === 0) {
return {
inputGlyphs: glyphIds,
outputGlyphs: glyphIds,
contextRanges: []
};
}
const result = this._findInternal(glyphIds.slice());
const finalResult: ILigatureData = {
inputGlyphs: glyphIds,
outputGlyphs: result.sequence,
contextRanges: result.ranges
};
if (this._cache) {
this._cache.set(text, finalResult);
}
return finalResult;
}
public findLigatureRanges(text: string): [number, number][] {
// Short circuit the process if there are no possible ligatures in the
// font
if (this._lookupTrees.length === 0) {
return [];
}
const cached = this._cache && this._cache.get(text);
if (cached) {
return Array.isArray(cached) ? cached : cached.contextRanges;
}
const glyphIds: number[] = [];
for (const char of text) {
glyphIds.push(this._font.charToGlyphIndex(char));
}
const result = this._findInternal(glyphIds);
if (this._cache) {
this._cache.set(text, result.ranges);
}
return result.ranges;
}
private _findInternal(sequence: number[]): { sequence: number[], ranges: [number, number][] } {
const ranges: [number, number][] = [];
let nextLookup = this._getNextLookup(sequence, 0);
while (nextLookup.index !== null) {
const lookup = this._lookupTrees[nextLookup.index];
if (lookup.processForward) {
let lastGlyphIndex = nextLookup.last;
for (let i = nextLookup.first; i < lastGlyphIndex; i++) {
const result = walkTree(lookup.tree, sequence, i, i);
if (result) {
for (let j = 0; j < result.substitutions.length; j++) {
const sub = result.substitutions[j];
if (sub !== null) {
sequence[i + j] = sub;
}
}
mergeRange(
ranges,
result.contextRange[0] + i,
result.contextRange[1] + i
);
// Substitutions can end up extending the search range
if (i + result.length >= lastGlyphIndex) {
lastGlyphIndex = i + result.length + 1;
}
i += result.length - 1;
}
}
} else {
// We don't need to do the lastGlyphIndex tracking here because
// reverse processing isn't allowed to replace more than one
// character at a time.
for (let i = nextLookup.last - 1; i >= nextLookup.first; i--) {
const result = walkTree(lookup.tree, sequence, i, i);
if (result) {
for (let j = 0; j < result.substitutions.length; j++) {
const sub = result.substitutions[j];
if (sub !== null) {
sequence[i + j] = sub;
}
}
mergeRange(
ranges,
result.contextRange[0] + i,
result.contextRange[1] + i
);
i -= result.length - 1;
}
}
}
nextLookup = this._getNextLookup(sequence, nextLookup.index + 1);
}
return { sequence, ranges };
}
/**
* Returns the lookup and glyph range for the first lookup that might
* contain a match.
*
* @param sequence Input glyph sequence
* @param start The first input to try
*/
private _getNextLookup(sequence: number[], start: number): { index: number | null, first: number, last: number } {
const result: { index: number | null, first: number, last: number } = {
index: null,
first: Infinity,
last: -1
};
// Loop through each glyph and find the first valid lookup for it
for (let i = 0; i < sequence.length; i++) {
const lookups = this._glyphLookups[sequence[i]];
if (!lookups) {
continue;
}
for (let j = 0; j < lookups.length; j++) {
const lookupIndex = lookups[j];
if (lookupIndex >= start) {
// Update the lookup information if it's the one we're
// storing or earlier than it.
if (result.index === null || lookupIndex <= result.index) {
result.index = lookupIndex;
if (result.first > i) {
result.first = i;
}
result.last = i + 1;
}
break;
}
}
}
return result;
}
}
/**
* Load the font from it's binary data. The returned value can be used to find
* ligatures for the font.
*
* @param buffer ArrayBuffer of the font to load
*/
export function loadBuffer(buffer: ArrayBuffer, options?: IOptions): IFont {
const font = opentype.parse(buffer);
return new FontImpl(font, {
cacheSize: 0,
...options
});
}
export { IFont as Font, ILigatureData as LigatureData, IOptions as Options };
@@ -0,0 +1,225 @@
/**
* Copyright (c) 2018 The xterm.js authors. All rights reserved.
* @license MIT
*/
import { assert } from 'chai';
import mergeTrees from './merge';
interface ILookupResult {
contextRange: [number, number];
index: number;
subIndex: number;
length: number;
substitutions: number[];
}
function lookup(substitutionGlyph: number, index?: number, subIndex?: number): ILookupResult {
return {
contextRange: [0, 1],
index: index || 0,
subIndex: subIndex || 0,
length: 1,
substitutions: [substitutionGlyph]
};
}
describe('addon-ligatures - merge', () => {
describe('mergeTrees', () => {
it('combines disjoint trees', () => {
const result = mergeTrees([
{
individual: {
'1': { lookup: lookup(1) }
},
range: []
},
{
individual: {},
range: [{
entry: { lookup: lookup(2) },
range: [2, 4]
}]
},
{
individual: {
'5': { lookup: lookup(3) }
},
range: []
},
{
individual: {},
range: [{
entry: { lookup: lookup(4) },
range: [8, 10]
}]
}
]);
assert.deepEqual(result, {
individual: {
'1': { lookup: lookup(1) },
'5': { lookup: lookup(3) }
},
range: [{
entry: { lookup: lookup(2) },
range: [2, 4]
}, {
entry: { lookup: lookup(4) },
range: [8, 10]
}]
});
});
it('merges matching individual glyphs', () => {
const result = mergeTrees([
{
individual: {
'1': { lookup: lookup(1, 1) }
},
range: []
},
{
individual: {
'1': { lookup: lookup(2, 0) }
},
range: []
},
{
individual: {
'1': { lookup: lookup(3, 2) }
},
range: []
}
]);
assert.deepEqual(result, {
individual: {
'1': { lookup: lookup(2, 0) }
},
range: []
});
});
it('merges range glyphs overlapping individual glyphs', () => {
const result = mergeTrees([
{
individual: {
'1': { lookup: lookup(1, 0) }
},
range: []
},
{
individual: {},
range: [{
entry: { lookup: lookup(2, 1) },
range: [0, 4]
}]
}
]);
assert.deepEqual(result, {
individual: {
'0': { lookup: lookup(2, 1) },
'1': { lookup: lookup(1, 0) }
},
range: [{
entry: { lookup: lookup(2, 1) },
range: [2, 4]
}]
});
});
it('merges individual glyphs overlapping range glyphs', () => {
const result = mergeTrees([
{
individual: {},
range: [{
entry: { lookup: lookup(2, 1) },
range: [0, 4]
}]
},
{
individual: {
'1': { lookup: lookup(1, 0) }
},
range: []
}
]);
assert.deepEqual(result, {
individual: {
'0': { lookup: lookup(2, 1) },
'1': { lookup: lookup(1, 0) }
},
range: [{
entry: { lookup: lookup(2, 1) },
range: [2, 4]
}]
});
});
it('merges multiple overlapping ranges', () => {
const result = mergeTrees([
{
individual: {},
range: [{
entry: { lookup: lookup(1, 2) },
range: [0, 3]
}, {
entry: { lookup: lookup(2, 1) },
range: [6, 12]
}, {
entry: { lookup: lookup(5, 3) },
range: [15, 20]
}, {
entry: { lookup: lookup(7, 4) },
range: [20, 22]
}]
},
{
individual: {},
range: [{
entry: { lookup: lookup(3, 0) },
range: [2, 8]
}, {
entry: { lookup: lookup(4, 0) },
range: [10, 13]
}, {
entry: { lookup: lookup(6, 0) },
range: [16, 21]
}]
}
]);
assert.deepEqual(result, {
individual: {
'2': { lookup: lookup(3, 0) },
'12': { lookup: lookup(4, 0) },
'15': { lookup: lookup(5, 3) },
'20': { lookup: lookup(6, 0) },
'21': { lookup: lookup(7, 4) }
},
range: [{
entry: { lookup: lookup(1, 2) },
range: [0, 2]
}, {
entry: { lookup: lookup(3, 0) },
range: [6, 8]
}, {
entry: { lookup: lookup(3, 0) },
range: [3, 6]
}, {
entry: { lookup: lookup(2, 1) },
range: [8, 10]
}, {
entry: { lookup: lookup(4, 0) },
range: [10, 12]
}, {
entry: { lookup: lookup(6, 0) },
range: [16, 20]
}]
});
});
});
});
@@ -0,0 +1,393 @@
import { ILookupTree, ILookupTreeEntry } from './types';
/**
* Merges the provided trees into a single lookup tree. When conflicting lookups
* are encountered between two trees, the one with the lower index, then the
* lower subindex is chosen.
*
* @param trees Array of trees to merge. Entries in earlier trees are favored
* over those in later trees when there is a choice.
*/
export default function mergeTrees(trees: ILookupTree[]): ILookupTree {
const result: ILookupTree = {
individual: {},
range: []
};
const mergedEntries = new WeakMap<ILookupTreeEntry, Set<ILookupTreeEntry>>();
for (const tree of trees) {
mergeSubtree(result, tree, mergedEntries);
}
return result;
}
/**
* Recursively merges the data for the mergeTree into the mainTree.
*
* @param mainTree The tree where the values should be merged
* @param mergeTree The tree to be merged into the mainTree
* @param mergedEntries WeakMap to track already merged entry pairs
*/
function mergeSubtree(mainTree: ILookupTree, mergeTree: ILookupTree, mergedEntries: WeakMap<ILookupTreeEntry, Set<ILookupTreeEntry>>): void {
// Need to fix this recursively (and handle lookups)
for (const [glyphId, value] of Object.entries(mergeTree.individual)) {
// The main tree is guaranteed to have no overlaps between the
// individual and range values, so if we match an invididual, there
// must not be a range
if (mainTree.individual[glyphId]) {
mergeTreeEntry(mainTree.individual[glyphId], value, mergedEntries);
} else {
let matched = false;
for (const [index, { range, entry }] of mainTree.range.entries()) {
const overlap = getIndividualOverlap(Number(glyphId), range);
// Don't overlap
if (overlap.both === null) {
continue;
}
matched = true;
// If they overlap, we have to split the range and then
// merge the overlap
mainTree.individual[glyphId] = value;
mergeTreeEntry(mainTree.individual[glyphId], cloneEntry(entry), mergedEntries);
// When there's an overlap, we also have to fix up the range
// that we had already processed
mainTree.range.splice(index, 1);
for (const glyph of overlap.second) {
if (Array.isArray(glyph)) {
mainTree.range.push({
range: glyph,
entry: cloneEntry(entry)
});
} else {
mainTree.individual[glyph] = cloneEntry(entry);
}
}
}
if (!matched) {
mainTree.individual[glyphId] = value;
}
}
}
for (const { range, entry } of mergeTree.range) {
// Ranges are more complicated, because they can overlap with
// multiple things, individual and range alike. We start by
// eliminating ranges that are already present in another range
let remainingRanges: (number | [number, number])[] = [range];
for (let index = 0; index < mainTree.range.length; index++) {
const { range, entry: resultEntry } = mainTree.range[index];
for (const [remainingIndex, remainingRange] of remainingRanges.entries()) {
if (Array.isArray(remainingRange)) {
const overlap = getRangeOverlap(remainingRange, range);
if (overlap.both === null) {
continue;
}
mainTree.range.splice(index, 1);
index--;
const entryToMerge: ILookupTreeEntry = cloneEntry(resultEntry);
if (Array.isArray(overlap.both)) {
mainTree.range.push({
range: overlap.both,
entry: entryToMerge
});
} else {
mainTree.individual[overlap.both] = entryToMerge;
}
mergeTreeEntry(entryToMerge, cloneEntry(entry), mergedEntries);
for (const second of overlap.second) {
if (Array.isArray(second)) {
mainTree.range.push({
range: second,
entry: cloneEntry(resultEntry)
});
} else {
mainTree.individual[second] = cloneEntry(resultEntry);
}
}
remainingRanges = overlap.first;
} else {
const overlap = getIndividualOverlap(remainingRange, range);
if (overlap.both === null) {
continue;
}
// If they overlap, we have to split the range and then
// merge the overlap
mainTree.individual[remainingRange] = cloneEntry(entry);
mergeTreeEntry(mainTree.individual[remainingRange], cloneEntry(resultEntry), mergedEntries);
// When there's an overlap, we also have to fix up the range
// that we had already processed
mainTree.range.splice(index, 1);
index--;
for (const glyph of overlap.second) {
if (Array.isArray(glyph)) {
mainTree.range.push({
range: glyph,
entry: cloneEntry(resultEntry)
});
} else {
mainTree.individual[glyph] = cloneEntry(resultEntry);
}
}
remainingRanges.splice(remainingIndex, 1, ...overlap.first);
break;
}
}
}
// Next, we run the same against any individual glyphs
for (const glyphId of Object.keys(mainTree.individual)) {
for (const [remainingIndex, remainingRange] of remainingRanges.entries()) {
if (Array.isArray(remainingRange)) {
const overlap = getIndividualOverlap(Number(glyphId), remainingRange);
if (overlap.both === null) {
continue;
}
// If they overlap, we have to merge the overlap
mergeTreeEntry(mainTree.individual[glyphId], cloneEntry(entry), mergedEntries);
// Update the remaining ranges
remainingRanges.splice(remainingIndex, 1, ...overlap.second);
break;
} else {
if (Number(glyphId) === remainingRange) {
mergeTreeEntry(mainTree.individual[glyphId], cloneEntry(entry), mergedEntries);
break;
}
}
}
}
// Any remaining ranges should just be added directly
for (const remainingRange of remainingRanges) {
if (Array.isArray(remainingRange)) {
mainTree.range.push({
range: remainingRange,
entry: cloneEntry(entry)
});
} else {
mainTree.individual[remainingRange] = cloneEntry(entry);
}
}
}
}
/**
* Recursively merges the entry forr the mergeTree into the mainTree
*
* @param mainTree The entry where the values should be merged
* @param mergeTree The entry to merge into the mainTree
* @param mergedEntries WeakMap to track already merged entry pairs
*/
function mergeTreeEntry(mainTree: ILookupTreeEntry, mergeTree: ILookupTreeEntry, mergedEntries: WeakMap<ILookupTreeEntry, Set<ILookupTreeEntry>>): void {
// Check if we've already merged this pair
let mergedSet = mergedEntries.get(mainTree);
if (mergedSet?.has(mergeTree)) {
return;
}
if (!mergedSet) {
mergedSet = new Set();
mergedEntries.set(mainTree, mergedSet);
}
mergedSet.add(mergeTree);
if (
mergeTree.lookup && (
!mainTree.lookup ||
mainTree.lookup.index > mergeTree.lookup.index ||
(mainTree.lookup.index === mergeTree.lookup.index && mainTree.lookup.subIndex > mergeTree.lookup.subIndex)
)
) {
mainTree.lookup = mergeTree.lookup;
}
if (mergeTree.forward) {
if (!mainTree.forward) {
mainTree.forward = mergeTree.forward;
} else {
mergeSubtree(mainTree.forward, mergeTree.forward, mergedEntries);
}
}
if (mergeTree.reverse) {
if (!mainTree.reverse) {
mainTree.reverse = mergeTree.reverse;
} else {
mergeSubtree(mainTree.reverse, mergeTree.reverse, mergedEntries);
}
}
}
interface IOverlap {
first: (number | [number, number])[];
second: (number | [number, number])[];
both: number | [number, number] | null;
}
/**
* Determines the overlap (if any) between two ranges. Returns the distinct
* ranges for each range and the overlap (if any).
*
* @param first First range
* @param second Second range
*/
function getRangeOverlap(first: [number, number], second: [number, number]): IOverlap {
const result: IOverlap = {
first: [],
second: [],
both: null
};
// Both
if (first[0] < second[1] && second[0] < first[1]) {
const start = Math.max(first[0], second[0]);
const end = Math.min(first[1], second[1]);
result.both = rangeOrIndividual(start, end);
}
// Before
if (first[0] < second[0]) {
const start = first[0];
const end = Math.min(second[0], first[1]);
result.first.push(rangeOrIndividual(start, end));
} else if (second[0] < first[0]) {
const start = second[0];
const end = Math.min(second[1], first[0]);
result.second.push(rangeOrIndividual(start, end));
}
// After
if (first[1] > second[1]) {
const start = Math.max(first[0], second[1]);
const end = first[1];
result.first.push(rangeOrIndividual(start, end));
} else if (second[1] > first[1]) {
const start = Math.max(first[1], second[0]);
const end = second[1];
result.second.push(rangeOrIndividual(start, end));
}
return result;
}
/**
* Determines the overlap (if any) between the individual glyph and the range
* provided. Returns the glyphs and/or ranges that are unique to each provided
* and the overlap (if any).
*
* @param first Individual glyph
* @param second Range
*/
function getIndividualOverlap(first: number, second: [number, number]): IOverlap {
// Disjoint
if (first < second[0] || first > second[1]) {
return {
first: [first],
second: [second],
both: null
};
}
const result: IOverlap = {
first: [],
second: [],
both: first
};
if (second[0] < first) {
result.second.push(rangeOrIndividual(second[0], first));
}
if (second[1] > first) {
result.second.push(rangeOrIndividual(first + 1, second[1]));
}
return result;
}
/**
* Returns an individual glyph if the range is of size one or a range if it is
* larger.
*
* @param start Beginning of the range (inclusive)
* @param end End of the range (exclusive)
*/
function rangeOrIndividual(start: number, end: number): number | [number, number] {
if (end - start === 1) {
return start;
}
return [start, end];
}
/**
* Clones an individual lookup tree entry.
*
* @param entry Lookup tree entry to clone
* @param visited Map to track already cloned entries (prevents infinite loops)
*/
function cloneEntry(entry: ILookupTreeEntry, visited: Map<ILookupTreeEntry, ILookupTreeEntry> = new Map()): ILookupTreeEntry {
if (visited.has(entry)) {
return visited.get(entry)!;
}
const result: ILookupTreeEntry = {};
visited.set(entry, result);
if (entry.forward) {
result.forward = cloneTree(entry.forward, visited);
}
if (entry.reverse) {
result.reverse = cloneTree(entry.reverse, visited);
}
if (entry.lookup) {
result.lookup = {
contextRange: entry.lookup.contextRange.slice() as [number, number],
index: entry.lookup.index,
length: entry.lookup.length,
subIndex: entry.lookup.subIndex,
substitutions: entry.lookup.substitutions.slice()
};
}
return result;
}
/**
* Clones a lookup tree.
*
* @param tree Lookup tree to clone
* @param visited Map to track already cloned entries (prevents infinite loops)
*/
function cloneTree(tree: ILookupTree, visited: Map<ILookupTreeEntry, ILookupTreeEntry> = new Map()): ILookupTree {
const individual: { [glyphId: string]: ILookupTreeEntry } = {};
for (const [glyphId, entry] of Object.entries(tree.individual)) {
individual[glyphId] = cloneEntry(entry, visited);
}
return {
individual,
range: tree.range.map(({ range, entry }) => ({
range: range.slice() as [number, number],
entry: cloneEntry(entry, visited)
}))
};
}

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