Compare commits

...
Author SHA1 Message Date
Daniel ImmsandCursor fd6a339bcb fix(browser): place imports before module const in TimeBasedDebouncer
RENDER_DEBOUNCE_THRESHOLD_MS was declared above the import, which
breaks ES module conventions and can confuse tooling. Move the import
to the top of the module after the license header.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-01 15:28:12 +00:00
Daniel ImmsandCursor a2552053f5 fix(browser): clear idle blink timer when handle is 0
_clearIdleTimer() used truthiness on _idleTimeout, so a setTimeout
handle of 0 would not be cleared. Compare against undefined, consistent
with RenderDebouncer rAF handling.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-01 15:28:12 +00:00
Daniel ImmsandCursor f3783729aa fix(browser): treat rAF id 0 as scheduled in RenderDebouncer
dispose(), addRefreshCallback(), and refresh() used truthiness on
_animationFrame, so a requestAnimationFrame handle of 0 would not be
cancelled or deduplicated. Compare against undefined instead, matching
other timer checks in the browser layer.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-01 15:28:12 +00:00
Daniel ImmsandCursor a97b77af3f fix(browser): cancel trailing debounce timeout on immediate refresh
TimeBasedDebouncer can schedule a trailing setTimeout when refresh
requests are throttled. If the debounce window later elapses and an
immediate refresh runs, that pending timeout was left armed and could
fire afterward, causing a duplicate _innerRefresh (e.g. extra live
region updates for screen readers).

Clear any pending timeout and reset _additionalRefreshRequested before
running the immediate refresh path.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-01 15:28:11 +00:00
Daniel ImmsandCursor 275d6d8a80 fix(browser): reset TimeBasedDebouncer state on dispose
dispose() cleared the pending timeout but left _refreshTimeoutID and
_additionalRefreshRequested set. After dispose, a stale timeout id or
trailing-refresh flag could confuse a new instance or tests. Match
RenderDebouncer by clearing the timeout id and reset the trailing flag.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-01 15:27:58 +00:00
Daniel ImmsandCursor 8679eba407 refactor(browser): remove unreachable coords guard in getCoords
getCoordsRelativeToElement always returns a two-element tuple, so the
array is always truthy and if (!coords) could never run. hasValidCharSize
already handles the only early-exit path before coordinates are computed.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-01 15:27:58 +00:00
Daniel ImmsandCursor 3960db39a9 fix(browser): parenthesize document fallback for CoreBrowserService
Nullish coalescing binds tighter than the conditional operator, so the
previous expression was parsed as (this._document ?? windowDefined) ?
window.document : null. When this._document was set (e.g. parent
ownerDocument or documentOverride), the ternary still returned
window.document instead of the configured document.

CoreBrowserService.mainDocument is used by AccessibilityManager, viewport
styles, and decoration renderers; CharSizeService already received
this._document directly. Add parentheses so the fallback applies only when
_document is nullish.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-01 15:27:58 +00:00
Daniel ImmsandCursor ff8a0d9c99 fix(browser): use startY in horizontalDirection row helper call
horizontalDirection called moveToRequestedRow with targetX as the first
argument, but that parameter is the starting row (startY). resetStartingRow
and moveToRequestedCol already pass startY correctly; only this copy-paste
mistake passed a column index where a row was expected.

That could miscompute wrapped-row offsets and emit the wrong horizontal
arrow key sequence when building selection/cursor movement strings.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-01 15:27:58 +00:00
Daniel Imms 6fd6003d6c Merge pull request #5971 from xtermjs/cursor/parseint-radix-10-89d6
fix: pass radix 10 to parseInt across core and addons
2026-06-01 08:12:17 -07:00
Daniel ImmsandCursor d407bf561f fix(webgl): pass radix 10 for SVG arc flag parsing
CustomGlyphRasterizer parsed large-arc and sweep flags with parseInt
without a radix when drawing SVG arc paths. Use base 10 for 0/1 flags.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-01 15:05:58 +00:00
Daniel ImmsandCursor a1a8d1abae fix(image): pass radix 10 when parsing kitty graphics numeric keys
KittyGraphicsTypes parsed numeric control values with parseInt
without a radix. Use base 10 explicitly for format, id, dimensions,
and offset fields.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-01 15:05:58 +00:00
Daniel ImmsandCursor f34d0a255c fix(image): pass radix 10 in IIPHandler dimension parsing
_dim() parsed percent, pixel, and cell dimension strings with
parseInt without an explicit radix. Use base 10 for all three paths,
consistent with other numeric parsing fixes in the repo.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-01 15:05:58 +00:00
Daniel ImmsandCursor a2cd1ab29c fix(fit): parse terminal padding with radix 10 and zero fallback
proposeDimensions summed padding-top/bottom/left/right via bare
parseInt, so non-numeric padding values produced NaN and broke
available width/height. Use radix 10 and || 0 on each side, consistent
with parent element size parsing.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-01 15:05:58 +00:00
Daniel ImmsandCursor 99550840d2 fix(fit): add radix and zero fallback for parent width parsing
Align parent width parsing with the height fix: parseInt with radix
10 and || 0 inside Math.max so non-numeric CSS width does not yield
NaN cols in proposeDimensions.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-01 15:05:57 +00:00
Daniel ImmsandCursor b0f6aed4a1 fix(common): pass radix 10 when parsing OSC color indices
InputHandler parsed palette indices from OSC color sequences with
parseInt without a radix after validating digits-only slots. Use
base 10 for set/report and restore color index parsing.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-01 15:05:43 +00:00
Daniel ImmsandCursor 878e3fc52d fix(browser): pass radix 10 when parsing Chrome major version
Wheel event DPR factoring parses Chrome/(\d+) with parseInt without a
radix. Add base 10 explicitly, matching getSafariVersion and other
version parsing in the codebase.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-01 15:05:43 +00:00
Daniel ImmsandCursor f04679a42e fix(common): pass radix 10 when parsing rgb() components in Color
rgba/rgb CSS parsing used parseInt without a radix on captured digit
groups. Add base 10 explicitly, matching getSafariVersion and other
call sites in the codebase.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-01 15:05:43 +00:00
Daniel ImmsandCursor 6ebcf79358 fix(common): pass radix 10 to parseInt in getSafariVersion
Safari major version parsing used parseInt without an explicit radix.
Other call sites in the repo use base 10; add the radix for consistency
and to avoid octal interpretation of leading-zero strings.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-01 15:05:43 +00:00
Daniel ImmsandCursor d4acf2fa45 fix(browser): use radix 10 for padding parseInt in Mouse coords
parseInt without a radix can mis-parse leading-zero padding values as
octal. Match AccessibilityManager and document rowCount JSDoc typo.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-01 15:05:43 +00:00
Daniel Imms ddf2b7d091 Merge pull request #5969 from Tyriar/import-no-cycle-5897-2
Add oxlint import/no-cycle and break existing src/common cycles
2026-05-31 15:13:58 -07:00
Daniel ImmsandCursor 5684f95c8e Colocate circular list types in CircularList.ts
The import/no-cycle refactor had moved IInsertEvent, IDeleteEvent, and
ICircularList into Types.ts, with CircularList.ts re-exporting the event
types from there. That split the list abstraction from its implementation
and pulled Types into the circular-list dependency graph.

Define IInsertEvent, IDeleteEvent, and ICircularList in CircularList.ts
alongside the CircularList class. CircularList.ts now depends only on
Lifecycle and Event, not Types.ts.

Remove those definitions from Types.ts (and the unused Emitter import).

Update callers to import list types from CircularList:
- buffer/Types.ts (IBuffer.lines)
- DecorationService.ts (buffer line insert/delete handlers)
- browser TestUtils.test.ts (mock buffer lines)

Types.ts no longer imports or re-exports circular-list types, preserving
the acyclic Types -> buffer/CircularList boundary.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-31 14:37:01 -07:00
Daniel ImmsandCursor cd3dcc52b2 Colocate buffer types in buffer/Types.ts
The import/no-cycle refactor had moved IBuffer, IBufferSet, and related
cell/line types into src/common/Types.ts, with buffer/Types.ts only
re-exporting them. That inverted the natural module boundary and made
buffer code depend on the top-level types hub.

Define buffer-owned types in buffer/Types.ts instead:
- CharData, IExtendedAttrs, IAttributeData, ICellData, IBufferLine
- IMarker, IBuffer, IBufferSet, BufferIndex

Restore UnderlineStyle to buffer/Constants.ts (used by IExtendedAttrs)
and remove those definitions from Types.ts.

buffer/Types.ts imports only what it needs from outside the buffer layer:
ICircularList and ICharset from Types, IDisposable from Lifecycle,
IEvent from Event, and UnderlineStyle from Constants. Types.ts does not
import buffer/Types.ts, so the previous Types <-> buffer cycle stays broken.

Update imports across common, browser, headless, addons, and tests to use
common/buffer/Types for buffer types rather than common/Types.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-31 14:35:08 -07:00
Daniel Imms 936442c813 Merge pull request #5964 from PerBothner/decoration-test
Avoid "fake markers" in DecorationService.test.ts.
2026-05-31 11:57:36 -07:00
Daniel Imms dfb65ca66f Add mise.toml to .gitignore (#5968)
<!-- CURSOR_AGENT_PR_BODY_BEGIN -->
Ignores local `mise.toml` configuration at the repository root so it is not committed.
<!-- CURSOR_AGENT_PR_BODY_END -->

<div><a href="https://cursor.com/agents/bc-02c1c884-aae2-4267-b29b-fe7b93574ebe"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/open-in-web-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/open-in-web-light.png"><img alt="Open in Web" width="114" height="28" src="https://cursor.com/assets/images/open-in-web-dark.png"></picture></a>&nbsp;<a href="https://cursor.com/background-agent?bcId=bc-02c1c884-aae2-4267-b29b-fe7b93574ebe"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/open-in-cursor-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/open-in-cursor-light.png"><img alt="Open in Cursor" width="131" height="28" src="https://cursor.com/assets/images/open-in-cursor-dark.png"></picture></a>&nbsp;</div>
2026-05-31 10:52:07 -07:00
Per Bothner 35c91cbf2d Merge branch 'master' into decoration-test 2026-05-31 10:13:17 -07:00
Cursor AgentandDaniel Imms 587856e2ca Add mise.toml to .gitignore
Co-authored-by: Daniel Imms <Tyriar@users.noreply.github.com>
2026-05-31 17:08:48 +00:00
Daniel ImmsandCursor 56a7195487 Add oxlint import/no-cycle and break existing src/common cycles
Enable oxlint's import/no-cycle rule (import plugin) so circular module
dependencies are caught in CI, similar to eslint-plugin-import/no-cycle
and biome no-import-cycles discussed in the issue.

Relocate shared types and dependencies to remove cycles under src/common:

- Types.ts: host IInsertEvent, IDeleteEvent, IParams, UnderlineStyle,
  IBuffer, and IBufferSet; drop imports from CircularList, buffer/Types,
  parser/Types, and Services
- CoreTerminal.ts: define and export ICoreTerminal
- ServiceRegistry.ts: define IServiceIdentifier (break Services registry loop)
- UnicodeService: stop constructing UnicodeV6; CoreTerminal registers the
  default v6 provider after service creation
- buffer/Types.ts and parser/Types.ts: re-export moved types for callers

Fixes https://github.com/xtermjs/xterm.js/issues/5897

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-31 10:08:04 -07:00
Daniel Imms 4aa04c6b3f Merge pull request #5967 from Tyriar/migrate-eslint-to-oxlint-5690
Migrate linting from ESLint to Oxlint (Fixes #5690)
2026-05-31 09:59:34 -07:00
Per Bothner 28eadddb79 Merge branch 'master' into decoration-test 2026-05-31 09:49:20 -07:00
Daniel ImmsandCursor 59d1ec7bf0 Migrate linting from ESLint to Oxlint (Fixes #5690)
Replace the primary lint pipeline with oxlint and oxlint-tsgolint for type-aware TypeScript rules, preserving existing rule behavior from eslint.config.mjs via @oxlint/migrate and targeted jsPlugins (@stylistic/eslint-plugin, eslint-plugin-jsdoc as jsdoc-js, oxlint-plugin-eslint for no-restricted-syntax and object-curly-spacing).

Keep a hybrid ESLint pass (eslint.config.naming.mjs) only for @typescript-eslint/naming-convention until tsgolint implements it (oxc-project/tsgolint#186). Legacy eslint.config.mjs and eslint.config.typings.mjs remain for reference but are no longer used by npm scripts.

Add .oxlintrc.json for src/addons/demo/test and .oxlintrc.typings.json for typings/ (syntax-only, no --type-aware). Update lint, lint-fix, lint-api, and lint-changes to run oxlint first, then naming ESLint. Platform binaries install via oxlint-tsgolint optionalDependencies on Linux, macOS, and Windows.

Fixes https://github.com/xtermjs/xterm.js/issues/5690

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-31 09:48:32 -07:00
Per Bothner 7c82c1a815 Update more DecorationService tests to use existing bufferService. 2026-05-31 09:48:19 -07:00
Daniel Imms 04a0b7347c Merge pull request #5966 from Tyriar/remove-path-mappings-5896
Remove TypeScript path mappings in favor of relative imports
2026-05-31 09:31:57 -07:00
Daniel ImmsandCursor 5d8da19c44 Keep path mappings for addons after core migration
Follow-up to https://github.com/xtermjs/xterm.js/issues/5896 / PR #5966.

Converting addons to ../../../src/common relative imports broke unit tests:
esbuild emits addon code under addons/*/out-esbuild/ without mirroring the
shared tree, so Node could not load TypeScript sources at runtime
(Cannot find module '../../../src/common/Lifecycle').

Core src/, test/, and headless continue to use relative imports with no
common/* path mappings. Addons keep the prior model until build layout
can share out-esbuild the same way as the main package.

Changes:
- Restore common/* and browser/* paths in addon src/test/benchmark tsconfigs
- Revert addon imports to common/ and browser/ path-mapped specifiers
- Restore NODE_PATH=out-esbuild in bin/test_unit.js for Mocha resolution
- Restore webpack resolve.alias on addons that bundle against out/

Fixes CI test-unit failures on remove-path-mappings-5896.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-31 09:25:12 -07:00
Daniel ImmsandCursor 17ad33dc3d Remove TypeScript path mappings in favor of relative imports
Fixes https://github.com/xtermjs/xterm.js/issues/5896

TypeScript path mappings for common/*, browser/*, and headless/*
were not rewritten in emitted JavaScript, which forced webpack resolve
aliases and NODE_PATH workarounds so runtime could resolve modules.
That added build complexity and broke tooling such as ESLint import/no-cycle.

Changes:
- Convert ~750 imports across src/, addons/, and test/ to relative paths
- Remove common/browser/headless paths and src catch-all mappings from tsconfigs
  (keep @xterm/addon-* demo and addon typings paths)
- Remove webpack resolve.alias for common, browser, headless, and vs
- Drop NODE_PATH from unit tests, benchmarks, and VS Code launch config
- Fix addon-image IIPImageStorage to import ./Types after removing catch-all *

Verified: npm run build, esbuild, package, package-headless
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-31 09:16:10 -07:00
Per Bothner f76c7aa8ea Avoid "fake markers" in DecorationService.test.ts.
Instead we use an actual Marker in an actual Buffer.
2026-05-30 14:07:03 -07:00
Daniel Imms 43e8365f0e Merge pull request #5960 from xtermjs/cursor/move-fixtures-1191
Move fixtures to test/fixtures
2026-05-28 07:06:21 -07:00
Cursor AgentandDaniel Imms c7a94fc0bc Move fixtures to test/fixtures and update references
Co-authored-by: Daniel Imms <Tyriar@users.noreply.github.com>
2026-05-28 14:01:12 +00:00
Daniel Imms 4bb9c06b5f Merge pull request #5953 from xtermjs/cursor/alt-click-for-mouse-events-5b17
Add altClickForMouseEvents option
2026-05-28 06:35:56 -07:00
Daniel Imms 00e54532b3 Merge pull request #5959 from xtermjs/cursor/setup-dev-environment-dc0c
Add Cursor Cloud specific instructions to AGENTS.md
2026-05-27 22:13:27 -07:00
Cursor AgentandDaniel Imms e96133603b Add Cursor Cloud specific instructions to AGENTS.md
Add development environment guidance for cloud agents including:
- Demo server startup notes
- Build-before-test requirements
- Note about no external services needed
- tsgo compiler note
- Lint optimization tip

Co-authored-by: Daniel Imms <Tyriar@users.noreply.github.com>
2026-05-28 05:10:34 +00:00
Daniel Imms b2003861ec Merge pull request #5859 from xtermjs/iip_enhancements
IIP enhancements
2026-05-27 20:36:13 -07:00
Daniel Imms 38f77fd4ca Merge branch 'master' into iip_enhancements 2026-05-27 20:16:36 -07:00
Daniel Imms d3352cf1d5 Merge pull request #5873 from xtermjs/package_upgrades
package upgrades & linter fixes
2026-05-27 20:16:08 -07:00
Daniel Imms 0c4ec34df8 Remove unnecessary @types/lru-cache 2026-05-27 20:11:20 -07:00
Daniel Imms 743852b60c Remove unneeded comment 2026-05-27 19:58:52 -07:00
Daniel Imms 2069176528 Fix lint 2026-05-27 19:58:21 -07:00
Daniel Imms acd2472961 Merge remote-tracking branch 'upstream/master' into package_upgrades 2026-05-27 19:23:43 -07:00
Daniel ImmsandCursor 1b95efa8c8 Add explicit void return type to alt cursor event listener.
Fixes explicit-function-return-type lint in AltMouseCursorController.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-27 16:54:23 -07:00
Daniel ImmsandCursor ce1f5f9608 Add MouseEventCssClasses const enum for enable-mouse-events class.
Centralize the CSS class name in MouseService.ts and use it across mouse mode handling and tests.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-27 16:23:17 -07:00
Daniel ImmsandCursor bf8af4a871 Toggle enable-mouse-events cursor while alt is held for mouseEventsRequireAlt.
Extract AltMouseCursorController to bind DOM listeners only when the option and mouse protocol are active.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-27 16:20:45 -07:00
Daniel ImmsandCursor f8e9a81d0e Fix mouseEventsRequireAlt by stripping alt from PTY mouse reports.
Alt is only used locally to gate passthrough; applications like tmux ignore alt-modified SGR sequences.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-27 16:11:35 -07:00
Cursor AgentandDaniel Imms 7711d780c2 Handle mouseEventsRequireAlt option changes in mouse mode
Co-authored-by: Daniel Imms <Tyriar@users.noreply.github.com>
2026-05-27 21:59:23 +00:00
Cursor AgentandDaniel Imms f8cef535c7 Rename altClickForMouseEvents to mouseEventsRequireAlt
Co-authored-by: Daniel Imms <Tyriar@users.noreply.github.com>
2026-05-27 21:41:44 +00:00
Cursor AgentandDaniel Imms 5ad56446b8 Explicitly enable selection when altClickForMouseEvents is on at attach
Co-authored-by: Daniel Imms <Tyriar@users.noreply.github.com>
2026-05-27 21:20:17 +00:00
Cursor AgentandDaniel Imms fbb2a9710f Add altClickForMouseEvents option for issue #5952
When enabled during mouse events mode, click/drag/move events require the alt
key while wheel events are unaffected. Selection stays enabled by default
and takes precedence over macOptionClickForcesSelection.

Co-authored-by: Daniel Imms <Tyriar@users.noreply.github.com>
2026-05-27 20:13:31 +00:00
Jörg Breitbart 5a6427e867 Merge branch 'master' into package_upgrades 2026-05-21 13:45:03 +02:00
Jörg Breitbart 8519ac6d99 Merge branch 'master' into package_upgrades 2026-05-21 13:34:12 +02:00
jerch ae611694de Merge branch 'master' into iip_enhancements 2026-05-21 12:47:09 +02:00
jerch 772df0cb98 Merge branch 'master' into iip_enhancements 2026-05-11 17:03:53 +02:00
jerch aa93189cc5 Merge branch 'master' into package_upgrades 2026-05-11 16:57:49 +02:00
Jörg Breitbart f82a8029d2 fix README 2026-05-10 21:27:52 +02:00
Jörg Breitbart a2f321c887 add multipart demo 2026-05-10 21:08:22 +02:00
Jörg Breitbart eb58cefa8e add integration tests for filepart 2026-05-10 20:34:43 +02:00
Jörg Breitbart d92ca87cbe add qoi metrics test 2026-05-10 19:32:47 +02:00
jerch 224167bbbe Merge branch 'master' into iip_enhancements 2026-05-09 22:59:22 +02:00
Jörg Breitbart 740fa44ae4 make webfont loading tests more resilient 2026-05-09 18:13:16 +02:00
Jörg Breitbart 636baa8d79 make webfont loading tests more resilient 2026-05-09 18:06:59 +02:00
Jörg Breitbart 44a27dc958 make webfont loading tests more resilient 2026-05-09 17:59:33 +02:00
Jörg Breitbart dc1ae4514b package upgrades & linter fixes 2026-05-09 17:42:49 +02:00
Jörg Breitbart 494cf1f2e1 make linter happy 2026-04-30 16:54:54 +02:00
Jörg Breitbart 645865efec fix cursor placement in IIP 2026-04-30 16:46:22 +02:00
Jörg Breitbart 83e3657a82 fix _mouseCoordsService access in demo 2026-04-30 15:21:41 +02:00
Jörg Breitbart 53554a05f7 use _coreBrowserService.dpr 2026-04-30 10:44:38 +02:00
Jörg Breitbart 51c8d8abf8 implement ReportCellSize 2026-04-30 10:36:41 +02:00
Jörg Breitbart 05ea5ac324 implement ReportCellSize 2026-04-30 10:33:34 +02:00
Jörg Breitbart 9e27c7debb filepart handling in IIP 2026-04-29 21:36:45 +02:00
380 changed files with 5557 additions and 3503 deletions
+1
View File
@@ -17,6 +17,7 @@ docs/
npm-debug.log
/.idea/
.env
mise.toml
build/
.DS_Store
yarn.lock
+374
View File
@@ -0,0 +1,374 @@
{
"$schema": "./node_modules/oxlint/configuration_schema.json",
"plugins": [
"import",
"typescript"
],
"categories": {
"correctness": "off"
},
"options": {
"typeAware": true,
"maxWarnings": 0
},
"env": {
"builtin": true
},
"ignorePatterns": [
"addons/*/src/third-party/*.ts",
"**/out/*",
"**/out-test/*",
"**/out-esbuild/*",
"**/out-esbuild-test/*",
"**/inwasm-sdks/*",
"**/typings/*.d.ts",
"**/node_modules",
"**/*.js",
"**/*.mjs"
],
"rules": {
"constructor-super": "error",
"for-direction": "error",
"getter-return": "error",
"no-async-promise-executor": "error",
"no-case-declarations": "error",
"no-class-assign": "error",
"no-compare-neg-zero": "error",
"no-cond-assign": "error",
"no-const-assign": "error",
"no-constant-binary-expression": "error",
"no-constant-condition": "error",
"no-control-regex": "error",
"no-debugger": "error",
"no-delete-var": "error",
"no-dupe-class-members": "error",
"no-dupe-else-if": "error",
"no-dupe-keys": "error",
"no-duplicate-case": "error",
"no-empty": "error",
"no-empty-character-class": "error",
"no-empty-pattern": "error",
"no-empty-static-block": "error",
"no-ex-assign": "error",
"no-extra-boolean-cast": "error",
"no-fallthrough": "error",
"no-func-assign": "error",
"no-global-assign": "error",
"no-import-assign": "error",
"no-invalid-regexp": "error",
"no-irregular-whitespace": "error",
"no-loss-of-precision": "error",
"no-misleading-character-class": "error",
"no-new-native-nonconstructor": "error",
"no-nonoctal-decimal-escape": "error",
"no-obj-calls": "error",
"no-prototype-builtins": "error",
"no-redeclare": "error",
"no-regex-spaces": "error",
"no-self-assign": "error",
"no-setter-return": "error",
"no-shadow-restricted-names": "error",
"no-sparse-arrays": "error",
"no-this-before-super": "error",
"no-unassigned-vars": "error",
"no-unexpected-multiline": "error",
"no-unreachable": "error",
"no-unsafe-finally": "error",
"no-unsafe-negation": "error",
"no-unsafe-optional-chaining": "error",
"no-unused-labels": "error",
"no-unused-private-class-members": "error",
"no-unused-vars": "error",
"no-useless-backreference": "error",
"no-useless-catch": "error",
"no-useless-escape": "error",
"no-with": "error",
"preserve-caught-error": "error",
"require-yield": "error",
"use-isnan": "error",
"valid-typeof": "error",
"no-array-constructor": "error",
"no-unused-expressions": "error",
"typescript/ban-ts-comment": "error",
"typescript/no-duplicate-enum-values": "error",
"typescript/no-empty-object-type": "error",
"typescript/no-explicit-any": "error",
"typescript/no-extra-non-null-assertion": "error",
"typescript/no-misused-new": "error",
"typescript/no-namespace": "error",
"typescript/no-non-null-asserted-optional-chain": "error",
"typescript/no-require-imports": "error",
"typescript/no-this-alias": "error",
"typescript/no-unnecessary-type-constraint": "error",
"typescript/no-unsafe-declaration-merging": "error",
"typescript/no-unsafe-function-type": "error",
"typescript/no-wrapper-object-types": "error",
"typescript/prefer-as-const": "error",
"typescript/prefer-namespace-keyword": "error",
"typescript/triple-slash-reference": "error",
"import/no-cycle": "error"
},
"overrides": [
{
"files": [
"**/*.ts",
"**/*.tsx",
"**/*.mts",
"**/*.cts"
],
"rules": {
"constructor-super": "off",
"getter-return": "off",
"no-class-assign": "off",
"no-const-assign": "off",
"no-dupe-class-members": "off",
"no-dupe-keys": "off",
"no-func-assign": "off",
"no-import-assign": "off",
"no-new-native-nonconstructor": "off",
"no-obj-calls": "off",
"no-redeclare": "off",
"no-setter-return": "off",
"no-this-before-super": "off",
"no-unreachable": "off",
"no-unsafe-negation": "off",
"no-var": "error",
"no-with": "off",
"prefer-const": "error",
"prefer-rest-params": "error",
"prefer-spread": "error"
}
},
{
"files": [
"**/*.ts"
],
"jsPlugins": [
"@stylistic/eslint-plugin",
{
"name": "jsdoc-js",
"specifier": "eslint-plugin-jsdoc"
},
"oxlint-plugin-eslint"
],
"rules": {
"@stylistic/comma-spacing": [
"warn",
{
"before": false,
"after": true
}
],
"@stylistic/indent": [
"warn",
2
],
"@stylistic/semi": [
"warn",
"always"
],
"@stylistic/quotes": [
"warn",
"single",
{
"allowTemplateLiterals": "always"
}
],
"@stylistic/member-delimiter-style": [
"warn",
{
"multiline": {
"delimiter": "semi",
"requireLast": true
},
"singleline": {
"delimiter": "comma",
"requireLast": false
}
}
],
"@stylistic/type-annotation-spacing": "warn",
"@stylistic/space-unary-ops": "warn",
"@stylistic/keyword-spacing": "warn",
"@stylistic/max-len": [
"warn",
{
"code": 1000,
"comments": 100,
"ignoreTrailingComments": true,
"ignoreUrls": true,
"ignorePattern": "^ *((?<vt_comment>(//|\\*) @vt)|(?<table_comment>\\* \\| )|(?<commented_code>// ))"
}
],
"@stylistic/new-parens": "warn",
"@stylistic/no-extra-semi": "error",
"@stylistic/no-trailing-spaces": "warn",
"eslint-js/object-curly-spacing": [
"warn",
"always"
],
"@stylistic/spaced-comment": [
"warn",
"always",
{
"markers": [
"/"
],
"exceptions": [
"-"
]
}
],
"jsdoc-js/check-alignment": "warn",
"jsdoc-js/check-param-names": "warn",
"jsdoc-js/multiline-blocks": "warn",
"jsdoc-js/no-multi-asterisks": "warn",
"eslint-js/one-var": [
"warn",
"never"
],
"eslint-js/no-restricted-syntax": [
"warn",
{
"selector": "CallExpression[callee.name='requestAnimationFrame']",
"message": "The global requestAnimationFrame() should be avoided, call it on the parent window from ICoreBrowserService."
},
{
"selector": "CallExpression[callee.name='cancelAnimationFrame']",
"message": "The global cancelAnimationFrame() should be avoided, call it on the parent window from ICoreBrowserService."
},
{
"selector": "CallExpression > MemberExpression[object.name='window'][property.name='requestAnimationFrame']",
"message": "window.requestAnimationFrame() should be avoided, call it on the parent window from ICoreBrowserService."
},
{
"selector": "CallExpression > MemberExpression[object.name='window'][property.name='cancelAnimationFrame']",
"message": "window.cancelAnimationFrame() should be avoided, call it on the parent window from ICoreBrowserService."
},
{
"selector": "MemberExpression[object.name='window'][property.name='devicePixelRatio']",
"message": "window.devicePixelRatio should be avoided, get it from ICoreBrowserService."
}
],
"curly": [
"warn",
"multi-line"
],
"eqeqeq": [
"warn",
"always"
],
"no-duplicate-imports": "warn",
"no-else-return": [
"warn",
{
"allowElseIf": false
}
],
"no-eval": "warn",
"no-irregular-whitespace": "warn",
"no-restricted-imports": [
"warn",
{
"patterns": [
".*\\/out\\/.*"
]
}
],
"no-unsafe-finally": "warn",
"no-unused-vars": "off",
"no-var": "warn",
"no-empty": "off",
"no-empty-pattern": "off",
"no-cond-assign": "off",
"no-case-declarations": "off",
"for-direction": "off",
"no-prototype-builtins": "off",
"no-useless-escape": "off",
"no-self-assign": "off",
"no-async-promise-executor": "off",
"prefer-rest-params": "off",
"no-control-regex": "off",
"no-fallthrough": "off",
"prefer-spread": "off",
"prefer-const": "warn",
"no-unused-expressions": "off",
"typescript/array-type": [
"warn",
{
"default": "array",
"readonly": "generic"
}
],
"typescript/consistent-type-assertions": "warn",
"typescript/consistent-type-definitions": "warn",
"typescript/explicit-function-return-type": [
"warn",
{
"allowExpressions": true
}
],
"typescript/explicit-member-accessibility": [
"warn",
{
"accessibility": "explicit",
"overrides": {
"constructors": "off"
}
}
],
"typescript/no-confusing-void-expression": [
"warn",
{
"ignoreArrowShorthand": true
}
],
"typescript/no-useless-constructor": "warn",
"typescript/prefer-nullish-coalescing": [
"warn",
{
"ignorePrimitives": true
}
],
"typescript/prefer-namespace-keyword": "warn",
"typescript/no-unused-vars": [
"warn",
{
"vars": "all",
"args": "none"
}
],
"typescript/no-require-imports": "off",
"typescript/no-explicit-any": "off",
"typescript/no-wrapper-object-types": "off",
"typescript/no-empty-object-type": "off",
"typescript/no-unsafe-function-type": "off",
"typescript/no-this-alias": "off",
"typescript/no-namespace": "off",
"typescript/no-duplicate-enum-values": "off"
}
},
{
"files": [
"**/*.api.ts",
"**/*.test.ts"
],
"rules": {
"eslint-js/object-curly-spacing": "off",
"@stylistic/max-len": "off",
"typescript/no-unused-vars": "off",
"typescript/explicit-function-return-type": "off",
"typescript/explicit-member-accessibility": "off"
}
},
{
"files": [
"demo/**/*.ts",
"**/*.benchmark.ts"
],
"rules": {
"typescript/prefer-nullish-coalescing": "off"
}
}
]
}
+120
View File
@@ -0,0 +1,120 @@
{
"$schema": "./node_modules/oxlint/configuration_schema.json",
"options": {
"maxWarnings": 0
},
"env": {
"builtin": true
},
"overrides": [
{
"files": [
"typings/**/*.d.ts"
],
"plugins": [
"typescript"
],
"jsPlugins": [
"@stylistic/eslint-plugin",
{
"name": "jsdoc-js",
"specifier": "eslint-plugin-jsdoc"
},
"oxlint-plugin-eslint"
],
"rules": {
"@stylistic/indent": [
"warn",
2
],
"@stylistic/semi": [
"warn",
"always"
],
"@stylistic/quotes": [
"warn",
"single",
{
"allowTemplateLiterals": "always"
}
],
"@stylistic/member-delimiter-style": [
"warn",
{
"multiline": {
"delimiter": "semi",
"requireLast": true
},
"singleline": {
"delimiter": "comma",
"requireLast": false
}
}
],
"@stylistic/type-annotation-spacing": "warn",
"@stylistic/keyword-spacing": "warn",
"@stylistic/max-len": [
"warn",
{
"code": 1000,
"comments": 80,
"ignoreUrls": true,
"ignorePattern": "^ *(?<ps_description>\\* Ps=)"
}
],
"@stylistic/no-extra-semi": "error",
"@stylistic/no-trailing-spaces": "warn",
"eslint-js/object-curly-spacing": [
"warn",
"always"
],
"@stylistic/spaced-comment": [
"warn",
"always",
{
"markers": [
"/"
],
"exceptions": [
"-"
]
}
],
"@stylistic/comma-dangle": [
"warn",
{
"objects": "never",
"arrays": "never",
"functions": "never"
}
],
"@stylistic/eol-last": "warn",
"curly": [
"warn",
"multi-line"
],
"eqeqeq": [
"warn",
"always"
],
"jsdoc-js/check-alignment": "warn",
"jsdoc-js/check-param-names": "warn",
"no-irregular-whitespace": "warn",
"typescript/array-type": [
"warn",
{
"default": "array",
"readonly": "generic"
}
],
"typescript/explicit-function-return-type": [
"warn",
{
"allowExpressions": true
}
],
"typescript/prefer-namespace-keyword": "warn"
}
}
]
}
-3
View File
@@ -15,9 +15,6 @@
"--recursive",
"${workspaceRoot}/out/**/*.test.js"
],
"env": {
"NODE_PATH": "${workspaceRoot}/out"
},
"sourceMaps": true,
"outFiles": [ "${workspaceRoot}/out/**/*.js" ],
"internalConsoleOptions": "openOnSessionStart"
+13
View File
@@ -26,6 +26,7 @@ npm run build && npm run esbuild # Build all TypeScript and bundle
- Integration tests: `npm run test-integration` (Playwright across Chrome/Firefox/WebKit)
- Integration tests by file: `npm run test-integration -- test/playwright/InputHandler.test.ts`. Never use grep to filter tests, it doesn't work
- Integration tests by addon: `npm run test-integration -- --suite=addon-search`. Suites always follow the format `addon-<something>`
- Lint: `npm run lint` (oxlint with type-aware rules, then ESLint for `naming-convention` only), `npm run lint-api` for `typings/`, `npm run lint-fix` for oxlint auto-fix
- Lint changes: `npm run lint-changes` to lint only changed files, `npm run lint-changes-fix` to fix them
## Addon Development Pattern
@@ -98,3 +99,15 @@ const cell = line?.getCell(0);
## Writing unit tests
- Unit tests live alongside the source code file of the thing it's testing with a .test.ts suffix.
## Cursor Cloud specific instructions
**Demo server**: Start with `npm start` (port 3000). The demo server uses node-pty to spawn real shell sessions over WebSocket. Integration tests auto-start it via Playwright's `webServer` config, so you don't need to start it manually for `npm run test-integration`.
**Build before testing**: Always run `npm run build && npm run esbuild` before `npm run test-unit`. Integration tests also need `npm run esbuild-demo-client` and `npm run esbuild-demo-server`. The update script handles this automatically on session start.
**No external services**: This project has zero external dependencies (no databases, Docker, or APIs). Everything runs locally with Node.js.
**TypeScript compiler**: The project uses `tsgo` (native TypeScript compiler preview) rather than standard `tsc`. It's installed via the `@typescript/native-preview` package.
**Lint only changed files**: Prefer `npm run lint-changes` over `npm run lint` when iterating on code changes — it's significantly faster.
+6 -6
View File
@@ -74,14 +74,14 @@ export class FitAddon implements ITerminalAddon, IFitApi {
: (this._terminal.options.scrollbar?.width ?? ViewportConstants.DEFAULT_SCROLL_BAR_WIDTH));
const parentElementStyle = _getComputedStyle(this._terminal.element.parentElement);
const parentElementHeight = parseInt(parentElementStyle.getPropertyValue('height'));
const parentElementWidth = Math.max(0, parseInt(parentElementStyle.getPropertyValue('width')));
const parentElementHeight = Math.max(0, parseInt(parentElementStyle.getPropertyValue('height'), 10) || 0);
const parentElementWidth = Math.max(0, parseInt(parentElementStyle.getPropertyValue('width'), 10) || 0);
const elementStyle = _getComputedStyle(this._terminal.element);
const elementPadding = {
top: parseInt(elementStyle.getPropertyValue('padding-top')),
bottom: parseInt(elementStyle.getPropertyValue('padding-bottom')),
right: parseInt(elementStyle.getPropertyValue('padding-right')),
left: parseInt(elementStyle.getPropertyValue('padding-left'))
top: parseInt(elementStyle.getPropertyValue('padding-top'), 10) || 0,
bottom: parseInt(elementStyle.getPropertyValue('padding-bottom'), 10) || 0,
right: parseInt(elementStyle.getPropertyValue('padding-right'), 10) || 0,
left: parseInt(elementStyle.getPropertyValue('padding-left'), 10) || 0
};
const elementPaddingVer = elementPadding.top + elementPadding.bottom;
const elementPaddingHor = elementPadding.right + elementPadding.left;
+20 -31
View File
@@ -1,6 +1,7 @@
## @xterm/addon-image
Inline image output in xterm.js. Supports SIXEL and iTerm's inline image protocol (IIP).
Inline image output in xterm.js. Supports SIXEL, iTerm's inline image protocol (IIP)
and partially Kitty's terminal graphics prtocol (TGP).
![](fixture/example.png)
@@ -43,9 +44,6 @@ terminal.loadAddon(imageAddon);
### General Notes
- *IMPORTANT:* The worker approach as done in previous releases got removed.
The addon contructor no longer expects a worker path as first argument.
- By default the addon will activate these `windowOptions` reports on the terminal:
- getWinSizePixels (CSI 14 t)
- getCellSizePixels (CSI 16 t)
@@ -73,7 +71,7 @@ terminal.loadAddon(imageAddon);
- **Cursor Positioning**
If scrolling is set, the cursor will be placed at the first image column of the last image row (VT340 mode).
Other cursor positioning modes as used by xterm or mintty are not supported.
Other cursor positioning modes as used by xterm or mintty are not supported for sixel.
- **SIXEL Palette Handling**
By default the addon limits the palette size to 256 registers (as demanded by the DEC specification).
@@ -100,17 +98,24 @@ terminal.loadAddon(imageAddon);
Set by default, change it with `{iipSupport: true}`.
The IIP implementation has the following features / restrictions (sequence will silently fail for unmet conditions):
- Supported formats: PNG, JPEG and GIF
- Supported formats: PNG, JPEG, GIF and QOI
- No animation support.
- Image type hinting is not supported (always deducted from data header).
- File download is not supported.
- Filename gets parsed but not used.
- Strict base64 handling as of RFC4648 §4 (standard alphabet, optional padding, no separator bytes allowed).
- Payload size may not exceed CEIL(sizeParameter * 4 / 3).
- Image scaling beyond terminal viewport size is allowed (e.g. `width=200%`).
- Image pixel size is restricted by `pixelLimit` (pre- and post resizing).
- Size parameter is restricted by `iipSizeLimit`.
- Cursor positioning behaves the same as for sixel (see above).
- Playload size is restricted by `iipSizeLimit`.
- size parameter is not mandatory anymore.
- Cursor gets positioned at the next cell of the last image line.
- ReportCellSize sequence is supported.
- Hi-res display support is currently limited (image gets blurred).
- **Kitty Graphics Support (TGP)**
Set by default, change it with `{kittySupport: true}`.
Note that the kitty graphics support is still WIP.
### Storage and Drawing Settings
@@ -127,6 +132,7 @@ The addon exposes two properties to interact with the storage limits at runtime:
By default the addon will show a placeholder pattern for evicted images that are still part
of the terminal (e.g. in the scrollback). The pattern can be deactivated by toggling `showPlaceholder`.
### Image Data Retrieval
The addon provides the following API endpoints to retrieve raw image data as canvas:
@@ -140,6 +146,7 @@ The addon provides the following API endpoints to retrieve raw image data as can
The buffer position is the 0-based absolute index (including scrollback at top).
Note that the canvas gets created and data copied over for every call, thus it is not suitable for performance critical actions.
### Memory Usage
The addon does most image processing in Javascript and therefore can occupy a rather big amount of memory. To get an idea where the memory gets eaten, lets look at the data flow and processing steps:
@@ -180,8 +187,8 @@ _How can I adjust the memory usage?_
A constructor setting, thus you would have to anticipate, whether (multiple) terminals in your page gonna do lots of concurrent decoding. Since this is normally not the case and the memory usage is only temporarily peaking, a rather high value should work even with multiple terminals in one page.
- `storageLimit`
A constructor and a runtime setting. In conjunction with `storageUsage` you can do runtime checks and adjust the limit to your needs. If you have to lower the limit below the current usage, images will be removed in FIFO order and may turn into a placeholder in the terminal's scrollback (if `showPlaceholder` is set). When adjusting keep in mind to leave enough room for memory peaking for decoding.
- `sixelSizeLimit`
A constructor setting. This has only a small impact on peaking memory during decoding. It is meant to avoid processing of overly big or broken SIXEL sequences at an earlier phase, thus may stop the decoder from entering its memory intensive task for potentially invalid data.
- `sixelSizeLimit | iipSizeLimit | kittySizeLimit`
Constructor settings. This has only a small impact on peaking memory during decoding. It is meant to avoid processing of overly big or broken image sequences at an earlier phase, thus may stop the invoked decoders from entering memory intensive tasks for potentially invalid data.
### Terminal Interaction
@@ -211,23 +218,5 @@ _How can I adjust the memory usage?_
### Status
- Sixel support and image handling in xterm.js is considered beta quality.
- IIP support is in alpha stage. Please file a bug for any awkwardities.
### Changelog
- 0.5.0 integrate with xtermjs base repo (at v0.4.3)
- 0.4.3 defer canvas creation
- 0.4.2 fix image canvas resize
- 0.4.1 compat release for xterm.js 5.2.0
- 0.4.0 IIP support
- 0.3.1 compat release for xterm.js 5.1.0
- 0.3.0 important change: worker removed from addon
- 0.2.0 compat release for xterm.js 5.0.0
- 0.1.3 bugfix: avoid striping
- 0.1.2 bugfix: reset clear flag
- 0.1.1 bugfixes:
- clear sticky image tiles on render
- create folder from bootstrap.sh
- fix peer dependency in package.json
- Sixel and IIP support and image handling in xterm.js is considered beta quality.
- Kitty support is in alpha stage. Please file a bug for any awkwardities or missing features.
Binary file not shown.
+73 -19
View File
@@ -8,7 +8,7 @@ import { IIPImageStorage } from './IIPImageStorage';
import { CELL_SIZE_DEFAULT } from './ImageStorage';
import Base64Decoder from 'xterm-wasm-parts/lib/base64/Base64Decoder.wasm';
import QoiDecoder from 'xterm-wasm-parts/lib/qoi/QoiDecoder.wasm';
import { HeaderParser, IHeaderFields, HeaderState } from './IIPHeaderParser';
import { HeaderParser, IHeaderFields, HeaderState, SequenceType } from './IIPHeaderParser';
import { imageType, UNSUPPORTED_TYPE } from './IIPMetrics';
// Local const enum mirror - esbuild can't inline const enums from external packages
@@ -23,6 +23,7 @@ const enum DecoderConst {
// default IIP header values
const DEFAULT_HEADER: IHeaderFields = {
type: SequenceType.INVALID,
name: 'Unnamed file',
size: 0,
width: 'auto',
@@ -39,6 +40,8 @@ export class IIPHandler implements IOscHandler, IResetHandler {
private _dec: Base64Decoder;
private _qoiDec: QoiDecoder;
private _metrics = UNSUPPORTED_TYPE;
private _isMultipart = false;
private _abortMulti = false;
constructor(
private readonly _opts: IImageAddonOptions,
@@ -52,11 +55,14 @@ export class IIPHandler implements IOscHandler, IResetHandler {
this._qoiDec = new QoiDecoder(DecoderConst.KEEP_DATA);
}
public reset(): void {}
public reset(): void {
this._hp.reset();
this._dec.release();
this._qoiDec.release();
}
public start(): void {
this._aborted = false;
this._header = DEFAULT_HEADER;
this._metrics = UNSUPPORTED_TYPE;
this._hp.reset();
}
@@ -76,15 +82,27 @@ export class IIPHandler implements IOscHandler, IResetHandler {
return;
}
if (dataPos > 0) {
this._header = Object.assign({}, DEFAULT_HEADER, this._hp.fields);
if (!this._header.inline || !this._header.size || this._header.size > this._opts.iipSizeLimit) {
const seqType = this._hp.fields.type;
if (seqType === SequenceType.FILE) {
if (this._isMultipart) {
this._isMultipart = false;
this._abortMulti = false;
this._dec.release();
}
this._header = Object.assign({}, DEFAULT_HEADER, this._hp.fields);
if (!this._header.inline) {
this._aborted = true;
return;
}
this._dec.init();
} else if (this._abortMulti) {
this._aborted = true;
return;
}
this._dec.init();
if ((this._dec.put(data.subarray(dataPos, end)) as number) !== DecoderConst.OK) {
this._dec.release();
this._aborted = true;
if (this._isMultipart) this._abortMulti = true;
}
}
}
@@ -93,22 +111,58 @@ export class IIPHandler implements IOscHandler, IResetHandler {
public end(success: boolean): boolean | Promise<boolean> {
if (this._aborted) return true;
if (this._hp.state !== HeaderState.END) {
if (this._hp.end()) return true;
}
const seqType = this._hp.fields.type;
if (seqType === SequenceType.FILEPART) return true;
if (seqType === SequenceType.REPORTCELLSIZE) {
// OSC 1337 ; ReportCellSize=[height];[width];[scale] ST
let width = CELL_SIZE_DEFAULT.width;
let height = CELL_SIZE_DEFAULT.height;
if (this._renderer.dimensions) {
width = this._renderer.dimensions.css.canvas.width / this._coreTerminal.cols;
height = this._renderer.dimensions.css.canvas.height / this._coreTerminal.rows;
}
const scale = this._coreTerminal._core._coreBrowserService?.dpr ?? 1;
const report = `\x1b]1337;ReportCellSize=${height.toFixed(3)};${width.toFixed(3)};${scale.toFixed(3)}\x1b\\`;
this._coreTerminal.input(report, false);
return true;
}
if (seqType === SequenceType.MULTIPARTFILE) {
this._header = Object.assign({}, DEFAULT_HEADER, this._hp.fields);
this._isMultipart = true;
this._abortMulti = false;
this._dec.release();
this._dec.init();
return true;
}
if (seqType === SequenceType.FILEEND) {
if (!this._isMultipart) return true;
this._isMultipart = false;
if (this._abortMulti || this._header.type !== SequenceType.MULTIPARTFILE) return true;
}
// fallthrough for SequenceType.FILE & SequenceType.FILEEND
let w = 0;
let h = 0;
// early exit condition chain
let cond: number | boolean = true;
let cond: number | boolean;
if (cond = success) {
if (cond = !this._dec.end()) {
if (cond = this._dec.data8.length === this._header.size) {
this._metrics = imageType(this._dec.data8);
if (cond = this._metrics.mime !== 'unsupported') {
w = this._metrics.width;
h = this._metrics.height;
if (cond = w && h && w * h < this._opts.pixelLimit) {
[w, h] = this._resize(w, h).map(Math.floor);
cond = w && h && w * h < this._opts.pixelLimit;
}
this._metrics = imageType(this._dec.data8);
if (cond = this._metrics.mime !== 'unsupported') {
w = this._metrics.width;
h = this._metrics.height;
if (cond = w && h && w * h < this._opts.pixelLimit) {
[w, h] = this._resize(w, h).map(Math.floor);
cond = w && h && w * h < this._opts.pixelLimit;
}
}
}
@@ -168,8 +222,8 @@ export class IIPHandler implements IOscHandler, IResetHandler {
private _dim(s: string, total: number, cdim: number): number {
if (s === 'auto') return 0;
if (s.endsWith('%')) return parseInt(s.slice(0, -1)) * total / 100;
if (s.endsWith('px')) return parseInt(s.slice(0, -2));
return parseInt(s) * cdim;
if (s.endsWith('%')) return parseInt(s.slice(0, -1), 10) * total / 100;
if (s.endsWith('px')) return parseInt(s.slice(0, -2), 10);
return parseInt(s, 10) * cdim;
}
}
+129 -7
View File
@@ -4,19 +4,20 @@
*/
import { assert } from 'chai';
import { HeaderParser, HeaderState, IHeaderFields } from './IIPHeaderParser';
import { HeaderParser, HeaderState, IHeaderFields, SequenceType } from './IIPHeaderParser';
const CASES: [string, IHeaderFields][] = [
['File=size=123456;name=dGVzdA==:', {name: 'test', size: 123456}],
['File=size=123456;name=dGVzdA:', {name: 'test', size: 123456}],
['File=size=123456;name=dGVzdA==:', {type: SequenceType.FILE, name: 'test', size: 123456}],
['File=size=123456;name=dGVzdA:', {type: SequenceType.FILE, name: 'test', size: 123456}],
// utf-8 encoding in name
['File=size=123456;name=w7xtbMOkdXTDnw==:', {name: 'ümläutß', size: 123456}],
['File=size=123456;name=w7xtbMOkdXTDnw:', {name: 'ümläutß', size: 123456}],
['File=size=123456;name=w7xtbMOkdXTDnw==:', {type: SequenceType.FILE, name: 'ümläutß', size: 123456}],
['File=size=123456;name=w7xtbMOkdXTDnw:', {type: SequenceType.FILE, name: 'ümläutß', size: 123456}],
// full header spec
[
'File=inline=1;width=10px;height=20%;preserveAspectRatio=1;size=123456;name=w7xtbMOkdXTDnw:',
{
type: SequenceType.FILE,
inline: 1,
width: '10px',
height: '20%',
@@ -28,6 +29,7 @@ const CASES: [string, IHeaderFields][] = [
[
'File=inline=1;width=auto;height=20;preserveAspectRatio=1;size=123456;name=w7xtbMOkdXTDnw:',
{
type: SequenceType.FILE,
inline: 1,
width: 'auto',
height: '20',
@@ -101,7 +103,7 @@ describe('IIPHeaderParser', () => {
let res = hp.parse(inp, 0, inp.length);
assert.strictEqual(res, inp.length);
assert.strictEqual(hp.state, HeaderState.END);
assert.deepEqual(hp.fields, {name: 'test', size: 0});
assert.deepEqual(hp.fields, {type: SequenceType.FILE, name: 'test', size: 0});
hp.reset();
inp = fromBs(CASES[0][0]);
res = hp.parse(inp, 0, inp.length);
@@ -115,7 +117,7 @@ describe('IIPHeaderParser', () => {
let res = hp.parse(inp, 0, inp.length);
assert.strictEqual(res, inp.length);
assert.strictEqual(hp.state, HeaderState.END);
assert.deepEqual(hp.fields, {name: '', size: 123456});
assert.deepEqual(hp.fields, {type: SequenceType.FILE, name: '', size: 123456});
hp.reset();
inp = fromBs(CASES[0][0]);
res = hp.parse(inp, 0, inp.length);
@@ -135,4 +137,124 @@ describe('IIPHeaderParser', () => {
assert.strictEqual(hp.state, HeaderState.END);
assert.deepEqual(hp.fields, CASES[0][1]);
});
it('FilePart sequence', () => {
const hp = new HeaderParser();
const inp = fromBs('FilePart=w7xtbMOkdXTDnw');
const res = hp.parse(inp, 0, inp.length);
assert.strictEqual(res, 9);
assert.strictEqual(hp.state, HeaderState.END);
assert.deepEqual(hp.fields.type, SequenceType.FILEPART);
});
it('FilePart sequence - bytewise', () => {
const hp = new HeaderParser();
const inp = fromBs('FilePart=w7xtbMOkdXTDnw');
let res = -2;
let pos = 0;
while (res === -2 && pos < inp.length) {
res = hp.parse(new Uint32Array([inp[pos++]]), 0, 1);
}
assert.strictEqual(res, 1);
assert.strictEqual(hp.state, HeaderState.END);
assert.deepEqual(hp.fields.type, SequenceType.FILEPART);
});
it('MultipartFile sequence', () => {
const hp = new HeaderParser();
const inp = fromBs('MultipartFile=inline=1;width=10px;height=20%;preserveAspectRatio=1;size=123456;name=w7xtbMOkdXTDnw');
let res = hp.parse(inp, 0, inp.length);
assert.strictEqual(res, -2);
assert.strictEqual(hp.state, HeaderState.VALUE);
res = hp.end();
assert.strictEqual(res, 0);
assert.strictEqual(hp.state, HeaderState.END);
assert.deepEqual(
hp.fields,
{
type: SequenceType.MULTIPARTFILE,
inline: 1,
width: '10px',
height: '20%',
preserveAspectRatio: 1,
size: 123456,
name: 'ümläutß'
}
);
});
it('MultipartFile sequence - bytewise', () => {
const hp = new HeaderParser();
const inp = fromBs('MultipartFile=inline=1;width=10px;height=20%;preserveAspectRatio=1;size=123456;name=w7xtbMOkdXTDnw');
let res = -2;
let pos = 0;
while (res === -2 && pos < inp.length) {
res = hp.parse(new Uint32Array([inp[pos++]]), 0, 1);
}
assert.strictEqual(res, -2);
assert.strictEqual(hp.state, HeaderState.VALUE);
res = hp.end();
assert.strictEqual(res, 0);
assert.strictEqual(hp.state, HeaderState.END);
assert.deepEqual(
hp.fields,
{
type: SequenceType.MULTIPARTFILE,
inline: 1,
width: '10px',
height: '20%',
preserveAspectRatio: 1,
size: 123456,
name: 'ümläutß'
}
);
});
it('FileEnd sequence', () => {
const hp = new HeaderParser();
const inp = fromBs('FileEnd');
let res = hp.parse(inp, 0, inp.length);
assert.strictEqual(res, -2);
assert.strictEqual(hp.state, HeaderState.START);
res = hp.end();
assert.strictEqual(res, 0);
assert.strictEqual(hp.state, HeaderState.END);
assert.deepEqual(hp.fields.type, SequenceType.FILEEND);
});
it('FileEnd sequence - bytewise', () => {
const hp = new HeaderParser();
const inp = fromBs('FileEnd');
let res = -2;
let pos = 0;
while (res === -2 && pos < inp.length) {
res = hp.parse(new Uint32Array([inp[pos++]]), 0, 1);
}
assert.strictEqual(res, -2);
assert.strictEqual(hp.state, HeaderState.START);
res = hp.end();
assert.strictEqual(res, 0);
assert.strictEqual(hp.state, HeaderState.END);
assert.deepEqual(hp.fields.type, SequenceType.FILEEND);
});
it('ReportCellSize sequence', () => {
const hp = new HeaderParser();
const inp = fromBs('ReportCellSize');
let res = hp.parse(inp, 0, inp.length);
assert.strictEqual(res, -2);
assert.strictEqual(hp.state, HeaderState.START);
res = hp.end();
assert.strictEqual(res, 0);
assert.strictEqual(hp.state, HeaderState.END);
assert.deepEqual(hp.fields, { type: SequenceType.REPORTCELLSIZE });
});
it('ReportCellSize sequence - bytewise', () => {
const hp = new HeaderParser();
const inp = fromBs('ReportCellSize');
let res = -2;
let pos = 0;
while (res === -2 && pos < inp.length) {
res = hp.parse(new Uint32Array([inp[pos++]]), 0, 1);
}
assert.strictEqual(res, -2);
assert.strictEqual(hp.state, HeaderState.START);
res = hp.end();
assert.strictEqual(res, 0);
assert.strictEqual(hp.state, HeaderState.END);
assert.deepEqual(hp.fields, { type: SequenceType.REPORTCELLSIZE });
});
});
+86 -11
View File
@@ -6,9 +6,27 @@
// eslint-disable-next-line
declare const Buffer: any;
export const enum HeaderState {
START = 0,
ABORT = 1,
KEY = 2,
VALUE = 3,
END = 4
}
export const enum SequenceType {
INVALID = 0,
FILE = 1,
MULTIPARTFILE = 2,
FILEPART = 3,
FILEEND = 4,
REPORTCELLSIZE = 5
}
export interface IHeaderFields {
[key: string]: number | string | Uint32Array | null | undefined;
// sequence type
type: SequenceType;
// base-64 encoded filename. Defaults to "Unnamed file".
name: string;
// File size in bytes. The file transfer will be canceled if this size is exceeded.
@@ -29,14 +47,6 @@ export interface IHeaderFields {
inline?: number;
}
export const enum HeaderState {
START = 0,
ABORT = 1,
KEY = 2,
VALUE = 3,
END = 4
}
// field value decoders
// ASCII bytes to string
@@ -92,7 +102,19 @@ const DECODERS: {[key: string]: (v: Uint32Array) => number | string} = {
};
// sequence type markers
// File
const FILE_MARKER = [70, 105, 108, 101];
// MultipartFile
const MULTIPARTFILE_MARKER = [77, 117, 108, 116, 105, 112, 97, 114, 116, 70, 105, 108, 101];
// FilePart
const FILEPART_MARKER = [70, 105, 108, 101, 80, 97, 114, 116];
// FileEnd
const FILEEND_MARKER = [70, 105, 108, 101, 69, 110, 100];
// ReportCellSize
const REPORTCELLSIZE_MARKER = [82, 101, 112, 111, 114, 116, 67, 101, 108, 108, 83, 105, 122, 101];
// max allowed chars for sequence header
const MAX_FIELDCHARS = 1024;
@@ -111,12 +133,43 @@ export class HeaderParser {
this._key = '';
}
public end(): number {
if (this.state === HeaderState.START) {
if (this._position === FILEEND_MARKER.length) {
for (let k = 0; k < FILEEND_MARKER.length; ++k) {
if (this._buffer[k] !== FILEEND_MARKER[k]) return this._a();
}
this.fields['type'] = SequenceType.FILEEND;
this.state = HeaderState.END;
return 0;
}
if (this._position === REPORTCELLSIZE_MARKER.length) {
for (let k = 0; k < REPORTCELLSIZE_MARKER.length; ++k) {
if (this._buffer[k] !== REPORTCELLSIZE_MARKER[k]) return this._a();
}
this.fields['type'] = SequenceType.REPORTCELLSIZE;
this.state = HeaderState.END;
return 0;
}
return this._a();
}
if (this.state === HeaderState.END) return 0;
if (this.state === HeaderState.VALUE
&& this.fields.type === SequenceType.MULTIPARTFILE
) {
if (!this._storeValue(this._position)) return this._a();
this.state = HeaderState.END;
return 0;
}
return this._a();
}
public parse(data: Uint32Array, start: number, end: number): number {
let state = this.state;
let pos = this._position;
const buffer = this._buffer;
if (state === HeaderState.ABORT || state === HeaderState.END) return -1;
if (state === HeaderState.START && pos > 6) return -1;
if (state === HeaderState.START && pos > 14) return -1;
for (let i = start; i < end; ++i) {
const c = data[i];
switch (c) {
@@ -127,8 +180,29 @@ export class HeaderParser {
break;
case 61: // =
if (state === HeaderState.START) {
for (let k = 0; k < FILE_MARKER.length; ++k) {
if (buffer[k] !== FILE_MARKER[k]) return this._a();
if (buffer[0] === 70) {
// 'File' or 'FilePart'
let k = 0;
for (; k < FILE_MARKER.length; ++k) {
if (buffer[k] !== FILE_MARKER[k]) return this._a();
}
this.fields['type'] = SequenceType.FILE;
if (pos === FILEPART_MARKER.length) {
for (; k < FILEPART_MARKER.length; ++k) {
if (buffer[k] !== FILEPART_MARKER[k]) return this._a();
}
this.fields['type'] = SequenceType.FILEPART;
this.state = HeaderState.END;
return i + 1;
}
} else if (buffer[0] === 77) {
// 'MultipartFile'
for (let k = 0; k < MULTIPARTFILE_MARKER.length; ++k) {
if (buffer[k] !== MULTIPARTFILE_MARKER[k]) return this._a();
}
this.fields['type'] = SequenceType.MULTIPARTFILE;
} else {
return this._a();
}
state = HeaderState.KEY;
pos = 0;
@@ -158,6 +232,7 @@ export class HeaderParser {
}
private _a(): number {
this.fields.type = SequenceType.INVALID;
this.state = HeaderState.ABORT;
return -1;
}
+3 -1
View File
@@ -3,6 +3,7 @@
* @license MIT
*/
import { IAddImageOpts } from './Types';
import { ImageStorage } from './ImageStorage';
/**
@@ -12,6 +13,7 @@ import { ImageStorage } from './ImageStorage';
* - Always uses scrolling mode (cursor advances with image)
*/
export class IIPImageStorage {
private _addImageOpts: IAddImageOpts = { scrolling: true, layer: 'top', zIndex: 0, cursorPos: 'iip' };
constructor(
private readonly _storage: ImageStorage
) {}
@@ -21,6 +23,6 @@ export class IIPImageStorage {
* Always uses scrolling mode — cursor advances past the image.
*/
public addImage(img: HTMLCanvasElement | ImageBitmap): void {
this._storage.addImage(img, true);
this._storage.addImage(img, this._addImageOpts);
}
}
+2 -1
View File
@@ -30,7 +30,8 @@ const TEST_IMAGES: [string, IMetrics][] = [
['iphone_hdr_YES.jpg', { mime: 'image/jpeg', width: 3264, height: 2448 }],
['nikon-e950.jpg', { mime: 'image/jpeg', width: 800, height: 600 }],
['agfa-makernotes.jpg', { mime: 'image/jpeg', width: 8, height: 8 }],
['sony-alpha-6000.jpg', { mime: 'image/jpeg', width: 6000, height: 4000 }]
['sony-alpha-6000.jpg', { mime: 'image/jpeg', width: 6000, height: 4000 }],
['dice.qoi', { mime: 'image/qoi', width: 800, height: 600 }],
];
+1 -6
View File
@@ -137,11 +137,6 @@ export class ImageAddon implements ITerminalAddon, IImageApi {
// enable size reports
if (this._opts.enableSizeReports) {
// const windowOptions = terminal.getOption('windowOptions');
// windowOptions.getWinSizePixels = true;
// windowOptions.getCellSizePixels = true;
// windowOptions.getWinSizeChars = true;
// terminal.setOption('windowOptions', windowOptions);
const windowOps = terminal.options.windowOptions ?? {};
windowOps.getWinSizePixels = true;
windowOps.getCellSizePixels = true;
@@ -260,7 +255,7 @@ export class ImageAddon implements ITerminalAddon, IImageApi {
}
private _report(s: string): void {
this._terminal?._core.coreService.triggerDataEvent(s);
this._terminal?._core.input(s, false);
}
private _decset(params: (number | number[])[]): boolean {
+1 -1
View File
@@ -146,7 +146,7 @@ export class ImageRenderer extends Disposable implements IDisposable {
public clearLines(start: number, end: number, layer?: ImageLayer): void {
const y = start * (this.dimensions?.css.cell.height || 0);
const w = this.dimensions?.css.canvas.width || 0;
const h = (++end - start) * (this.dimensions?.css.cell.height || 0);
const h = (end + 1 - start) * (this.dimensions?.css.cell.height || 0);
if (!layer || layer === 'top') {
this._layers.get('top')?.clearRect(0, y, w, h);
}
+22 -13
View File
@@ -5,7 +5,11 @@
import { IDisposable } from '@xterm/xterm';
import { ImageRenderer } from './ImageRenderer';
import { ITerminalExt, IExtendedAttrsImage, IImageAddonOptions, IImageSpec, IBufferLineExt, BgFlags, Cell, Content, ICellSize, ExtFlags, Attributes, UnderlineStyle, ImageLayer } from './Types';
import {
ITerminalExt, IExtendedAttrsImage, IImageAddonOptions, IImageSpec,
IBufferLineExt, BgFlags, Cell, Content, ICellSize, ExtFlags, Attributes,
UnderlineStyle, IAddImageOpts
} from './Types';
// fallback default cell size
@@ -235,13 +239,15 @@ export class ImageStorage implements IDisposable {
/**
* Method to add an image to the storage.
* @param img - The image to add (canvas or bitmap).
* @param scrolling - When true, cursor advances with the image (lineFeed per row).
* When false, image is placed at (0,0) and cursor is restored (DECSET 80 / sixel origin mode).
* @param layer - Which canvas layer to render on ('top' or 'bottom').
* @param zIndex - Z-index for image layering within the same layer.
* @param opts - Options for addImage:
* - scrolling: When true, cursor advances with the image.
* When false, image is placed at ORIGIN and cursor does not move.
* - layer: Which canvas layer to render on ('top' or 'bottom').
* - zIndex: Z-index for image layering within the same layer.
* - cursorPos: 'vt340' for bottom-left, 'iip' for bottom.right.
* @returns The internal image ID assigned to the stored image.
*/
public addImage(img: HTMLCanvasElement | ImageBitmap, scrolling: boolean, layer: ImageLayer = 'top', zIndex: number = 0): number {
public addImage(img: HTMLCanvasElement | ImageBitmap, opts: IAddImageOpts): number {
// never allow storage to exceed memory limit
this._evictOldest(img.width * img.height);
@@ -263,7 +269,7 @@ export class ImageStorage implements IDisposable {
let offset = originX;
let tileCount = 0;
if (!scrolling) {
if (!opts.scrolling) {
buffer.x = 0;
buffer.y = 0;
offset = 0;
@@ -277,7 +283,7 @@ export class ImageStorage implements IDisposable {
this._writeToCell(line as IBufferLineExt, offset + col, imageId, row * cols + col);
tileCount++;
}
if (scrolling) {
if (opts.scrolling) {
if (row < rows - 1) this._terminal._core._inputHandler.lineFeed();
} else {
if (++buffer.y >= termRows) break;
@@ -287,8 +293,12 @@ export class ImageStorage implements IDisposable {
this._terminal._core._inputHandler._dirtyRowTracker.markDirty(buffer.y);
// cursor positioning modes
if (scrolling) {
buffer.x = offset;
if (opts.scrolling) {
if (opts.cursorPos === 'iip') {
buffer.x = Math.min(offset + cols, termCols);
} else {
buffer.x = offset;
}
} else {
buffer.x = originX;
buffer.y = originY;
@@ -331,8 +341,8 @@ export class ImageStorage implements IDisposable {
marker: endMarker || undefined,
tileCount,
bufferType: this._terminal.buffer.active.type,
layer,
zIndex
layer: opts.layer,
zIndex: opts.zIndex
};
// finally add the image
@@ -455,7 +465,6 @@ export class ImageStorage implements IDisposable {
if (!nextE || nextE.imageId !== imageId || nextE.tileId !== startTile + count) {
break;
}
e = nextE;
count++;
}
col--;
+4 -2
View File
@@ -4,7 +4,7 @@
*/
import { ImageStorage, CELL_SIZE_DEFAULT } from './ImageStorage';
import { IImageAddonOptions, ITerminalExt } from './Types';
import { IImageAddonOptions, ITerminalExt, IAddImageOpts } from './Types';
import { ImageRenderer } from './ImageRenderer';
/**
@@ -15,6 +15,7 @@ import { ImageRenderer } from './ImageRenderer';
* - advanceCursor for empty sixels carrying only height
*/
export class SixelImageStorage {
private _addImageOpts: IAddImageOpts = { scrolling: true, layer: 'top', zIndex: 0, cursorPos: 'vt340' };
constructor(
private readonly _storage: ImageStorage,
private readonly _opts: IImageAddonOptions,
@@ -27,7 +28,8 @@ export class SixelImageStorage {
* Cursor behavior depends on the sixelScrolling option (DECSET 80).
*/
public addImage(img: HTMLCanvasElement | ImageBitmap): void {
this._storage.addImage(img, this._opts.sixelScrolling);
this._addImageOpts.scrolling = this._opts.sixelScrolling;
this._storage.addImage(img, this._addImageOpts);
}
/**
+10 -1
View File
@@ -9,7 +9,8 @@ import { IDisposable, IMarker, Terminal } from '@xterm/xterm';
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 { IInputHandler } from 'common/Types';
import type { IBufferLine, IExtendedAttrs } from 'common/buffer/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';
@@ -100,6 +101,14 @@ export interface ICellSize {
}
export type ImageLayer = 'top' | 'bottom';
export type CursorPos = 'vt340' | 'iip';
export interface IAddImageOpts {
scrolling: boolean;
layer: ImageLayer;
zIndex: number;
cursorPos: CursorPos;
}
export interface IImageSpec {
orig: HTMLCanvasElement | ImageBitmap | undefined;
@@ -168,7 +168,7 @@ export function parseKittyCommand(data: string): IKittyCommand {
cmd.deleteSelector = value;
continue;
}
const numValue = parseInt(value);
const numValue = parseInt(value, 10);
switch (key) {
case KittyKey.FORMAT: cmd.format = numValue; break;
case KittyKey.ID: cmd.id = numValue; break;
@@ -5,7 +5,7 @@
import { IDisposable } from '@xterm/xterm';
import { ImageStorage } from '../ImageStorage';
import { ImageLayer } from '../Types';
import { ImageLayer, IAddImageOpts } from '../Types';
import { IKittyImageData } from './KittyGraphicsTypes';
// Kitty-specific image storage controller.
@@ -42,6 +42,7 @@ export class KittyImageStorage implements IDisposable {
this._images.delete(kittyId);
}
};
private _addImageOpts: IAddImageOpts = { scrolling: true, layer: 'top', zIndex: 0, cursorPos: 'iip' };
constructor(
private readonly _storage: ImageStorage
@@ -98,7 +99,10 @@ export class KittyImageStorage implements IDisposable {
if (oldStorageId !== undefined) {
this._storageIdToKittyId.delete(oldStorageId);
}
const storageId = this._storage.addImage(image, scrolling, layer, zIndex);
this._addImageOpts.scrolling = scrolling;
this._addImageOpts.layer = layer;
this._addImageOpts.zIndex = zIndex;
const storageId = this._storage.addImage(image, this._addImageOpts);
this._kittyIdToStorageId.set(kittyId, storageId);
this._storageIdToKittyId.set(storageId, kittyId);
}
@@ -290,6 +290,67 @@ test.describe('ImageAddon', () => {
});
});
test.describe('IIP support', () => {
test('size params is optional/informative', async () => {
// too small size param
await ctx.proxy.write(`\x1b]1337;File=inline=1;size=5:${PALETTE_PNG_BASE64}\x07`);
deepStrictEqual(await getOrigSize(1), [640, 80]);
// no size aparm at all
await ctx.proxy.write(`\x1b[10H\x1b]1337;File=inline=1:${PALETTE_QOI_BASE64}\x07`);
deepStrictEqual(await getOrigSize(2), [640, 80]);
});
test.skip('FilePart support - bytewise', async () => {
// opener
await ctx.proxy.write(`\x1b]1337;MultipartFile=inline=1\x07`);
// byte-wise
for (let i = 0; i < PALETTE_PNG_BASE64.length; ++i) {
await ctx.proxy.write(`\x1b]1337;FilePart=${PALETTE_PNG_BASE64[i]}\x07`);
}
// finalizer
await ctx.proxy.write(`\x1b]1337;FileEnd\x07`);
deepStrictEqual(await getOrigSize(1), [640, 80]);
});
test('FilePart support - chunks', async () => {
// opener
await ctx.proxy.write(`\x1b]1337;MultipartFile=inline=1\x07`);
// chunks
for (let i = 0; i < PALETTE_PNG_BASE64.length; i += 256) {
await ctx.proxy.write(`\x1b]1337;FilePart=${PALETTE_PNG_BASE64.slice(i, i + 256)}\x07`);
}
// finalizer
await ctx.proxy.write(`\x1b]1337;FileEnd\x07`);
deepStrictEqual(await getOrigSize(1), [640, 80]);
});
test('FilePart support - edgecases', async () => {
// multiple opener do not harm
for (let i = 0; i < 10; ++i) {
await ctx.proxy.write(`\x1b]1337;MultipartFile=inline=1\x07`);
}
// multiple finalizer do not harm
for (let i = 0; i < 5; ++i) {
await ctx.proxy.write(`\x1b]1337;FileEnd\x07`);
}
// chunks w'o opener are ignored
for (let i = 0; i < PALETTE_PNG_BASE64.length; i += 256) {
await ctx.proxy.write(`\x1b]1337;FilePart=${PALETTE_PNG_BASE64.slice(i, i + 256)}\x07`);
}
await ctx.proxy.write(`\x1b]1337;FileEnd\x07`);
pollFor(ctx.page, 'window.imageAddon._storage._images.size', 0);
});
test('FilePart support - other IIP sequence cancels chunked image', async () => {
// opener
await ctx.proxy.write(`\x1b]1337;MultipartFile=inline=1\x07`);
// chunks
for (let i = 0; i < PALETTE_PNG_BASE64.length; i += 256) {
await ctx.proxy.write(`\x1b]1337;FilePart=${PALETTE_PNG_BASE64.slice(i, i + 256)}\x07`);
}
// finalizer skipped
// write spinfox.png
await ctx.proxy.write(TESTDATA_IIP[1][0]);
deepStrictEqual(await getOrigSize(1), TESTDATA_IIP[1][1]);
});
});
test.describe('IIP support - testimages', () => {
test('palette.png', async () => {
await ctx.proxy.write(TESTDATA_IIP[0][0]);
+6 -12
View File
@@ -539,7 +539,6 @@ test.describe('Kitty Graphics Protocol', () => {
test.describe('Query support (a=q)', () => {
test('responds with OK for capability query without payload', async () => {
let response = '';
await ctx.page.evaluate(() => {
(window as any).kittyResponse = '';
(window as any).term.onData((data: string) => { (window as any).kittyResponse = data; });
@@ -548,12 +547,11 @@ test.describe('Kitty Graphics Protocol', () => {
await ctx.proxy.write('\x1b_Gi=31,a=q;\x1b\\');
await timeout(100);
response = await ctx.page.evaluate('window.kittyResponse');
const response = await ctx.page.evaluate('window.kittyResponse');
strictEqual(response, '\x1b_Gi=31;OK\x1b\\');
});
test('responds with OK for valid PNG query', async () => {
let response = '';
await ctx.page.evaluate(() => {
(window as any).kittyResponse = '';
(window as any).term.onData((data: string) => { (window as any).kittyResponse = data; });
@@ -562,7 +560,7 @@ test.describe('Kitty Graphics Protocol', () => {
await ctx.proxy.write(`\x1b_Gi=42,a=q,f=100;${KITTY_BLACK_1X1_BASE64}\x1b\\`);
await timeout(100);
response = await ctx.page.evaluate('window.kittyResponse');
const response = await ctx.page.evaluate('window.kittyResponse');
strictEqual(response, '\x1b_Gi=42;OK\x1b\\');
});
@@ -578,7 +576,6 @@ test.describe('Kitty Graphics Protocol', () => {
});
test('responds with error for invalid base64', async () => {
let response = '';
await ctx.page.evaluate(() => {
(window as any).kittyResponse = '';
(window as any).term.onData((data: string) => { (window as any).kittyResponse = data; });
@@ -587,12 +584,11 @@ test.describe('Kitty Graphics Protocol', () => {
await ctx.proxy.write('\x1b_Gi=60,a=q,f=100;!!!invalid!!!\x1b\\');
await timeout(100);
response = await ctx.page.evaluate('window.kittyResponse');
const response: string = await ctx.page.evaluate('window.kittyResponse');
strictEqual(response.startsWith('\x1b_Gi=60;EINVAL:'), true);
});
test('responds with error for RGB data without dimensions', async () => {
let response = '';
await ctx.page.evaluate(() => {
(window as any).kittyResponse = '';
(window as any).term.onData((data: string) => { (window as any).kittyResponse = data; });
@@ -601,7 +597,7 @@ test.describe('Kitty Graphics Protocol', () => {
await ctx.proxy.write('\x1b_Gi=70,a=q,f=24;AAAA\x1b\\');
await timeout(100);
response = await ctx.page.evaluate('window.kittyResponse');
const response = await ctx.page.evaluate('window.kittyResponse');
strictEqual(response, '\x1b_Gi=70;EINVAL:width and height required for raw pixel data\x1b\\');
});
@@ -642,7 +638,6 @@ test.describe('Kitty Graphics Protocol', () => {
});
test('responds with EINVAL when both i and I keys are specified', async () => {
let response = '';
await ctx.page.evaluate(() => {
(window as any).kittyResponse = '';
(window as any).term.onData((data: string) => { (window as any).kittyResponse = data; });
@@ -652,12 +647,11 @@ test.describe('Kitty Graphics Protocol', () => {
await ctx.proxy.write(`\x1b_Gi=100,I=200,a=q,f=100;${KITTY_BLACK_1X1_BASE64}\x1b\\`);
await timeout(100);
response = await ctx.page.evaluate('window.kittyResponse');
const response = await ctx.page.evaluate('window.kittyResponse');
strictEqual(response, '\x1b_Gi=100;EINVAL:cannot specify both i and I keys\x1b\\');
});
test('responds with EINVAL for i+I conflict even without payload', async () => {
let response = '';
await ctx.page.evaluate(() => {
(window as any).kittyResponse = '';
(window as any).term.onData((data: string) => { (window as any).kittyResponse = data; });
@@ -667,7 +661,7 @@ test.describe('Kitty Graphics Protocol', () => {
await ctx.proxy.write('\x1b_Gi=101,I=201,a=d\x1b\\');
await timeout(100);
response = await ctx.page.evaluate('window.kittyResponse');
const response = await ctx.page.evaluate('window.kittyResponse');
strictEqual(response, '\x1b_Gi=101;EINVAL:cannot specify both i and I keys\x1b\\');
});
});
+4 -8
View File
@@ -31,15 +31,11 @@
],
"license": "MIT",
"dependencies": {
"lru-cache": "^6.0.0",
"opentype.js": "^0.8.0"
"lru-cache": "^11.3.6",
"opentype.js": "^2.0.0"
},
"devDependencies": {
"@types/lru-cache": "^5.1.0",
"@types/opentype.js": "^0.7.0",
"axios": "^1.15.2",
"font-finder": "^1.1.0",
"mkdirp": "0.5.5",
"yauzl": "^3.2.1"
"@types/opentype.js": "^1.3.9",
"font-finder": "^1.1.0"
}
}
@@ -1,5 +1,5 @@
import * as opentype from 'opentype.js';
import LRUCache = require('lru-cache');
import { LRUCache } from 'lru-cache';
import { IFont, ILigatureData, IFlattenedLookupTree, ILookupTree, IOptions } from './types';
import mergeTrees from './merge';
@@ -23,8 +23,8 @@ class FontImpl implements IFont {
if (options.cacheSize > 0) {
this._cache = new LRUCache({
max: options.cacheSize,
length: ((val: ILigatureData | [number, number][], key: string) => key.length) as any
maxSize: options.cacheSize,
sizeCalculation: ((val: ILigatureData | [number, number][], key: string) => key.length) as any
});
}
@@ -44,7 +44,6 @@ export default function mergeRange(ranges: [number, number][], newRangeStart: nu
// current range
ranges[i - 1][1] = Math.max(newRangeEnd, range[1]);
ranges.splice(i, 1);
inRange = false;
return ranges;
}
// Case 7: New range extends from previous range past the
+3 -3
View File
@@ -174,7 +174,7 @@ export class SearchEngine {
this._terminal.clearSelection();
let startRow = this._terminal.buffer.active.baseY + this._terminal.rows - 1;
let startCol = this._terminal.cols;
const startCol = this._terminal.cols;
const isReverseSearch = true;
this._lineCache.initLinesCache();
@@ -186,14 +186,14 @@ export class SearchEngine {
let result: ISearchResult | undefined;
if (prevSelectedPos) {
searchPosition.startRow = startRow = prevSelectedPos.start.y;
searchPosition.startCol = startCol = prevSelectedPos.start.x;
searchPosition.startCol = prevSelectedPos.start.x;
if (cachedSearchTerm !== term) {
// Try to expand selection to right first.
result = this._findInLine(term, searchPosition, searchOptions, false);
if (!result) {
// If selection was not able to be expanded to the right, then try reverse search
searchPosition.startRow = startRow = prevSelectedPos.end.y;
searchPosition.startCol = startCol = prevSelectedPos.end.x;
searchPosition.startCol = prevSelectedPos.end.x;
}
}
}
@@ -7,10 +7,10 @@
"rootDir": "..",
"outDir": "../out-benchmark",
"types": ["../../../node_modules/@types/node"],
"moduleResolution": "node",
"moduleResolution": "nodenext",
"strict": false,
"target": "es2021",
"module": "commonjs",
"module": "nodenext",
"paths": {
"common/*": ["../../../src/common/*"],
"browser/*": ["../../../src/browser/*"],
+3 -3
View File
@@ -21,8 +21,8 @@
"package": "../../node_modules/.bin/webpack",
"prepublishOnly": "npm run package",
"start": "node ../../demo/start",
"benchmark": "NODE_PATH=../../out:./out:./out-benchmark/ ../../node_modules/.bin/xterm-benchmark -r 5 -c benchmark/benchmark.json",
"benchmark-baseline": "NODE_PATH=../../out:./out:./out-benchmark/ ../../node_modules/.bin/xterm-benchmark -r 5 -c benchmark/benchmark.json --baseline out-benchmark/benchmark/*benchmark.js",
"benchmark-eval": "NODE_PATH=../../out:./out:./out-benchmark/ ../../node_modules/.bin/xterm-benchmark -r 5 -c benchmark/benchmark.json --eval out-benchmark/benchmark/*benchmark.js"
"benchmark": "../../node_modules/.bin/xterm-benchmark -r 5 -c benchmark/benchmark.json",
"benchmark-baseline": "../../node_modules/.bin/xterm-benchmark -r 5 -c benchmark/benchmark.json --baseline out-benchmark/benchmark/*benchmark.js",
"benchmark-eval": "../../node_modules/.bin/xterm-benchmark -r 5 -c benchmark/benchmark.json --eval out-benchmark/benchmark/*benchmark.js"
}
}
+2 -1
View File
@@ -7,7 +7,8 @@
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 { IColor } from 'common/Types';
import { IAttributeData } from 'common/buffer/Types';
import { DEFAULT_ANSI_COLORS } from 'browser/Types';
import { UnderlineStyle } from 'common/buffer/Constants';
@@ -4,10 +4,10 @@
"rootDir": "..",
"outDir": "../out-benchmark",
"types": ["../../../node_modules/@types/node"],
"moduleResolution": "node",
"moduleResolution": "nodenext",
"strict": false,
"target": "es2015",
"module": "commonjs",
"module": "nodenext",
"paths": {
"common/*": ["../../../src/common/*"],
"browser/*": ["../../../src/browser/*"],
+3 -3
View File
@@ -21,8 +21,8 @@
"package": "../../node_modules/.bin/webpack",
"prepublishOnly": "npm run package",
"start": "node ../../demo/start",
"benchmark": "NODE_PATH=../../out:./out:./out-benchmark/ ../../node_modules/.bin/xterm-benchmark -r 5 -c benchmark/benchmark.json out-benchmark/benchmark/*benchmark.js",
"benchmark-baseline": "NODE_PATH=../../out:./out:./out-benchmark/ ../../node_modules/.bin/xterm-benchmark -r 5 -c benchmark/benchmark.json --baseline out-benchmark/benchmark/*benchmark.js",
"benchmark-eval": "NODE_PATH=../../out:./out:./out-benchmark/ ../../node_modules/.bin/xterm-benchmark -r 5 -c benchmark/benchmark.json --eval out-benchmark/benchmark/*benchmark.js"
"benchmark": "../../node_modules/.bin/xterm-benchmark -r 5 -c benchmark/benchmark.json out-benchmark/benchmark/*benchmark.js",
"benchmark-baseline": "../../node_modules/.bin/xterm-benchmark -r 5 -c benchmark/benchmark.json --baseline out-benchmark/benchmark/*benchmark.js",
"benchmark-eval": "../../node_modules/.bin/xterm-benchmark -r 5 -c benchmark/benchmark.json --eval out-benchmark/benchmark/*benchmark.js"
}
}
@@ -78,7 +78,22 @@ test.describe('WebFontsAddon', () => {
const data = await ctx.page.evaluate(`
document.styleSheets[0].insertRule("@font-face {font-family: Kongtext; src: url(/fonts/kongtext.regular.ttf) format('truetype'); unicode-range: U+00A0-00FF}", 0);
`);
deepStrictEqual(await getDocumentFonts(), [{ family: 'Kongtext', status: 'unloaded' }]);
let fonts = await getDocumentFonts();
// safari may have loaded the font, firefox & chrome never load it
if (browser.browserType().name() === 'webkit') {
try {
deepStrictEqual(fonts, [{ family: 'Kongtext', status: 'loading' }]);
} catch (e) {
try {
deepStrictEqual(fonts, [{ family: 'Kongtext', status: 'loaded' }]);
} catch (e) {
deepStrictEqual(fonts, [{ family: 'Kongtext', status: 'unloaded' }]);
}
}
} else {
deepStrictEqual(fonts, [{ family: 'Kongtext', status: 'unloaded' }]);
}
// broken case: webfont in ctor without addon usage
await ctx.page.evaluate(`
@@ -86,15 +101,20 @@ test.describe('WebFontsAddon', () => {
window.helperTerm.open(term.element);
`);
// safari loads the font, firefox & chrome dont
fonts = await getDocumentFonts();
// safari might load the font, firefox & chrome dont
if (browser.browserType().name() === 'webkit') {
try {
deepStrictEqual(await getDocumentFonts(), [{ family: 'Kongtext', status: 'loading' }]);
deepStrictEqual(fonts, [{ family: 'Kongtext', status: 'loading' }]);
} catch (e) {
deepStrictEqual(await getDocumentFonts(), [{ family: 'Kongtext', status: 'loaded' }]);
try {
deepStrictEqual(fonts, [{ family: 'Kongtext', status: 'loaded' }]);
} catch (e) {
deepStrictEqual(fonts, [{ family: 'Kongtext', status: 'unloaded' }]);
}
}
} else {
deepStrictEqual(await getDocumentFonts(), [{ family: 'Kongtext', status: 'unloaded' }]);
deepStrictEqual(fonts, [{ family: 'Kongtext', status: 'unloaded' }]);
}
// good case: addon fixes layout for webfont in ctor
@@ -5,7 +5,6 @@
"lib": [
"es2021",
],
// "downlevelIteration": true,
"rootDir": ".",
"outDir": "../out-test",
"sourceMap": true,
@@ -113,8 +113,8 @@ export class LinkComputer {
let line: IBufferLine | undefined;
let topIdx = lineIndex;
let bottomIdx = lineIndex;
let length = 0;
let content = '';
let length;
let content;
const lines: string[] = [];
if ((line = terminal.buffer.active.getLine(lineIndex))) {
@@ -5,7 +5,6 @@
"lib": [
"es2021",
],
// "downlevelIteration": true,
"rootDir": ".",
"outDir": "../out-test",
"sourceMap": true,
+1 -1
View File
@@ -3,7 +3,7 @@ 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 { ICellData } from 'common/buffer/Types';
import { Terminal } from '@xterm/xterm';
import { rgba } from 'common/Color';
import { treatGlyphAsBackgroundColor } from 'browser/renderer/shared/RendererUtils';
+3 -3
View File
@@ -10,7 +10,7 @@ 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 { CharData, IBufferLine, ICellData } from 'common/buffer/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';
@@ -427,8 +427,8 @@ export class WebglRenderer extends Disposable implements IRenderer {
let line: IBufferLine;
let joinedRanges: [number, number][];
let isJoined: boolean;
let skipJoinedCheckUntilX: number = 0;
let isValidJoinRange: boolean = true;
let skipJoinedCheckUntilX: number;
let isValidJoinRange: boolean;
let lastCharX: number;
let range: [number, number];
let isCursorRow: boolean;
@@ -5,7 +5,7 @@
import { CustomGlyphDefinitionType, CustomGlyphScaleType, CustomGlyphVectorType, type CustomGlyphCharacterDefinition, type CustomGlyphDefinitionPart, type CustomGlyphPathDrawFunctionDefinition } from './Types';
/* eslint-disable max-len */
/* oxlint-disable @stylistic/max-len -- generated glyph path data */
const enum Shapes {
/** │ */ TOP_TO_BOTTOM = 'M.5,0 L.5,1',
@@ -221,8 +221,8 @@ function drawPathDefinitionCharacter(
const rx = parseFloat(args[0]) * deviceCellWidth;
const ry = parseFloat(args[1]) * deviceCellHeight;
const xAxisRotation = parseFloat(args[2]) * Math.PI / 180;
const largeArcFlag = parseInt(args[3]);
const sweepFlag = parseInt(args[4]);
const largeArcFlag = parseInt(args[3], 10);
const sweepFlag = parseInt(args[4], 10);
const x = xOffset + parseFloat(args[5]) * deviceCellWidth;
const y = yOffset + parseFloat(args[6]) * deviceCellHeight;
drawSvgArc(ctx, currentX, currentY, rx, ry, xAxisRotation, largeArcFlag, sweepFlag, x, y);
-1
View File
@@ -25,7 +25,6 @@
]
},
"strict": true,
"downlevelIteration": true,
"experimentalDecorators": true,
"types": [
"../../../node_modules/@types/mocha"
+41 -11
View File
@@ -33,17 +33,47 @@ if (files.length === 0) {
console.log(`Linting ${files.length} changed file(s)...`);
const eslintArgs = ['--max-warnings', '0', '--no-warn-ignored'];
if (fix) {
eslintArgs.push('--fix');
const cwd = path.join(__dirname, '..');
const isWin = process.platform === 'win32';
/**
* @param {string} command
* @param {string[]} args
* @returns {Promise<void>}
*/
function run(command, args) {
return new Promise(/** @type {(resolve: () => void, reject: (reason: Error) => void) => void} */ ((resolve, reject) => {
const child = spawn(command, args, {
stdio: 'inherit',
cwd,
shell: isWin
});
child.on('close', code => {
if (code === 0) {
resolve();
} else {
reject(new Error(`${command} exited with code ${code}`));
}
});
}));
}
eslintArgs.push(...files);
const eslint = process.platform === 'win32' ? 'eslint.cmd' : 'eslint';
const child = spawn(eslint, eslintArgs, {
stdio: 'inherit',
cwd: path.join(__dirname, '..'),
shell: process.platform === 'win32'
async function main() {
const oxlintArgs = ['--type-aware', '--max-warnings', '0'];
if (fix) {
oxlintArgs.push('--fix');
}
oxlintArgs.push(...files);
const oxlint = isWin ? 'oxlint.cmd' : 'oxlint';
await run(oxlint, oxlintArgs);
const eslintArgs = ['--config', 'eslint.config.naming.mjs', '--max-warnings', '0', ...files];
const eslint = isWin ? 'eslint.cmd' : 'eslint';
await run(eslint, eslintArgs);
}
main().catch(err => {
console.error(err.message);
process.exit(1);
});
child.on('close', code => process.exit(code ?? 0));
+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 so addon path-mapped imports (common/*, browser/*) resolve.
const env = { ...process.env };
env.NODE_PATH = path.resolve(__dirname, '../out-esbuild');
@@ -65,7 +65,8 @@ export class AddonImageWindow extends BaseWindow implements IControlWindow {
const dtIip = document.createElement('dt');
dtIip.textContent = 'IIP (iTerm)';
dlIip.appendChild(dtIip);
this._addDdWithButton(dlIip, 'image-demo3', 'palette');
this._addDdWithButton(dlIip, 'image-demo3', 'palette (png File)');
this._addDdWithButton(dlIip, 'image-demo4', 'dice (qoi MultipartFile)');
container.appendChild(dlIip);
// Kitty demos
@@ -141,6 +142,21 @@ export class AddonImageWindow extends BaseWindow implements IControlWindow {
this._terminal.write(`\x1b]1337;File=inline=1;size=${data.length}:${btoa(sdata)}\x1b\\`);
});
const iipDemoMulti = (url: string) => () => fetch(url)
.then(resp => resp.arrayBuffer())
.then(buffer => {
const data = new Uint8Array(buffer);
let sdata = '';
for (let i = 0; i < data.length; ++i) sdata += String.fromCharCode(data[i]);
const encoded = btoa(sdata);
this._terminal.write('\r\n');
this._terminal.write(`\x1b]1337;MultipartFile=inline=1\x1b\\`);
for (let i = 0; i < encoded.length; i += 100) {
this._terminal.write(`\x1b]1337;FilePart=${encoded.slice(i, i + 100)}\x1b\\`);
}
this._terminal.write(`\x1b]1337;FileEnd\x1b\\`);
});
const kittyDemo = (url: string) => () => fetch(url)
.then(resp => resp.arrayBuffer())
.then(buffer => {
@@ -158,6 +174,8 @@ export class AddonImageWindow extends BaseWindow implements IControlWindow {
sixelDemo('https://raw.githubusercontent.com/jerch/node-sixel/master/testfiles/test2.sixel'));
document.getElementById('image-demo3')!.addEventListener('click',
iipDemo('https://raw.githubusercontent.com/jerch/node-sixel/master/palette.png'));
document.getElementById('image-demo4')!.addEventListener('click',
iipDemoMulti('https://raw.githubusercontent.com/jerch/xterm-wasm-parts/master/fixtures/qoi/dice.qoi'));
document.getElementById('image-demo-kitty1')!.addEventListener('click',
kittyDemo('https://raw.githubusercontent.com/jerch/node-sixel/master/palette.png'));
@@ -176,7 +194,7 @@ export class AddonImageWindow extends BaseWindow implements IControlWindow {
// }
// }
const pos = (this._terminal as any)._core._mouseService!.getCoords(ev, (this._terminal as any)._core.screenElement!, this._terminal.cols, this._terminal.rows);
const pos = (this._terminal as any)._core._mouseCoordsService!.getCoords(ev, (this._terminal as any)._core.screenElement!, this._terminal.cols, this._terminal.rows);
const x = pos[0] - 1;
const y = pos[1] - 1;
const canvas = ev.shiftKey
+1 -1
View File
@@ -43,7 +43,7 @@ export class GpuWindow extends BaseWindow implements IControlWindow {
}
private _styleAtlasPage(canvas: HTMLCanvasElement): void {
// eslint-disable-next-line no-restricted-syntax
// oxlint-disable-next-line eslint-js/no-restricted-syntax
const dpr = window.devicePixelRatio;
canvas.style.width = `${canvas.width / dpr}px`;
canvas.style.height = `${canvas.height / dpr}px`;
+1 -1
View File
@@ -43,7 +43,7 @@ export class WebglWindow extends BaseWindow implements IControlWindow {
}
private _styleAtlasPage(canvas: HTMLCanvasElement): void {
// eslint-disable-next-line no-restricted-syntax
// oxlint-disable-next-line eslint-js/no-restricted-syntax
const dpr = window.devicePixelRatio;
canvas.style.width = `${canvas.width / dpr}px`;
canvas.style.height = `${canvas.height / dpr}px`;
+42 -13
View File
@@ -2,25 +2,54 @@
"compilerOptions": {
"module": "commonjs",
"target": "es2021",
"types": [
"node"
],
"outDir": "../out-demo/client",
"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"],
"@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"
],
"*": ["./*"]
}
},
+1 -1
View File
@@ -118,7 +118,7 @@ function startServer(): void {
});
appWs.ws('/terminals/:pid', function (ws, req) {
const term = terminals[parseInt(req.params.pid)];
const term = terminals[parseInt(Array.isArray(req.params.pid) ? req.params.pid[0] : req.params.pid)];
console.log('Connected to terminal ' + term.pid);
temporaryDisposable[term.pid].dispose();
delete temporaryDisposable[term.pid];
+1 -1
View File
@@ -37,7 +37,7 @@ export default tseslint.config(
'@stylistic/comma-spacing': ['warn', { before: false, after: true }],
'@stylistic/indent': ['warn', 2],
'@stylistic/semi': ['warn', 'always'],
'@stylistic/quotes': ['warn', 'single', { allowTemplateLiterals: true }],
'@stylistic/quotes': ['warn', 'single', { allowTemplateLiterals: 'always' }],
'@stylistic/member-delimiter-style': ['warn', {
multiline: { delimiter: 'semi', requireLast: true },
singleline: { delimiter: 'comma', requireLast: false }
+71
View File
@@ -0,0 +1,71 @@
// @ts-check
import tseslint from 'typescript-eslint';
const ignores = [
'addons/*/src/third-party/*.ts',
'**/out/*',
'**/out-test/*',
'**/out-esbuild/*',
'**/out-esbuild-test/*',
'**/inwasm-sdks/*',
'**/node_modules',
'**/*.js',
'**/*.mjs'
];
const namingConventionMain = [
'warn',
{ selector: 'default', format: ['camelCase'], filter: { regex: '^[a-z]', match: true } },
{ selector: 'variable', format: ['camelCase', 'UPPER_CASE'] },
{ selector: 'variable', filter: '^I.+Service$', format: ['PascalCase'], prefix: ['I'] },
{ selector: 'memberLike', modifiers: ['private'], format: ['camelCase'], leadingUnderscore: 'require' },
{ selector: 'memberLike', modifiers: ['protected'], format: ['camelCase'], leadingUnderscore: 'require' },
{ selector: 'enumMember', format: ['UPPER_CASE'] },
{ selector: 'property', modifiers: ['public'], format: ['camelCase', 'UPPER_CASE'], filter: { regex: '^[a-z]', match: true } },
{ selector: 'method', modifiers: ['public'], format: ['camelCase', 'UPPER_CASE'], custom: { regex: '^on[A-Z].+', match: false } },
{ selector: 'method', modifiers: ['private'], format: ['camelCase'], leadingUnderscore: 'require', custom: { regex: '^on[A-Z].+', match: false } },
{ selector: 'method', modifiers: ['protected'], format: ['camelCase'], leadingUnderscore: 'require', custom: { regex: '^on[A-Z].+', match: false } },
{ selector: 'typeLike', format: ['PascalCase'] },
{ selector: 'interface', format: ['PascalCase'], prefix: ['I'] }
];
const namingConventionTypings = [
'warn',
{ selector: 'typeLike', format: ['PascalCase'] },
{ selector: 'interface', format: ['PascalCase'], prefix: ['I'] }
];
export default tseslint.config(
{
ignores
},
{
files: ['**/*.ts'],
ignores: ['typings/**/*.d.ts', 'addons/**/typings/**'],
plugins: {
'@typescript-eslint': tseslint.plugin
},
languageOptions: {
parser: tseslint.parser,
parserOptions: {
projectService: true,
tsconfigRootDir: import.meta.dirname
}
},
rules: {
'@typescript-eslint/naming-convention': namingConventionMain
}
},
{
files: ['typings/**/*.d.ts'],
plugins: {
'@typescript-eslint': tseslint.plugin
},
languageOptions: {
parser: tseslint.parser
},
rules: {
'@typescript-eslint/naming-convention': namingConventionTypings
}
}
);
+1 -1
View File
@@ -17,7 +17,7 @@ export default tseslint.config(
rules: {
'@stylistic/indent': ['warn', 2],
'@stylistic/semi': ['warn', 'always'],
'@stylistic/quotes': ['warn', 'single', { allowTemplateLiterals: true }],
'@stylistic/quotes': ['warn', 'single', { allowTemplateLiterals: 'always' }],
'@stylistic/member-delimiter-style': ['warn', {
multiline: { delimiter: 'semi', requireLast: true },
singleline: { delimiter: 'comma', requireLast: false }
+3113 -2288
View File
File diff suppressed because it is too large Load Diff
+35 -30
View File
@@ -48,11 +48,11 @@
"esbuild-demo-server-watch": "node bin/esbuild.mjs --demo-server --watch",
"test": "npm run test-unit",
"posttest": "npm run lint",
"lint": "eslint --max-warnings 0 src/ addons/ demo/ test/",
"lint": "oxlint --type-aware --max-warnings 0 src addons demo test && eslint --config eslint.config.naming.mjs --max-warnings 0 src addons demo test",
"lint-changes": "node ./bin/lint_changes.js",
"lint-changes-fix": "node ./bin/lint_changes.js --fix",
"lint-fix": "eslint --fix src/ addons/ demo/ test/",
"lint-api": "eslint --config eslint.config.typings.mjs --max-warnings 0 typings/",
"lint-fix": "oxlint --type-aware --fix --max-warnings 0 src addons demo test",
"lint-api": "oxlint --max-warnings 0 -c .oxlintrc.typings.json typings && eslint --config eslint.config.naming.mjs --max-warnings 0 typings",
"test-unit": "node ./bin/test_unit.js",
"test-unit-slow-tests": "npm run test-unit | grep \"ms)\"",
"test-unit-coverage": "node ./bin/test_unit.js --coverage",
@@ -61,9 +61,9 @@
"test-integration-firefox": "node ./bin/test_integration.js --workers=75% \"--project=FirefoxStable\"",
"test-integration-webkit": "node ./bin/test_integration.js --workers=75% \"--project=WebKit\"",
"test-integration-debug": "node ./bin/test_integration.js --workers=1 --headed --timeout=30000",
"benchmark": "NODE_PATH=./out xterm-benchmark -r 5 -c test/benchmark/benchmark.json",
"benchmark-baseline": "NODE_PATH=./out xterm-benchmark -r 5 -c test/benchmark/benchmark.json --baseline out-test/benchmark/*benchmark.js",
"benchmark-eval": "NODE_PATH=./out xterm-benchmark -r 5 -c test/benchmark/benchmark.json --eval out-test/benchmark/*benchmark.js",
"benchmark": "xterm-benchmark -r 5 -c test/benchmark/benchmark.json",
"benchmark-baseline": "xterm-benchmark -r 5 -c test/benchmark/benchmark.json --baseline out-test/benchmark/*benchmark.js",
"benchmark-eval": "xterm-benchmark -r 5 -c test/benchmark/benchmark.json --eval out-test/benchmark/*benchmark.js",
"clean": "rm -rf lib out addons/*/lib addons/*/out",
"vtfeatures": "node bin/extract_vtfeatures.js src/*/*.ts src/*/*/*.ts src/*.ts addons/**/src/*.ts",
"prepackage": "npm run build",
@@ -75,40 +75,45 @@
"prepublishOnly": "npm run package"
},
"devDependencies": {
"@eslint/js": "^10.0.1",
"@lunapaint/png-codec": "^0.2.0",
"@playwright/test": "^1.57.0",
"@stylistic/eslint-plugin": "^4.4.1",
"@types/chai": "^4.2.22",
"@types/express": "4",
"@playwright/test": "^1.59.1",
"@stylistic/eslint-plugin": "^5.10.0",
"@types/chai": "^5.2.3",
"@types/express": "^5.0.6",
"@types/express-ws": "^3.0.1",
"@types/jsdom": "^27.0.0",
"@types/mocha": "^9.0.0",
"@types/jsdom": "^28.0.1",
"@types/mocha": "^10.0.10",
"@types/node": "^22.19.3",
"@types/webpack": "^5.28.0",
"@types/ws": "^8.2.0",
"@typescript-eslint/eslint-plugin": "^8.50.1",
"@typescript-eslint/parser": "^8.50.1",
"@typescript/native-preview": "^7.0.0-dev.20260213.1",
"chai": "^4.3.4",
"@typescript-eslint/eslint-plugin": "^8.59.2",
"@typescript-eslint/parser": "^8.59.2",
"@typescript/native-preview": "^7.0.0-dev.20260509.2",
"chai": "^6.2.2",
"concurrently": "^9.1.2",
"esbuild": "~0.25.2",
"eslint": "^9.39.2",
"eslint-plugin-jsdoc": "^50.8.0",
"express": "^4.19.2",
"esbuild": "^0.28.0",
"eslint": "^10.3.0",
"eslint-plugin-jsdoc": "^62.9.0",
"eslint-plugin-oxlint": "^1.67.0",
"express": "^5.2.1",
"express-ws": "^5.0.2",
"jsdom": "^27.3.0",
"mocha": "^10.1.0",
"jsdom": "^29.1.1",
"mocha": "^11.7.5",
"mustache": "^4.2.0",
"node-pty": "^1.2.0-beta.9",
"nyc": "^17.1.0",
"source-map-loader": "^3.0.0",
"nyc": "^18.0.0",
"oxlint": "^1.67.0",
"oxlint-plugin-eslint": "^1.67.0",
"oxlint-tsgolint": "^0.23.0",
"source-map-loader": "^5.0.0",
"source-map-support": "^0.5.20",
"ts-loader": "^9.3.1",
"ts-loader": "^9.5.7",
"typescript": "^5.9.3",
"typescript-eslint": "^8.50.1",
"webpack": "^5.61.0",
"webpack-cli": "^4.9.1",
"ws": "^8.2.3",
"xterm-benchmark": "^0.3.1"
"typescript-eslint": "^8.59.2",
"webpack": "^5.106.2",
"webpack-cli": "^7.0.2",
"ws": "^8.20.0",
"xterm-benchmark": "^0.3.2"
}
}
+8 -8
View File
@@ -3,14 +3,14 @@
* @license MIT
*/
import * as Strings from 'browser/LocalizableStrings';
import { ITerminal, IRenderDebouncer } from 'browser/Types';
import { TimeBasedDebouncer } from 'browser/TimeBasedDebouncer';
import { Disposable, toDisposable } from 'common/Lifecycle';
import { ICoreBrowserService, IRenderService } from 'browser/services/Services';
import { IBuffer } from 'common/buffer/Types';
import { IInstantiationService } from 'common/services/Services';
import { addDisposableListener } from 'browser/Dom';
import * as Strings from './LocalizableStrings';
import { ITerminal, IRenderDebouncer } from './Types';
import { TimeBasedDebouncer } from './TimeBasedDebouncer';
import { Disposable, toDisposable } from '../common/Lifecycle';
import { ICoreBrowserService, IRenderService } from './services/Services';
import { IBuffer } from '../common/buffer/Types';
import { IInstantiationService } from '../common/services/Services';
import { addDisposableListener } from './Dom';
const enum Constants {
MAX_ROWS_TO_READ = 20
+1 -1
View File
@@ -4,7 +4,7 @@
*/
import { assert } from 'chai';
import * as Clipboard from 'browser/Clipboard';
import * as Clipboard from './Clipboard';
describe('evaluatePastedTextProcessing', () => {
it('should replace carriage return and/or line feed with carriage return', () => {
+2 -2
View File
@@ -3,8 +3,8 @@
* @license MIT
*/
import { ISelectionService } from 'browser/services/Services';
import { ICoreService, IOptionsService } from 'common/services/Services';
import { ISelectionService } from './services/Services';
import { ICoreService, IOptionsService } from '../common/services/Services';
/**
* Prepares text to be pasted into the terminal by normalizing the line endings
+1 -1
View File
@@ -4,7 +4,7 @@
*/
import { assert } from 'chai';
import { ColorContrastCache } from 'browser/ColorContrastCache';
import { ColorContrastCache } from './ColorContrastCache';
describe('ColorContrastCache', () => {
let cache: ColorContrastCache;
+3 -3
View File
@@ -3,9 +3,9 @@
* @license MIT
*/
import { IColorContrastCache } from 'browser/Types';
import { IColor } from 'common/Types';
import { TwoKeyMap } from 'common/MultiKeyMap';
import { IColorContrastCache } from './Types';
import { IColor } from '../common/Types';
import { TwoKeyMap } from '../common/MultiKeyMap';
export class ColorContrastCache implements IColorContrastCache {
private _color: TwoKeyMap</* bg */number, /* fg */number, IColor | null> = new TwoKeyMap();
+39 -38
View File
@@ -22,43 +22,43 @@
*/
import { IDecoration, IDecorationOptions, IDisposable, ILinkProvider, IMarker, IRenderDimensions as IRenderDimensionsApi } from '@xterm/xterm';
import { copyHandler, handlePasteEvent, moveTextAreaUnderMouseCursor, paste, rightClickHandler } from 'browser/Clipboard';
import * as Strings from 'browser/LocalizableStrings';
import { OscLinkProvider } from 'browser/OscLinkProvider';
import { CharacterJoinerHandler, CustomKeyEventHandler, CustomWheelEventHandler, IBrowser, IBufferRange, ICompositionHelper, ILinkifier2, ITerminal } from 'browser/Types';
import { Viewport } from 'browser/Viewport';
import { BufferDecorationRenderer } from 'browser/decorations/BufferDecorationRenderer';
import { OverviewRulerRenderer } from 'browser/decorations/OverviewRulerRenderer';
import { CompositionHelper } from 'browser/input/CompositionHelper';
import { DomRenderer } from 'browser/renderer/dom/DomRenderer';
import { IRenderer } from 'browser/renderer/shared/Types';
import { CharSizeService } from 'browser/services/CharSizeService';
import { CharacterJoinerService } from 'browser/services/CharacterJoinerService';
import { CoreBrowserService } from 'browser/services/CoreBrowserService';
import { LinkProviderService } from 'browser/services/LinkProviderService';
import { MouseCoordsService } from 'browser/services/MouseCoordsService';
import { MouseService } from 'browser/services/MouseService';
import { RenderService } from 'browser/services/RenderService';
import { SelectionService } from 'browser/services/SelectionService';
import { ICharSizeService, ICharacterJoinerService, ICoreBrowserService, IKeyboardService, ILinkProviderService, IMouseCoordsService, IMouseService, IRenderService, ISelectionService, IThemeService } from 'browser/services/Services';
import { ThemeService } from 'browser/services/ThemeService';
import { KeyboardService } from 'browser/services/KeyboardService';
import { channels, color, rgb } from 'common/Color';
import { CoreTerminal } from 'common/CoreTerminal';
import * as Browser from 'common/Platform';
import { ColorRequestType, IColorEvent, ITerminalOptions, KeyboardResultType, SpecialColorIndex } from 'common/Types';
import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine';
import { IBuffer } from 'common/buffer/Types';
import { C0, C1ESCAPED } from 'common/data/EscapeSequences';
import { toRgbString } from 'common/input/XParseColor';
import { DecorationService } from 'common/services/DecorationService';
import { IDecorationService } from 'common/services/Services';
import { copyHandler, handlePasteEvent, moveTextAreaUnderMouseCursor, paste, rightClickHandler } from './Clipboard';
import * as Strings from './LocalizableStrings';
import { OscLinkProvider } from './OscLinkProvider';
import { CharacterJoinerHandler, CustomKeyEventHandler, CustomWheelEventHandler, IBrowser, IBufferRange, ICompositionHelper, ILinkifier2, ITerminal } from './Types';
import { Viewport } from './Viewport';
import { BufferDecorationRenderer } from './decorations/BufferDecorationRenderer';
import { OverviewRulerRenderer } from './decorations/OverviewRulerRenderer';
import { CompositionHelper } from './input/CompositionHelper';
import { DomRenderer } from './renderer/dom/DomRenderer';
import { IRenderer } from './renderer/shared/Types';
import { CharSizeService } from './services/CharSizeService';
import { CharacterJoinerService } from './services/CharacterJoinerService';
import { CoreBrowserService } from './services/CoreBrowserService';
import { LinkProviderService } from './services/LinkProviderService';
import { MouseCoordsService } from './services/MouseCoordsService';
import { MouseEventCssClasses, MouseService } from './services/MouseService';
import { RenderService } from './services/RenderService';
import { SelectionService } from './services/SelectionService';
import { ICharSizeService, ICharacterJoinerService, ICoreBrowserService, IKeyboardService, ILinkProviderService, IMouseCoordsService, IMouseService, IRenderService, ISelectionService, IThemeService } from './services/Services';
import { ThemeService } from './services/ThemeService';
import { KeyboardService } from './services/KeyboardService';
import { channels, color, rgb } from '../common/Color';
import { CoreTerminal } from '../common/CoreTerminal';
import * as Browser from '../common/Platform';
import { ColorRequestType, IColorEvent, ITerminalOptions, KeyboardResultType, SpecialColorIndex } from '../common/Types';
import { DEFAULT_ATTR_DATA } from '../common/buffer/BufferLine';
import { IBuffer } from '../common/buffer/Types';
import { C0, C1ESCAPED } from '../common/data/EscapeSequences';
import { toRgbString } from '../common/input/XParseColor';
import { DecorationService } from '../common/services/DecorationService';
import { IDecorationService } from '../common/services/Services';
import { WindowsOptionsReportType } from '../common/InputHandler';
import { AccessibilityManager } from './AccessibilityManager';
import { Linkifier } from './Linkifier';
import { Emitter, EventUtils, type IEvent } from 'common/Event';
import { addDisposableListener } from 'browser/Dom';
import { MutableDisposable, toDisposable } from 'common/Lifecycle';
import { Emitter, EventUtils, type IEvent } from '../common/Event';
import { addDisposableListener } from './Dom';
import { MutableDisposable, toDisposable } from '../common/Lifecycle';
export class CoreBrowserTerminal extends CoreTerminal implements ITerminal {
public textarea: HTMLTextAreaElement | undefined;
@@ -212,7 +212,7 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal {
if (!this._themeService) return;
for (const req of event) {
let acc: 'foreground' | 'background' | 'cursor' | 'ansi';
let ident = '';
let ident: string;
switch (req.index) {
case SpecialColorIndex.FOREGROUND: // OSC 10 | 110
acc = 'foreground';
@@ -505,7 +505,7 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal {
this.textarea,
parent.ownerDocument.defaultView ?? window,
// Force unsafe null in node.js environment for tests
this._document ?? (typeof window !== 'undefined') ? window.document : null as any
this._document ?? ((typeof window !== 'undefined') ? window.document : null as any)
));
this._instantiationService.setService(ICoreBrowserService, this._coreBrowserService);
@@ -618,11 +618,12 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal {
this._register(addDisposableListener(this.element, 'mousedown', (e: MouseEvent) => this._selectionService!.handleMouseDown(e)));
// apply mouse event classes set by escape codes before terminal was attached
if (this.mouseStateService.areMouseEventsActive) {
if (this.mouseStateService.areMouseEventsActive && !this.options.mouseEventsRequireAlt) {
this._selectionService.disable();
this.element.classList.add('enable-mouse-events');
this.element.classList.add(MouseEventCssClasses.ENABLE_MOUSE_EVENTS);
} else {
this._selectionService.enable();
this.element.classList.remove(MouseEventCssClasses.ENABLE_MOUSE_EVENTS);
}
if (this.options.screenReaderMode) {
+2 -2
View File
@@ -5,8 +5,8 @@
* Minimal DOM helpers for xterm.js browser code.
*/
import { IntervalTimer } from 'common/Async';
import { IDisposable } from 'common/Lifecycle';
import { IntervalTimer } from '../common/Async';
import { IDisposable } from '../common/Lifecycle';
export function getWindow(e: Node | UIEvent | undefined | null): Window {
const candidateNode = e as Node | undefined | null;
+4 -4
View File
@@ -4,11 +4,11 @@
*/
import { assert } from 'chai';
import { IBufferService } from 'common/services/Services';
import { IBufferService } from '../common/services/Services';
import { Linkifier } from './Linkifier';
import { MockBufferService } from 'common/TestUtils.test';
import { ILink } from 'browser/Types';
import { LinkProviderService } from 'browser/services/LinkProviderService';
import { MockBufferService } from '../common/TestUtils.test';
import { ILink } from './Types';
import { LinkProviderService } from './services/LinkProviderService';
import jsdom = require('jsdom');
class TestLinkifier2 extends Linkifier {
+6 -6
View File
@@ -3,13 +3,13 @@
* @license MIT
*/
import { IBufferCellPosition, ILink, ILinkDecorations, ILinkWithState, ILinkifier2, ILinkifierEvent } from 'browser/Types';
import { Disposable, dispose, toDisposable } from 'common/Lifecycle';
import { IDisposable } from 'common/Types';
import { IBufferService } from 'common/services/Services';
import { IBufferCellPosition, ILink, ILinkDecorations, ILinkWithState, ILinkifier2, ILinkifierEvent } from './Types';
import { Disposable, dispose, toDisposable } from '../common/Lifecycle';
import { IDisposable } from '../common/Types';
import { IBufferService } from '../common/services/Services';
import { ILinkProviderService, IMouseCoordsService, IRenderService } from './services/Services';
import { Emitter } from 'common/Event';
import { addDisposableListener } from 'browser/Dom';
import { Emitter } from '../common/Event';
import { addDisposableListener } from './Dom';
export class Linkifier extends Disposable implements ILinkifier2 {
public get currentLink(): ILinkWithState | undefined { return this._currentLink; }
+6 -5
View File
@@ -4,11 +4,12 @@
*/
import { assert } from 'chai';
import { OscLinkProvider } from 'browser/OscLinkProvider';
import { ILink } from 'browser/Types';
import { createCellData, MockBufferService, MockOptionsService } from 'common/TestUtils.test';
import { IBufferService, IOscLinkService } from 'common/services/Services';
import { IBufferLine, IOscLinkData } from 'common/Types';
import { OscLinkProvider } from './OscLinkProvider';
import { ILink } from './Types';
import { createCellData, MockBufferService, MockOptionsService } from '../common/TestUtils.test';
import { IBufferService, IOscLinkService } from '../common/services/Services';
import { IOscLinkData } from '../common/Types';
import { IBufferLine } from '../common/buffer/Types';
class TestOscLinkService implements IOscLinkService {
public serviceBrand: any;
+5 -5
View File
@@ -3,11 +3,11 @@
* @license MIT
*/
import { IBufferRange, ILink } from 'browser/Types';
import { ILinkProvider } from 'browser/services/Services';
import { CellData } from 'common/buffer/CellData';
import { IBufferLine } from 'common/Types';
import { IBufferService, IOptionsService, IOscLinkService } from 'common/services/Services';
import { IBufferRange, ILink } from './Types';
import { ILinkProvider } from './services/Services';
import { CellData } from '../common/buffer/CellData';
import { IBufferLine } from '../common/buffer/Types';
import { IBufferService, IOptionsService, IOscLinkService } from '../common/services/Services';
export class OscLinkProvider implements ILinkProvider {
private readonly _workCell = new CellData();
+5 -5
View File
@@ -3,8 +3,8 @@
* @license MIT
*/
import { IRenderDebouncerWithCallback } from 'browser/Types';
import { ICoreBrowserService } from 'browser/services/Services';
import { IRenderDebouncerWithCallback } from './Types';
import { ICoreBrowserService } from './services/Services';
/**
* Debounces calls to render terminal rows using animation frames.
@@ -23,7 +23,7 @@ export class RenderDebouncer implements IRenderDebouncerWithCallback {
}
public dispose(): void {
if (this._animationFrame) {
if (this._animationFrame !== undefined) {
this._coreBrowserService.window.cancelAnimationFrame(this._animationFrame);
this._animationFrame = undefined;
}
@@ -31,7 +31,7 @@ export class RenderDebouncer implements IRenderDebouncerWithCallback {
public addRefreshCallback(callback: FrameRequestCallback): number {
this._refreshCallbacks.push(callback);
if (!this._animationFrame) {
if (this._animationFrame === undefined) {
this._animationFrame = this._coreBrowserService.window.requestAnimationFrame(() => this._innerRefresh());
}
return this._animationFrame;
@@ -46,7 +46,7 @@ export class RenderDebouncer implements IRenderDebouncerWithCallback {
this._rowStart = this._rowStart !== undefined ? Math.min(this._rowStart, rowStart) : rowStart;
this._rowEnd = this._rowEnd !== undefined ? Math.max(this._rowEnd, rowEnd) : rowEnd;
if (this._animationFrame) {
if (this._animationFrame !== undefined) {
return;
}
+6 -6
View File
@@ -3,13 +3,13 @@
* @license MIT
*/
import { MockCompositionHelper, MockRenderer, MockViewport, TestTerminal } from 'browser/TestUtils.test';
import type { IBrowser } from 'browser/Types';
import { MockCompositionHelper, MockRenderer, MockViewport, TestTerminal } from './TestUtils.test';
import type { IBrowser } from './Types';
import { assert } from 'chai';
import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine';
import { CellData } from 'common/buffer/CellData';
import { MockUnicodeService, createCellData } from 'common/TestUtils.test';
import { IMarker } from 'common/Types';
import { DEFAULT_ATTR_DATA } from '../common/buffer/BufferLine';
import { CellData } from '../common/buffer/CellData';
import { MockUnicodeService, createCellData } from '../common/TestUtils.test';
import { IMarker } from '../common/buffer/Types';
const INIT_COLS = 80;
const INIT_ROWS = 24;
+3 -3
View File
@@ -7,14 +7,14 @@ import * as path from 'path';
import * as os from 'os';
import * as fs from 'fs';
import * as pty from 'node-pty';
import { CoreBrowserTerminal } from 'browser/CoreBrowserTerminal';
import { CoreBrowserTerminal } from './CoreBrowserTerminal';
import { IDisposable } from '@xterm/xterm';
// all test files expect terminal in 80x25
const COLS = 80;
const ROWS = 25;
const escapeSequenceFilesDir = path.join(__dirname, '../../fixtures/escape_sequence_files');
const escapeSequenceFilesDir = path.join(__dirname, '../../test/fixtures/escape_sequence_files');
const TESTFILES = fs.readdirSync(escapeSequenceFilesDir)
.filter(f => f.endsWith('.in'))
.map(f => path.join(escapeSequenceFilesDir, f));
@@ -125,7 +125,7 @@ function formatError(input: string, output: string, expected: string): string {
// simple debug output of terminal cells
function terminalToString(term: CoreBrowserTerminal): string {
let result = '';
let lineText = '';
let lineText: string;
for (let line = term.buffer.ybase; line < term.buffer.ybase + term.rows; line++) {
lineText = term.buffer.lines.get(line)!.translateToString(true);
// rtrim empty cells as xterm does
+16 -15
View File
@@ -4,21 +4,22 @@
*/
import { IDisposable, IMarker, ILinkProvider, IDecorationOptions, IDecoration, IRenderDimensions as IRenderDimensionsApi } from '@xterm/xterm';
import { ICharacterJoinerService, ICharSizeService, ICoreBrowserService, IMouseService, IRenderService, ISelectionService, IThemeService } from 'browser/services/Services';
import { IRenderDimensions, IRenderer, IRequestRedrawEvent } from 'browser/renderer/shared/Types';
import { IColorSet, ITerminal, ILinkifier2, IBrowser, IViewport, ICompositionHelper, CharacterJoinerHandler, IBufferRange, ReadonlyColorSet, IBufferElementProvider } from 'browser/Types';
import { IBuffer, IBufferSet } from 'common/buffer/Types';
import { IBufferLine, ICellData, IAttributeData, ICircularList, XtermListener, ICharset, ITerminalOptions, ColorIndex } from 'common/Types';
import { Buffer } from 'common/buffer/Buffer';
import * as Browser from 'common/Platform';
import { CoreBrowserTerminal } from 'browser/CoreBrowserTerminal';
import { IUnicodeService, IOptionsService, ICoreService, IMouseStateService } from 'common/services/Services';
import { IFunctionIdentifier, IParams } from 'common/parser/Types';
import { AttributeData } from 'common/buffer/AttributeData';
import { ISelectionRedrawRequestEvent, ISelectionRequestScrollLinesEvent } from 'browser/selection/Types';
import { css } from 'common/Color';
import { createRenderDimensions } from 'browser/renderer/shared/RendererUtils';
import { Emitter, type IEvent } from 'common/Event';
import { ICharacterJoinerService, ICharSizeService, ICoreBrowserService, IMouseService, IRenderService, ISelectionService, IThemeService } from './services/Services';
import { IRenderDimensions, IRenderer, IRequestRedrawEvent } from './renderer/shared/Types';
import { IColorSet, ITerminal, ILinkifier2, IBrowser, IViewport, ICompositionHelper, CharacterJoinerHandler, IBufferRange, ReadonlyColorSet, IBufferElementProvider } from './Types';
import { IAttributeData, IBuffer, IBufferLine, IBufferSet, ICellData } from '../common/buffer/Types';
import { ICircularList } from '../common/CircularList';
import { XtermListener, ICharset, ITerminalOptions, ColorIndex } from '../common/Types';
import { Buffer } from '../common/buffer/Buffer';
import * as Browser from '../common/Platform';
import { CoreBrowserTerminal } from './CoreBrowserTerminal';
import { IUnicodeService, IOptionsService, ICoreService, IMouseStateService } from '../common/services/Services';
import { IFunctionIdentifier, IParams } from '../common/parser/Types';
import { AttributeData } from '../common/buffer/AttributeData';
import { ISelectionRedrawRequestEvent, ISelectionRequestScrollLinesEvent } from './selection/Types';
import { css } from '../common/Color';
import { createRenderDimensions } from './renderer/shared/RendererUtils';
import { Emitter, type IEvent } from '../common/Event';
export class TestTerminal extends CoreBrowserTerminal {
public get curAttrData(): IAttributeData { return (this as any)._inputHandler._curAttrData; }
+10 -3
View File
@@ -3,9 +3,9 @@
* @license MIT
*/
const RENDER_DEBOUNCE_THRESHOLD_MS = 1000; // 1 Second
import { IRenderDebouncer } from './Types';
import { IRenderDebouncer } from 'browser/Types';
const RENDER_DEBOUNCE_THRESHOLD_MS = 1000; // 1 Second
/**
* Debounces calls to update screen readers to update at most once configurable interval of time.
@@ -31,7 +31,9 @@ export class TimeBasedDebouncer implements IRenderDebouncer {
public dispose(): void {
if (this._refreshTimeoutID) {
clearTimeout(this._refreshTimeoutID);
this._refreshTimeoutID = undefined;
}
this._additionalRefreshRequested = false;
}
public refresh(rowStart: number | undefined, rowEnd: number | undefined, rowCount: number): void {
@@ -47,7 +49,12 @@ export class TimeBasedDebouncer implements IRenderDebouncer {
// enough time to pass before refreshing again.
const refreshRequestTime: number = performance.now();
if (refreshRequestTime - this._lastRefreshMs >= this._debounceThresholdMS) {
// Enough time has lapsed since the last refresh; refresh immediately
// Enough time has elapsed since the last refresh; refresh immediately
if (this._refreshTimeoutID !== undefined) {
clearTimeout(this._refreshTimeoutID);
this._refreshTimeoutID = undefined;
this._additionalRefreshRequested = false;
}
this._lastRefreshMs = refreshRequestTime;
this._innerRefresh();
} else if (!this._additionalRefreshRequested) {
+5 -4
View File
@@ -3,11 +3,12 @@
* @license MIT
*/
import { CharData, IColor, ICoreTerminal, ITerminalOptions } from 'common/Types';
import { IBuffer } from 'common/buffer/Types';
import { IColor, ITerminalOptions } from '../common/Types';
import { CharData, IBuffer } from '../common/buffer/Types';
import { ICoreTerminal } from '../common/CoreTerminal';
import { IDisposable, IRenderDimensions as IRenderDimensionsApi, Terminal as ITerminalApi } from '@xterm/xterm';
import { channels, css } from 'common/Color';
import type { IEvent } from 'common/Event';
import { channels, css } from '../common/Color';
import type { IEvent } from '../common/Event';
/**
* A portion of the public API that are implemented identially internally and simply passed through.
+10 -10
View File
@@ -3,16 +3,16 @@
* @license MIT
*/
import { ICoreBrowserService, IRenderService, IThemeService } from 'browser/services/Services';
import { ViewportConstants } from 'browser/shared/Constants';
import { Disposable, toDisposable } from 'common/Lifecycle';
import { IBufferService, ICoreService, IMouseStateService, IOptionsService } from 'common/services/Services';
import { CoreMouseEventType } from 'common/Types';
import { scheduleAtNextAnimationFrame } from 'browser/Dom';
import { SmoothScrollableElement } from 'browser/scrollable/scrollableElement';
import type { IScrollableElementChangeOptions } from 'browser/scrollable/scrollableElementOptions';
import { Emitter, EventUtils } from 'common/Event';
import { Scrollable, ScrollbarVisibility, type IScrollEvent } from 'browser/scrollable/scrollable';
import { ICoreBrowserService, IRenderService, IThemeService } from './services/Services';
import { ViewportConstants } from './shared/Constants';
import { Disposable, toDisposable } from '../common/Lifecycle';
import { IBufferService, ICoreService, IMouseStateService, IOptionsService } from '../common/services/Services';
import { CoreMouseEventType } from '../common/Types';
import { scheduleAtNextAnimationFrame } from './Dom';
import { SmoothScrollableElement } from './scrollable/scrollableElement';
import type { IScrollableElementChangeOptions } from './scrollable/scrollableElementOptions';
import { Emitter, EventUtils } from '../common/Event';
import { Scrollable, ScrollbarVisibility, type IScrollEvent } from './scrollable/scrollable';
export class Viewport extends Disposable {
@@ -3,9 +3,9 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ICoreBrowserService, IRenderService } from 'browser/services/Services';
import { Disposable, toDisposable } from 'common/Lifecycle';
import { IBufferService, IDecorationService, IInternalDecoration } from 'common/services/Services';
import { ICoreBrowserService, IRenderService } from '../services/Services';
import { Disposable, toDisposable } from '../../common/Lifecycle';
import { IBufferService, IDecorationService, IInternalDecoration } from '../../common/services/Services';
export class BufferDecorationRenderer extends Disposable {
private readonly _container: HTMLElement;
@@ -4,7 +4,7 @@
*/
import { assert } from 'chai';
import { ColorZoneStore } from 'browser/decorations/ColorZoneStore';
import { ColorZoneStore } from './ColorZoneStore';
const optionsRedFull = {
overviewRulerOptions: {
+1 -1
View File
@@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IInternalDecoration } from 'common/services/Services';
import { IInternalDecoration } from '../../common/services/Services';
export interface IColorZoneStore {
readonly zones: IColorZone[];
@@ -3,10 +3,10 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ColorZoneStore, IColorZone, IColorZoneStore } from 'browser/decorations/ColorZoneStore';
import { ICoreBrowserService, IRenderService, IThemeService } from 'browser/services/Services';
import { Disposable, toDisposable } from 'common/Lifecycle';
import { IBufferService, IDecorationService, IOptionsService } from 'common/services/Services';
import { ColorZoneStore, IColorZone, IColorZoneStore } from './ColorZoneStore';
import { ICoreBrowserService, IRenderService, IThemeService } from '../services/Services';
import { Disposable, toDisposable } from '../../common/Lifecycle';
import { IBufferService, IDecorationService, IOptionsService } from '../../common/services/Services';
const enum Constants {
OVERVIEW_RULER_BORDER_WIDTH = 1
+3 -3
View File
@@ -4,9 +4,9 @@
*/
import { assert } from 'chai';
import { CompositionHelper } from 'browser/input/CompositionHelper';
import { MockRenderService } from 'browser/TestUtils.test';
import { MockCoreService, MockBufferService, MockOptionsService } from 'common/TestUtils.test';
import { CompositionHelper } from './CompositionHelper';
import { MockRenderService } from '../TestUtils.test';
import { MockCoreService, MockBufferService, MockOptionsService } from '../../common/TestUtils.test';
describe('CompositionHelper', () => {
let compositionHelper: CompositionHelper;
+3 -3
View File
@@ -3,9 +3,9 @@
* @license MIT
*/
import { IRenderService } from 'browser/services/Services';
import { IBufferService, ICoreService, IOptionsService } from 'common/services/Services';
import { C0 } from 'common/data/EscapeSequences';
import { IRenderService } from '../services/Services';
import { IBufferService, ICoreService, IOptionsService } from '../../common/services/Services';
import { C0 } from '../../common/data/EscapeSequences';
interface IPosition {
start: number;
+1 -1
View File
@@ -5,7 +5,7 @@
import jsdom = require('jsdom');
import { assert } from 'chai';
import { getCoords } from 'browser/input/Mouse';
import { getCoords } from './Mouse';
const CHAR_WIDTH = 10;
const CHAR_HEIGHT = 20;
+3 -7
View File
@@ -6,8 +6,8 @@
export function getCoordsRelativeToElement(window: Pick<Window, 'getComputedStyle'>, event: {clientX: number, clientY: number}, element: HTMLElement): [number, number] {
const rect = element.getBoundingClientRect();
const elementStyle = window.getComputedStyle(element);
const leftPadding = parseInt(elementStyle.getPropertyValue('padding-left'));
const topPadding = parseInt(elementStyle.getPropertyValue('padding-top'));
const leftPadding = parseInt(elementStyle.getPropertyValue('padding-left'), 10);
const topPadding = parseInt(elementStyle.getPropertyValue('padding-top'), 10);
return [
event.clientX - rect.left - leftPadding,
event.clientY - rect.top - topPadding
@@ -22,7 +22,7 @@ export function getCoordsRelativeToElement(window: Pick<Window, 'getComputedStyl
* @param event The mouse event.
* @param element The terminal's container element.
* @param colCount The number of columns in the terminal.
* @param rowCount The number of rows n the terminal.
* @param rowCount The number of rows in the terminal.
* @param hasValidCharSize Whether there is a valid character size available.
* @param cssCellWidth The cell width device pixel render dimensions.
* @param cssCellHeight The cell height device pixel render dimensions.
@@ -37,10 +37,6 @@ export function getCoords(window: Pick<Window, 'getComputedStyle'>, event: Pick<
}
const coords = getCoordsRelativeToElement(window, event, element);
if (!coords) {
return undefined;
}
coords[0] = Math.ceil((coords[0] + (isSelection ? cssCellWidth / 2 : 0)) / cssCellWidth);
coords[1] = Math.ceil(coords[1] / cssCellHeight);
+2 -2
View File
@@ -4,8 +4,8 @@
*/
import { assert } from 'chai';
import { IBufferService } from 'common/services/Services';
import { MockBufferService } from 'common/TestUtils.test';
import { IBufferService } from '../../common/services/Services';
import { MockBufferService } from '../../common/TestUtils.test';
import { moveToCellSequence } from './MoveToCell';
describe('MoveToCell', () => {
+3 -3
View File
@@ -3,8 +3,8 @@
* @license MIT
*/
import { C0 } from 'common/data/EscapeSequences';
import { IBufferService } from 'common/services/Services';
import { C0 } from '../../common/data/EscapeSequences';
import { IBufferService } from '../../common/services/Services';
const enum Direction {
UP = 'A',
@@ -156,7 +156,7 @@ function wrappedRowsForRow(currentRow: number, bufferService: IBufferService): n
*/
function horizontalDirection(startX: number, startY: number, targetX: number, targetY: number, bufferService: IBufferService, applicationCursor: boolean): Direction {
let startRow;
if (moveToRequestedRow(targetX, targetY, bufferService, applicationCursor).length > 0) {
if (moveToRequestedRow(startY, targetY, bufferService, applicationCursor).length > 0) {
startRow = targetY - wrappedRowsForRow(targetY, bufferService);
} else {
startRow = startY;
+10 -10
View File
@@ -3,17 +3,17 @@
* @license MIT
*/
import * as Strings from 'browser/LocalizableStrings';
import { CoreBrowserTerminal as TerminalCore } from 'browser/CoreBrowserTerminal';
import { IBufferRange, ITerminal } from 'browser/Types';
import { Disposable } from 'common/Lifecycle';
import { ITerminalOptions } from 'common/Types';
import { AddonManager } from 'common/public/AddonManager';
import { BufferNamespaceApi } from 'common/public/BufferNamespaceApi';
import { ParserApi } from 'common/public/ParserApi';
import { UnicodeApi } from 'common/public/UnicodeApi';
import * as Strings from '../LocalizableStrings';
import { CoreBrowserTerminal as TerminalCore } from '../CoreBrowserTerminal';
import { IBufferRange, ITerminal } from '../Types';
import { Disposable } from '../../common/Lifecycle';
import { ITerminalOptions } from '../../common/Types';
import { AddonManager } from '../../common/public/AddonManager';
import { BufferNamespaceApi } from '../../common/public/BufferNamespaceApi';
import { ParserApi } from '../../common/public/ParserApi';
import { UnicodeApi } from '../../common/public/UnicodeApi';
import { IBufferNamespace as IBufferNamespaceApi, IDecoration, IDecorationOptions, IDisposable, ILinkProvider, ILocalizableStrings, IMarker, IModes, IParser, IRenderDimensions, ITerminalAddon, Terminal as ITerminalApi, ITerminalInitOnlyOptions, IUnicodeHandling } from '@xterm/xterm';
import type { IEvent } from 'common/Event';
import type { IEvent } from '../../common/Event';
/**
* The set of options that only have an effect when set in the Terminal constructor.
+15 -15
View File
@@ -3,20 +3,20 @@
* @license MIT
*/
import { DomRendererRowFactory, RowCss } from 'browser/renderer/dom/DomRendererRowFactory';
import { WidthCache } from 'browser/renderer/dom/WidthCache';
import { INVERTED_DEFAULT_COLOR, RendererConstants } from 'browser/renderer/shared/Constants';
import { createRenderDimensions } from 'browser/renderer/shared/RendererUtils';
import { createSelectionRenderModel } from 'browser/renderer/shared/SelectionRenderModel';
import { TextBlinkStateManager } from 'browser/renderer/shared/TextBlinkStateManager';
import { IRenderDimensions, IRenderer, IRequestRedrawEvent, ISelectionRenderModel } from 'browser/renderer/shared/Types';
import { ICharSizeService, ICoreBrowserService, IThemeService } from 'browser/services/Services';
import { ILinkifier2, ILinkifierEvent, ITerminal, ReadonlyColorSet } from 'browser/Types';
import { color } from 'common/Color';
import { Disposable, toDisposable } from 'common/Lifecycle';
import { IBufferService, ICoreService, IInstantiationService, IOptionsService } from 'common/services/Services';
import { Emitter } from 'common/Event';
import { addDisposableListener } from 'browser/Dom';
import { DomRendererRowFactory, RowCss } from './DomRendererRowFactory';
import { WidthCache } from './WidthCache';
import { INVERTED_DEFAULT_COLOR, RendererConstants } from '../shared/Constants';
import { createRenderDimensions } from '../shared/RendererUtils';
import { createSelectionRenderModel } from '../shared/SelectionRenderModel';
import { TextBlinkStateManager } from '../shared/TextBlinkStateManager';
import { IRenderDimensions, IRenderer, IRequestRedrawEvent, ISelectionRenderModel } from '../shared/Types';
import { ICharSizeService, ICoreBrowserService, IThemeService } from '../../services/Services';
import { ILinkifier2, ILinkifierEvent, ITerminal, ReadonlyColorSet } from '../../Types';
import { color } from '../../../common/Color';
import { Disposable, toDisposable } from '../../../common/Lifecycle';
import { IBufferService, ICoreService, IInstantiationService, IOptionsService } from '../../../common/services/Services';
import { Emitter } from '../../../common/Event';
import { addDisposableListener } from '../../Dom';
const enum Constants {
@@ -694,7 +694,7 @@ class CursorBlinkStateManager {
}
private _clearIdleTimer(): void {
if (this._idleTimeout) {
if (this._idleTimeout !== undefined) {
this._coreBrowserService.window.clearTimeout(this._idleTimeout);
this._idleTimeout = undefined;
}
@@ -5,15 +5,15 @@
import jsdom = require('jsdom');
import { assert } from 'chai';
import { DomRendererRowFactory } from 'browser/renderer/dom/DomRendererRowFactory';
import { NULL_CELL_CODE, NULL_CELL_WIDTH, NULL_CELL_CHAR, DEFAULT_ATTR, FgFlags, BgFlags, Attributes, UnderlineStyle } from 'common/buffer/Constants';
import { BufferLine, DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine';
import { BufferLineStringCache } from 'common/buffer/BufferLineStringCache';
import { IBufferLine } from 'common/Types';
import { CellData } from 'common/buffer/CellData';
import { MockCoreService, MockDecorationService, MockOptionsService, createCellData, NULL_CELL_DATA } from 'common/TestUtils.test';
import { MockCharacterJoinerService, MockCoreBrowserService, MockThemeService } from 'browser/TestUtils.test';
import { TestWidthCache } from 'browser/renderer/dom/WidthCache.test';
import { DomRendererRowFactory } from './DomRendererRowFactory';
import { NULL_CELL_CODE, NULL_CELL_WIDTH, NULL_CELL_CHAR, DEFAULT_ATTR, FgFlags, BgFlags, Attributes, UnderlineStyle } from '../../../common/buffer/Constants';
import { BufferLine, DEFAULT_ATTR_DATA } from '../../../common/buffer/BufferLine';
import { BufferLineStringCache } from '../../../common/buffer/BufferLineStringCache';
import { IBufferLine } from '../../../common/buffer/Types';
import { CellData } from '../../../common/buffer/CellData';
import { MockCoreService, MockDecorationService, MockOptionsService, createCellData, NULL_CELL_DATA } from '../../../common/TestUtils.test';
import { MockCharacterJoinerService, MockCoreBrowserService, MockThemeService } from '../../TestUtils.test';
import { TestWidthCache } from './WidthCache.test';
const TEST_STRING_CACHE = new BufferLineStringCache();
@@ -3,18 +3,19 @@
* @license MIT
*/
import { IBufferLine, ICellData, IColor } from 'common/Types';
import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/shared/Constants';
import { WHITESPACE_CELL_CHAR, Attributes } from 'common/buffer/Constants';
import { CellData } from 'common/buffer/CellData';
import { ICoreService, IDecorationService, IOptionsService } from 'common/services/Services';
import { channels, color } from 'common/Color';
import { ICharacterJoinerService, ICoreBrowserService, IThemeService } from 'browser/services/Services';
import { JoinedCellData } from 'browser/services/CharacterJoinerService';
import { treatGlyphAsBackgroundColor } from 'browser/renderer/shared/RendererUtils';
import { AttributeData } from 'common/buffer/AttributeData';
import { WidthCache } from 'browser/renderer/dom/WidthCache';
import { IColorContrastCache } from 'browser/Types';
import { IColor } from '../../../common/Types';
import { IBufferLine, ICellData } from '../../../common/buffer/Types';
import { INVERTED_DEFAULT_COLOR } from '../shared/Constants';
import { WHITESPACE_CELL_CHAR, Attributes } from '../../../common/buffer/Constants';
import { CellData } from '../../../common/buffer/CellData';
import { ICoreService, IDecorationService, IOptionsService } from '../../../common/services/Services';
import { channels, color } from '../../../common/Color';
import { ICharacterJoinerService, ICoreBrowserService, IThemeService } from '../../services/Services';
import { JoinedCellData } from '../../services/CharacterJoinerService';
import { treatGlyphAsBackgroundColor } from '../shared/RendererUtils';
import { AttributeData } from '../../../common/buffer/AttributeData';
import { WidthCache } from './WidthCache';
import { IColorContrastCache } from '../../Types';
export const enum RowCss {
@@ -90,14 +91,14 @@ export class DomRendererRowFactory {
let charElement: HTMLSpanElement | undefined;
let cellAmount = 0;
let text = '';
let i = 0;
let i;
let oldBg = 0;
let oldFg = 0;
let oldExt = 0;
let oldLinkHover: number | boolean = false;
let oldSpacing = 0;
let oldIsInSelection: boolean = false;
let spacing = 0;
let spacing;
let skipJoinedCheckUntilX = 0;
const classes: string[] = [];
+1 -1
View File
@@ -4,7 +4,7 @@
*/
import * as assert from 'assert';
import { IWidthCacheFontVariantCanvas, WidthCache, WidthCacheSettings } from 'browser/renderer/dom/WidthCache';
import { IWidthCacheFontVariantCanvas, WidthCache, WidthCacheSettings } from './WidthCache';
class MockWidthCacheFontVariantCanvas implements IWidthCacheFontVariantCanvas {
+4 -4
View File
@@ -3,9 +3,9 @@
* @license MIT
*/
import { throwIfFalsy } from 'browser/renderer/shared/RendererUtils';
import { IDisposable } from 'common/Types';
import { FontWeight } from 'common/services/Services';
import { throwIfFalsy } from '../shared/RendererUtils';
import { IDisposable } from '../../../common/Types';
import { FontWeight } from '../../../common/services/Services';
export const enum WidthCacheSettings {
@@ -110,7 +110,7 @@ export class WidthCache implements IDisposable {
* `variant` denotes the font variant to be used.
*/
public get(c: string, bold: boolean | number, italic: boolean | number): number {
let cp = 0;
let cp: number;
if (!bold && !italic && c.length === 1 && (cp = c.charCodeAt(0)) < WidthCacheSettings.FLAT_SIZE) {
if (this._flat[cp] !== WidthCacheSettings.FLAT_UNSET) {
return this._flat[cp];
@@ -3,7 +3,7 @@
* @license MIT
*/
import { computeNextVariantOffset } from 'browser/renderer/shared/RendererUtils';
import { computeNextVariantOffset } from './RendererUtils';
import { assert } from 'chai';
describe('RendererUtils', () => {
+1 -1
View File
@@ -3,7 +3,7 @@
* @license MIT
*/
import { IDimensions, IRenderDimensions } from 'browser/renderer/shared/Types';
import { IDimensions, IRenderDimensions } from './Types';
export function throwIfFalsy<T>(value: T | undefined | null): T {
if (!value) {
@@ -3,8 +3,8 @@
* @license MIT
*/
import { ITerminal } from 'browser/Types';
import { ISelectionRenderModel } from 'browser/renderer/shared/Types';
import { ITerminal } from '../../Types';
import { ISelectionRenderModel } from './Types';
import { Terminal } from '@xterm/xterm';
class SelectionRenderModel implements ISelectionRenderModel {
@@ -4,10 +4,10 @@
*/
import { assert } from 'chai';
import { TextBlinkStateManager } from 'browser/renderer/shared/TextBlinkStateManager';
import { MockOptionsService } from 'common/TestUtils.test';
import type { ICoreBrowserService } from 'browser/services/Services';
import { Emitter } from 'common/Event';
import { TextBlinkStateManager } from './TextBlinkStateManager';
import { MockOptionsService } from '../../../common/TestUtils.test';
import type { ICoreBrowserService } from '../../services/Services';
import { Emitter } from '../../../common/Event';
class FakeWindow {
public nextId = 1;
@@ -3,9 +3,9 @@
* @license MIT
*/
import { ICoreBrowserService } from 'browser/services/Services';
import { Disposable, toDisposable } from 'common/Lifecycle';
import { IOptionsService } from 'common/services/Services';
import { ICoreBrowserService } from '../../services/Services';
import { Disposable, toDisposable } from '../../../common/Lifecycle';
import { IOptionsService } from '../../../common/services/Services';
export class TextBlinkStateManager extends Disposable {
private _intervalDuration: number = 0;
+3 -3
View File
@@ -4,9 +4,9 @@
*/
import { Terminal } from '@xterm/xterm';
import { ITerminal } from 'browser/Types';
import { IDisposable } from 'common/Types';
import type { IEvent } from 'common/Event';
import { ITerminal } from '../../Types';
import { IDisposable } from '../../../common/Types';
import type { IEvent } from '../../../common/Event';
export interface IDimensions {
width: number;
+1 -1
View File
@@ -11,7 +11,7 @@ import { ScrollbarArrow, IScrollbarArrowOptions } from './scrollbarArrow';
import { ScrollbarState } from './scrollbarState';
import { ScrollbarVisibilityController } from './scrollbarVisibilityController';
import { Widget } from './widget';
import * as platform from 'common/Platform';
import * as platform from '../../common/Platform';
import { INewScrollPosition, Scrollable, ScrollbarVisibility } from './scrollable';
/**
@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import * as dom from '../Dom';
import { DisposableStore, IDisposable, toDisposable } from 'common/Lifecycle';
import { DisposableStore, IDisposable, toDisposable } from '../../common/Lifecycle';
type PointerMoveCallback = (event: PointerEvent) => void;
type OnStopCallback = () => void;
+2 -2
View File
@@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as platform from 'common/Platform';
import * as platform from '../../common/Platform';
interface IWindowChainElement {
readonly window: WeakRef<Window>;
@@ -217,7 +217,7 @@ export class StandardWheelEvent {
let shouldFactorDPR: boolean = false;
if (platform.isChrome) {
const chromeVersionMatch = navigator.userAgent.match(/Chrome\/(\d+)/);
const chromeMajorVersion = chromeVersionMatch ? parseInt(chromeVersionMatch[1]) : 123;
const chromeMajorVersion = chromeVersionMatch ? parseInt(chromeVersionMatch[1], 10) : 123;
shouldFactorDPR = chromeMajorVersion <= 122;
}
+2 -2
View File
@@ -3,8 +3,8 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Emitter, IEvent } from 'common/Event';
import { Disposable, IDisposable } from 'common/Lifecycle';
import { Emitter, IEvent } from '../../common/Event';
import { Disposable, IDisposable } from '../../common/Lifecycle';
export const enum ScrollbarVisibility {
AUTO = 1,
+4 -4
View File
@@ -11,10 +11,10 @@ import { HorizontalScrollbar } from './horizontalScrollbar';
import { IScrollableElementChangeOptions, IScrollableElementCreationOptions, IScrollableElementResolvedOptions } from './scrollableElementOptions';
import { VerticalScrollbar } from './verticalScrollbar';
import { Widget } from './widget';
import { TimeoutTimer } from 'common/Async';
import { Emitter, IEvent } from 'common/Event';
import { IDisposable, dispose } from 'common/Lifecycle';
import * as platform from 'common/Platform';
import { TimeoutTimer } from '../../common/Async';
import { Emitter, IEvent } from '../../common/Event';
import { IDisposable, dispose } from '../../common/Lifecycle';
import * as platform from '../../common/Platform';
import { INewScrollDimensions, INewScrollPosition, IScrollDimensions, IScrollPosition, IScrollEvent, Scrollable, ScrollbarVisibility } from './scrollable';
// import 'vs/css!./media/scrollbars';
+1 -1
View File
@@ -5,7 +5,7 @@
import { GlobalPointerMoveMonitor } from './globalPointerMoveMonitor';
import { Widget } from './widget';
import { TimeoutTimer } from 'common/Async';
import { TimeoutTimer } from '../../common/Async';
import * as dom from '../Dom';
export interface IScrollbarArrowOptions {
@@ -4,8 +4,8 @@
*--------------------------------------------------------------------------------------------*/
import { FastDomNode } from './fastDomNode';
import { TimeoutTimer } from 'common/Async';
import { Disposable } from 'common/Lifecycle';
import { TimeoutTimer } from '../../common/Async';
import { Disposable } from '../../common/Lifecycle';
import { ScrollbarVisibility } from './scrollable';
export class ScrollbarVisibilityController extends Disposable {
+2 -2
View File
@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import * as DomUtils from '../Dom';
import { Disposable, IDisposable, toDisposable } from 'common/Lifecycle';
import { Disposable, IDisposable, toDisposable } from '../../common/Lifecycle';
const mainWindow = (typeof window === 'object' ? window : globalThis) as Window & typeof globalThis;
@@ -367,7 +367,7 @@ export class Gesture extends Disposable {
private _dispatchEvent(event: IGestureEvent): void {
if (event.type === EventType.TAP) {
const currentTime = (new Date()).getTime();
let setTapCount = 0;
let setTapCount;
if (currentTime - this._lastSetTapCountTime > Gesture._clearTapCountTime) {
setTapCount = 1;
} else {

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