Compare commits

..
Author SHA1 Message Date
Cursor AgentandDaniel Imms 18100d32fb Use src/ imports in addons; bundle test output via esbuild
Addons no longer import from out/ or out-esbuild in source. TypeScript
and editors resolve ../../../src/common|browser directly. Addon unit test
artifacts bundle those dependencies from source (platform node, npm
packages external).

Reverts preserveConstEnums; const enums are handled in bundled output.

Co-authored-by: Daniel Imms <Tyriar@users.noreply.github.com>
2026-05-28 01:28:31 +00:00
Cursor AgentandDaniel Imms 07bdaf8767 Add Node smoke tests for @xterm/headless package
Verify CJS and ESM bundles load, accept writes, expose buffer API,
resize, and dispose. Add npm run test-headless-node script.

Co-authored-by: Daniel Imms <Tyriar@users.noreply.github.com>
2026-05-28 01:23:21 +00:00
Cursor AgentandDaniel Imms 584954b4fb Fix addon and dist builds after path mapping removal
Import shared core from ../../../out/common|browser in addons so tsc
and runtime agree. Enable preserveConstEnums so const enums remain in
compiled out/ for addon consumers.

Build xterm esbuild before addons so out-esbuild exists for parallel
addon jobs. Verified: npm run setup, esbuild-demo-client, package.

Co-authored-by: Daniel Imms <Tyriar@users.noreply.github.com>
2026-05-28 01:08:32 +00:00
Cursor AgentandDaniel Imms 9a96b27566 Remove TypeScript path mappings in favor of relative imports
Replace common/*, browser/*, and headless/* path-mapped imports with
relative paths across src/, addons/, test/, and demo/. Remove paths
from all tsconfig.json files and include addon typings directly.

Addon sources import shared code via out-esbuild/ so unit tests resolve
compiled modules with runtime const enums. Fix benchmark bare imports
and normalize corrupted tsconfig formatting.

Resolves #5896

Co-authored-by: Daniel Imms <Tyriar@users.noreply.github.com>
2026-05-28 01:01:25 +00:00
Cursor AgentandDaniel Imms a04941a936 fix: repair corrupted tsconfig.json files from regex script
- Fix malformed skipLibCheck and types array closing braces
- Repair broken include arrays and trailing commas
- Add addon typings to addon src includes (paths not restored)
- Normalize formatting to valid JSON with 2-space indent

Co-authored-by: Daniel Imms <Tyriar@users.noreply.github.com>
2026-05-28 00:52:24 +00:00
Daniel Imms 91fcb46a1a Merge pull request #5955 from xtermjs/cursor/gitignore-cursor-logs-5a86
Ignore .cursor/*.log files in git
2026-05-27 16:25:02 -07:00
Cursor AgentandDaniel Imms 1d569f873d Ignore .cursor/*.log files in git
Co-authored-by: Daniel Imms <Tyriar@users.noreply.github.com>
2026-05-27 23:22:32 +00:00
Daniel Imms db244ea5a8 Merge pull request #5954 from xtermjs/cursor/align-headless-marker-alt-buffer-2866
Allow markers on alt buffer in headless terminal
2026-05-27 14:51:35 -07:00
Cursor AgentandDaniel Imms fdcd1c2720 Align xterm-headless marker JSDoc with xterm.d.ts
Co-authored-by: Daniel Imms <Tyriar@users.noreply.github.com>
2026-05-27 21:46:20 +00:00
Cursor AgentandDaniel Imms 57b48f6048 Allow markers on alt buffer in headless terminal
Remove the alt-buffer guard in headless registerMarker so behavior
matches the browser terminal. Update headless public API and typings
to return IMarker instead of IMarker | undefined.

Co-authored-by: Daniel Imms <Tyriar@users.noreply.github.com>
2026-05-27 21:28:36 +00:00
Daniel Imms ec0c93fd5a Merge pull request #5950 from xtermjs/cursor/writebuffer-dispose-timeout-124c
Cancel WriteBuffer innerWrite timers on dispose
2026-05-27 14:14:41 -07:00
Daniel Imms be339bf796 Merge pull request #5947 from xtermjs/cursor/decoration-bucket-merge-loop-7132
Avoid spread when merging decoration line buckets
2026-05-27 13:15:37 -07:00
Daniel Imms 7294124736 Merge pull request #5945 from xtermjs/cursor/bufferline-stringbuilder-5939-79f8
Use StringBuilder in BufferLine.translateToString
2026-05-27 13:11:02 -07:00
Daniel Imms 2c3fad39af Merge pull request #5948 from xtermjs/cursor/hoist-get-current-link-id-e29d
Hoist _getCurrentLinkId() in print() loop
2026-05-27 13:09:09 -07:00
Cursor AgentandDaniel Imms edb4f89dd8 Add WriteBuffer regression tests for dispose and hot paths
Co-authored-by: Daniel Imms <Tyriar@users.noreply.github.com>
2026-05-27 20:00:06 +00:00
Cursor AgentandDaniel Imms 224c113b64 Move linkId declaration above use site in print loop
Co-authored-by: Daniel Imms <Tyriar@users.noreply.github.com>
2026-05-27 19:36:45 +00:00
Cursor AgentandDaniel Imms 3e0c7d18e5 Register WriteBuffer cleanup via toDisposable in constructor
Co-authored-by: Daniel Imms <Tyriar@users.noreply.github.com>
2026-05-27 19:35:58 +00:00
Daniel Imms 95d9224b8c Merge pull request #5946 from xtermjs/cursor/fix-duplicate-buildnumber-guard-cbb8
Fix duplicate buildNumber guard in Windows ConPTY logic
2026-05-27 12:34:08 -07:00
Cursor AgentandDaniel Imms 7499a2181d Use DisposableStore.isDisposed instead of duplicate flag
Co-authored-by: Daniel Imms <Tyriar@users.noreply.github.com>
2026-05-27 19:34:08 +00:00
Daniel Imms 7dc820f5fa Merge pull request #5949 from xtermjs/cursor/rename-headless-register-marker-22c3
Rename headless core addMarker → registerMarker
2026-05-27 12:33:02 -07:00
Cursor AgentandDaniel Imms e59a6c1f7b Reuse shared StringBuilder in BufferLine.translateToString
Avoid per-call allocation when translating many lines in sequence by
resetting a module-level work StringBuilder before and after each use.

Co-authored-by: Daniel Imms <Tyriar@users.noreply.github.com>
2026-05-27 19:31:00 +00:00
Daniel Imms f3e2c08a5d Merge pull request #5944 from xtermjs/cursor/buffer-reflow-single-loop-sum-705e
perf: use single loop for cellsNeeded sum in BufferReflow
2026-05-27 12:28:08 -07:00
Daniel Imms b138bf38d0 Merge pull request #5943 from xtermjs/cursor/remove-duplicate-defineproperty-57b2
Remove duplicate Object.defineProperty loop in headless Terminal
2026-05-27 12:26:36 -07:00
Cursor AgentandDaniel Imms c6e4627b45 Hoist _getCurrentLinkId() in print() loop
Avoid calling _getCurrentLinkId() twice per codepoint when a hyperlink
is active by reading the link id once before the print loop.

Fixes #5940

Co-authored-by: Daniel Imms <Tyriar@users.noreply.github.com>
2026-05-27 18:18:24 +00:00
Cursor AgentandDaniel Imms d91045dc5f Cancel WriteBuffer innerWrite timers on dispose
Guard _innerWrite and public write methods after disposal so pending
setTimeout continuations cannot run against a disposed terminal.

Fixes #5936

Co-authored-by: Daniel Imms <Tyriar@users.noreply.github.com>
2026-05-27 18:18:22 +00:00
Cursor AgentandDaniel Imms 41dfdba2cf Avoid spread when merging decoration line buckets
Replace existing.push(...bucket) with an indexed loop in
_mergeLineBucket to avoid intermediate array allocations during
buffer trim/insert/delete re-indexing.

Reuse _mergeLineBucket in _handleBufferLinesTrim for consistency.

Fixes #5941

Co-authored-by: Daniel Imms <Tyriar@users.noreply.github.com>
2026-05-27 18:18:13 +00:00
Cursor AgentandDaniel Imms ba33decb68 Rename headless core addMarker to registerMarker
Align headless Terminal core API with browser CoreBrowserTerminal
and the public registerMarker naming.

Fixes #5938

Co-authored-by: Daniel Imms <Tyriar@users.noreply.github.com>
2026-05-27 18:18:01 +00:00
Cursor AgentandDaniel Imms 17e3b84bcc perf: use single loop for cellsNeeded sum in BufferReflow
Replace map().reduce() with one accumulator loop in
reflowSmallerGetNewLineLengths to avoid allocating an intermediate
array when summing wrapped line trimmed lengths.

Fixes #5942

Co-authored-by: Daniel Imms <Tyriar@users.noreply.github.com>
2026-05-27 18:17:17 +00:00
Cursor AgentandDaniel Imms 182ff2f635 Remove duplicate Object.defineProperty loop in headless Terminal
The constructor defined property getters/setters twice on _publicOptions;
the first block was immediately overwritten by the second. Align with the
browser public Terminal implementation.

Fixes #5935

Co-authored-by: Daniel Imms <Tyriar@users.noreply.github.com>
2026-05-27 18:17:08 +00:00
Cursor AgentandDaniel Imms bcb4e5298b Fix duplicate buildNumber guard in Windows ConPTY logic
The windowsPty option change handler checked buildNumber twice
instead of requiring both backend and buildNumber to be defined.
This aligns with Buffer.ts and ensures ConPTY wrapping heuristics
are evaluated only when both fields are present.

Fixes #5934

Co-authored-by: Daniel Imms <Tyriar@users.noreply.github.com>
2026-05-27 18:16:54 +00:00
Cursor AgentandDaniel Imms 42facac2d9 Use StringBuilder in BufferLine.translateToString
Replace O(n²) string concatenation in the translate loop with
StringBuilder.append, matching the pattern used elsewhere in the project.

Fixes #5939

Co-authored-by: Daniel Imms <Tyriar@users.noreply.github.com>
2026-05-27 18:16:51 +00:00
Daniel Imms 2ab0f10a26 Merge pull request #5929 from xtermjs/cursor/atlas-cache-texture-size-7cad
fix(webgl): include texture size and cell dimensions in atlas cache equality
2026-05-27 10:31:32 -07:00
Daniel Imms 280dc0a36a Merge pull request #5927 from xtermjs/cursor/webgl-vao-dispose-ef2f
fix(addon-webgl): delete VAOs on renderer dispose
2026-05-27 09:34:46 -07:00
Daniel Imms ebe8cb749a Merge pull request #5928 from xtermjs/cursor/fix-dcs-osc-apc-payload-o-n-f60b
Fix O(n²) payload accumulation in DCS/OSC/APC handlers
2026-05-27 09:32:42 -07:00
Daniel Imms c28f3c8d73 Merge pull request #5922 from xtermjs/cursor/remove-stale-sgr-fixme-e362
Remove stale FIXME comments from charAttributes JSDoc
2026-05-27 09:28:29 -07:00
Daniel Imms 3bd7ac083a Merge pull request #5923 from xtermjs/cursor/texture-atlas-remove-emitter-leak-75ac
Fix TextureAtlas remove-emitter leak on dispose
2026-05-27 09:27:51 -07:00
Cursor AgentandDaniel Imms 30b42838fd Extract StringBuilder and LimitedStringBuilder with unit tests
Replace PayloadStringBuffer with LimitedStringBuilder from common.
Add thorough StringBuilder and LimitedStringBuilder test coverage.

Co-authored-by: Daniel Imms <Tyriar@users.noreply.github.com>
2026-05-27 16:27:26 +00:00
Daniel Imms 80e832e417 Merge pull request #5930 from xtermjs/cursor/webgl-ilogservice-5921-3745
fix(addon-webgl): use ILogService instead of console for logging
2026-05-27 09:21:12 -07:00
Daniel Imms 470947abd8 Merge pull request #5931 from xtermjs/cursor/headless-clear-markers-ccc0
fix(headless): clear markers when clear() is called
2026-05-27 09:18:17 -07:00
Daniel Imms bb49a80ace Merge branch 'master' into cursor/headless-clear-markers-ccc0 2026-05-27 09:13:38 -07:00
Daniel Imms 65b87e0f40 Merge pull request #5933 from xtermjs/cursor/replace-clone-with-structuredclone-34fc
Replace custom clone utility with structuredClone
2026-05-27 09:12:41 -07:00
Daniel Imms 39fc0f259e Merge pull request #5932 from xtermjs/cursor/webgl-context-loss-timeout-dispose-3f04
Clear WebGL context-loss timeout on WebglRenderer dispose
2026-05-27 09:10:07 -07:00
Daniel Imms b787ac8b1b Merge pull request #5924 from xtermjs/cursor/selection-trim-listener-leak-135c
Fix SelectionService trim listener leak
2026-05-27 09:10:04 -07:00
Daniel Imms 457efbf55d Merge pull request #5925 from xtermjs/cursor/fix-onwillopen-error-logging-7e14
Log onWillOpen handler errors instead of swallowing them
2026-05-27 09:00:33 -07:00
Daniel Imms 42270a0b68 Merge pull request #5926 from xtermjs/cursor/fix-flushsync-onwriteparsed-7a21
Fix flushSync() to fire onWriteParsed
2026-05-27 09:00:13 -07:00
Cursor AgentandDaniel Imms 090b77f354 Replace custom clone utility with structuredClone
Remove common/Clone.ts and its tests. Use the native structuredClone API
for cloning default modes, options, and test fixtures.

Fixes #5911

Co-authored-by: Daniel Imms <Tyriar@users.noreply.github.com>
2026-05-27 15:41:00 +00:00
Cursor AgentandDaniel Imms 534ce1365b Clear WebGL context-loss timeout on WebglRenderer dispose
Fixes #5919

Co-authored-by: Daniel Imms <Tyriar@users.noreply.github.com>
2026-05-27 15:40:10 +00:00
Cursor AgentandDaniel Imms f757c965ee fix(addon-webgl): keep VAO WebGL types non-nullable
Co-authored-by: Daniel Imms <Tyriar@users.noreply.github.com>
2026-05-27 15:38:58 +00:00
Cursor AgentandDaniel Imms 4aa02f3fd9 fix(addon-webgl): route logging through ILogService instead of console
Replace direct console.log/warn/error calls in the WebGL addon with
ILogService so messages respect the terminal logLevel and custom logger.

Fixes xtermjs/xterm.js#5921

Co-authored-by: Daniel Imms <Tyriar@users.noreply.github.com>
2026-05-27 15:37:09 +00:00
Cursor AgentandDaniel Imms f43273b3f0 fix(webgl): include texture size and cell dimensions in atlas cache equality
configEquals omitted deviceMaxTextureSize, deviceCellWidth, and
deviceCellHeight, which could cause the char atlas cache to reuse an
atlas built for a different GPU texture limit or cell size.

Fixes #5915

Co-authored-by: Daniel Imms <Tyriar@users.noreply.github.com>
2026-05-27 15:35:05 +00:00
Cursor AgentandDaniel Imms c06c3025f2 fix(addon-webgl): delete VAOs on renderer dispose
Register vertex array objects for disposal in GlyphRenderer and
RectangleRenderer, matching existing buffer and program cleanup.

Fixes #5918

Co-authored-by: Daniel Imms <Tyriar@users.noreply.github.com>
2026-05-27 15:35:04 +00:00
Daniel Imms 1064e4f783 Merge pull request #5812 from JeffreyCA/osc8-hover-fix
Underline entire wrapped OSC 8 links on hover
2026-05-27 08:34:50 -07:00
Cursor AgentandDaniel Imms fad4821fd9 Fix O(n²) payload accumulation in DCS/OSC/APC handlers
Replace repeated string concatenation with a chunk buffer that joins
once on completion. Fixes #5916.

Co-authored-by: Daniel Imms <Tyriar@users.noreply.github.com>
2026-05-27 15:34:48 +00:00
Cursor AgentandDaniel Imms 306c3d73ec Fix flushSync() to fire onWriteParsed
flushSync() processes pending writes synchronously (e.g. during
CoreTerminal.resize()) but did not emit onWriteParsed, unlike the
async _innerWrite path. Addons listening for parse completion could
miss updates after resize.

Fixes #5912

Co-authored-by: Daniel Imms <Tyriar@users.noreply.github.com>
2026-05-27 15:34:26 +00:00
Cursor AgentandDaniel Imms 8f2bb32c9c fix(headless): clear markers when clear() is called
Headless clear() now calls buffer.clearAllMarkers() to match the browser
implementation, preventing markers from surviving clear() and pointing at
wrong lines.

Fixes #5910

Co-authored-by: Daniel Imms <Tyriar@users.noreply.github.com>
2026-05-27 15:34:00 +00:00
Cursor AgentandDaniel Imms 7b6c814566 Log errors from onWillOpen handlers instead of swallowing them
Fixes #5920

Co-authored-by: Daniel Imms <Tyriar@users.noreply.github.com>
2026-05-27 15:33:48 +00:00
Cursor AgentandDaniel Imms adc9dea083 Fix TextureAtlas remove-emitter leak on dispose
Dispose _onRemoveTextureAtlasCanvas in TextureAtlas.dispose() alongside
_onAddTextureAtlasCanvas to avoid leaking listeners when the atlas is torn down.

Fixes #5917

Co-authored-by: Daniel Imms <Tyriar@users.noreply.github.com>
2026-05-27 15:33:28 +00:00
Cursor AgentandDaniel Imms d565a2750e Fix SelectionService trim listener leak with MutableDisposable
Register _trimListener via _register(MutableDisposable) so it is disposed
when SelectionService is torn down. Use .value assignment on buffer
activate instead of manual dispose/swap.

Fixes #5914

Co-authored-by: Daniel Imms <Tyriar@users.noreply.github.com>
2026-05-27 15:33:20 +00:00
Cursor AgentandDaniel Imms c5ab83d697 Remove stale FIXME comments from charAttributes JSDoc
Fixes #5913. Blinking is supported in renderers via TextBlinkStateManager,
and bright background colors (100-107) are handled in the SGR loop.

Co-authored-by: Daniel Imms <Tyriar@users.noreply.github.com>
2026-05-27 15:33:03 +00:00
c62e6dd765 Register BufferNamespaceApi onBufferActivate listener with disposables (#5906)
Wrap the onBufferActivate subscription in this._register so it is
disposed when BufferNamespaceApi is disposed, matching the pattern used
elsewhere in the codebase.

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Daniel Imms <Tyriar@users.noreply.github.com>
2026-05-27 04:39:27 +00:00
Daniel Imms 29a07f1d8f Merge pull request #5905 from xtermjs/cursor/multiline-comment-style-5899-d349
Add multiline-comment-style lint rule
2026-05-26 21:05:48 -07:00
Cursor AgentandDaniel Imms 88cef97049 Attach method signatures to last handler JSDoc line in Types.ts
Co-authored-by: Daniel Imms <Tyriar@users.noreply.github.com>
2026-05-27 04:01:00 +00:00
Cursor AgentandDaniel Imms 0e823004e6 Use single-line JSDoc comments per handler in Types.ts
Document multi-variant escape handlers with separate /** ... */ lines
instead of multiline blocks.

Co-authored-by: Daniel Imms <Tyriar@users.noreply.github.com>
2026-05-27 03:59:42 +00:00
Cursor AgentandDaniel Imms df3422fb0b Use jsdoc/multiline-blocks instead of multiline-comment-style
Replace multiline-comment-style (starred-block), which also forces
multiline // comments into block comments, with jsdoc/multiline-blocks.
That rule only enforces starred JSDoc layout for /** blocks (no text on
the opening line) and leaves multiline // comments unchanged for
internal documentation.

Fix multiline JSDoc in Types.ts.

Fixes #5899

Co-authored-by: Daniel Imms <Tyriar@users.noreply.github.com>
2026-05-26 20:32:23 +00:00
Cursor AgentandDaniel Imms 4ec85e67ba Add multiline-comment-style lint rule and fix violations
Enable multiline-comment-style with starred-block to require JSDoc-style
block comments with content on separate lines from the opening delimiter.
Auto-fix existing violations across the codebase.

Fixes #5899

Co-authored-by: Daniel Imms <Tyriar@users.noreply.github.com>
2026-05-26 20:15:55 +00:00
Daniel Imms 1a67b6f4b3 Merge pull request #5903 from xtermjs/cursor/space-unary-ops-5898-604c
Add space-unary-ops lint rule to prevent space after !
2026-05-26 13:01:20 -07:00
Daniel Imms afaed108e4 Merge pull request #5828 from lawrence3699/fix/addon-ligatures-prepublish-only
fix(addon-ligatures): run package build on publish
2026-05-26 12:26:53 -07:00
Daniel Imms c27d6bb462 Merge branch 'master' into fix/addon-ligatures-prepublish-only 2026-05-26 12:26:23 -07:00
Daniel Imms 9c4f0f96ca Merge pull request #5831 from juspay/fix/intersection-observer-nullify
Clear IntersectionObserver reference on dispose
2026-05-26 12:23:13 -07:00
Daniel Imms 18c370a154 Merge pull request #5813 from CodinGame/fix-wrong-window-regression
Use element window instead of global object
2026-05-26 12:18:29 -07:00
Daniel Imms 850f4ad738 Merge pull request #5872 from xtermjs/dependabot/npm_and_yarn/fast-uri-3.1.2
Bump fast-uri from 3.1.0 to 3.1.2
2026-05-26 12:15:57 -07:00
Daniel Imms b665f6259a Merge pull request #5871 from xtermjs/dependabot/npm_and_yarn/axios-1.15.2
Bump axios from 1.15.0 to 1.15.2
2026-05-26 12:15:54 -07:00
Cursor AgentandDaniel Imms d195dc9a3b Add space-unary-ops lint rule and fix SixelHandler violation
Enable @stylistic/space-unary-ops to warn on spaces after unary
operators like ! in conditions. Fix existing violation in SixelHandler.

Fixes #5898

Co-authored-by: Daniel Imms <Tyriar@users.noreply.github.com>
2026-05-26 18:03:15 +00:00
Daniel Imms 4e5994d8a2 Merge pull request #5902 from Tyriar/tyriar/cell_opt
Optimize forEachDecorationAtCell by maintaining cache against buffer line index
2026-05-26 10:52:17 -07:00
Daniel ImmsandCursor 4a0cb5ae6b Add MicrotaskTimer and use it for decoration line index sync
Introduce MicrotaskTimer as a TimeoutTimer-style helper that schedules a
single runner via queueMicrotask, with set and cancel but no cancelAndSet.

Use it in DecorationLineCache to batch insert/delete line-index updates after
marker line adjustments, replacing ad-hoc queueMicrotask scheduling.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-26 09:52:53 -07:00
Daniel ImmsandCursor 9e72a4b6d3 Refactor decoration line index into DecorationLineCache
Extract the per-line lookup index from DecorationService into an exported
DecorationLineCache class in the same module, with DecorationService listed
first for readability.

Optimize buffer insert/delete maintenance to shift map keys in O(unique
indexed lines) instead of full re-indexing every affected decoration. Only
multi-line decorations that span the mutation point get a full re-index.

Add a direct DecorationLineCache unit test in DecorationService.test.ts.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-26 09:47:11 -07:00
Daniel ImmsandCursor f484584b70 Speed up decoration cell lookup with a per-line index
Refs #5176.

forEachDecorationAtCell and getDecorationsAtCell previously scanned every
registered decoration on each call. With thousands of search highlights this
becomes O(total decorations) per cell and blocks rendering during viewport
refreshes.

Maintain a Map of buffer line -> decorations that cover that line. Multi-line
decorations are indexed on every line in their span, so mixed single-line and
multi-line workloads stay fast without falling back to a full scan.

Keep the index aligned with buffer mutations:
- Register and unregister decorations in the line buckets on add/dispose.
- On trim, shift line keys synchronously (before marker handlers run).
- On insert/delete, re-index affected decorations in a microtask after marker
  line updates are applied.

Inject IBufferService so line listeners follow the active buffer. Add unit
tests for trim, dispose-on-trim, insert, and dense single-line plus multi-line
lookup. Add DecorationService.benchmark.ts to measure sparse hits and
viewport-sized grid scans with 20k decorations.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-26 09:30:25 -07:00
Daniel Imms e749cb6125 Merge pull request #5891 from Tyriar/cross-env
Remove cross-env dep
2026-05-21 03:17:50 -07:00
Daniel Imms 8df996f9e7 Merge pull request #5892 from Tyriar/tyriar/deps
Remove @types/debug and trusted-types
2026-05-21 03:17:24 -07:00
Daniel Imms 51f6186352 Remove @types/debug and trusted-types 2026-05-21 03:09:41 -07:00
Daniel Imms ae5c41c518 Merge pull request #5890 from Tyriar/utf8
Remove utf8 dep
2026-05-21 03:09:04 -07:00
dependabot[bot] 17e7dafa17 Bump axios from 1.15.0 to 1.15.2
Bumps [axios](https://github.com/axios/axios) from 1.15.0 to 1.15.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.15.0...v1.15.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-21 10:06:53 +00:00
Daniel Imms df9fb7e473 Remove cross-env dep 2026-05-21 03:06:53 -07:00
dependabot[bot] 41d9c4080f Bump fast-uri from 3.1.0 to 3.1.2
Bumps [fast-uri](https://github.com/fastify/fast-uri) from 3.1.0 to 3.1.2.
- [Release notes](https://github.com/fastify/fast-uri/releases)
- [Commits](https://github.com/fastify/fast-uri/compare/v3.1.0...v3.1.2)

---
updated-dependencies:
- dependency-name: fast-uri
  dependency-version: 3.1.2
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-21 10:06:23 +00:00
Daniel Imms f100e93ba5 Remove utf8 dep 2026-05-21 03:04:16 -07:00
Daniel Imms 1d3825d341 Merge pull request #5889 from Tyriar/deepEqual
Remove deep-equal/types packages
2026-05-21 03:03:37 -07:00
Daniel Imms 57315e6cc9 Remove deep-equal/types packages 2026-05-21 02:58:43 -07:00
Daniel Imms 2be4fc68e5 Merge pull request #5884 from sotasan/addon-image-kitty-q2-suppresses-ok
addon-image: Kitty graphics q=2 should suppress OK responses too
2026-05-21 02:37:53 -07:00
Daniel Imms 66fabfc232 Merge pull request #5878 from PerBothner/demo-size-style
demo - indicate screen area size when non-auto
2026-05-21 02:36:24 -07:00
Daniel Imms 3e2a42eb29 Merge pull request #5883 from vsych/fix-webgl-atlas-merge-stale-vertices
Fix webgl rendering corruption from atlas page merges
2026-05-21 02:26:03 -07:00
Daniel Imms 6a47d09b4c Merge pull request #5888 from Tyriar/tyriar/nal-trace
Git ignore cursor nal-traces
2026-05-21 02:18:06 -07:00
Daniel Imms b36001ee7b Git ignore cursor nal-traces 2026-05-21 02:17:43 -07:00
Daniel Imms e64f25a0dd Merge pull request #5880 from ciembor/master
Add 4bit to the Real-world uses section.
2026-05-21 02:13:34 -07:00
Victor Sych 3bcb57545e Re-upload atlas textures after page merges 2026-05-21 04:59:37 +02:00
Victor Sych dc726a2ae6 Fix webgl rendering corruption from atlas page merges 2026-05-19 02:27:15 +02:00
Sōta 06e5ac7ab4 addon-image: Kitty graphics q=2 should suppress OK responses too 2026-05-18 18:26:32 +02:00
Maciej Ciemborowicz c6e3492a4a Add 4bit to the Real-world uses section. 2026-05-14 04:45:52 +02:00
Per Bothner 86291b6e96 Highlight xterm-screen when size is non-auto 2026-05-12 17:53:33 -07:00
Sridhar Ratnakumar 685434ca84 Fix IntersectionObserver retention on dispose 2026-04-23 11:35:56 -04:00
lawrence3699 ff72c00219 fix(addon-ligatures): run package build on publish 2026-04-22 12:57:27 +10:00
Loïc Mangeonjean 69ee4ad23b fix: use element window instead of global object 2026-04-13 12:51:17 +02:00
Jeffrey Chen 253d1e9beb Underline entire wrapped OSC 8 link on hover 2026-04-12 20:06:43 -07:00
266 changed files with 2499 additions and 3791 deletions
+4
View File
@@ -22,6 +22,10 @@ build/
yarn.lock
test-results/
# Tracing/debug files
.cursor/nal-trace/
.cursor/*.log
# Keep bundled code out of Git
dist/
demo/dist/
+1
View File
@@ -253,6 +253,7 @@ Xterm.js is used in many world-class applications to provide great terminal expe
- [**SSH Connection Manager**](https://github.com/Amtrend/ssh-manager): Modern web-based SSH terminal and SFTP manager with AES-256 encryption, PWA support, and session management.
- [**WooTTY**](https://github.com/icoretech/wootty): Flawless browser terminal for real operators.
- [**Orca**](https://github.com/stablyai/orca): Agentic development environment for 100x builders.
- [**4bit**](https://ciembor.github.io/4bit): Terminal Color Scheme Designer.
- [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. Please add any new contributions to the end of the list.
+3 -7
View File
@@ -13,15 +13,11 @@
"strict": true,
"types": [
"../../../node_modules/@types/mocha"
],
"paths": {
"@xterm/addon-attach": [
"../typings/addon-attach.d.ts"
]
}
]
},
"include": [
"./**/*",
"../../../typings/xterm.d.ts"
"../../../typings/xterm.d.ts",
"../typings/addon-attach.d.ts"
]
}
+1 -12
View File
@@ -3,23 +3,12 @@
"module": "commonjs",
"target": "ESNext",
"lib": [
"es2021",
"es2021"
],
"rootDir": ".",
"outDir": "../out-test",
"sourceMap": true,
"removeComments": true,
"paths": {
"common/*": [
"../../../src/common/*"
],
"browser/*": [
"../../../src/browser/*"
],
"*": [
"./*"
]
},
"strict": true,
"types": [
"../../../node_modules/@types/node"
+6 -2
View File
@@ -2,7 +2,11 @@
"files": [],
"include": [],
"references": [
{ "path": "./src" },
{ "path": "./test" }
{
"path": "./src"
},
{
"path": "./test"
}
]
}
+3 -10
View File
@@ -13,19 +13,12 @@
"strict": true,
"types": [
"../../../node_modules/@types/mocha"
],
"paths": {
"browser/*": [
"../../../src/browser/*"
],
"@xterm/addon-clipboard": [
"../typings/addon-clipboard.d.ts"
]
}
]
},
"include": [
"./**/*",
"../../../typings/xterm.d.ts"
"../../../typings/xterm.d.ts",
"../typings/addon-clipboard.d.ts"
],
"references": [
{
+1 -12
View File
@@ -3,23 +3,12 @@
"module": "commonjs",
"target": "ESNext",
"lib": [
"es2021",
"es2021"
],
"rootDir": ".",
"outDir": "../out-test",
"sourceMap": true,
"removeComments": true,
"paths": {
"common/*": [
"../../../src/common/*"
],
"browser/*": [
"../../../src/browser/*"
],
"*": [
"./*"
]
},
"strict": true,
"types": [
"../../../node_modules/@types/node"
+6 -2
View File
@@ -2,7 +2,11 @@
"files": [],
"include": [],
"references": [
{ "path": "./src" },
{ "path": "./test" }
{
"path": "./src"
},
{
"path": "./test"
}
]
}
+1 -1
View File
@@ -5,7 +5,7 @@
import type { Terminal, ITerminalAddon, IRenderDimensions } from '@xterm/xterm';
import type { FitAddon as IFitApi } from '@xterm/addon-fit';
import { ViewportConstants } from 'browser/shared/Constants';
import { ViewportConstants } from '../../../src/browser/shared/Constants';
interface ITerminalDimensions {
/**
+3 -10
View File
@@ -13,19 +13,12 @@
"strict": true,
"types": [
"../../../node_modules/@types/mocha"
],
"paths": {
"browser/*": [
"../../../src/browser/*"
],
"@xterm/addon-fit": [
"../typings/addon-fit.d.ts"
]
}
]
},
"include": [
"./**/*",
"../../../typings/xterm.d.ts"
"../../../typings/xterm.d.ts",
"../typings/addon-fit.d.ts"
],
"references": [
{
+1 -12
View File
@@ -3,23 +3,12 @@
"module": "commonjs",
"target": "ESNext",
"lib": [
"es2021",
"es2021"
],
"rootDir": ".",
"outDir": "../out-test",
"sourceMap": true,
"removeComments": true,
"paths": {
"common/*": [
"../../../src/common/*"
],
"browser/*": [
"../../../src/browser/*"
],
"*": [
"./*"
]
},
"strict": true,
"types": [
"../../../node_modules/@types/node"
+6 -2
View File
@@ -2,7 +2,11 @@
"files": [],
"include": [],
"references": [
{ "path": "./src" },
{ "path": "./test" }
{
"path": "./src"
},
{
"path": "./test"
}
]
}
+1 -1
View File
@@ -5,7 +5,7 @@
import type { ITerminalAddon, IDisposable } from '@xterm/xterm';
import type { ImageAddon as IImageApi } from '@xterm/addon-image';
import { Emitter, type IEvent } from 'common/Event';
import { Emitter, type IEvent } from '../../../src/common/Event';
import { IIPHandler } from './IIPHandler';
import { ImageRenderer } from './ImageRenderer';
import { ImageStorage, CELL_SIZE_DEFAULT } from './ImageStorage';
+1 -1
View File
@@ -6,7 +6,7 @@
import { toRGBA8888 } from 'sixel/lib/Colors';
import { IDisposable } from '@xterm/xterm';
import { ICellSize, ImageLayer, ITerminalExt, IImageSpec, IRenderDimensions, IRenderService } from './Types';
import { Disposable, MutableDisposable, toDisposable } from 'common/Lifecycle';
import { Disposable, MutableDisposable, toDisposable } from '../../../src/common/Lifecycle';
const enum Constants {
PLACEHOLDER_LENGTH = 4096,
+1 -1
View File
@@ -91,7 +91,7 @@ export class SixelHandler implements IDcsHandler, IResetHandler {
const height = this._dec.height;
// partial fix for https://github.com/jerch/xterm-addon-image/issues/37
if (!width || ! height) {
if (!width || !height) {
if (height) {
this._storage.advanceCursor(height);
}
+7 -7
View File
@@ -6,13 +6,13 @@
import { IDisposable, IMarker, Terminal } from '@xterm/xterm';
// private imports from base repo we build against
import { Attributes, BgFlags, Content, ExtFlags, UnderlineStyle } from 'common/buffer/Constants';
import type { AttributeData } from 'common/buffer/AttributeData';
import type { IParams, IDcsHandler, IOscHandler, IApcHandler, IEscapeSequenceParser } from 'common/parser/Types';
import type { IBufferLine, IExtendedAttrs, IInputHandler } from 'common/Types';
import type { ITerminal, ReadonlyColorSet } from 'browser/Types';
import type { IRenderDimensions } from 'browser/renderer/shared/Types';
import type { ICoreBrowserService, IRenderService, IThemeService } from 'browser/services/Services';
import { Attributes, BgFlags, Content, ExtFlags, UnderlineStyle } from '../../../src/common/buffer/Constants';
import type { AttributeData } from '../../../src/common/buffer/AttributeData';
import type { IParams, IDcsHandler, IOscHandler, IApcHandler, IEscapeSequenceParser } from '../../../src/common/parser/Types';
import type { IBufferLine, IExtendedAttrs, IInputHandler } from '../../../src/common/Types';
import type { ITerminal, ReadonlyColorSet } from '../../../src/browser/Types';
import type { IRenderDimensions } from '../../../src/browser/renderer/shared/Types';
import type { ICoreBrowserService, IRenderService, IThemeService } from '../../../src/browser/services/Services';
export const enum Cell {
CONTENT = 0, // codepoint and wcwidth information (enum Content)
@@ -533,8 +533,8 @@ export class KittyGraphicsHandler implements IApcHandler, IResetHandler, IDispos
private _sendResponse(id: number, message: string, quiet: number, placementId?: number): void {
const isOk = message === 'OK';
if (isOk && quiet === 1) return;
if (!isOk && quiet === 2) return;
if (isOk && quiet >= 1) return;
if (!isOk && quiet >= 2) return;
const pPart = placementId ? `,p=${placementId}` : '';
const response = `\x1b_Gi=${id}${pPart};${message}\x1b\\`;
+9 -10
View File
@@ -10,20 +10,19 @@
"preserveWatchOutput": true,
"types": [
"../../../node_modules/@types/mocha"
],
"paths": {
"browser/*": [ "../../../src/browser/*" ],
"common/*": [ "../../../src/common/*" ],
"@xterm/addon-image": [ "../typings/addon-image.d.ts" ],
"*": [ "./*" ]
}
]
},
"include": [
"./**/*",
"../../../typings/xterm.d.ts"
"../../../typings/xterm.d.ts",
"../typings/addon-image.d.ts"
],
"references": [
{ "path": "../../../src/browser" },
{ "path": "../../../src/common" }
{
"path": "../../../src/browser"
},
{
"path": "../../../src/common"
}
]
}
@@ -617,6 +617,18 @@ test.describe('Kitty Graphics Protocol', () => {
strictEqual(await ctx.page.evaluate('window.kittyGotResponse'), false);
});
test('suppresses OK response when q=2', async () => {
await ctx.page.evaluate(() => {
(window as any).kittyGotResponse = false;
(window as any).term.onData(() => { (window as any).kittyGotResponse = true; });
});
await ctx.proxy.write(`\x1b_Gi=81,a=q,q=2,f=100;${KITTY_BLACK_1X1_BASE64}\x1b\\`);
await timeout(100);
strictEqual(await ctx.page.evaluate('window.kittyGotResponse'), false);
});
test('suppresses error response when q=2', async () => {
await ctx.page.evaluate(() => {
(window as any).kittyGotResponse = false;
@@ -797,6 +809,30 @@ test.describe('Kitty Graphics Protocol', () => {
strictEqual(await ctx.page.evaluate('window.kittyGotResponse'), false);
});
test('a=t OK suppressed by q=2', async () => {
await ctx.page.evaluate(() => {
(window as any).kittyGotResponse = false;
(window as any).term.onData(() => { (window as any).kittyGotResponse = true; });
});
await ctx.proxy.write(`\x1b_Gi=181,a=t,q=2,f=100;${KITTY_BLACK_1X1_BASE64}\x1b\\`);
await timeout(100);
strictEqual(await ctx.page.evaluate('window.kittyGotResponse'), false);
});
test('a=T OK suppressed by q=2', async () => {
await ctx.page.evaluate(() => {
(window as any).kittyGotResponse = false;
(window as any).term.onData(() => { (window as any).kittyGotResponse = true; });
});
await ctx.proxy.write(`\x1b_Gi=191,a=T,q=2,f=100;${KITTY_BLACK_1X1_BASE64}\x1b\\`);
await timeout(100);
strictEqual(await ctx.page.evaluate('window.kittyGotResponse'), false);
});
});
test.describe('Transmission medium rejection', () => {
@@ -1146,6 +1182,22 @@ test.describe('Kitty Graphics Protocol', () => {
strictEqual(await getImageStorageLength(), 1);
});
test('OK response suppressed by q=2', async () => {
await ctx.proxy.write(`\x1b_Ga=t,f=100,i=221;${KITTY_BLACK_1X1_BASE64}\x1b\\`);
await timeout(100);
await ctx.page.evaluate(() => {
(window as any).kittyGotResponse = false;
(window as any).term.onData(() => { (window as any).kittyGotResponse = true; });
});
await ctx.proxy.write(`\x1b_Ga=p,i=221,q=2\x1b\\`);
await timeout(100);
strictEqual(await ctx.page.evaluate('window.kittyGotResponse'), false);
strictEqual(await getImageStorageLength(), 1);
});
test('ENOENT error suppressed by q=2', async () => {
await ctx.page.evaluate(() => {
(window as any).kittyGotResponse = false;
+1 -12
View File
@@ -3,23 +3,12 @@
"module": "commonjs",
"target": "ESNext",
"lib": [
"es2021",
"es2021"
],
"rootDir": ".",
"outDir": "../out-test",
"sourceMap": true,
"removeComments": true,
"paths": {
"common/*": [
"../../../src/common/*"
],
"browser/*": [
"../../../src/browser/*"
],
"*": [
"./*"
]
},
"strict": true,
"types": [
"../../../node_modules/@types/node"
+6 -2
View File
@@ -2,7 +2,11 @@
"files": [],
"include": [],
"references": [
{ "path": "./src" },
{ "path": "./test" }
{
"path": "./src"
},
{
"path": "./test"
}
]
}
+2 -2
View File
@@ -20,7 +20,7 @@
"package": "webpack",
"pretest": "npm run build",
"test": "nyc mocha out/**/*.test.js",
"prepublish": "npm run package"
"prepublishOnly": "npm run package"
},
"keywords": [
"font",
@@ -37,7 +37,7 @@
"devDependencies": {
"@types/lru-cache": "^5.1.0",
"@types/opentype.js": "^0.7.0",
"axios": "^1.15.0",
"axios": "^1.15.2",
"font-finder": "^1.1.0",
"mkdirp": "0.5.5",
"yauzl": "^3.2.1"
+3 -8
View File
@@ -10,17 +10,12 @@
"preserveWatchOutput": true,
"types": [
"../../../node_modules/@types/mocha",
// HACK: src shouldn't use node types but it's needed for index.test.ts
"../../../node_modules/@types/node"
],
"paths": {
"@xterm/addon-ligatures" : [
"../typings/addon-ligatures.d.ts"
]
}
]
},
"include": [
"./**/*",
"../../../typings/xterm.d.ts"
"../../../typings/xterm.d.ts",
"../typings/addon-ligatures.d.ts"
]
}
@@ -10,11 +10,6 @@
"sourceMap": true,
"removeComments": true,
"strict": true,
"paths": {
"*": [
"./*"
]
},
"types": [
"../../../node_modules/@types/mocha",
"../../../node_modules/@types/node"
+6 -2
View File
@@ -2,7 +2,11 @@
"files": [],
"include": [],
"references": [
{ "path": "./src" },
{ "path": "./test" }
{
"path": "./src"
},
{
"path": "./test"
}
]
}
+1 -1
View File
@@ -5,7 +5,7 @@
import type { Terminal, ITerminalAddon, IDisposable } from '@xterm/xterm';
import type { ProgressAddon as IProgressApi, IProgressState } from '@xterm/addon-progress';
import type { Emitter, IEvent } from 'common/Event';
import type { Emitter, IEvent } from '../../../src/common/Event';
const enum ProgressType {
+3 -13
View File
@@ -13,22 +13,12 @@
"strict": true,
"types": [
"../../../node_modules/@types/mocha"
],
"paths": {
"common/*": [
"../../../src/common/*"
],
"browser/*": [
"../../../src/browser/*"
],
"@xterm/addon-progress": [
"../typings/addon-progress.d.ts"
]
}
]
},
"include": [
"./**/*",
"../../../typings/xterm.d.ts"
"../../../typings/xterm.d.ts",
"../typings/addon-progress.d.ts"
],
"references": [
{
+1 -12
View File
@@ -3,23 +3,12 @@
"module": "commonjs",
"target": "ESNext",
"lib": [
"es2021",
"es2021"
],
"rootDir": ".",
"outDir": "../out-test",
"sourceMap": true,
"removeComments": true,
"paths": {
"common/*": [
"../../../src/common/*"
],
"browser/*": [
"../../../src/browser/*"
],
"*": [
"./*"
]
},
"strict": true,
"types": [
"../../../node_modules/@types/node"
+6 -2
View File
@@ -2,7 +2,11 @@
"files": [],
"include": [],
"references": [
{ "path": "./src" },
{ "path": "./test" }
{
"path": "./src"
},
{
"path": "./test"
}
]
}
+1 -1
View File
@@ -5,7 +5,7 @@
import type { Terminal, IDisposable, IDecoration } from '@xterm/xterm';
import type { ISearchDecorationOptions } from '@xterm/addon-search';
import { dispose, Disposable, toDisposable } from 'common/Lifecycle';
import { dispose, Disposable, toDisposable } from '../../../src/common/Lifecycle';
import type { ISearchResult } from './SearchEngine';
/**
+3 -3
View File
@@ -5,9 +5,9 @@
import type { Terminal, IDisposable, ITerminalAddon } from '@xterm/xterm';
import type { SearchAddon as ISearchApi, ISearchOptions, ISearchAddonOptions, ISearchResultChangeEvent, ISearchDecorationOptions } from '@xterm/addon-search';
import { Emitter, type IEvent } from 'common/Event';
import { Disposable, MutableDisposable, toDisposable } from 'common/Lifecycle';
import { disposableTimeout } from 'common/Async';
import { Emitter, type IEvent } from '../../../src/common/Event';
import { Disposable, MutableDisposable, toDisposable } from '../../../src/common/Lifecycle';
import { disposableTimeout } from '../../../src/common/Async';
import { SearchLineCache } from './SearchLineCache';
import { SearchState } from './SearchState';
import { SearchEngine, type ISearchResult } from './SearchEngine';
+2 -2
View File
@@ -5,9 +5,9 @@
import { assert } from 'chai';
import { SearchEngine } from './SearchEngine';
import { SearchLineCache } from './SearchLineCache';
import { Terminal } from 'browser/public/Terminal';
import { Terminal } from '../../../src/browser/public/Terminal';
import type { ISearchOptions } from '@xterm/addon-search';
import { DisposableStore } from 'common/Lifecycle';
import { DisposableStore } from '../../../src/common/Lifecycle';
function writeP(terminal: Terminal, data: string): Promise<void> {
return new Promise(r => terminal.write(data, r));
@@ -4,8 +4,8 @@
*/
import { assert } from 'chai';
import { SearchLineCache, LineCacheEntry } from './SearchLineCache';
import { Terminal } from 'browser/public/Terminal';
import { timeout } from 'common/Async';
import { Terminal } from '../../../src/browser/public/Terminal';
import { timeout } from '../../../src/common/Async';
function writeP(terminal: Terminal, data: string): Promise<void> {
return new Promise(r => terminal.write(data, r));
+2 -2
View File
@@ -4,8 +4,8 @@
*/
import type { Terminal } from '@xterm/xterm';
import { combinedDisposable, Disposable, MutableDisposable, toDisposable } from 'common/Lifecycle';
import { disposableTimeout } from 'common/Async';
import { combinedDisposable, Disposable, MutableDisposable, toDisposable } from '../../../src/common/Lifecycle';
import { disposableTimeout } from '../../../src/common/Async';
export type LineCacheEntry = [
/**
@@ -5,8 +5,8 @@
import type { ISearchResultChangeEvent } from '@xterm/addon-search';
import type { IDisposable } from '@xterm/xterm';
import { Emitter, type IEvent } from 'common/Event';
import { Disposable } from 'common/Lifecycle';
import { Emitter, type IEvent } from '../../../src/common/Event';
import { Disposable } from '../../../src/common/Lifecycle';
import type { ISearchResult } from './SearchEngine';
/**
+4 -14
View File
@@ -4,7 +4,7 @@
"target": "es2021",
"lib": [
"dom",
"es2021",
"es2021"
],
"rootDir": ".",
"outDir": "../out",
@@ -13,22 +13,12 @@
"strict": true,
"types": [
"../../../node_modules/@types/mocha"
],
"paths": {
"common/*": [
"../../../src/common/*"
],
"browser/*": [
"../../../src/browser/*"
],
"@xterm/addon-search" : [
"../typings/addon-search.d.ts"
]
}
]
},
"include": [
"./**/*",
"../../../typings/xterm.d.ts"
"../../../typings/xterm.d.ts",
"../typings/addon-search.d.ts"
],
"references": [
{
+1 -12
View File
@@ -3,23 +3,12 @@
"module": "commonjs",
"target": "ESNext",
"lib": [
"es2021",
"es2021"
],
"rootDir": ".",
"outDir": "../out-test",
"sourceMap": true,
"removeComments": true,
"paths": {
"common/*": [
"../../../src/common/*"
],
"browser/*": [
"../../../src/browser/*"
],
"*": [
"./*"
]
},
"strict": true,
"types": [
"../../../node_modules/@types/node"
+6 -2
View File
@@ -2,7 +2,11 @@
"files": [],
"include": [],
"references": [
{ "path": "./src" },
{ "path": "./test" }
{
"path": "./src"
},
{
"path": "./test"
}
]
}
@@ -6,9 +6,9 @@
import { perfContext, before, ThroughputRuntimeCase } from 'xterm-benchmark';
import { spawn } from 'node-pty';
import { Utf8ToUtf32, stringFromCodePoint } from 'common/input/TextDecoder';
import { Terminal } from 'browser/public/Terminal';
import { SerializeAddon } from 'SerializeAddon';
import { Utf8ToUtf32, stringFromCodePoint } from '../../../src/common/input/TextDecoder';
import { Terminal } from '../../../src/browser/public/Terminal';
import { SerializeAddon } from '../src/SerializeAddon';
class TestTerminal extends Terminal {
public writeSync(data: string): void {
+18 -14
View File
@@ -6,24 +6,28 @@
],
"rootDir": "..",
"outDir": "../out-benchmark",
"types": ["../../../node_modules/@types/node"],
"types": [
"../../../node_modules/@types/node"
],
"moduleResolution": "node",
"strict": false,
"target": "es2021",
"module": "commonjs",
"paths": {
"common/*": ["../../../src/common/*"],
"browser/*": ["../../../src/browser/*"],
"SerializeAddon": ["../src/SerializeAddon"],
"@xterm/addon-serialize": [
"../typings/addon-serialize.d.ts"
]
}
"module": "commonjs"
},
"include": ["../**/*", "../../../typings/xterm.d.ts"],
"exclude": ["../../../**/*test.ts", "../../**/*api.ts"],
"include": [
"../**/*",
"../../../typings/xterm.d.ts"
],
"exclude": [
"../../../**/*test.ts",
"../../**/*api.ts"
],
"references": [
{ "path": "../../../src/common" },
{ "path": "../../../src/browser" }
{
"path": "../../../src/common"
},
{
"path": "../../../src/browser"
}
]
}
@@ -6,10 +6,10 @@
import jsdom = require('jsdom');
import { assert } from 'chai';
import { SerializeAddon } from './SerializeAddon';
import { Terminal } from 'browser/public/Terminal';
import { SelectionModel } from 'browser/selection/SelectionModel';
import { IBufferService } from 'common/services/Services';
import { ThemeService } from 'browser/services/ThemeService';
import { Terminal } from '../../../src/browser/public/Terminal';
import { SelectionModel } from '../../../src/browser/selection/SelectionModel';
import { IBufferService } from '../../../src/common/services/Services';
import { ThemeService } from '../../../src/browser/services/ThemeService';
function sgr(...seq: string[]): string {
return `\x1b[${seq.join(';')}m`;
+3 -3
View File
@@ -7,9 +7,9 @@
import type { IBuffer, IBufferCell, IBufferRange, ITerminalAddon, Terminal } from '@xterm/xterm';
import type { IHTMLSerializeOptions, SerializeAddon as ISerializeApi, ISerializeOptions, ISerializeRange } from '@xterm/addon-serialize';
import { IAttributeData, IColor } from 'common/Types';
import { DEFAULT_ANSI_COLORS } from 'browser/Types';
import { UnderlineStyle } from 'common/buffer/Constants';
import { IAttributeData, IColor } from '../../../src/common/Types';
import { DEFAULT_ANSI_COLORS } from '../../../src/browser/Types';
import { UnderlineStyle } from '../../../src/common/buffer/Constants';
function constrain(value: number, low: number, high: number): number {
return Math.max(low, Math.min(value, high));
+2 -12
View File
@@ -10,17 +10,6 @@
"outDir": "../out",
"sourceMap": true,
"removeComments": true,
"paths": {
"common/*": [
"../../../src/common/*"
],
"browser/*": [
"../../../src/browser/*"
],
"@xterm/addon-serialize": [
"../typings/addon-serialize.d.ts"
]
},
"strict": true,
"skipLibCheck": true,
"types": [
@@ -29,7 +18,8 @@
},
"include": [
"./**/*",
"../../../typings/xterm.d.ts"
"../../../typings/xterm.d.ts",
"../typings/addon-serialize.d.ts"
],
"references": [
{
+1 -9
View File
@@ -3,20 +3,12 @@
"module": "commonjs",
"target": "ESNext",
"lib": [
"es2021",
"es2021"
],
"rootDir": ".",
"outDir": "../out-test",
"sourceMap": true,
"removeComments": true,
"paths": {
"common/*": [
"../../../src/common/*"
],
"browser/*": [
"../../../src/browser/*"
]
},
"strict": true,
"types": [
"../../../node_modules/@types/node"
+9 -3
View File
@@ -2,8 +2,14 @@
"files": [],
"include": [],
"references": [
{ "path": "./src" },
{ "path": "./test" },
{ "path": "./benchmark" }
{
"path": "./src"
},
{
"path": "./test"
},
{
"path": "./benchmark"
}
]
}
@@ -6,9 +6,9 @@
import { perfContext, before, ThroughputRuntimeCase } from 'xterm-benchmark';
import { spawn } from 'node-pty';
import { Utf8ToUtf32, stringFromCodePoint } from 'common/input/TextDecoder';
import { CoreBrowserTerminal } from 'browser/CoreBrowserTerminal';
import { UnicodeGraphemeProvider } from 'UnicodeGraphemeProvider';
import { Utf8ToUtf32, stringFromCodePoint } from '../../../src/common/input/TextDecoder';
import { CoreBrowserTerminal } from '../../../src/browser/CoreBrowserTerminal';
import { UnicodeGraphemeProvider } from '../src/UnicodeGraphemeProvider';
function fakedAddonLoad(terminal: any): void {
@@ -1,26 +1,33 @@
{
"compilerOptions": {
"lib": ["dom", "es6"],
"lib": [
"dom",
"es6"
],
"rootDir": "..",
"outDir": "../out-benchmark",
"types": ["../../../node_modules/@types/node"],
"types": [
"../../../node_modules/@types/node"
],
"moduleResolution": "node",
"strict": false,
"target": "es2015",
"module": "commonjs",
"paths": {
"common/*": ["../../../src/common/*"],
"browser/*": ["../../../src/browser/*"],
"UnicodeGraphemeProvider": ["../src/UnicodeGraphemeProvider"],
"@xterm/addon-unicode-graphemes": [
"../typings/addon-unicode-graphemes.d.ts"
]
}
"module": "commonjs"
},
"include": ["../**/*", "../../../typings/xterm.d.ts"],
"exclude": ["../../../**/*test.ts", "../../**/*api.ts"],
"include": [
"../**/*",
"../../../typings/xterm.d.ts"
],
"exclude": [
"../../../**/*test.ts",
"../../**/*api.ts"
],
"references": [
{ "path": "../../../src/common" },
{ "path": "../../../src/browser" }
{
"path": "../../../src/common"
},
{
"path": "../../../src/browser"
}
]
}
@@ -4,8 +4,8 @@
*/
import { IUnicodeVersionProvider } from '@xterm/xterm';
import { UnicodeCharProperties, UnicodeCharWidth } from 'common/services/Services';
import { UnicodeService } from 'common/services/UnicodeService';
import { UnicodeCharProperties, UnicodeCharWidth } from '../../../src/common/services/Services';
import { UnicodeService } from '../../../src/common/services/UnicodeService';
import * as UC from './third-party/UnicodeProperties';
export class UnicodeGraphemeProvider implements IUnicodeVersionProvider {
@@ -11,21 +11,14 @@
"sourceMap": true,
"removeComments": true,
"strict": true,
"paths": {
"common/*": [
"../../../src/common/*"
],
"@xterm/addon-unicode-graphemes": [
"../typings/addon-unicode-graphemes.d.ts"
]
},
"types": [
"../../../node_modules/@types/mocha"
]
},
"include": [
"./**/*",
"../../../typings/xterm.d.ts"
"../../../typings/xterm.d.ts",
"../typings/addon-unicode-graphemes.d.ts"
],
"references": [
{
@@ -3,20 +3,12 @@
"module": "commonjs",
"target": "ESNext",
"lib": [
"es2021",
"es2021"
],
"rootDir": ".",
"outDir": "../out-test",
"sourceMap": true,
"removeComments": true,
"paths": {
"common/*": [
"../../../src/common/*"
],
"browser/*": [
"../../../src/browser/*"
]
},
"strict": true,
"types": [
"../../../node_modules/@types/node"
+9 -3
View File
@@ -2,8 +2,14 @@
"files": [],
"include": [],
"references": [
{ "path": "./src" },
{ "path": "./test" },
{ "path": "./benchmark" }
{
"path": "./src"
},
{
"path": "./test"
},
{
"path": "./benchmark"
}
]
}
+2 -2
View File
@@ -4,8 +4,8 @@
*/
import { IUnicodeVersionProvider } from '@xterm/xterm';
import { UnicodeCharProperties, UnicodeCharWidth } from 'common/services/Services';
import { UnicodeService } from 'common/services/UnicodeService';
import { UnicodeCharProperties, UnicodeCharWidth } from '../../../src/common/services/Services';
import { UnicodeService } from '../../../src/common/services/UnicodeService';
const BMP_COMBINING = [
[0x0300, 0x036F], [0x0483, 0x0489], [0x0591, 0x05BD],
+2 -12
View File
@@ -11,24 +11,14 @@
"sourceMap": true,
"removeComments": true,
"strict": true,
"paths": {
"common/*": [
"../../../src/common/*"
],
"@xterm/addon-unicode11": [
"../typings/addon-unicode11.d.ts"
],
"*": [
"./*"
]
},
"types": [
"../../../node_modules/@types/mocha"
]
},
"include": [
"./**/*",
"../../../typings/xterm.d.ts"
"../../../typings/xterm.d.ts",
"../typings/addon-unicode11.d.ts"
],
"references": [
{
+1 -12
View File
@@ -3,23 +3,12 @@
"module": "commonjs",
"target": "ESNext",
"lib": [
"es2021",
"es2021"
],
"rootDir": ".",
"outDir": "../out-test",
"sourceMap": true,
"removeComments": true,
"paths": {
"common/*": [
"../../../src/common/*"
],
"browser/*": [
"../../../src/browser/*"
],
"*": [
"./*"
]
},
"strict": true,
"types": [
"../../../node_modules/@types/node"
+6 -2
View File
@@ -2,7 +2,11 @@
"files": [],
"include": [],
"references": [
{ "path": "./src" },
{ "path": "./test" }
{
"path": "./src"
},
{
"path": "./test"
}
]
}
+3 -7
View File
@@ -14,15 +14,11 @@
"strict": true,
"types": [
"../../../node_modules/@types/mocha"
],
"paths": {
"@xterm/addon-web-fonts": [
"../typings/addon-web-fonts.d.ts"
]
}
]
},
"include": [
"./**/*",
"../../../typings/xterm.d.ts"
"../../../typings/xterm.d.ts",
"../typings/addon-web-fonts.d.ts"
]
}
+1 -13
View File
@@ -3,24 +3,12 @@
"module": "commonjs",
"target": "es2021",
"lib": [
"es2021",
"es2021"
],
// "downlevelIteration": true,
"rootDir": ".",
"outDir": "../out-test",
"sourceMap": true,
"removeComments": true,
"paths": {
"common/*": [
"../../../src/common/*"
],
"browser/*": [
"../../../src/browser/*"
],
"*": [
"./*"
]
},
"strict": true,
"types": [
"../../../node_modules/@types/node"
+6 -2
View File
@@ -2,7 +2,11 @@
"files": [],
"include": [],
"references": [
{ "path": "./src" },
{ "path": "./test" }
{
"path": "./src"
},
{
"path": "./test"
}
]
}
+3 -7
View File
@@ -13,15 +13,11 @@
"strict": true,
"types": [
"../../../node_modules/@types/mocha"
],
"paths": {
"@xterm/addon-web-links": [
"../typings/addon-web-links.d.ts"
]
}
]
},
"include": [
"./**/*",
"../../../typings/xterm.d.ts"
"../../../typings/xterm.d.ts",
"../typings/addon-web-links.d.ts"
]
}
+1 -13
View File
@@ -3,24 +3,12 @@
"module": "commonjs",
"target": "es2021",
"lib": [
"es2021",
"es2021"
],
// "downlevelIteration": true,
"rootDir": ".",
"outDir": "../out-test",
"sourceMap": true,
"removeComments": true,
"paths": {
"common/*": [
"../../../src/common/*"
],
"browser/*": [
"../../../src/browser/*"
],
"*": [
"./*"
]
},
"strict": true,
"types": [
"../../../node_modules/@types/node"
+6 -2
View File
@@ -2,7 +2,11 @@
"files": [],
"include": [],
"references": [
{ "path": "./src" },
{ "path": "./test" }
{
"path": "./src"
},
{
"path": "./test"
}
]
}
+8 -8
View File
@@ -1,12 +1,12 @@
import { ISelectionRenderModel } from 'browser/renderer/shared/Types';
import { ICoreBrowserService, IThemeService } from 'browser/services/Services';
import { ReadonlyColorSet } from 'browser/Types';
import { Attributes, BgFlags, ExtFlags, FgFlags, NULL_CELL_CODE, UnderlineStyle } from 'common/buffer/Constants';
import { IDecorationService, IOptionsService } from 'common/services/Services';
import { ICellData } from 'common/Types';
import { ISelectionRenderModel } from '../../../src/browser/renderer/shared/Types';
import { ICoreBrowserService, IThemeService } from '../../../src/browser/services/Services';
import { ReadonlyColorSet } from '../../../src/browser/Types';
import { Attributes, BgFlags, ExtFlags, FgFlags, NULL_CELL_CODE, UnderlineStyle } from '../../../src/common/buffer/Constants';
import { IDecorationService, IOptionsService } from '../../../src/common/services/Services';
import { ICellData } from '../../../src/common/Types';
import { Terminal } from '@xterm/xterm';
import { rgba } from 'common/Color';
import { treatGlyphAsBackgroundColor } from 'browser/renderer/shared/RendererUtils';
import { rgba } from '../../../src/common/Color';
import { treatGlyphAsBackgroundColor } from '../../../src/browser/renderer/shared/RendererUtils';
import { blockPatternCodepoints } from './customGlyphs/CustomGlyphDefinitions';
// Work variables to avoid garbage collection
+2 -2
View File
@@ -5,10 +5,10 @@
import { TextureAtlas } from './TextureAtlas';
import { ITerminalOptions, Terminal } from '@xterm/xterm';
import { ITerminal, ReadonlyColorSet } from 'browser/Types';
import { ITerminal, ReadonlyColorSet } from '../../../src/browser/Types';
import { ICharAtlasConfig, ITextureAtlas } from './Types';
import { generateConfig, configEquals } from './CharAtlasUtils';
import type { ILogService } from 'common/services/Services';
import type { ILogService } from '../../../src/common/services/Services';
interface ITextureAtlasCacheEntry {
atlas: ITextureAtlas;
@@ -0,0 +1,87 @@
/**
* Copyright (c) 2026 The xterm.js authors. All rights reserved.
* @license MIT
*/
import { assert } from 'chai';
import { configEquals } from './CharAtlasUtils';
import { ICharAtlasConfig } from './Types';
import { NULL_COLOR } from '../../../src/common/Color';
import { IColor } from '../../../src/common/Types';
function createTestConfig(overrides: Partial<ICharAtlasConfig> = {}): ICharAtlasConfig {
const color: IColor = { css: '#ffffff', rgba: 0xffffffff };
const contrastCache = {
clear: () => {},
setCss: () => {},
getCss: () => undefined,
setColor: () => {},
getColor: () => undefined
};
const colors = {
foreground: color,
background: color,
cursor: NULL_COLOR,
cursorAccent: NULL_COLOR,
selectionForeground: undefined,
selectionBackgroundTransparent: NULL_COLOR,
selectionBackgroundOpaque: NULL_COLOR,
selectionInactiveBackgroundTransparent: NULL_COLOR,
selectionInactiveBackgroundOpaque: NULL_COLOR,
overviewRulerBorder: NULL_COLOR,
scrollbarSliderBackground: NULL_COLOR,
scrollbarSliderHoverBackground: NULL_COLOR,
scrollbarSliderActiveBackground: NULL_COLOR,
ansi: new Array(256).fill(color),
contrastCache,
halfContrastCache: contrastCache
};
return {
customGlyphs: true,
devicePixelRatio: 1,
deviceMaxTextureSize: 4096,
letterSpacing: 0,
lineHeight: 1,
fontSize: 15,
fontFamily: 'monospace',
fontWeight: 'normal',
fontWeightBold: 'bold',
deviceCellWidth: 10,
deviceCellHeight: 20,
deviceCharWidth: 8,
deviceCharHeight: 16,
allowTransparency: false,
drawBoldTextInBrightColors: true,
minimumContrastRatio: 1,
colors,
...overrides
};
}
describe('CharAtlasUtils', () => {
describe('configEquals', () => {
it('should return true for identical configs', () => {
const a = createTestConfig();
const b = createTestConfig();
assert.ok(configEquals(a, b));
});
it('should return false when deviceMaxTextureSize differs', () => {
const a = createTestConfig();
const b = createTestConfig({ deviceMaxTextureSize: 8192 });
assert.ok(!configEquals(a, b));
});
it('should return false when deviceCellWidth differs', () => {
const a = createTestConfig();
const b = createTestConfig({ deviceCellWidth: 11 });
assert.ok(!configEquals(a, b));
});
it('should return false when deviceCellHeight differs', () => {
const a = createTestConfig();
const b = createTestConfig({ deviceCellHeight: 21 });
assert.ok(!configEquals(a, b));
});
});
});
+6 -3
View File
@@ -4,10 +4,10 @@
*/
import { ICharAtlasConfig } from './Types';
import { Attributes } from 'common/buffer/Constants';
import { Attributes } from '../../../src/common/buffer/Constants';
import { ITerminalOptions } from '@xterm/xterm';
import { IColorSet, ReadonlyColorSet } from 'browser/Types';
import { NULL_COLOR } from 'common/Color';
import { IColorSet, ReadonlyColorSet } from '../../../src/browser/Types';
import { NULL_COLOR } from '../../../src/common/Color';
export function generateConfig(deviceCellWidth: number, deviceCellHeight: number, deviceCharWidth: number, deviceCharHeight: number, options: Required<ITerminalOptions>, colors: ReadonlyColorSet, devicePixelRatio: number, deviceMaxTextureSize: number, customGlyphs: boolean = true): ICharAtlasConfig {
// null out some fields that don't matter
@@ -59,6 +59,7 @@ export function configEquals(a: ICharAtlasConfig, b: ICharAtlasConfig): boolean
}
}
return a.devicePixelRatio === b.devicePixelRatio &&
a.deviceMaxTextureSize === b.deviceMaxTextureSize &&
a.customGlyphs === b.customGlyphs &&
a.lineHeight === b.lineHeight &&
a.letterSpacing === b.letterSpacing &&
@@ -67,6 +68,8 @@ export function configEquals(a: ICharAtlasConfig, b: ICharAtlasConfig): boolean
a.fontWeight === b.fontWeight &&
a.fontWeightBold === b.fontWeightBold &&
a.allowTransparency === b.allowTransparency &&
a.deviceCellWidth === b.deviceCellWidth &&
a.deviceCellHeight === b.deviceCellHeight &&
a.deviceCharWidth === b.deviceCharWidth &&
a.deviceCharHeight === b.deviceCharHeight &&
a.drawBoldTextInBrightColors === b.drawBoldTextInBrightColors &&
+1 -1
View File
@@ -3,7 +3,7 @@
* @license MIT
*/
import { isFirefox, isLegacyEdge } from 'common/Platform';
import { isFirefox, isLegacyEdge } from '../../../src/common/Platform';
export const DIM_OPACITY = 0.5;
// The text baseline is set conditionally by browser. Using 'ideographic' for Firefox or Legacy Edge
@@ -3,8 +3,8 @@
* @license MIT
*/
import { RendererConstants } from 'browser/renderer/shared/Constants';
import { ICoreBrowserService } from 'browser/services/Services';
import { RendererConstants } from '../../../src/browser/renderer/shared/Constants';
import { ICoreBrowserService } from '../../../src/browser/services/Services';
const enum Constants {
/**
@@ -3,7 +3,7 @@
* @license MIT
*/
import { toDisposable, IDisposable } from 'common/Lifecycle';
import { toDisposable, IDisposable } from '../../../src/common/Lifecycle';
export function observeDevicePixelDimensions(element: HTMLElement, parentWindow: Window & typeof globalThis, callback: (deviceWidth: number, deviceHeight: number) => void): IDisposable {
// Observe any resizes to the element and extract the actual pixel size of the element if the
+18 -10
View File
@@ -3,14 +3,14 @@
* @license MIT
*/
import { TextureAtlas } from './TextureAtlas';
import { IRenderDimensions } from 'browser/renderer/shared/Types';
import { NULL_CELL_CODE } from 'common/buffer/Constants';
import { Disposable, toDisposable } from 'common/Lifecycle';
import { IRenderDimensions } from '../../../src/browser/renderer/shared/Types';
import { NULL_CELL_CODE } from '../../../src/common/buffer/Constants';
import { Disposable, toDisposable } from '../../../src/common/Lifecycle';
import { Terminal } from '@xterm/xterm';
import { IRenderModel, IWebGL2RenderingContext, IWebGLVertexArrayObject, type IRasterizedGlyph, type ITextureAtlas } from './Types';
import { createProgram, GLTexture, PROJECTION_MATRIX } from './WebglUtils';
import type { IOptionsService } from 'common/services/Services';
import { allowRescaling, throwIfFalsy } from 'browser/renderer/shared/RendererUtils';
import type { ILogService, IOptionsService } from '../../../src/common/services/Services';
import { allowRescaling, throwIfFalsy } from '../../../src/browser/renderer/shared/RendererUtils';
interface IVertices {
attributes: Float32Array;
@@ -114,7 +114,8 @@ export class GlyphRenderer extends Disposable {
private readonly _terminal: Terminal,
private readonly _gl: IWebGL2RenderingContext,
private _dimensions: IRenderDimensions,
private readonly _optionsService: IOptionsService
private readonly _optionsService: IOptionsService,
private readonly _logService: ILogService
) {
super();
@@ -127,7 +128,7 @@ export class GlyphRenderer extends Disposable {
TextureAtlas.maxTextureSize = throwIfFalsy(gl.getParameter(gl.MAX_TEXTURE_SIZE) as number | null);
}
this._program = throwIfFalsy(createProgram(gl, vertexShaderSource, createFragmentShaderSource(TextureAtlas.maxAtlasPages)));
this._program = throwIfFalsy(createProgram(gl, vertexShaderSource, createFragmentShaderSource(TextureAtlas.maxAtlasPages), this._logService));
this._register(toDisposable(() => gl.deleteProgram(this._program)));
// Uniform locations
@@ -136,8 +137,9 @@ export class GlyphRenderer extends Disposable {
this._textureLocation = throwIfFalsy(gl.getUniformLocation(this._program, 'u_texture'));
// Create and set the vertex array object
this._vertexArrayObject = gl.createVertexArray();
gl.bindVertexArray(this._vertexArrayObject);
const vertexArrayObject = this._vertexArrayObject = gl.createVertexArray();
this._register(toDisposable(() => gl.deleteVertexArray(vertexArrayObject)));
gl.bindVertexArray(vertexArrayObject);
// Setup a_unitquad, this defines the 4 vertices of a rectangle
const unitQuadVertices = new Float32Array([0, 0, 1, 0, 0, 1, 1, 1]);
@@ -358,7 +360,9 @@ export class GlyphRenderer extends Disposable {
gl.bindBuffer(gl.ARRAY_BUFFER, this._attributesBuffer);
gl.bufferData(gl.ARRAY_BUFFER, activeBuffer.subarray(0, bufferLength), gl.STREAM_DRAW);
// Bind the atlas page texture if they have changed
// Bind the atlas page texture if they have changed. AtlasPage.version is globally
// monotonic, so a page object swap at the same index (which happens after a page merge)
// is detected by the same comparison.
for (let i = 0; i < this._atlas.pages.length; i++) {
if (this._atlas.pages[i].version !== this._atlasTextures[i].version) {
this._bindAtlasPageTexture(gl, this._atlas, i);
@@ -371,6 +375,10 @@ export class GlyphRenderer extends Disposable {
public setAtlas(atlas: ITextureAtlas): void {
this._atlas = atlas;
this.invalidateAtlasTextures();
}
public invalidateAtlasTextures(): void {
for (const glTexture of this._atlasTextures) {
glTexture.version = -1;
}
+17 -13
View File
@@ -3,17 +3,18 @@
* @license MIT
*/
import { IRenderDimensions } from 'browser/renderer/shared/Types';
import { IThemeService } from 'browser/services/Services';
import { ReadonlyColorSet } from 'browser/Types';
import { Attributes, FgFlags } from 'common/buffer/Constants';
import { Disposable, toDisposable } from 'common/Lifecycle';
import { IColor } from 'common/Types';
import { IRenderDimensions } from '../../../src/browser/renderer/shared/Types';
import { IThemeService } from '../../../src/browser/services/Services';
import { ReadonlyColorSet } from '../../../src/browser/Types';
import { Attributes, FgFlags } from '../../../src/common/buffer/Constants';
import { Disposable, toDisposable } from '../../../src/common/Lifecycle';
import { IColor } from '../../../src/common/Types';
import { Terminal } from '@xterm/xterm';
import { RenderModelConstants } from './RenderModel';
import { IRenderModel, IWebGL2RenderingContext, IWebGLVertexArrayObject } from './Types';
import { createProgram, expandFloat32Array, PROJECTION_MATRIX } from './WebglUtils';
import { throwIfFalsy } from 'browser/renderer/shared/RendererUtils';
import { throwIfFalsy } from '../../../src/browser/renderer/shared/RendererUtils';
import type { ILogService } from '../../../src/common/services/Services';
const enum VertexAttribLocations {
POSITION = 0,
@@ -89,21 +90,23 @@ export class RectangleRenderer extends Disposable {
private _terminal: Terminal,
private _gl: IWebGL2RenderingContext,
private _dimensions: IRenderDimensions,
private readonly _themeService: IThemeService
private readonly _themeService: IThemeService,
private readonly _logService: ILogService
) {
super();
const gl = this._gl;
this._program = throwIfFalsy(createProgram(gl, vertexShaderSource, fragmentShaderSource));
this._program = throwIfFalsy(createProgram(gl, vertexShaderSource, fragmentShaderSource, this._logService));
this._register(toDisposable(() => gl.deleteProgram(this._program)));
// Uniform locations
this._projectionLocation = throwIfFalsy(gl.getUniformLocation(this._program, 'u_projection'));
// Create and set the vertex array object
this._vertexArrayObject = gl.createVertexArray();
gl.bindVertexArray(this._vertexArrayObject);
const vertexArrayObject = this._vertexArrayObject = gl.createVertexArray();
this._register(toDisposable(() => gl.deleteVertexArray(vertexArrayObject)));
gl.bindVertexArray(vertexArrayObject);
// Setup a_unitquad, this defines the 4 vertices of a rectangle
const unitQuadVertices = new Float32Array([0, 0, 1, 0, 0, 1, 1, 1]);
@@ -336,8 +339,9 @@ export class RectangleRenderer extends Disposable {
}
}
if (vertices.attributes.length < offset + 4) {
vertices.attributes = expandFloat32Array(vertices.attributes, this._terminal.rows * this._terminal.cols * INDICES_PER_RECTANGLE);
if (vertices.attributes.length < offset + INDICES_PER_RECTANGLE) {
// +1 for the viewport-clear rectangle at offset 0.
vertices.attributes = expandFloat32Array(vertices.attributes, (this._terminal.rows * this._terminal.cols + 1) * INDICES_PER_RECTANGLE);
}
$x1 = startX * this._dimensions.device.cell.width;
$y1 = y * this._dimensions.device.cell.height;
+2 -2
View File
@@ -4,8 +4,8 @@
*/
import { ICursorRenderModel, IRenderModel } from './Types';
import { ISelectionRenderModel } from 'browser/renderer/shared/Types';
import { createSelectionRenderModel } from 'browser/renderer/shared/SelectionRenderModel';
import { ISelectionRenderModel } from '../../../src/browser/renderer/shared/Types';
import { createSelectionRenderModel } from '../../../src/browser/renderer/shared/SelectionRenderModel';
export const enum RenderModelConstants {
INDICIES_PER_CELL = 4,
+25 -18
View File
@@ -3,19 +3,19 @@
* @license MIT
*/
import { IColorContrastCache } from 'browser/Types';
import { IColorContrastCache } from '../../../src/browser/Types';
import { DIM_OPACITY, TEXT_BASELINE } from './Constants';
import { tryDrawCustomGlyph } from './customGlyphs/CustomGlyphRasterizer';
import { computeNextVariantOffset, treatGlyphAsBackgroundColor, isPowerlineGlyph, isRestrictedPowerlineGlyph, throwIfFalsy } from 'browser/renderer/shared/RendererUtils';
import { computeNextVariantOffset, treatGlyphAsBackgroundColor, isPowerlineGlyph, isRestrictedPowerlineGlyph, throwIfFalsy } from '../../../src/browser/renderer/shared/RendererUtils';
import { IBoundingBox, ICharAtlasConfig, IRasterizedGlyph, ITextureAtlas } from './Types';
import { NULL_COLOR, channels, color, rgba } from 'common/Color';
import { FourKeyMap } from 'common/MultiKeyMap';
import { IdleTaskQueue } from 'common/TaskQueue';
import { IColor } from 'common/Types';
import { AttributeData } from 'common/buffer/AttributeData';
import { Attributes, DEFAULT_COLOR, DEFAULT_EXT, UnderlineStyle } from 'common/buffer/Constants';
import { ILogService, IUnicodeService } from 'common/services/Services';
import { Emitter } from 'common/Event';
import { NULL_COLOR, channels, color, rgba } from '../../../src/common/Color';
import { FourKeyMap } from '../../../src/common/MultiKeyMap';
import { IdleTaskQueue } from '../../../src/common/TaskQueue';
import { IColor } from '../../../src/common/Types';
import { AttributeData } from '../../../src/common/buffer/AttributeData';
import { Attributes, DEFAULT_COLOR, DEFAULT_EXT, UnderlineStyle } from '../../../src/common/buffer/Constants';
import { ILogService, IUnicodeService } from '../../../src/common/services/Services';
import { Emitter } from '../../../src/common/Event';
/**
* A shared object which is used to draw nothing for a particular cell.
@@ -109,6 +109,7 @@ export class TextureAtlas implements ITextureAtlas {
page.canvas.remove();
}
this._onAddTextureAtlasCanvas.dispose();
this._onRemoveTextureAtlasCanvas.dispose();
}
public warmUp(): void {
@@ -133,7 +134,9 @@ export class TextureAtlas implements ITextureAtlas {
private _requestClearModel = false;
public beginFrame(): boolean {
return this._requestClearModel;
const result = this._requestClearModel;
this._requestClearModel = false;
return result;
}
public clearTexture(): void {
@@ -193,7 +196,7 @@ export class TextureAtlas implements ITextureAtlas {
// Merge into the new page
const mergedPage = this._mergePages(mergingPages, mergedPageIndex);
mergedPage.version++;
mergedPage.version = ++AtlasPage.nextVersion;
// Delete the pages, shifting glyph texture pages as needed
for (let i = sortedMergingPagesIndexes.length - 1; i >= 0; i--) {
@@ -251,7 +254,7 @@ export class TextureAtlas implements ITextureAtlas {
for (const g of adjustingPage.glyphs) {
g.texturePage--;
}
adjustingPage.version++;
adjustingPage.version = ++AtlasPage.nextVersion;
}
}
@@ -527,7 +530,7 @@ export class TextureAtlas implements ITextureAtlas {
let customGlyph = false;
if (this._config.customGlyphs !== false) {
const variantOffset = this._workAttributeData.getUnderlineVariantOffset();
customGlyph = tryDrawCustomGlyph(this._tmpCtx, chars, padding, padding, this._config.deviceCellWidth, this._config.deviceCellHeight, this._config.deviceCharWidth, this._config.deviceCharHeight, this._config.fontSize, this._config.devicePixelRatio, backgroundColor.css, variantOffset);
customGlyph = tryDrawCustomGlyph(this._tmpCtx, chars, padding, padding, this._config.deviceCellWidth, this._config.deviceCellHeight, this._config.deviceCharWidth, this._config.deviceCharHeight, this._config.fontSize, this._config.devicePixelRatio, this._logService, backgroundColor.css, variantOffset);
}
// Whether to clear pixels based on a threshold difference between the glyph color and the
@@ -937,7 +940,7 @@ export class TextureAtlas implements ITextureAtlas {
rasterizedGlyph.size.y
);
activePage.addGlyph(rasterizedGlyph);
activePage.version++;
activePage.version = ++AtlasPage.nextVersion;
return rasterizedGlyph;
}
@@ -1047,9 +1050,13 @@ class AtlasPage {
}
/**
* Used to check whether the canvas of the atlas page has changed.
* Monotonically increasing across all atlas pages globally. Used to detect when the texture
* unit at a given index needs to be re-uploaded — both for content changes within the same
* page and for a page object swap at the same index (which happens after a page merge,
* where a per-page counter could coincide with the previously-bound page's value).
*/
public version = 0;
public static nextVersion: number = 0;
public version = ++AtlasPage.nextVersion;
// Texture atlas current positioning data. The texture packing strategy used is to fill from
// left-to-right and top-to-bottom. When the glyph being written is less than half of the current
@@ -1092,7 +1099,7 @@ class AtlasPage {
this.currentRow.y = 0;
this.currentRow.height = 0;
this.fixedRows.length = 0;
this.version++;
this.version = ++AtlasPage.nextVersion;
}
}
+5 -4
View File
@@ -4,10 +4,10 @@
*/
import { FontWeight } from '@xterm/xterm';
import { IColorSet } from 'browser/Types';
import { ISelectionRenderModel } from 'browser/renderer/shared/Types';
import { CursorInactiveStyle, CursorStyle, type IDisposable } from 'common/Types';
import type { IEvent } from 'common/Event';
import { IColorSet } from '../../../src/browser/Types';
import { ISelectionRenderModel } from '../../../src/browser/renderer/shared/Types';
import { CursorInactiveStyle, CursorStyle, type IDisposable } from '../../../src/common/Types';
import type { IEvent } from '../../../src/common/Event';
export interface IRenderModel {
cells: Uint32Array;
@@ -28,6 +28,7 @@ export interface ICursorRenderModel {
export interface IWebGL2RenderingContext extends WebGLRenderingContext {
vertexAttribDivisor(index: number, divisor: number): void;
createVertexArray(): IWebGLVertexArrayObject;
deleteVertexArray(vao: IWebGLVertexArrayObject): void;
bindVertexArray(vao: IWebGLVertexArrayObject): void;
drawElementsInstanced(mode: number, count: number, type: number, offset: number, instanceCount: number): void;
}
+8 -6
View File
@@ -5,14 +5,14 @@
import type { ITerminalAddon, Terminal } from '@xterm/xterm';
import type { IWebglAddonOptions, WebglAddon as IWebglApi } from '@xterm/addon-webgl';
import { ICharacterJoinerService, ICharSizeService, ICoreBrowserService, IRenderService, IThemeService } from 'browser/services/Services';
import { ITerminal } from 'browser/Types';
import { Disposable, toDisposable } from 'common/Lifecycle';
import { getSafariVersion, isSafari } from 'common/Platform';
import { ICoreService, IDecorationService, IOptionsService } from 'common/services/Services';
import { ICharacterJoinerService, ICharSizeService, ICoreBrowserService, IRenderService, IThemeService } from '../../../src/browser/services/Services';
import { ITerminal } from '../../../src/browser/Types';
import { Disposable, toDisposable } from '../../../src/common/Lifecycle';
import { getSafariVersion, isSafari } from '../../../src/common/Platform';
import { ICoreService, IDecorationService, ILogService, IOptionsService } from '../../../src/common/services/Services';
import { IWebGL2RenderingContext } from './Types';
import { WebglRenderer } from './WebglRenderer';
import { Emitter, EventUtils } from 'common/Event';
import { Emitter, EventUtils } from '../../../src/common/Event';
export class WebglAddon extends Disposable implements ITerminalAddon, IWebglApi {
private _terminal?: Terminal;
@@ -65,6 +65,7 @@ export class WebglAddon extends Disposable implements ITerminalAddon, IWebglApi
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;
this._renderer = this._register(new WebglRenderer(
@@ -74,6 +75,7 @@ export class WebglAddon extends Disposable implements ITerminalAddon, IWebglApi
coreBrowserService,
coreService,
decorationService,
logService,
optionsService,
themeService,
this._customGlyphs,
+38 -18
View File
@@ -3,19 +3,19 @@
* @license MIT
*/
import { ITerminal } from 'browser/Types';
import { ITerminal } from '../../../src/browser/Types';
import { CellColorResolver } from './CellColorResolver';
import { acquireTextureAtlas, removeTerminalFromCache } from './CharAtlasCache';
import { CursorBlinkStateManager } from './CursorBlinkStateManager';
import { observeDevicePixelDimensions } from './DevicePixelObserver';
import { IRenderDimensions, IRenderer, IRequestRedrawEvent } from 'browser/renderer/shared/Types';
import { ICharSizeService, ICharacterJoinerService, ICoreBrowserService, IThemeService } from 'browser/services/Services';
import { CharData, IBufferLine, ICellData } from 'common/Types';
import { AttributeData } from 'common/buffer/AttributeData';
import { CellData } from 'common/buffer/CellData';
import { Attributes, Content, FgFlags, NULL_CELL_CHAR, NULL_CELL_CODE } from 'common/buffer/Constants';
import { TextBlinkStateManager } from 'browser/renderer/shared/TextBlinkStateManager';
import { ICoreService, IDecorationService, IOptionsService } from 'common/services/Services';
import { IRenderDimensions, IRenderer, IRequestRedrawEvent } from '../../../src/browser/renderer/shared/Types';
import { ICharSizeService, ICharacterJoinerService, ICoreBrowserService, IThemeService } from '../../../src/browser/services/Services';
import { CharData, IBufferLine, ICellData } from '../../../src/common/Types';
import { AttributeData } from '../../../src/common/buffer/AttributeData';
import { CellData } from '../../../src/common/buffer/CellData';
import { Attributes, Content, FgFlags, NULL_CELL_CHAR, NULL_CELL_CODE } from '../../../src/common/buffer/Constants';
import { TextBlinkStateManager } from '../../../src/browser/renderer/shared/TextBlinkStateManager';
import { ICoreService, IDecorationService, ILogService, IOptionsService } from '../../../src/common/services/Services';
import { Terminal } from '@xterm/xterm';
import { GlyphRenderer } from './GlyphRenderer';
import { RectangleRenderer } from './RectangleRenderer';
@@ -23,10 +23,14 @@ import { COMBINED_CHAR_BIT_MASK, RenderModel, RenderModelConstants } from './Ren
import { IWebGL2RenderingContext, type ITextureAtlas } from './Types';
import { LinkRenderLayer } from './renderLayer/LinkRenderLayer';
import { IRenderLayer } from './renderLayer/Types';
import { Emitter, EventUtils } from 'common/Event';
import { addDisposableListener } from 'browser/Dom';
import { combinedDisposable, Disposable, MutableDisposable, toDisposable } from 'common/Lifecycle';
import { createRenderDimensions } from 'browser/renderer/shared/RendererUtils';
import { Emitter, EventUtils } from '../../../src/common/Event';
import { addDisposableListener } from '../../../src/browser/Dom';
import { combinedDisposable, Disposable, MutableDisposable, toDisposable } from '../../../src/common/Lifecycle';
import { createRenderDimensions } from '../../../src/browser/renderer/shared/RendererUtils';
const enum Constants {
MERGE_RETRY_LIMIT = 32
}
export class WebglRenderer extends Disposable implements IRenderer {
private _renderLayers: IRenderLayer[];
@@ -73,6 +77,7 @@ export class WebglRenderer extends Disposable implements IRenderer {
private readonly _coreBrowserService: ICoreBrowserService,
private readonly _coreService: ICoreService,
private readonly _decorationService: IDecorationService,
private readonly _logService: ILogService,
private readonly _optionsService: IOptionsService,
private readonly _themeService: IThemeService,
private readonly _customGlyphs: boolean = true,
@@ -118,19 +123,19 @@ export class WebglRenderer extends Disposable implements IRenderer {
this._deviceMaxTextureSize = this._gl.getParameter(this._gl.MAX_TEXTURE_SIZE);
this._register(addDisposableListener(this._canvas, 'webglcontextlost', (e) => {
console.log('webglcontextlost event received');
this._logService.debug('webglcontextlost event received');
// Prevent the default behavior in order to enable WebGL context restoration.
e.preventDefault();
// Wait a few seconds to see if the 'webglcontextrestored' event is fired.
// If not, dispatch the onContextLoss notification to observers.
this._contextRestorationTimeout = setTimeout(() => {
this._contextRestorationTimeout = undefined;
console.warn('webgl context not restored; firing onContextLoss');
this._logService.warn('webgl context not restored; firing onContextLoss');
this._onContextLoss.fire(e);
}, 3000 /* ms */);
}));
this._register(addDisposableListener(this._canvas, 'webglcontextrestored', (e) => {
console.warn('webglcontextrestored event received');
this._logService.warn('webglcontextrestored event received');
clearTimeout(this._contextRestorationTimeout);
this._contextRestorationTimeout = undefined;
// The texture atlas and glyph renderer must be fully reinitialized
@@ -154,6 +159,8 @@ export class WebglRenderer extends Disposable implements IRenderer {
this._isAttached = this._core.screenElement!.isConnected;
this._register(toDisposable(() => {
clearTimeout(this._contextRestorationTimeout);
this._contextRestorationTimeout = undefined;
for (const l of this._renderLayers) {
l.dispose();
}
@@ -270,8 +277,8 @@ export class WebglRenderer extends Disposable implements IRenderer {
* Initializes members dependent on WebGL context state.
*/
private _initializeWebGLState(): [RectangleRenderer, GlyphRenderer] {
this._rectangleRenderer.value = new RectangleRenderer(this._terminal, this._gl, this.dimensions, this._themeService);
this._glyphRenderer.value = new GlyphRenderer(this._terminal, this._gl, this.dimensions, this._optionsService);
this._rectangleRenderer.value = new RectangleRenderer(this._terminal, this._gl, this.dimensions, this._themeService, this._logService);
this._glyphRenderer.value = new GlyphRenderer(this._terminal, this._gl, this.dimensions, this._optionsService, this._logService);
// Update dimensions and acquire char atlas
this.handleCharSizeChanged();
@@ -375,6 +382,19 @@ export class WebglRenderer extends Disposable implements IRenderer {
this._updateModel(start, end);
}
// A mid-update atlas page merge invalidates vertex data and may not bump the host
// page's version, so re-run the update and force a full texture rebind.
let merged = false;
let mergeRetries = 0;
while (this._charAtlas && this._glyphRenderer.value.beginFrame() && mergeRetries++ < Constants.MERGE_RETRY_LIMIT) {
merged = true;
this._clearModel(true);
this._updateModel(0, this._terminal.rows - 1);
}
if (merged) {
this._glyphRenderer.value.invalidateAtlasTextures();
}
// Render
this._rectangleRenderer.value.renderBackgrounds();
this._glyphRenderer.value.render(this._model);
+8 -7
View File
@@ -3,7 +3,8 @@
* @license MIT
*/
import { throwIfFalsy } from 'browser/renderer/shared/RendererUtils';
import { throwIfFalsy } from '../../../src/browser/renderer/shared/RendererUtils';
import type { ILogService } from '../../../src/common/services/Services';
/**
* A matrix that when multiplies will translate 0-1 coordinates (left to right,
@@ -16,21 +17,21 @@ export const PROJECTION_MATRIX = new Float32Array([
-1, 1, 0, 1
]);
export function createProgram(gl: WebGLRenderingContext, vertexSource: string, fragmentSource: string): WebGLProgram | undefined {
export function createProgram(gl: WebGLRenderingContext, vertexSource: string, fragmentSource: string, logService: ILogService): WebGLProgram | undefined {
const program = throwIfFalsy(gl.createProgram());
gl.attachShader(program, throwIfFalsy(createShader(gl, gl.VERTEX_SHADER, vertexSource)));
gl.attachShader(program, throwIfFalsy(createShader(gl, gl.FRAGMENT_SHADER, fragmentSource)));
gl.attachShader(program, throwIfFalsy(createShader(gl, gl.VERTEX_SHADER, vertexSource, logService)));
gl.attachShader(program, throwIfFalsy(createShader(gl, gl.FRAGMENT_SHADER, fragmentSource, logService)));
gl.linkProgram(program);
const success = gl.getProgramParameter(program, gl.LINK_STATUS);
if (success) {
return program;
}
console.error(gl.getProgramInfoLog(program));
logService.error(gl.getProgramInfoLog(program));
gl.deleteProgram(program);
}
export function createShader(gl: WebGLRenderingContext, type: number, source: string): WebGLShader | undefined {
export function createShader(gl: WebGLRenderingContext, type: number, source: string, logService: ILogService): WebGLShader | undefined {
const shader = throwIfFalsy(gl.createShader(type));
gl.shaderSource(shader, source);
gl.compileShader(shader);
@@ -39,7 +40,7 @@ export function createShader(gl: WebGLRenderingContext, type: number, source: st
return shader;
}
console.error(gl.getShaderInfoLog(shader));
logService.error(gl.getShaderInfoLog(shader));
gl.deleteShader(shader);
}
@@ -3,7 +3,8 @@
* @license MIT
*/
import { throwIfFalsy } from 'browser/renderer/shared/RendererUtils';
import { throwIfFalsy } from '../../../../src/browser/renderer/shared/RendererUtils';
import type { ILogService } from '../../../../src/common/services/Services';
import { customGlyphDefinitions } from './CustomGlyphDefinitions';
import { CustomGlyphDefinitionType, CustomGlyphScaleType, CustomGlyphVectorType, type CustomGlyphDefinitionPart, type CustomGlyphPathDrawFunctionDefinition, type CustomGlyphPatternDefinition, type ICustomGlyphSolidOctantBlockVector, type ICustomGlyphVectorShape } from './Types';
@@ -22,6 +23,7 @@ export function tryDrawCustomGlyph(
deviceCharHeight: number,
fontSize: number,
devicePixelRatio: number,
logService: ILogService,
backgroundColor?: string,
variantOffset: number = 0
): boolean {
@@ -30,7 +32,7 @@ export function tryDrawCustomGlyph(
// Normalize to array for uniform handling
const parts = Array.isArray(unifiedCharDefinition) ? unifiedCharDefinition : [unifiedCharDefinition];
for (const part of parts) {
drawDefinitionPart(ctx, part, xOffset, yOffset, deviceCellWidth, deviceCellHeight, deviceCharWidth, deviceCharHeight, fontSize, devicePixelRatio, backgroundColor, variantOffset);
drawDefinitionPart(ctx, part, xOffset, yOffset, deviceCellWidth, deviceCellHeight, deviceCharWidth, deviceCharHeight, fontSize, devicePixelRatio, logService, backgroundColor, variantOffset);
}
return true;
}
@@ -49,6 +51,7 @@ function drawDefinitionPart(
deviceCharHeight: number,
fontSize: number,
devicePixelRatio: number,
logService: ILogService,
backgroundColor?: string,
variantOffset: number = 0
): void {
@@ -79,7 +82,7 @@ function drawDefinitionPart(
drawPatternChar(ctx, part.data, drawXOffset, drawYOffset, drawWidth, drawHeight, variantOffset);
break;
case CustomGlyphDefinitionType.PATH_FUNCTION:
drawPathFunctionCharacter(ctx, part.data, drawXOffset, drawYOffset, drawWidth, drawHeight, devicePixelRatio, part.strokeWidth);
drawPathFunctionCharacter(ctx, part.data, drawXOffset, drawYOffset, drawWidth, drawHeight, devicePixelRatio, logService, part.strokeWidth);
break;
case CustomGlyphDefinitionType.PATH:
drawPathDefinitionCharacter(ctx, part.data, drawXOffset, drawYOffset, drawWidth, drawHeight, devicePixelRatio, part.strokeWidth);
@@ -88,7 +91,7 @@ function drawDefinitionPart(
drawPathNegativeDefinitionCharacter(ctx, part.data, drawXOffset, drawYOffset, drawWidth, drawHeight, devicePixelRatio, backgroundColor);
break;
case CustomGlyphDefinitionType.VECTOR_SHAPE:
drawVectorShape(ctx, part.data, drawXOffset, drawYOffset, drawWidth, drawHeight, fontSize, devicePixelRatio);
drawVectorShape(ctx, part.data, drawXOffset, drawYOffset, drawWidth, drawHeight, fontSize, devicePixelRatio, logService);
break;
case CustomGlyphDefinitionType.BRAILLE:
drawBrailleCharacter(ctx, part.data, drawXOffset, drawYOffset, drawWidth, drawHeight);
@@ -510,6 +513,7 @@ function drawPathFunctionCharacter(
deviceCellWidth: number,
deviceCellHeight: number,
devicePixelRatio: number,
logService: ILogService,
strokeWidth?: number
): void {
ctx.save();
@@ -536,7 +540,7 @@ function drawPathFunctionCharacter(
}
const f = svgToCanvasInstructionMap[type];
if (!f) {
console.error(`Could not find drawing instructions for "${type}"`);
logService.error(`Could not find drawing instructions for "${type}"`);
continue;
}
const args: string[] = instruction.substring(1).split(',');
@@ -598,7 +602,8 @@ function drawVectorShape(
deviceCellWidth: number,
deviceCellHeight: number,
fontSize: number,
devicePixelRatio: number
devicePixelRatio: number,
logService: ILogService
): void {
// Clip the cell to make sure drawing doesn't occur beyond bounds
const clipRegion = new Path2D();
@@ -619,7 +624,7 @@ function drawVectorShape(
}
const f = svgToCanvasInstructionMap[type];
if (!f) {
console.error(`Could not find drawing instructions for "${type}"`);
logService.error(`Could not find drawing instructions for "${type}"`);
continue;
}
const args: string[] = instruction.substring(1).split(',');
@@ -3,16 +3,16 @@
* @license MIT
*/
import { ReadonlyColorSet } from 'browser/Types';
import { ReadonlyColorSet } from '../../../../src/browser/Types';
import { acquireTextureAtlas } from '../CharAtlasCache';
import { IRenderDimensions } from 'browser/renderer/shared/Types';
import { ICoreBrowserService, IThemeService } from 'browser/services/Services';
import { Disposable, toDisposable } from 'common/Lifecycle';
import { CellData } from 'common/buffer/CellData';
import { IOptionsService } from 'common/services/Services';
import { IRenderDimensions } from '../../../../src/browser/renderer/shared/Types';
import { ICoreBrowserService, IThemeService } from '../../../../src/browser/services/Services';
import { Disposable, toDisposable } from '../../../../src/common/Lifecycle';
import { CellData } from '../../../../src/common/buffer/CellData';
import { IOptionsService } from '../../../../src/common/services/Services';
import { Terminal } from '@xterm/xterm';
import { IRenderLayer } from './Types';
import { throwIfFalsy } from 'browser/renderer/shared/RendererUtils';
import { throwIfFalsy } from '../../../../src/browser/renderer/shared/RendererUtils';
import { TEXT_BASELINE } from '../Constants';
import type { ITextureAtlas } from '../Types';
@@ -4,11 +4,11 @@
*/
import { is256Color } from '../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 { ILinkifier2, ILinkifierEvent } from 'browser/Types';
import { IOptionsService } from 'common/services/Services';
import { INVERTED_DEFAULT_COLOR } from '../../../../src/browser/renderer/shared/Constants';
import { IRenderDimensions } from '../../../../src/browser/renderer/shared/Types';
import { ICoreBrowserService, IThemeService } from '../../../../src/browser/services/Services';
import { ILinkifier2, ILinkifierEvent } from '../../../../src/browser/Types';
import { IOptionsService } from '../../../../src/common/services/Services';
import { Terminal } from '@xterm/xterm';
import { BaseRenderLayer } from './BaseRenderLayer';
+1 -1
View File
@@ -4,7 +4,7 @@
*/
import { IDisposable, Terminal } from '@xterm/xterm';
import { IRenderDimensions } from 'browser/renderer/shared/Types';
import { IRenderDimensions } from '../../../../src/browser/renderer/shared/Types';
export interface IRenderLayer extends IDisposable {
/**
+2 -15
View File
@@ -10,20 +10,6 @@
"outDir": "../out",
"sourceMap": true,
"removeComments": true,
"paths": {
"common/*": [
"../../../src/common/*"
],
"browser/*": [
"../../../src/browser/*"
],
"@xterm/addon-webgl": [
"../typings/addon-webgl.d.ts"
],
"*": [
"./*"
]
},
"strict": true,
"downlevelIteration": true,
"experimentalDecorators": true,
@@ -33,7 +19,8 @@
},
"include": [
"./**/*",
"../../../typings/xterm.d.ts"
"../../../typings/xterm.d.ts",
"../typings/addon-webgl.d.ts"
],
"references": [
{
+1 -12
View File
@@ -3,23 +3,12 @@
"module": "commonjs",
"target": "ESNext",
"lib": [
"es2021",
"es2021"
],
"rootDir": ".",
"outDir": "../out-test",
"sourceMap": true,
"removeComments": true,
"paths": {
"common/*": [
"../../../src/common/*"
],
"browser/*": [
"../../../src/browser/*"
],
"*": [
"./*"
]
},
"strict": true,
"types": [
"../../../node_modules/@types/node",
+6 -2
View File
@@ -2,7 +2,11 @@
"files": [],
"include": [],
"references": [
{ "path": "./src" },
{ "path": "./test" }
{
"path": "./src"
},
{
"path": "./test"
}
]
}
+10 -3
View File
@@ -107,13 +107,20 @@ if (config.addon) {
};
outConfig = {
...outConfig,
bundle: true,
platform: 'node',
packages: 'external',
entryPoints: [`addons/addon-${config.addon}/src/**/*.ts`],
outdir: `addons/addon-${config.addon}/out-esbuild/`
outdir: `addons/addon-${config.addon}/out-esbuild/`,
// Public API stays external; src/common|browser are bundled from source.
external: ['@xterm/xterm', '@xterm/headless', '@xterm/addon-*'],
};
// Playwright suites only (not run by unit tests); keep unbundled.
outTestConfig = {
...outConfig,
format: 'cjs',
sourcemap: true,
entryPoints: [`addons/addon-${config.addon}/test/**/*.ts`],
outdir: `addons/addon-${config.addon}/out-esbuild-test/`
outdir: `addons/addon-${config.addon}/out-esbuild-test/`,
};
if (config.addon === 'ligatures') {
+15 -4
View File
@@ -7,19 +7,30 @@ import { argv } from "process";
/** @type {{cp: import("child_process").ChildProcessByStdio<any, any, any>, name: string}[]} */
const jobs = [];
// Core job
jobs.push(createJob('xterm', []));
// Core job must finish before addons (addon tests resolve src/* via out-esbuild).
const xtermJob = createJob('xterm', []);
const xtermCode = await new Promise(r => {
xtermJob.cp.on('exit', code => {
log(`Finished \x1b[32m${xtermJob.name}\x1b[0m${code ? ' \x1b[31mwith errors\x1b[0m' : ''}`);
r(code ?? 1);
});
});
if (xtermCode !== 0) {
process.exit(xtermCode);
}
// Addon jobs
const addons = (await readdir('addons')).filter(e => e.startsWith('addon-')).map(e => e.replace('addon-', ''));
/** @type {{cp: import("child_process").ChildProcessByStdio<any, any, any>, name: string}[]} */
const addonJobs = [];
for (const addon of addons) {
jobs.push(createJob(`xterm-addon-${addon}`, [`--addon=${addon}`]));
addonJobs.push(createJob(`xterm-addon-${addon}`, [`--addon=${addon}`]));
}
// Demo job - This requires the others to be built so it's not included when building all
// jobs.push(createJob('demo-client', [`--demo-client`]));
await Promise.all(jobs.map((job, i) => {
await Promise.all(addonJobs.map(job => {
return new Promise(r => {
job.cp.on('exit', code => {
log(`Finished \x1b[32m${job.name}\x1b[0m${code ? ' \x1b[31mwith errors\x1b[0m' : ''}`);
-196
View File
@@ -1,196 +0,0 @@
from termios import tcgetattr, tcsetattr, TCSADRAIN, TIOCGWINSZ, TCSAFLUSH
from tty import setcbreak, setraw
import os
import sys
from contextlib import contextmanager
from select import select
from time import sleep
from json import dumps, loads
from typing import Optional, Tuple, Union
class TerminalContext:
def __init__(self, fd: int, close_fd=False) -> None:
if not os.isatty(fd):
raise TypeError('fd is not a terminal')
self.close_fd = close_fd
self.fd = fd
self.is_cbreak = False
self.is_raw = False
self._initial_attr = self.termios_attributes
@classmethod
def from_cterm(cls):
fd = os.open(os.ctermid(), os.O_RDWR)
return TerminalContext(fd, True)
def close(self) -> None:
tcsetattr(self.fd, TCSADRAIN, self._initial_attr)
if self.close_fd:
os.close(self.fd)
@property
def termios_attributes(self) -> list:
return tcgetattr(self.fd)
@property
def ttyname(self) -> str:
return os.ttyname(self.fd)
@contextmanager
def cbreak_mode(self):
"""
Enter cbreak mode context.
"""
if self.is_cbreak:
yield
return
tattr = self.termios_attributes
try:
setcbreak(self.fd, TCSADRAIN)
self.is_cbreak = True
yield
finally:
tcsetattr(self.fd, TCSADRAIN, tattr)
self.is_cbreak = False
@contextmanager
def raw_mode(self):
"""
Enter cbreak mode context.
"""
if self.is_raw:
yield
return
tattr = self.termios_attributes
try:
setraw(self.fd, TCSADRAIN)
self.is_raw = True
yield
finally:
tcsetattr(self.fd, TCSADRAIN, tattr)
self.is_raw = False
@contextmanager
def custom_state(self, undo=None):
"""
Enter custom terminal state, that needs to to be undone by ``undo``.
Useful, if you want to apply a custom terminal state and
have to make sure, that it gets properly reset to previous state.
"""
try:
yield
finally:
if undo:
undo()
def write(self, data: Union[str, bytes]) -> None:
"""
Write string or bytes directly to the terminal.
"""
if isinstance(data, str):
data = data.encode('utf-8')
sent = os.write(self.fd, data)
while sent:
data = data[sent:]
sent = os.write(self.fd, data)
def read(self, amount: int = 1024, timeout: Optional[float] = None) -> bytes:
"""
Blocking read from the terminal.
If nothing was sent from the terminal within ``timeout``,
empty bytes are returned.
"""
can_read, _, _ = select([self.fd], [], [], timeout)
if can_read:
return os.read(self.fd, amount)
return b''
@contextmanager
def cterminal_context():
t = TerminalContext.from_cterm()
try:
yield t
finally:
t.close()
def extract_events(data: list[str]):
if len(data) < 5:
raise Exception('not enough reports')
types = set(data)
if len(types) == 1:
return {'PRESS': data[0], 'REPEAT': data[0], 'RELEASE': None}
if len(types) == 2:
last = data.pop()
if last != data[0] and len(set(data)) == 1:
return {'PRESS': data[0], 'REPEAT': data[0], 'RELEASE': last}
raise Exception('weird reports, 2 types')
if len(types) == 3:
first = data.pop(0)
last = data.pop()
if first != last and first != data[0] and last != data[0]:
return {'PRESS': first, 'REPEAT': data[0], 'RELEASE': last}
raise Exception('weird reports, 3 types')
raise Exception('more than 3 types')
def query(term: TerminalContext, mode: int):
with term.custom_state(undo=lambda:term.write('\x1b[<u')):
term.write(f'\x1b[>{mode}u')
term.read(timeout=.1)
print('PRESS (within 5s) and HOLD (for 5s)\r')
data: list[bytes] = []
cur = term.read(timeout=5)
while cur:
data.append(cur)
cur = term.read(timeout=.5)
if len(data) > 5:
print('RELEASE\r', end='')
try:
return extract_events([b.decode('utf-8') for b in data])
except Exception as e:
print('Error:', e, '\r')
print(data, '\r')
term.read(timeout=.1)
def save(filedata: dict[str, dict[str, str]], filename, entry, mode, events):
try:
filedata[entry][mode] = events
except KeyError:
filedata[entry] = {mode: events}
with open(filename, 'w') as f:
f.write(dumps(filedata, indent=2))
def main():
mode = 1
filedata = {}
print(sys.argv)
if len(sys.argv) != 3:
print('ERROR: not enough arguments')
print('Usage: python kitty_keytester.py <mode> <json-file>')
return
mode = int(sys.argv[1])
filename = sys.argv[2]
if os.path.exists(filename):
with open(filename) as f:
filedata = loads(f.read())
with cterminal_context() as term:
while True:
with term.raw_mode():
events = query(term, mode)
if events:
print(events)
entry = input('Entry: ')
if entry:
save(filedata, filename, entry, str(mode), events)
cont = input('Continue? (y)')
if cont not in ['y', '']:
break
if __name__ == '__main__':
main()
+1 -1
View File
@@ -8,7 +8,7 @@ const path = require('path');
const COVERAGE_LINES_THRESHOLD = 40;
// Add `out` to the NODE_PATH so absolute paths can be resolved.
// Add `out-esbuild` to NODE_PATH for any remaining non-relative requires in test bundles.
const env = { ...process.env };
env.NODE_PATH = path.resolve(__dirname, '../out-esbuild');
@@ -6,7 +6,7 @@
import { BaseWindow } from './baseWindow';
import type { Terminal, ITheme } from '@xterm/xterm';
import type { IControlWindow } from '../controlBar';
import type { AddonCollection } from 'types';
import type { AddonCollection } from '../../types';
const xtermjsTheme: ITheme = Object.freeze({
foreground: '#F8F8F8',
@@ -252,6 +252,11 @@ export class OptionsWindow extends BaseWindow implements IControlWindow {
input.value = 'auto';
this._handlers.updateTerminalSize();
}
const screenElement = document.querySelector('.xterm-screen');
if (screenElement instanceof HTMLElement) {
screenElement.style.outline = this._autoResize ? ''
: 'solid thin red';
}
} else if (o === 'theme') {
value = this._getTheme();
}
+1 -1
View File
@@ -7,7 +7,7 @@ import { writeUnicodeTable } from '../../unicodeTable';
import type { IControlWindow } from '../controlBar';
import { BaseWindow } from './baseWindow';
import type { IDisposable, Terminal } from '@xterm/xterm';
import type { AddonCollection } from 'types';
import type { AddonCollection } from '../../types';
export class TestWindow extends BaseWindow implements IControlWindow {
public readonly id = 'test';
+1 -17
View File
@@ -6,23 +6,7 @@
"rootDir": ".",
"sourceMap": true,
"strict": true,
"skipLibCheck": true,
"paths": {
"@xterm/addon-attach": ["../../addons/addon-attach"],
"@xterm/addon-clipboard": ["../../addons/addon-clipboard"],
"@xterm/addon-fit": ["../../addons/addon-fit"],
"@xterm/addon-image": ["../../addons/addon-image"],
"@xterm/addon-progress": ["../../addons/addon-progress"],
"@xterm/addon-search": ["../../addons/addon-search"],
"@xterm/addon-serialize": ["../../addons/addon-serialize"],
"@xterm/addon-web-fonts": ["../../addons/addon-web-fonts"],
"@xterm/addon-web-links": ["../../addons/addon-web-links"],
"@xterm/addon-webgl": ["../../addons/addon-webgl"],
"@xterm/addon-unicode11": ["../../addons/addon-unicode11"],
"@xterm/addon-unicode-graphemes": ["../../addons/addon-unicode-graphemes"],
"@xterm/addon-ligatures": ["../../addons/addon-ligatures"],
"*": ["./*"]
}
"skipLibCheck": true
},
"include": [
"./**/*",
+1 -1
View File
@@ -9,6 +9,6 @@
"skipLibCheck": true
},
"include": [
"./**/*",
"./**/*"
]
}
+6 -2
View File
@@ -1,7 +1,11 @@
{
"references": [
{ "path": "./client" },
{ "path": "./server" }
{
"path": "./client"
},
{
"path": "./server"
}
],
"files": []
}
+2
View File
@@ -43,6 +43,7 @@ export default tseslint.config(
singleline: { delimiter: 'comma', requireLast: false }
}],
'@stylistic/type-annotation-spacing': 'warn',
'@stylistic/space-unary-ops': 'warn',
'@typescript-eslint/array-type': ['warn', { default: 'array', readonly: 'generic' }],
'@typescript-eslint/consistent-type-assertions': 'warn',
@@ -85,6 +86,7 @@ export default tseslint.config(
'eqeqeq': ['warn', 'always'],
'jsdoc/check-alignment': 'warn',
'jsdoc/check-param-names': 'warn',
'jsdoc/multiline-blocks': 'warn',
'jsdoc/no-multi-asterisks': 'warn',
'keyword-spacing': 'warn',
'max-len': ['warn', {
@@ -1,373 +0,0 @@
{
"Escape +CapsLock": {
"1": {
"PRESS": "\u001b[27;65u",
"REPEAT": "\u001b[27;65u",
"RELEASE": null
}
},
"F1 +CapsLock": {
"1": {
"PRESS": "\u001b[1;65P",
"REPEAT": "\u001b[1;65P",
"RELEASE": null
}
},
"Insert +CapsLock": {
"1": {
"PRESS": "\u001b[2;65~",
"REPEAT": "\u001b[2;65~",
"RELEASE": null
}
},
"Delete +CapsLock": {
"1": {
"PRESS": "\u001b[3;65~",
"REPEAT": "\u001b[3;65~",
"RELEASE": null
}
},
"PageUp +CapsLock": {
"1": {
"PRESS": "\u001b[5;65~",
"REPEAT": "\u001b[5;65~",
"RELEASE": null
}
},
"Dead (Backquote) +CapsLock": {
"1": {
"PRESS": "^",
"REPEAT": "^",
"RELEASE": null
}
},
"1 (Digit1) +CapsLock": {
"1": {
"PRESS": "1",
"REPEAT": "1",
"RELEASE": null
}
},
"\u00df (Minus) +CapsLock": {
"1": {
"PRESS": "\u1e9e",
"REPEAT": "\u1e9e",
"RELEASE": null
}
},
"Dead (Equal) +CapsLock": {
"1": {
"PRESS": "\u00b4",
"REPEAT": "\u00b4",
"RELEASE": null
}
},
"Backspace +CapsLock": {
"1": {
"PRESS": "\u007f",
"REPEAT": "\u007f",
"RELEASE": null
}
},
"Tab +CapsLock": {
"1": {
"PRESS": "\t",
"REPEAT": "\t",
"RELEASE": null
}
},
"Q (KeyQ) +CapsLock": {
"1": {
"PRESS": "Q",
"REPEAT": "Q",
"RELEASE": null
}
},
"\u00dc (BracketLeft) +CapsLock": {
"1": {
"PRESS": "\u00dc",
"REPEAT": "\u00dc",
"RELEASE": null
}
},
"+ (BracketRight) +CapsLock": {
"1": {
"PRESS": "+",
"REPEAT": "+",
"RELEASE": null
}
},
"Enter +CapsLock": {
"1": {
"PRESS": "\r",
"REPEAT": "\r",
"RELEASE": null
}
},
"\u00d6 (Semicolon) +CapsLock": {
"1": {
"PRESS": "\u00d6",
"REPEAT": "\u00d6",
"RELEASE": null
}
},
"\u00c4 (Quote) +CapsLock": {
"1": {
"PRESS": "\u00c4",
"REPEAT": "\u00c4",
"RELEASE": null
}
},
"# (Bashslash) +CapsLock": {
"1": {
"PRESS": "#",
"REPEAT": "#",
"RELEASE": null
}
},
"< (IntlBackslash) +CapsLock": {
"1": {
"PRESS": "<",
"REPEAT": "<",
"RELEASE": null
}
},
", (Comma) +CapsLock": {
"1": {
"PRESS": ",",
"REPEAT": ",",
"RELEASE": null
}
},
". (Period) +CapsLock": {
"1": {
"PRESS": ".",
"REPEAT": ".",
"RELEASE": null
}
},
"- (Slash) +CapsLock": {
"1": {
"PRESS": "-",
"REPEAT": "-",
"RELEASE": null
}
},
"ArrowUp +CapsLock": {
"1": {
"PRESS": "\u001b[1;65A",
"REPEAT": "\u001b[1;65A",
"RELEASE": null
}
},
"PageDown +CapsLock": {
"1": {
"PRESS": "\u001b[6;65~",
"REPEAT": "\u001b[6;65~",
"RELEASE": null
}
},
"Home +CapsLock": {
"1": {
"PRESS": "\u001b[1;65H",
"REPEAT": "\u001b[1;65H",
"RELEASE": null
}
},
"End +CapsLock": {
"1": {
"PRESS": "\u001b[1;65F",
"REPEAT": "\u001b[1;65F",
"RELEASE": null
}
},
"+ (NumpadAdd) +CapsLock": {
"1": {
"PRESS": "+",
"REPEAT": "+",
"RELEASE": null
}
},
"Enter (NumpadEnter) +CapsLock": {
"1": {
"PRESS": "\u001b[57414;65u",
"REPEAT": "\u001b[57414;65u",
"RELEASE": null
}
},
"Insert (Numpad0) +CapsLock": {
"1": {
"PRESS": "\u001b[57425;65u",
"REPEAT": "\u001b[57425;65u",
"RELEASE": null
}
},
"\u0000 (NumpadDecimal) +CapsLock": {
"1": {
"PRESS": "\u001b[57426;65u",
"REPEAT": "\u001b[57426;65u",
"RELEASE": null
}
},
"Numlock +CapsLock": {
"1": {
"PRESS": null,
"REPEAT": null,
"RELEASE": null
}
},
"CapsLock +CapsLock": {
"1": {
"PRESS": null,
"REPEAT": null,
"RELEASE": null
}
},
"Shift (ShiftLeft) +CapsLock": {
"1": {
"PRESS": null,
"REPEAT": null,
"RELEASE": null
}
},
"Shift (ShiftRight) +CapsLock": {
"1": {
"PRESS": null,
"REPEAT": null,
"RELEASE": null
}
},
"Control (ControlLeft) +CapsLock": {
"1": {
"PRESS": null,
"REPEAT": null,
"RELEASE": null
}
},
"Meta (MetaLeft) +CapsLock": {
"1": {
"PRESS": null,
"REPEAT": null,
"RELEASE": null
}
},
"Alt (AltLeft) +CapsLock": {
"1": {
"PRESS": null,
"REPEAT": null,
"RELEASE": null
}
},
"AltGraph (AltRight) +CapsLock": {
"1": {
"PRESS": null,
"REPEAT": null,
"RELEASE": null
}
},
"Control (ControlRight) +CapsLock": {
"1": {
"PRESS": null,
"REPEAT": null,
"RELEASE": null
}
},
"/ (NumpadDivide) +NumLock": {
"1": {
"PRESS": "/",
"REPEAT": "/",
"RELEASE": null
}
},
"* (NumpadMultiply) +NumLock": {
"1": {
"PRESS": "*",
"REPEAT": "*",
"RELEASE": null
}
},
"- (NumpadSubtract) +NumLock": {
"1": {
"PRESS": "-",
"REPEAT": "-",
"RELEASE": null
}
},
"0 (Numpad0) +NumLock": {
"1": {
"PRESS": "0",
"REPEAT": "0",
"RELEASE": null
}
},
"1 (Numpad1) +NumLock": {
"1": {
"PRESS": "1",
"REPEAT": "1",
"RELEASE": null
}
},
"2 (Numpad2) +NumLock": {
"1": {
"PRESS": "2",
"REPEAT": "2",
"RELEASE": null
}
},
"3 (Numpad3) +NumLock": {
"1": {
"PRESS": "3",
"REPEAT": "3",
"RELEASE": null
}
},
"4 (Numpad4) +NumLock": {
"1": {
"PRESS": "4",
"REPEAT": "4",
"RELEASE": null
}
},
"5 (Numpad5) +NumLock": {
"1": {
"PRESS": "5",
"REPEAT": "5",
"RELEASE": null
}
},
"6 (Numpad6) +NumLock": {
"1": {
"PRESS": "6",
"REPEAT": "6",
"RELEASE": null
}
},
"7 (Numpad7) +NumLock": {
"1": {
"PRESS": "7",
"REPEAT": "7",
"RELEASE": null
}
},
"8 (Numpad8) +NumLock": {
"1": {
"PRESS": "8",
"REPEAT": "8",
"RELEASE": null
}
},
"9 (Numpad9) +NumLock": {
"1": {
"PRESS": "9",
"REPEAT": "9",
"RELEASE": null
}
},
", (NumpadDecimal) +NumLock": {
"1": {
"PRESS": ",",
"REPEAT": ",",
"RELEASE": null
}
}
}
@@ -1,702 +0,0 @@
{
"Escape": {
"1": {
"PRESS": "\u001b[27u",
"REPEAT": "\u001b[27u",
"RELEASE": null
}
},
"F1": {
"1": {
"PRESS": "\u001b[P",
"REPEAT": "\u001b[P",
"RELEASE": null
}
},
"F2": {
"1": {
"PRESS": "\u001b[Q",
"REPEAT": "\u001b[Q",
"RELEASE": null
}
},
"F3": {
"1": {
"PRESS": "\u001b[13~",
"REPEAT": "\u001b[13~",
"RELEASE": null
}
},
"F4": {
"1": {
"PRESS": "\u001b[S",
"REPEAT": "\u001b[S",
"RELEASE": null
}
},
"F5": {
"1": {
"PRESS": "\u001b[15~",
"REPEAT": "\u001b[15~",
"RELEASE": null
}
},
"F6": {
"1": {
"PRESS": "\u001b[17~",
"REPEAT": "\u001b[17~",
"RELEASE": null
}
},
"F7": {
"1": {
"PRESS": "\u001b[18~",
"REPEAT": "\u001b[18~",
"RELEASE": null
}
},
"F8": {
"1": {
"PRESS": "\u001b[19~",
"REPEAT": "\u001b[19~",
"RELEASE": null
}
},
"F9": {
"1": {
"PRESS": "\u001b[20~",
"REPEAT": "\u001b[20~",
"RELEASE": null
}
},
"F10": {
"1": {
"PRESS": "\u001b[21~",
"REPEAT": "\u001b[21~",
"RELEASE": null
}
},
"F11": {
"1": {
"PRESS": "\u001b[23~",
"REPEAT": "\u001b[23~",
"RELEASE": null
}
},
"F12": {
"1": {
"PRESS": "\u001b[24~",
"REPEAT": "\u001b[24~",
"RELEASE": null
}
},
"Insert": {
"1": {
"PRESS": "\u001b[2~",
"REPEAT": "\u001b[2~",
"RELEASE": null
}
},
"Delete": {
"1": {
"PRESS": "\u001b[3~",
"REPEAT": "\u001b[3~",
"RELEASE": null
}
},
"PageUp": {
"1": {
"PRESS": "\u001b[5~",
"REPEAT": "\u001b[5~",
"RELEASE": null
}
},
"PageDown": {
"1": {
"PRESS": "\u001b[6~",
"REPEAT": "\u001b[6~",
"RELEASE": null
}
},
"Home": {
"1": {
"PRESS": "\u001b[H",
"REPEAT": "\u001b[H",
"RELEASE": null
}
},
"End": {
"1": {
"PRESS": "\u001b[F",
"REPEAT": "\u001b[F",
"RELEASE": null
}
},
"Dead (Backquote)": {
"1": {
"PRESS": "^",
"REPEAT": "^",
"RELEASE": null
}
},
"1 (Digit1)": {
"1": {
"PRESS": "1",
"REPEAT": "1",
"RELEASE": null
}
},
"2 (Digit2)": {
"1": {
"PRESS": "2",
"REPEAT": "2",
"RELEASE": null
}
},
"3 (Digit3)": {
"1": {
"PRESS": "3",
"REPEAT": "3",
"RELEASE": null
}
},
"4 (Digit4)": {
"1": {
"PRESS": "4",
"REPEAT": "4",
"RELEASE": null
}
},
"5 (Digit5)": {
"1": {
"PRESS": "5",
"REPEAT": "5",
"RELEASE": null
}
},
"6 (Digit6)": {
"1": {
"PRESS": "6",
"REPEAT": "6",
"RELEASE": null
}
},
"7 (Digit7)": {
"1": {
"PRESS": "7",
"REPEAT": "7",
"RELEASE": null
}
},
"8 (Digit8)": {
"1": {
"PRESS": "8",
"REPEAT": "8",
"RELEASE": null
}
},
"9 (Digit9)": {
"1": {
"PRESS": "9",
"REPEAT": "9",
"RELEASE": null
}
},
"0 (Digit0)": {
"1": {
"PRESS": "0",
"REPEAT": "0",
"RELEASE": null
}
},
"\u00df (Minus)": {
"1": {
"PRESS": "\u00df",
"REPEAT": "\u00df",
"RELEASE": null
}
},
"Dead (Equal)": {
"1": {
"PRESS": "\u00b4",
"REPEAT": "\u00b4",
"RELEASE": null
}
},
"Backspace": {
"1": {
"PRESS": "\u007f",
"REPEAT": "\u007f",
"RELEASE": null
}
},
"Tab": {
"1": {
"PRESS": "\t",
"REPEAT": "\t",
"RELEASE": null
}
},
"q (KeyQ)": {
"1": {
"PRESS": "q",
"REPEAT": "q",
"RELEASE": null
}
},
"w (KeyW)": {
"1": {
"PRESS": "w",
"REPEAT": "w",
"RELEASE": null
}
},
"e (KeyE)": {
"1": {
"PRESS": "e",
"REPEAT": "e",
"RELEASE": null
}
},
"r (KeyR)": {
"1": {
"PRESS": "r",
"REPEAT": "r",
"RELEASE": null
}
},
"t (KeyT)": {
"1": {
"PRESS": "t",
"REPEAT": "t",
"RELEASE": null
}
},
"z (KeyY)": {
"1": {
"PRESS": "z",
"REPEAT": "z",
"RELEASE": null
}
},
"u (KeyU)": {
"1": {
"PRESS": "u",
"REPEAT": "u",
"RELEASE": null
}
},
"i (KeyI)": {
"1": {
"PRESS": "i",
"REPEAT": "i",
"RELEASE": null
}
},
"o (KeyO)": {
"1": {
"PRESS": "o",
"REPEAT": "o",
"RELEASE": null
}
},
"p (KeyP)": {
"1": {
"PRESS": "p",
"REPEAT": "p",
"RELEASE": null
}
},
"\u00fc (BracketLeft)": {
"1": {
"PRESS": "\u00fc",
"REPEAT": "\u00fc",
"RELEASE": null
}
},
"+ (BracketRight)": {
"1": {
"PRESS": "+",
"REPEAT": "+",
"RELEASE": null
}
},
"Enter": {
"1": {
"PRESS": "\r",
"REPEAT": "\r",
"RELEASE": null
}
},
"a (KeyA)": {
"1": {
"PRESS": "a",
"REPEAT": "a",
"RELEASE": null
}
},
"s (KeyS)": {
"1": {
"PRESS": "s",
"REPEAT": "s",
"RELEASE": null
}
},
"d (KeyD)": {
"1": {
"PRESS": "d",
"REPEAT": "d",
"RELEASE": null
}
},
"f (KeyF)": {
"1": {
"PRESS": "f",
"REPEAT": "f",
"RELEASE": null
}
},
"g (KeyG)": {
"1": {
"PRESS": "g",
"REPEAT": "g",
"RELEASE": null
}
},
"h (KeyH)": {
"1": {
"PRESS": "h",
"REPEAT": "h",
"RELEASE": null
}
},
"j (KeyJ)": {
"1": {
"PRESS": "j",
"REPEAT": "j",
"RELEASE": null
}
},
"k (KeyK)": {
"1": {
"PRESS": "k",
"REPEAT": "k",
"RELEASE": null
}
},
"l (KeyL)": {
"1": {
"PRESS": "l",
"REPEAT": "l",
"RELEASE": null
}
},
"\u00f6 (Semicolon)": {
"1": {
"PRESS": "\u00f6",
"REPEAT": "\u00f6",
"RELEASE": null
}
},
"\u00e4 (Quote)": {
"1": {
"PRESS": "\u00e4",
"REPEAT": "\u00e4",
"RELEASE": null
}
},
"# (Backslash)": {
"1": {
"PRESS": "#",
"REPEAT": "#",
"RELEASE": null
}
},
"< (IntlBackslash)": {
"1": {
"PRESS": "<",
"REPEAT": "<",
"RELEASE": null
}
},
"y (KeyZ)": {
"1": {
"PRESS": "y",
"REPEAT": "y",
"RELEASE": null
}
},
"x (KeyX)": {
"1": {
"PRESS": "x",
"REPEAT": "x",
"RELEASE": null
}
},
"c (KeyC)": {
"1": {
"PRESS": "c",
"REPEAT": "c",
"RELEASE": null
}
},
"v (KeyV)": {
"1": {
"PRESS": "v",
"REPEAT": "v",
"RELEASE": null
}
},
"b (KeyB)": {
"1": {
"PRESS": "b",
"REPEAT": "b",
"RELEASE": null
}
},
"n (KeyN)": {
"1": {
"PRESS": "n",
"REPEAT": "n",
"RELEASE": null
}
},
"m (KeyM)": {
"1": {
"PRESS": "m",
"REPEAT": "m",
"RELEASE": null
}
},
", (Comma)": {
"1": {
"PRESS": ",",
"REPEAT": ",",
"RELEASE": null
}
},
". (Period)": {
"1": {
"PRESS": ".",
"REPEAT": ".",
"RELEASE": null
}
},
"- (Slash)": {
"1": {
"PRESS": "-",
"REPEAT": "-",
"RELEASE": null
}
},
"ArrowLeft": {
"1": {
"PRESS": "\u001b[D",
"REPEAT": "\u001b[D",
"RELEASE": null
}
},
"ArrowUp": {
"1": {
"PRESS": "\u001b[A",
"REPEAT": "\u001b[A",
"RELEASE": null
}
},
"ArrowRight": {
"1": {
"PRESS": "\u001b[C",
"REPEAT": "\u001b[C",
"RELEASE": null
}
},
"ArrowDown": {
"1": {
"PRESS": "\u001b[B",
"REPEAT": "\u001b[B",
"RELEASE": null
}
},
"/ (NumpadDivide)": {
"1": {
"PRESS": "/",
"REPEAT": "/",
"RELEASE": null
}
},
"* (NumpadMultiply)": {
"1": {
"PRESS": "*",
"REPEAT": "*",
"RELEASE": null
}
},
"- (NumpadSubtract)": {
"1": {
"PRESS": "-",
"REPEAT": "-",
"RELEASE": null
}
},
"Home (Numpad7)": {
"1": {
"PRESS": "\u001b[57423u",
"REPEAT": "\u001b[57423u",
"RELEASE": null
}
},
"ArrowUp (Numpad8)": {
"1": {
"PRESS": "\u001b[57419u",
"REPEAT": "\u001b[57419u",
"RELEASE": null
}
},
"PageUp (Numpad9)": {
"1": {
"PRESS": "\u001b[57421u",
"REPEAT": "\u001b[57421u",
"RELEASE": null
}
},
"+ (NumpadAdd)": {
"1": {
"PRESS": "+",
"REPEAT": "+",
"RELEASE": null
}
},
"ArrowLeft (Numpad4)": {
"1": {
"PRESS": "\u001b[57417u",
"REPEAT": "\u001b[57417u",
"RELEASE": null
}
},
"Clear (Numpad5)": {
"1": {
"PRESS": "\u001b[E",
"REPEAT": "\u001b[E",
"RELEASE": null
}
},
"ArrowRight (Numpad6)": {
"1": {
"PRESS": "\u001b[57418u",
"REPEAT": "\u001b[57418u",
"RELEASE": null
}
},
"End (Numpad1)": {
"1": {
"PRESS": "\u001b[57424u",
"REPEAT": "\u001b[57424u",
"RELEASE": null
}
},
"ArrowDown (Numpad2)": {
"1": {
"PRESS": "\u001b[57420u",
"REPEAT": "\u001b[57420u",
"RELEASE": null
}
},
"PageDown (Numpad3)": {
"1": {
"PRESS": "\u001b[57422u",
"REPEAT": "\u001b[57422u",
"RELEASE": null
}
},
"Enter (NumpadEnter)": {
"1": {
"PRESS": "\u001b[57414u",
"REPEAT": "\u001b[57414u",
"RELEASE": null
}
},
"Insert (Numpad0)": {
"1": {
"PRESS": "\u001b[57425u",
"REPEAT": "\u001b[57425u",
"RELEASE": null
}
},
"\u0000 (NumpadDecimal)": {
"1": {
"PRESS": "\u001b[57426u",
"REPEAT": "\u001b[57426u",
"RELEASE": null
}
},
"Numlock": {
"1": {
"PRESS": null,
"REPEAT": null,
"RELEASE": null
}
},
"CapsLock": {
"1": {
"PRESS": null,
"REPEAT": null,
"RELEASE": null
}
},
"Shift (ShiftLeft)": {
"1": {
"PRESS": null,
"REPEAT": null,
"RELEASE": null
}
},
"Shift (ShiftRight)": {
"1": {
"PRESS": null,
"REPEAT": null,
"RELEASE": null
}
},
"Control (ControlLeft)": {
"1": {
"PRESS": null,
"REPEAT": null,
"RELEASE": null
}
},
"Meta (MetaLeft)": {
"1": {
"PRESS": null,
"REPEAT": null,
"RELEASE": null
}
},
"Alt (AltLeft)": {
"1": {
"PRESS": null,
"REPEAT": null,
"RELEASE": null
}
},
" (Space)": {
"1": {
"PRESS": " ",
"REPEAT": " ",
"RELEASE": null
}
},
"AltGraph (AltRight)": {
"1": {
"PRESS": null,
"REPEAT": null,
"RELEASE": null
}
},
"Control (ControlRight)": {
"1": {
"PRESS": null,
"REPEAT": null,
"RELEASE": null
}
}
}
@@ -1,331 +0,0 @@
{
"Escape +Shift": {
"1": {
"PRESS": "\u001b[27;2u",
"REPEAT": "\u001b[27;2u",
"RELEASE": null
}
},
"F1 +Shift": {
"1": {
"PRESS": "\u001b[1;2P",
"REPEAT": "\u001b[1;2P",
"RELEASE": null
}
},
"Delete +Shift": {
"1": {
"PRESS": "\u001b[3;2~",
"REPEAT": "\u001b[3;2~",
"RELEASE": null
}
},
"PageUp +Shift": {
"1": {
"PRESS": "\u001b[5;2~",
"REPEAT": "\u001b[5;2~",
"RELEASE": null
}
},
"Dead (Backquote) +Shift": {
"1": {
"PRESS": "\u00b0",
"REPEAT": "\u00b0",
"RELEASE": null
}
},
"! (Digit1) +Shift": {
"1": {
"PRESS": "!",
"REPEAT": "!",
"RELEASE": null
}
},
"\" (Digit2) +Shift": {
"1": {
"PRESS": "\"",
"REPEAT": "\"",
"RELEASE": null
}
},
"\u00a7 (Digit3) +Shift": {
"1": {
"PRESS": "\u00a7",
"REPEAT": "\u00a7",
"RELEASE": null
}
},
"$ (Digit4) +Shifft": {
"1": {
"PRESS": "$",
"REPEAT": "$",
"RELEASE": null
}
},
"% (Digit5) +Shift": {
"1": {
"PRESS": "%",
"REPEAT": "%",
"RELEASE": null
}
},
"& (Digit6) +Shift": {
"1": {
"PRESS": "&",
"REPEAT": "&",
"RELEASE": null
}
},
"/ (Digit7) +Shift": {
"1": {
"PRESS": "/",
"REPEAT": "/",
"RELEASE": null
}
},
"( (Digit8) +Shift": {
"1": {
"PRESS": "(",
"REPEAT": "(",
"RELEASE": null
}
},
") (Digit9) +Shift": {
"1": {
"PRESS": ")",
"REPEAT": ")",
"RELEASE": null
}
},
"= (Digit0) +Shift": {
"1": {
"PRESS": "=",
"REPEAT": "=",
"RELEASE": null
}
},
"? (Minus) +Shift": {
"1": {
"PRESS": "?",
"REPEAT": "?",
"RELEASE": null
}
},
"Backspace +Shift": {
"1": {
"PRESS": "\u001b[127;2u",
"REPEAT": "\u001b[127;2u",
"RELEASE": null
}
},
"Tab +Shift": {
"1": {
"PRESS": "\u001b[9;2u",
"REPEAT": "\u001b[9;2u",
"RELEASE": null
}
},
"Q (KeyQ) +Shift": {
"1": {
"PRESS": "Q",
"REPEAT": "Q",
"RELEASE": null
}
},
"\u00dc (BracketLeft) +Shift": {
"1": {
"PRESS": "\u00dc",
"REPEAT": "\u00dc",
"RELEASE": null
}
},
"* (BracketRight) +Shift": {
"1": {
"PRESS": "*",
"REPEAT": "*",
"RELEASE": null
}
},
"Enter +Shift": {
"1": {
"PRESS": "\u001b[13;2u",
"REPEAT": "\u001b[13;2u",
"RELEASE": null
}
},
"\u00d6 (Semicolon) +Shift": {
"1": {
"PRESS": "\u00d6",
"REPEAT": "\u00d6",
"RELEASE": null
}
},
"\u00c4 (Quote) +Shift": {
"1": {
"PRESS": "\u00c4",
"REPEAT": "\u00c4",
"RELEASE": null
}
},
"' (Backslash) +Shift": {
"1": {
"PRESS": "'",
"REPEAT": "'",
"RELEASE": null
}
},
"> (IntlBackslash) +Shift": {
"1": {
"PRESS": ">",
"REPEAT": ">",
"RELEASE": null
}
},
"; (Comma) +Shift": {
"1": {
"PRESS": ";",
"REPEAT": ";",
"RELEASE": null
}
},
": (Period) +Shift": {
"1": {
"PRESS": ":",
"REPEAT": ":",
"RELEASE": null
}
},
"_ (Slash) +Shift": {
"1": {
"PRESS": "_",
"REPEAT": "_",
"RELEASE": null
}
},
"ArrowUp +Shift": {
"1": {
"PRESS": "\u001b[1;2A",
"REPEAT": "\u001b[1;2A",
"RELEASE": null
}
},
"PageDown +Shift": {
"1": {
"PRESS": "\u001b[6;2~",
"REPEAT": "\u001b[6;2~",
"RELEASE": null
}
},
"Home +Shift": {
"1": {
"PRESS": "\u001b[1;2H",
"REPEAT": "\u001b[1;2H",
"RELEASE": null
}
},
"Home (Numpad7) +Shift": {
"1": {
"PRESS": "\u001b[57423;2u",
"REPEAT": "\u001b[57423;2u",
"RELEASE": null
}
},
"ArrowUp (Numapd8) +Shift": {
"1": {
"PRESS": "\u001b[57419;2u",
"REPEAT": "\u001b[57419;2u",
"RELEASE": null
}
},
"Clear (Numpad5) +Shift": {
"1": {
"PRESS": "\u001b[1;2E",
"REPEAT": "\u001b[1;2E",
"RELEASE": null
}
},
"Enter (NumpadEnter) +Shift": {
"1": {
"PRESS": "\u001b[57414;2u",
"REPEAT": "\u001b[57414;2u",
"RELEASE": null
}
},
"Insert (Numpad0) +Shift": {
"1": {
"PRESS": "\u001b[57425;2u",
"REPEAT": "\u001b[57425;2u",
"RELEASE": null
}
},
"\u0000 (NumpadDecimal) +Shift": {
"1": {
"PRESS": "\u001b[57426;2u",
"REPEAT": "\u001b[57426;2u",
"RELEASE": null
}
},
"Numlock +Shift": {
"1": {
"PRESS": null,
"REPEAT": null,
"RELEASE": null
}
},
"CapsLock +Shift": {
"1": {
"PRESS": null,
"REPEAT": null,
"RELEASE": null
}
},
"Shift (ShiftRight) +Shift": {
"1": {
"PRESS": null,
"REPEAT": null,
"RELEASE": null
}
},
"Control (ControlLeft) +Shift": {
"1": {
"PRESS": null,
"REPEAT": null,
"RELEASE": null
}
},
"Meta (MetaLeft) +Shift": {
"1": {
"PRESS": null,
"REPEAT": null,
"RELEASE": null
}
},
"Alt (AltLeft) +Shift": {
"1": {
"PRESS": null,
"REPEAT": null,
"RELEASE": null
}
},
" (Space) +Shift": {
"1": {
"PRESS": " ",
"REPEAT": " ",
"RELEASE": null
}
},
"AltGraph (AltRight) +Shift": {
"1": {
"PRESS": null,
"REPEAT": null,
"RELEASE": null
}
},
"Control (ControlRight) +Shift": {
"1": {
"PRESS": null,
"REPEAT": null,
"RELEASE": null
}
}
}
+5 -7
View File
@@ -3,15 +3,13 @@
"description": "A headless terminal component that runs in Node.js",
"version": "6.0.0",
"main": "lib-headless/xterm-headless.js",
"module": "lib-headless/xterm-headless.mjs",
"module": "lib/xterm.mjs",
"types": "typings/xterm-headless.d.ts",
"exports": {
"types": "./typings/xterm-headless.d.ts",
"import": "./lib-headless/xterm-headless.mjs",
"require": "./lib-headless/xterm-headless.js"
},
"repository": "https://github.com/xtermjs/xterm.js",
"license": "MIT",
"workspaces": [
"addons/*"
],
"keywords": [
"cli",
"command-line",
@@ -27,4 +25,4 @@
"webgl",
"xterm"
]
}
}
+39
View File
@@ -0,0 +1,39 @@
/**
* Smoke test: @xterm/headless loads and runs in Node.js (require).
*/
const { strictEqual } = require('node:assert');
const { Terminal } = require('./lib-headless/xterm-headless.js');
function writeSync(term, data) {
return new Promise(resolve => term.write(data, resolve));
}
function lineEquals(term, index, text) {
strictEqual(term.buffer.active.getLine(index).translateToString(true), text);
}
(async () => {
const term = new Terminal({ cols: 80, rows: 24, allowProposedApi: true });
strictEqual(term.cols, 80);
strictEqual(term.rows, 24);
await writeSync(term, 'hello from node');
lineEquals(term, 0, 'hello from node');
await writeSync(term, 'foo');
await writeSync(term, 'bar');
await writeSync(term, '文');
lineEquals(term, 0, 'hello from nodefoobar文');
term.resize(60, 15);
strictEqual(term.cols, 60);
strictEqual(term.rows, 15);
term.dispose();
strictEqual(term._core._store.isDisposed, true);
console.log('headless package (CJS): load, write, buffer, resize, dispose OK');
})().catch(err => {
console.error(err);
process.exit(1);
});
+29
View File
@@ -0,0 +1,29 @@
/**
* Smoke test: @xterm/headless loads and runs in Node.js (ESM + CJS).
*/
import { createRequire } from 'node:module';
import { strictEqual } from 'node:assert';
const require = createRequire(import.meta.url);
function writeSync(term, data) {
return new Promise(resolve => term.write(data, resolve));
}
function lineEquals(term, index, text) {
strictEqual(term.buffer.active.getLine(index).translateToString(true), text);
}
const { Terminal: TerminalEsm } = await import('./lib-headless/xterm-headless.mjs');
const termEsm = new TerminalEsm({ cols: 80, rows: 24 });
await writeSync(termEsm, 'esm ok');
lineEquals(termEsm, 0, 'esm ok');
termEsm.dispose();
const { Terminal: TerminalCjs } = require('./lib-headless/xterm-headless.js');
const termCjs = new TerminalCjs({ cols: 40, rows: 10 });
await writeSync(termCjs, 'cjs ok');
lineEquals(termCjs, 0, 'cjs ok');
termCjs.dispose();
console.log('headless package: ESM and CJS load, write, and buffer read OK');
+9 -706
View File
File diff suppressed because it is too large Load Diff

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