Compare commits

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

Reverts preserveConstEnums; const enums are handled in bundled output.

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

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

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

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

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

Resolves #5896

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

Co-authored-by: Daniel Imms <Tyriar@users.noreply.github.com>
2026-05-28 00:52:24 +00:00
369 changed files with 3384 additions and 5555 deletions
-1
View File
@@ -17,7 +17,6 @@ docs/
npm-debug.log
/.idea/
.env
mise.toml
build/
.DS_Store
yarn.lock
-374
View File
@@ -1,374 +0,0 @@
{
"$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
@@ -1,120 +0,0 @@
{
"$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,6 +15,9 @@
"--recursive",
"${workspaceRoot}/out/**/*.test.js"
],
"env": {
"NODE_PATH": "${workspaceRoot}/out"
},
"sourceMaps": true,
"outFiles": [ "${workspaceRoot}/out/**/*.js" ],
"internalConsoleOptions": "openOnSessionStart"
-13
View File
@@ -26,7 +26,6 @@ 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
@@ -99,15 +98,3 @@ 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.
+1 -1
View File
@@ -39,7 +39,7 @@ The preferred way to report bugs or request features is to use
It's pretty common for maintainers of large open source projects to suffer from burnout, especially when needing to triage a large number of incoming issues instead of actually building things. Here are some of the steps we take to try mitigate this:
- Support questions live in [GH discussions](https://github.com/xtermjs/xterm.js/discussions), issues may be transferred there without further comment and core maintainers may or may not participate in discussions.
- Support questions live in [GH discussions](https://github.com/xtermjs/xterm.js/discussions), issues may be transfered there without further comment and core maintainers may or may not participate in discussions.
- Issues are strictly for well defined features or bugs that are _actionable_.
- Sometimes features are out of scope. A common example of this is a niche feature that the pricipal implementation ([VS Code](https://code.visualstudio.com/)) won't leverage and therefore would be difficult to maintain and likely suffer from bitrot. The reporter may not agree with this, but you could always create an addon if that works or maintain your own fork if it comes to that.
- If a feature does not have a clear way forward or needs more discussion it may be closed ro moved to a discussion.
+3 -7
View File
@@ -13,15 +13,11 @@
"strict": true,
"types": [
"../../../node_modules/@types/mocha"
],
"paths": {
"@xterm/addon-attach": [
"../typings/addon-attach.d.ts"
]
}
]
},
"include": [
"./**/*",
"../../../typings/xterm.d.ts"
"../../../typings/xterm.d.ts",
"../typings/addon-attach.d.ts"
]
}
+1 -12
View File
@@ -3,23 +3,12 @@
"module": "commonjs",
"target": "ESNext",
"lib": [
"es2021",
"es2021"
],
"rootDir": ".",
"outDir": "../out-test",
"sourceMap": true,
"removeComments": true,
"paths": {
"common/*": [
"../../../src/common/*"
],
"browser/*": [
"../../../src/browser/*"
],
"*": [
"./*"
]
},
"strict": true,
"types": [
"../../../node_modules/@types/node"
+6 -2
View File
@@ -2,7 +2,11 @@
"files": [],
"include": [],
"references": [
{ "path": "./src" },
{ "path": "./test" }
{
"path": "./src"
},
{
"path": "./test"
}
]
}
+3 -10
View File
@@ -13,19 +13,12 @@
"strict": true,
"types": [
"../../../node_modules/@types/mocha"
],
"paths": {
"browser/*": [
"../../../src/browser/*"
],
"@xterm/addon-clipboard": [
"../typings/addon-clipboard.d.ts"
]
}
]
},
"include": [
"./**/*",
"../../../typings/xterm.d.ts"
"../../../typings/xterm.d.ts",
"../typings/addon-clipboard.d.ts"
],
"references": [
{
+1 -12
View File
@@ -3,23 +3,12 @@
"module": "commonjs",
"target": "ESNext",
"lib": [
"es2021",
"es2021"
],
"rootDir": ".",
"outDir": "../out-test",
"sourceMap": true,
"removeComments": true,
"paths": {
"common/*": [
"../../../src/common/*"
],
"browser/*": [
"../../../src/browser/*"
],
"*": [
"./*"
]
},
"strict": true,
"types": [
"../../../node_modules/@types/node"
+6 -2
View File
@@ -2,7 +2,11 @@
"files": [],
"include": [],
"references": [
{ "path": "./src" },
{ "path": "./test" }
{
"path": "./src"
},
{
"path": "./test"
}
]
}
+1 -1
View File
@@ -82,7 +82,7 @@ declare module '@xterm/addon-clipboard' {
/**
* The clipboard provider interface that enables xterm.js to access the browser clipboard.
*
* Note that this clipboard provider does not implement own cut buffers as xterm does,
* Note this this clipboard provider does not implement own cut buffers as xterm does,
* but redirect the selection parameter always to navigator.clipboard.
*/
export class BrowserClipboardProvider implements IClipboardProvider{
+7 -7
View File
@@ -5,7 +5,7 @@
import type { Terminal, ITerminalAddon, IRenderDimensions } from '@xterm/xterm';
import type { FitAddon as IFitApi } from '@xterm/addon-fit';
import { ViewportConstants } from 'browser/shared/Constants';
import { ViewportConstants } from '../../../src/browser/shared/Constants';
interface ITerminalDimensions {
/**
@@ -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 = Math.max(0, parseInt(parentElementStyle.getPropertyValue('height'), 10) || 0);
const parentElementWidth = Math.max(0, parseInt(parentElementStyle.getPropertyValue('width'), 10) || 0);
const parentElementHeight = parseInt(parentElementStyle.getPropertyValue('height'));
const parentElementWidth = Math.max(0, parseInt(parentElementStyle.getPropertyValue('width')));
const elementStyle = _getComputedStyle(this._terminal.element);
const elementPadding = {
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
top: parseInt(elementStyle.getPropertyValue('padding-top')),
bottom: parseInt(elementStyle.getPropertyValue('padding-bottom')),
right: parseInt(elementStyle.getPropertyValue('padding-right')),
left: parseInt(elementStyle.getPropertyValue('padding-left'))
};
const elementPaddingVer = elementPadding.top + elementPadding.bottom;
const elementPaddingHor = elementPadding.right + elementPadding.left;
+3 -10
View File
@@ -13,19 +13,12 @@
"strict": true,
"types": [
"../../../node_modules/@types/mocha"
],
"paths": {
"browser/*": [
"../../../src/browser/*"
],
"@xterm/addon-fit": [
"../typings/addon-fit.d.ts"
]
}
]
},
"include": [
"./**/*",
"../../../typings/xterm.d.ts"
"../../../typings/xterm.d.ts",
"../typings/addon-fit.d.ts"
],
"references": [
{
+1 -12
View File
@@ -3,23 +3,12 @@
"module": "commonjs",
"target": "ESNext",
"lib": [
"es2021",
"es2021"
],
"rootDir": ".",
"outDir": "../out-test",
"sourceMap": true,
"removeComments": true,
"paths": {
"common/*": [
"../../../src/common/*"
],
"browser/*": [
"../../../src/browser/*"
],
"*": [
"./*"
]
},
"strict": true,
"types": [
"../../../node_modules/@types/node"
+6 -2
View File
@@ -2,7 +2,11 @@
"files": [],
"include": [],
"references": [
{ "path": "./src" },
{ "path": "./test" }
{
"path": "./src"
},
{
"path": "./test"
}
]
}
+31 -20
View File
@@ -1,7 +1,6 @@
## @xterm/addon-image
Inline image output in xterm.js. Supports SIXEL, iTerm's inline image protocol (IIP)
and partially Kitty's terminal graphics prtocol (TGP).
Inline image output in xterm.js. Supports SIXEL and iTerm's inline image protocol (IIP).
![](fixture/example.png)
@@ -44,6 +43,9 @@ 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)
@@ -71,7 +73,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 for sixel.
Other cursor positioning modes as used by xterm or mintty are not supported.
- **SIXEL Palette Handling**
By default the addon limits the palette size to 256 registers (as demanded by the DEC specification).
@@ -98,24 +100,17 @@ 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, GIF and QOI
- Supported formats: PNG, JPEG and GIF
- 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).
- 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.
- Size parameter is restricted by `iipSizeLimit`.
- Cursor positioning behaves the same as for sixel (see above).
### Storage and Drawing Settings
@@ -132,7 +127,6 @@ 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:
@@ -146,7 +140,6 @@ 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:
@@ -187,8 +180,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 | 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.
- `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.
### Terminal Interaction
@@ -218,5 +211,23 @@ _How can I adjust the memory usage?_
### Status
- 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.
- 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
Binary file not shown.
+19 -73
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, SequenceType } from './IIPHeaderParser';
import { HeaderParser, IHeaderFields, HeaderState } from './IIPHeaderParser';
import { imageType, UNSUPPORTED_TYPE } from './IIPMetrics';
// Local const enum mirror - esbuild can't inline const enums from external packages
@@ -23,7 +23,6 @@ const enum DecoderConst {
// default IIP header values
const DEFAULT_HEADER: IHeaderFields = {
type: SequenceType.INVALID,
name: 'Unnamed file',
size: 0,
width: 'auto',
@@ -40,8 +39,6 @@ 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,
@@ -55,14 +52,11 @@ export class IIPHandler implements IOscHandler, IResetHandler {
this._qoiDec = new QoiDecoder(DecoderConst.KEEP_DATA);
}
public reset(): void {
this._hp.reset();
this._dec.release();
this._qoiDec.release();
}
public reset(): void {}
public start(): void {
this._aborted = false;
this._header = DEFAULT_HEADER;
this._metrics = UNSUPPORTED_TYPE;
this._hp.reset();
}
@@ -82,27 +76,15 @@ export class IIPHandler implements IOscHandler, IResetHandler {
return;
}
if (dataPos > 0) {
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._header = Object.assign({}, DEFAULT_HEADER, this._hp.fields);
if (!this._header.inline || !this._header.size || this._header.size > this._opts.iipSizeLimit) {
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;
}
}
}
@@ -111,58 +93,22 @@ 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;
let cond: number | boolean = true;
if (cond = success) {
if (cond = !this._dec.end()) {
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;
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;
}
}
}
}
@@ -222,8 +168,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), 10) * total / 100;
if (s.endsWith('px')) return parseInt(s.slice(0, -2), 10);
return parseInt(s, 10) * cdim;
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;
}
}
+7 -129
View File
@@ -4,20 +4,19 @@
*/
import { assert } from 'chai';
import { HeaderParser, HeaderState, IHeaderFields, SequenceType } from './IIPHeaderParser';
import { HeaderParser, HeaderState, IHeaderFields } from './IIPHeaderParser';
const CASES: [string, IHeaderFields][] = [
['File=size=123456;name=dGVzdA==:', {type: SequenceType.FILE, name: 'test', size: 123456}],
['File=size=123456;name=dGVzdA:', {type: SequenceType.FILE, name: 'test', size: 123456}],
['File=size=123456;name=dGVzdA==:', {name: 'test', size: 123456}],
['File=size=123456;name=dGVzdA:', {name: 'test', size: 123456}],
// utf-8 encoding in name
['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}],
['File=size=123456;name=w7xtbMOkdXTDnw==:', {name: 'ümläutß', size: 123456}],
['File=size=123456;name=w7xtbMOkdXTDnw:', {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%',
@@ -29,7 +28,6 @@ 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',
@@ -103,7 +101,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, {type: SequenceType.FILE, name: 'test', size: 0});
assert.deepEqual(hp.fields, {name: 'test', size: 0});
hp.reset();
inp = fromBs(CASES[0][0]);
res = hp.parse(inp, 0, inp.length);
@@ -117,7 +115,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, {type: SequenceType.FILE, name: '', size: 123456});
assert.deepEqual(hp.fields, {name: '', size: 123456});
hp.reset();
inp = fromBs(CASES[0][0]);
res = hp.parse(inp, 0, inp.length);
@@ -137,124 +135,4 @@ 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 });
});
});
+11 -86
View File
@@ -6,27 +6,9 @@
// 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.
@@ -47,6 +29,14 @@ 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
@@ -102,19 +92,7 @@ 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;
@@ -133,43 +111,12 @@ 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 > 14) return -1;
if (state === HeaderState.START && pos > 6) return -1;
for (let i = start; i < end; ++i) {
const c = data[i];
switch (c) {
@@ -180,29 +127,8 @@ export class HeaderParser {
break;
case 61: // =
if (state === HeaderState.START) {
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();
for (let k = 0; k < FILE_MARKER.length; ++k) {
if (buffer[k] !== FILE_MARKER[k]) return this._a();
}
state = HeaderState.KEY;
pos = 0;
@@ -232,7 +158,6 @@ export class HeaderParser {
}
private _a(): number {
this.fields.type = SequenceType.INVALID;
this.state = HeaderState.ABORT;
return -1;
}
+1 -3
View File
@@ -3,7 +3,6 @@
* @license MIT
*/
import { IAddImageOpts } from './Types';
import { ImageStorage } from './ImageStorage';
/**
@@ -13,7 +12,6 @@ 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
) {}
@@ -23,6 +21,6 @@ export class IIPImageStorage {
* Always uses scrolling mode — cursor advances past the image.
*/
public addImage(img: HTMLCanvasElement | ImageBitmap): void {
this._storage.addImage(img, this._addImageOpts);
this._storage.addImage(img, true);
}
}
+1 -2
View File
@@ -30,8 +30,7 @@ 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 }],
['dice.qoi', { mime: 'image/qoi', width: 800, height: 600 }],
['sony-alpha-6000.jpg', { mime: 'image/jpeg', width: 6000, height: 4000 }]
];
+7 -2
View File
@@ -5,7 +5,7 @@
import type { ITerminalAddon, IDisposable } from '@xterm/xterm';
import type { ImageAddon as IImageApi } from '@xterm/addon-image';
import { Emitter, type IEvent } from 'common/Event';
import { Emitter, type IEvent } from '../../../src/common/Event';
import { IIPHandler } from './IIPHandler';
import { ImageRenderer } from './ImageRenderer';
import { ImageStorage, CELL_SIZE_DEFAULT } from './ImageStorage';
@@ -137,6 +137,11 @@ 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;
@@ -255,7 +260,7 @@ export class ImageAddon implements ITerminalAddon, IImageApi {
}
private _report(s: string): void {
this._terminal?._core.input(s, false);
this._terminal?._core.coreService.triggerDataEvent(s);
}
private _decset(params: (number | number[])[]): boolean {
+2 -2
View File
@@ -6,7 +6,7 @@
import { toRGBA8888 } from 'sixel/lib/Colors';
import { IDisposable } from '@xterm/xterm';
import { ICellSize, ImageLayer, ITerminalExt, IImageSpec, IRenderDimensions, IRenderService } from './Types';
import { Disposable, MutableDisposable, toDisposable } from 'common/Lifecycle';
import { Disposable, MutableDisposable, toDisposable } from '../../../src/common/Lifecycle';
const enum Constants {
PLACEHOLDER_LENGTH = 4096,
@@ -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 + 1 - start) * (this.dimensions?.css.cell.height || 0);
const h = (++end - start) * (this.dimensions?.css.cell.height || 0);
if (!layer || layer === 'top') {
this._layers.get('top')?.clearRect(0, y, w, h);
}
+13 -22
View File
@@ -5,11 +5,7 @@
import { IDisposable } from '@xterm/xterm';
import { ImageRenderer } from './ImageRenderer';
import {
ITerminalExt, IExtendedAttrsImage, IImageAddonOptions, IImageSpec,
IBufferLineExt, BgFlags, Cell, Content, ICellSize, ExtFlags, Attributes,
UnderlineStyle, IAddImageOpts
} from './Types';
import { ITerminalExt, IExtendedAttrsImage, IImageAddonOptions, IImageSpec, IBufferLineExt, BgFlags, Cell, Content, ICellSize, ExtFlags, Attributes, UnderlineStyle, ImageLayer } from './Types';
// fallback default cell size
@@ -239,15 +235,13 @@ export class ImageStorage implements IDisposable {
/**
* Method to add an image to the storage.
* @param img - The image to add (canvas or bitmap).
* @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.
* @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.
* @returns The internal image ID assigned to the stored image.
*/
public addImage(img: HTMLCanvasElement | ImageBitmap, opts: IAddImageOpts): number {
public addImage(img: HTMLCanvasElement | ImageBitmap, scrolling: boolean, layer: ImageLayer = 'top', zIndex: number = 0): number {
// never allow storage to exceed memory limit
this._evictOldest(img.width * img.height);
@@ -269,7 +263,7 @@ export class ImageStorage implements IDisposable {
let offset = originX;
let tileCount = 0;
if (!opts.scrolling) {
if (!scrolling) {
buffer.x = 0;
buffer.y = 0;
offset = 0;
@@ -283,7 +277,7 @@ export class ImageStorage implements IDisposable {
this._writeToCell(line as IBufferLineExt, offset + col, imageId, row * cols + col);
tileCount++;
}
if (opts.scrolling) {
if (scrolling) {
if (row < rows - 1) this._terminal._core._inputHandler.lineFeed();
} else {
if (++buffer.y >= termRows) break;
@@ -293,12 +287,8 @@ export class ImageStorage implements IDisposable {
this._terminal._core._inputHandler._dirtyRowTracker.markDirty(buffer.y);
// cursor positioning modes
if (opts.scrolling) {
if (opts.cursorPos === 'iip') {
buffer.x = Math.min(offset + cols, termCols);
} else {
buffer.x = offset;
}
if (scrolling) {
buffer.x = offset;
} else {
buffer.x = originX;
buffer.y = originY;
@@ -341,8 +331,8 @@ export class ImageStorage implements IDisposable {
marker: endMarker || undefined,
tileCount,
bufferType: this._terminal.buffer.active.type,
layer: opts.layer,
zIndex: opts.zIndex
layer,
zIndex
};
// finally add the image
@@ -465,6 +455,7 @@ export class ImageStorage implements IDisposable {
if (!nextE || nextE.imageId !== imageId || nextE.tileId !== startTile + count) {
break;
}
e = nextE;
count++;
}
col--;
+2 -4
View File
@@ -4,7 +4,7 @@
*/
import { ImageStorage, CELL_SIZE_DEFAULT } from './ImageStorage';
import { IImageAddonOptions, ITerminalExt, IAddImageOpts } from './Types';
import { IImageAddonOptions, ITerminalExt } from './Types';
import { ImageRenderer } from './ImageRenderer';
/**
@@ -15,7 +15,6 @@ 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,
@@ -28,8 +27,7 @@ export class SixelImageStorage {
* Cursor behavior depends on the sixelScrolling option (DECSET 80).
*/
public addImage(img: HTMLCanvasElement | ImageBitmap): void {
this._addImageOpts.scrolling = this._opts.sixelScrolling;
this._storage.addImage(img, this._addImageOpts);
this._storage.addImage(img, this._opts.sixelScrolling);
}
/**
+7 -16
View File
@@ -6,14 +6,13 @@
import { IDisposable, IMarker, Terminal } from '@xterm/xterm';
// private imports from base repo we build against
import { Attributes, BgFlags, Content, ExtFlags, UnderlineStyle } from 'common/buffer/Constants';
import type { AttributeData } from 'common/buffer/AttributeData';
import type { IParams, IDcsHandler, IOscHandler, IApcHandler, IEscapeSequenceParser } from 'common/parser/Types';
import type { 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';
import { Attributes, BgFlags, Content, ExtFlags, UnderlineStyle } from '../../../src/common/buffer/Constants';
import type { AttributeData } from '../../../src/common/buffer/AttributeData';
import type { IParams, IDcsHandler, IOscHandler, IApcHandler, IEscapeSequenceParser } from '../../../src/common/parser/Types';
import type { IBufferLine, IExtendedAttrs, IInputHandler } from '../../../src/common/Types';
import type { ITerminal, ReadonlyColorSet } from '../../../src/browser/Types';
import type { IRenderDimensions } from '../../../src/browser/renderer/shared/Types';
import type { ICoreBrowserService, IRenderService, IThemeService } from '../../../src/browser/services/Services';
export const enum Cell {
CONTENT = 0, // codepoint and wcwidth information (enum Content)
@@ -101,14 +100,6 @@ 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, 10);
const numValue = parseInt(value);
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, IAddImageOpts } from '../Types';
import { ImageLayer } from '../Types';
import { IKittyImageData } from './KittyGraphicsTypes';
// Kitty-specific image storage controller.
@@ -42,7 +42,6 @@ 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
@@ -99,10 +98,7 @@ export class KittyImageStorage implements IDisposable {
if (oldStorageId !== undefined) {
this._storageIdToKittyId.delete(oldStorageId);
}
this._addImageOpts.scrolling = scrolling;
this._addImageOpts.layer = layer;
this._addImageOpts.zIndex = zIndex;
const storageId = this._storage.addImage(image, this._addImageOpts);
const storageId = this._storage.addImage(image, scrolling, layer, zIndex);
this._kittyIdToStorageId.set(kittyId, storageId);
this._storageIdToKittyId.set(storageId, kittyId);
}
+9 -10
View File
@@ -10,20 +10,19 @@
"preserveWatchOutput": true,
"types": [
"../../../node_modules/@types/mocha"
],
"paths": {
"browser/*": [ "../../../src/browser/*" ],
"common/*": [ "../../../src/common/*" ],
"@xterm/addon-image": [ "../typings/addon-image.d.ts" ],
"*": [ "./*" ]
}
]
},
"include": [
"./**/*",
"../../../typings/xterm.d.ts"
"../../../typings/xterm.d.ts",
"../typings/addon-image.d.ts"
],
"references": [
{ "path": "../../../src/browser" },
{ "path": "../../../src/common" }
{
"path": "../../../src/browser"
},
{
"path": "../../../src/common"
}
]
}
@@ -290,67 +290,6 @@ 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]);
+12 -6
View File
@@ -539,6 +539,7 @@ 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; });
@@ -547,11 +548,12 @@ test.describe('Kitty Graphics Protocol', () => {
await ctx.proxy.write('\x1b_Gi=31,a=q;\x1b\\');
await timeout(100);
const response = await ctx.page.evaluate('window.kittyResponse');
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; });
@@ -560,7 +562,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);
const response = await ctx.page.evaluate('window.kittyResponse');
response = await ctx.page.evaluate('window.kittyResponse');
strictEqual(response, '\x1b_Gi=42;OK\x1b\\');
});
@@ -576,6 +578,7 @@ 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; });
@@ -584,11 +587,12 @@ test.describe('Kitty Graphics Protocol', () => {
await ctx.proxy.write('\x1b_Gi=60,a=q,f=100;!!!invalid!!!\x1b\\');
await timeout(100);
const response: string = await ctx.page.evaluate('window.kittyResponse');
response = 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; });
@@ -597,7 +601,7 @@ test.describe('Kitty Graphics Protocol', () => {
await ctx.proxy.write('\x1b_Gi=70,a=q,f=24;AAAA\x1b\\');
await timeout(100);
const response = await ctx.page.evaluate('window.kittyResponse');
response = await ctx.page.evaluate('window.kittyResponse');
strictEqual(response, '\x1b_Gi=70;EINVAL:width and height required for raw pixel data\x1b\\');
});
@@ -638,6 +642,7 @@ 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; });
@@ -647,11 +652,12 @@ 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);
const response = await ctx.page.evaluate('window.kittyResponse');
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; });
@@ -661,7 +667,7 @@ test.describe('Kitty Graphics Protocol', () => {
await ctx.proxy.write('\x1b_Gi=101,I=201,a=d\x1b\\');
await timeout(100);
const response = await ctx.page.evaluate('window.kittyResponse');
response = await ctx.page.evaluate('window.kittyResponse');
strictEqual(response, '\x1b_Gi=101;EINVAL:cannot specify both i and I keys\x1b\\');
});
});
+1 -12
View File
@@ -3,23 +3,12 @@
"module": "commonjs",
"target": "ESNext",
"lib": [
"es2021",
"es2021"
],
"rootDir": ".",
"outDir": "../out-test",
"sourceMap": true,
"removeComments": true,
"paths": {
"common/*": [
"../../../src/common/*"
],
"browser/*": [
"../../../src/browser/*"
],
"*": [
"./*"
]
},
"strict": true,
"types": [
"../../../node_modules/@types/node"
+6 -2
View File
@@ -2,7 +2,11 @@
"files": [],
"include": [],
"references": [
{ "path": "./src" },
{ "path": "./test" }
{
"path": "./src"
},
{
"path": "./test"
}
]
}
+8 -4
View File
@@ -31,11 +31,15 @@
],
"license": "MIT",
"dependencies": {
"lru-cache": "^11.3.6",
"opentype.js": "^2.0.0"
"lru-cache": "^6.0.0",
"opentype.js": "^0.8.0"
},
"devDependencies": {
"@types/opentype.js": "^1.3.9",
"font-finder": "^1.1.0"
"@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"
}
}
@@ -1,5 +1,5 @@
import * as opentype from 'opentype.js';
import { LRUCache } from 'lru-cache';
import LRUCache = require('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({
maxSize: options.cacheSize,
sizeCalculation: ((val: ILigatureData | [number, number][], key: string) => key.length) as any
max: options.cacheSize,
length: ((val: ILigatureData | [number, number][], key: string) => key.length) as any
});
}
@@ -44,6 +44,7 @@ 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
+1 -1
View File
@@ -21,7 +21,7 @@ const CACHE_SIZE = 100000;
/**
* Enable ligature support for the provided Terminal instance. To function
* properly, this must be called after `open()` is called on the terminal. If
* properly, this must be called after `open()` is called on the therminal. If
* the font currently in use supports ligatures, the terminal will automatically
* start to render them.
* @param term Terminal instance from xterm.js
+3 -8
View File
@@ -10,17 +10,12 @@
"preserveWatchOutput": true,
"types": [
"../../../node_modules/@types/mocha",
// HACK: src shouldn't use node types but it's needed for index.test.ts
"../../../node_modules/@types/node"
],
"paths": {
"@xterm/addon-ligatures" : [
"../typings/addon-ligatures.d.ts"
]
}
]
},
"include": [
"./**/*",
"../../../typings/xterm.d.ts"
"../../../typings/xterm.d.ts",
"../typings/addon-ligatures.d.ts"
]
}
@@ -10,11 +10,6 @@
"sourceMap": true,
"removeComments": true,
"strict": true,
"paths": {
"*": [
"./*"
]
},
"types": [
"../../../node_modules/@types/mocha",
"../../../node_modules/@types/node"
+6 -2
View File
@@ -2,7 +2,11 @@
"files": [],
"include": [],
"references": [
{ "path": "./src" },
{ "path": "./test" }
{
"path": "./src"
},
{
"path": "./test"
}
]
}
+1 -1
View File
@@ -5,7 +5,7 @@
import type { Terminal, ITerminalAddon, IDisposable } from '@xterm/xterm';
import type { ProgressAddon as IProgressApi, IProgressState } from '@xterm/addon-progress';
import type { Emitter, IEvent } from 'common/Event';
import type { Emitter, IEvent } from '../../../src/common/Event';
const enum ProgressType {
+3 -13
View File
@@ -13,22 +13,12 @@
"strict": true,
"types": [
"../../../node_modules/@types/mocha"
],
"paths": {
"common/*": [
"../../../src/common/*"
],
"browser/*": [
"../../../src/browser/*"
],
"@xterm/addon-progress": [
"../typings/addon-progress.d.ts"
]
}
]
},
"include": [
"./**/*",
"../../../typings/xterm.d.ts"
"../../../typings/xterm.d.ts",
"../typings/addon-progress.d.ts"
],
"references": [
{
@@ -9,18 +9,15 @@ import { ITestContext, createTestContext, openTerminal } from '../../../test/pla
let ctx: ITestContext;
test.beforeAll(async ({ browser }) => {
ctx = await createTestContext(browser);
ctx.page.setViewportSize({ width: 1024, height: 768 });
await openTerminal(ctx);
});
test.afterAll(async () => await ctx.page.close());
test.describe('ProgressAddon', () => {
test.describe.configure({ mode: 'serial' });
test.beforeAll(async ({ browser }) => {
ctx = await createTestContext(browser);
await openTerminal(ctx);
});
test.afterAll(async () => {
await ctx?.page?.close();
});
test.beforeEach(async function(): Promise<any> {
await ctx.page.evaluate(`
window.progressStack = [];
+1 -12
View File
@@ -3,23 +3,12 @@
"module": "commonjs",
"target": "ESNext",
"lib": [
"es2021",
"es2021"
],
"rootDir": ".",
"outDir": "../out-test",
"sourceMap": true,
"removeComments": true,
"paths": {
"common/*": [
"../../../src/common/*"
],
"browser/*": [
"../../../src/browser/*"
],
"*": [
"./*"
]
},
"strict": true,
"types": [
"../../../node_modules/@types/node"
+6 -2
View File
@@ -2,7 +2,11 @@
"files": [],
"include": [],
"references": [
{ "path": "./src" },
{ "path": "./test" }
{
"path": "./src"
},
{
"path": "./test"
}
]
}
+1 -1
View File
@@ -5,7 +5,7 @@
import type { Terminal, IDisposable, IDecoration } from '@xterm/xterm';
import type { ISearchDecorationOptions } from '@xterm/addon-search';
import { dispose, Disposable, toDisposable } from 'common/Lifecycle';
import { dispose, Disposable, toDisposable } from '../../../src/common/Lifecycle';
import type { ISearchResult } from './SearchEngine';
/**
+3 -3
View File
@@ -5,9 +5,9 @@
import type { Terminal, IDisposable, ITerminalAddon } from '@xterm/xterm';
import type { SearchAddon as ISearchApi, ISearchOptions, ISearchAddonOptions, ISearchResultChangeEvent, ISearchDecorationOptions } from '@xterm/addon-search';
import { Emitter, type IEvent } from 'common/Event';
import { Disposable, MutableDisposable, toDisposable } from 'common/Lifecycle';
import { disposableTimeout } from 'common/Async';
import { Emitter, type IEvent } from '../../../src/common/Event';
import { Disposable, MutableDisposable, toDisposable } from '../../../src/common/Lifecycle';
import { disposableTimeout } from '../../../src/common/Async';
import { SearchLineCache } from './SearchLineCache';
import { SearchState } from './SearchState';
import { SearchEngine, type ISearchResult } from './SearchEngine';
+2 -2
View File
@@ -5,9 +5,9 @@
import { assert } from 'chai';
import { SearchEngine } from './SearchEngine';
import { SearchLineCache } from './SearchLineCache';
import { Terminal } from 'browser/public/Terminal';
import { Terminal } from '../../../src/browser/public/Terminal';
import type { ISearchOptions } from '@xterm/addon-search';
import { DisposableStore } from 'common/Lifecycle';
import { DisposableStore } from '../../../src/common/Lifecycle';
function writeP(terminal: Terminal, data: string): Promise<void> {
return new Promise(r => terminal.write(data, r));
+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;
const startCol = this._terminal.cols;
let 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 = prevSelectedPos.start.x;
searchPosition.startCol = 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 = prevSelectedPos.end.x;
searchPosition.startCol = startCol = prevSelectedPos.end.x;
}
}
}
@@ -4,8 +4,8 @@
*/
import { assert } from 'chai';
import { SearchLineCache, LineCacheEntry } from './SearchLineCache';
import { Terminal } from 'browser/public/Terminal';
import { timeout } from 'common/Async';
import { Terminal } from '../../../src/browser/public/Terminal';
import { timeout } from '../../../src/common/Async';
function writeP(terminal: Terminal, data: string): Promise<void> {
return new Promise(r => terminal.write(data, r));
+2 -2
View File
@@ -4,8 +4,8 @@
*/
import type { Terminal } from '@xterm/xterm';
import { combinedDisposable, Disposable, MutableDisposable, toDisposable } from 'common/Lifecycle';
import { disposableTimeout } from 'common/Async';
import { combinedDisposable, Disposable, MutableDisposable, toDisposable } from '../../../src/common/Lifecycle';
import { disposableTimeout } from '../../../src/common/Async';
export type LineCacheEntry = [
/**
@@ -5,8 +5,8 @@
import type { ISearchResultChangeEvent } from '@xterm/addon-search';
import type { IDisposable } from '@xterm/xterm';
import { Emitter, type IEvent } from 'common/Event';
import { Disposable } from 'common/Lifecycle';
import { Emitter, type IEvent } from '../../../src/common/Event';
import { Disposable } from '../../../src/common/Lifecycle';
import type { ISearchResult } from './SearchEngine';
/**
+4 -14
View File
@@ -4,7 +4,7 @@
"target": "es2021",
"lib": [
"dom",
"es2021",
"es2021"
],
"rootDir": ".",
"outDir": "../out",
@@ -13,22 +13,12 @@
"strict": true,
"types": [
"../../../node_modules/@types/mocha"
],
"paths": {
"common/*": [
"../../../src/common/*"
],
"browser/*": [
"../../../src/browser/*"
],
"@xterm/addon-search" : [
"../typings/addon-search.d.ts"
]
}
]
},
"include": [
"./**/*",
"../../../typings/xterm.d.ts"
"../../../typings/xterm.d.ts",
"../typings/addon-search.d.ts"
],
"references": [
{
+1 -12
View File
@@ -3,23 +3,12 @@
"module": "commonjs",
"target": "ESNext",
"lib": [
"es2021",
"es2021"
],
"rootDir": ".",
"outDir": "../out-test",
"sourceMap": true,
"removeComments": true,
"paths": {
"common/*": [
"../../../src/common/*"
],
"browser/*": [
"../../../src/browser/*"
],
"*": [
"./*"
]
},
"strict": true,
"types": [
"../../../node_modules/@types/node"
+6 -2
View File
@@ -2,7 +2,11 @@
"files": [],
"include": [],
"references": [
{ "path": "./src" },
{ "path": "./test" }
{
"path": "./src"
},
{
"path": "./test"
}
]
}
@@ -6,9 +6,9 @@
import { perfContext, before, ThroughputRuntimeCase } from 'xterm-benchmark';
import { spawn } from 'node-pty';
import { Utf8ToUtf32, stringFromCodePoint } from 'common/input/TextDecoder';
import { Terminal } from 'browser/public/Terminal';
import { SerializeAddon } from 'SerializeAddon';
import { Utf8ToUtf32, stringFromCodePoint } from '../../../src/common/input/TextDecoder';
import { Terminal } from '../../../src/browser/public/Terminal';
import { SerializeAddon } from '../src/SerializeAddon';
class TestTerminal extends Terminal {
public writeSync(data: string): void {
+19 -15
View File
@@ -6,24 +6,28 @@
],
"rootDir": "..",
"outDir": "../out-benchmark",
"types": ["../../../node_modules/@types/node"],
"moduleResolution": "nodenext",
"types": [
"../../../node_modules/@types/node"
],
"moduleResolution": "node",
"strict": false,
"target": "es2021",
"module": "nodenext",
"paths": {
"common/*": ["../../../src/common/*"],
"browser/*": ["../../../src/browser/*"],
"SerializeAddon": ["../src/SerializeAddon"],
"@xterm/addon-serialize": [
"../typings/addon-serialize.d.ts"
]
}
"module": "commonjs"
},
"include": ["../**/*", "../../../typings/xterm.d.ts"],
"exclude": ["../../../**/*test.ts", "../../**/*api.ts"],
"include": [
"../**/*",
"../../../typings/xterm.d.ts"
],
"exclude": [
"../../../**/*test.ts",
"../../**/*api.ts"
],
"references": [
{ "path": "../../../src/common" },
{ "path": "../../../src/browser" }
{
"path": "../../../src/common"
},
{
"path": "../../../src/browser"
}
]
}
+3 -3
View File
@@ -21,8 +21,8 @@
"package": "../../node_modules/.bin/webpack",
"prepublishOnly": "npm run package",
"start": "node ../../demo/start",
"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"
"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"
}
}
@@ -6,10 +6,10 @@
import jsdom = require('jsdom');
import { assert } from 'chai';
import { SerializeAddon } from './SerializeAddon';
import { Terminal } from 'browser/public/Terminal';
import { SelectionModel } from 'browser/selection/SelectionModel';
import { IBufferService } from 'common/services/Services';
import { ThemeService } from 'browser/services/ThemeService';
import { Terminal } from '../../../src/browser/public/Terminal';
import { SelectionModel } from '../../../src/browser/selection/SelectionModel';
import { IBufferService } from '../../../src/common/services/Services';
import { ThemeService } from '../../../src/browser/services/ThemeService';
function sgr(...seq: string[]): string {
return `\x1b[${seq.join(';')}m`;
+6 -7
View File
@@ -7,10 +7,9 @@
import type { IBuffer, IBufferCell, IBufferRange, ITerminalAddon, Terminal } from '@xterm/xterm';
import type { IHTMLSerializeOptions, SerializeAddon as ISerializeApi, ISerializeOptions, ISerializeRange } from '@xterm/addon-serialize';
import { IColor } from 'common/Types';
import { IAttributeData } from 'common/buffer/Types';
import { DEFAULT_ANSI_COLORS } from 'browser/Types';
import { UnderlineStyle } from 'common/buffer/Constants';
import { IAttributeData, IColor } from '../../../src/common/Types';
import { DEFAULT_ANSI_COLORS } from '../../../src/browser/Types';
import { UnderlineStyle } from '../../../src/common/buffer/Constants';
function constrain(value: number, low: number, high: number): number {
return Math.max(low, Math.min(value, high));
@@ -173,7 +172,7 @@ class StringSerializeHandler extends BaseSerializeHandler {
private _thisRowLastSecondChar: IBufferCell = this._buffer.getNullCell();
private _nextRowFirstChar: IBufferCell = this._buffer.getNullCell();
protected _rowEnd(row: number, isLastRow: boolean): void {
// if there is colorful empty cell at line end, we must pad it back, or the color block
// if there is colorful empty cell at line end, whe must pad it back, or the the color block
// will missing
if (this._nullCellCount > 0 && !equalBg(this._cursorStyle, this._backgroundCell)) {
// use clear right to set background.
@@ -221,7 +220,7 @@ class StringSerializeHandler extends BaseSerializeHandler {
// you can't use control sequence to move cursor to (x === row)
(thisRowLastChar.getChars() || thisRowLastChar.getWidth() === 0) &&
// change background of the first wrapped cell also affects BCE
// so we mark it as invalid to simplify the process to determine line separator
// so we mark it as invalid to simply the process to determine line separator
equalBg(thisRowLastChar, nextRowFirstChar)
) {
isValid = true;
@@ -233,7 +232,7 @@ class StringSerializeHandler extends BaseSerializeHandler {
isNextRowFirstCharDoubleWidth &&
(thisRowLastSecondChar.getChars() || thisRowLastSecondChar.getWidth() === 0) &&
// change background of the first wrapped cell also affects BCE
// so we mark it as invalid to simplify the process to determine line separator
// so we mark it as invalid to simply the process to determine line separator
equalBg(thisRowLastChar, nextRowFirstChar) &&
equalBg(thisRowLastSecondChar, nextRowFirstChar)
) {
+2 -12
View File
@@ -10,17 +10,6 @@
"outDir": "../out",
"sourceMap": true,
"removeComments": true,
"paths": {
"common/*": [
"../../../src/common/*"
],
"browser/*": [
"../../../src/browser/*"
],
"@xterm/addon-serialize": [
"../typings/addon-serialize.d.ts"
]
},
"strict": true,
"skipLibCheck": true,
"types": [
@@ -29,7 +18,8 @@
},
"include": [
"./**/*",
"../../../typings/xterm.d.ts"
"../../../typings/xterm.d.ts",
"../typings/addon-serialize.d.ts"
],
"references": [
{
@@ -192,7 +192,7 @@ test.describe('SerializeAddon', () => {
const cols = 10;
const line = '+'.repeat(cols);
const lines: string[] = [
sgr(FG_P16_GREEN) + line, // Workaround: If we clear all flags at the end, serialize will use \x1b[0m to clear instead of the specific disable sequence
sgr(FG_P16_GREEN) + line, // Workaround: If we clear all flags a the end, serialize will use \x1b[0m to clear instead of the sepcific disable sequence
sgr(INVERSE) + line,
sgr(BOLD) + line,
sgr(UNDERLINED) + line,
+1 -9
View File
@@ -3,20 +3,12 @@
"module": "commonjs",
"target": "ESNext",
"lib": [
"es2021",
"es2021"
],
"rootDir": ".",
"outDir": "../out-test",
"sourceMap": true,
"removeComments": true,
"paths": {
"common/*": [
"../../../src/common/*"
],
"browser/*": [
"../../../src/browser/*"
]
},
"strict": true,
"types": [
"../../../node_modules/@types/node"
+9 -3
View File
@@ -2,8 +2,14 @@
"files": [],
"include": [],
"references": [
{ "path": "./src" },
{ "path": "./test" },
{ "path": "./benchmark" }
{
"path": "./src"
},
{
"path": "./test"
},
{
"path": "./benchmark"
}
]
}
@@ -6,9 +6,9 @@
import { perfContext, before, ThroughputRuntimeCase } from 'xterm-benchmark';
import { spawn } from 'node-pty';
import { Utf8ToUtf32, stringFromCodePoint } from 'common/input/TextDecoder';
import { CoreBrowserTerminal } from 'browser/CoreBrowserTerminal';
import { UnicodeGraphemeProvider } from 'UnicodeGraphemeProvider';
import { Utf8ToUtf32, stringFromCodePoint } from '../../../src/common/input/TextDecoder';
import { CoreBrowserTerminal } from '../../../src/browser/CoreBrowserTerminal';
import { UnicodeGraphemeProvider } from '../src/UnicodeGraphemeProvider';
function fakedAddonLoad(terminal: any): void {
@@ -1,26 +1,33 @@
{
"compilerOptions": {
"lib": ["dom", "es6"],
"lib": [
"dom",
"es6"
],
"rootDir": "..",
"outDir": "../out-benchmark",
"types": ["../../../node_modules/@types/node"],
"moduleResolution": "nodenext",
"types": [
"../../../node_modules/@types/node"
],
"moduleResolution": "node",
"strict": false,
"target": "es2015",
"module": "nodenext",
"paths": {
"common/*": ["../../../src/common/*"],
"browser/*": ["../../../src/browser/*"],
"UnicodeGraphemeProvider": ["../src/UnicodeGraphemeProvider"],
"@xterm/addon-unicode-graphemes": [
"../typings/addon-unicode-graphemes.d.ts"
]
}
"module": "commonjs"
},
"include": ["../**/*", "../../../typings/xterm.d.ts"],
"exclude": ["../../../**/*test.ts", "../../**/*api.ts"],
"include": [
"../**/*",
"../../../typings/xterm.d.ts"
],
"exclude": [
"../../../**/*test.ts",
"../../**/*api.ts"
],
"references": [
{ "path": "../../../src/common" },
{ "path": "../../../src/browser" }
{
"path": "../../../src/common"
},
{
"path": "../../../src/browser"
}
]
}
+3 -3
View File
@@ -21,8 +21,8 @@
"package": "../../node_modules/.bin/webpack",
"prepublishOnly": "npm run package",
"start": "node ../../demo/start",
"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"
"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"
}
}
@@ -4,8 +4,8 @@
*/
import { IUnicodeVersionProvider } from '@xterm/xterm';
import { UnicodeCharProperties, UnicodeCharWidth } from 'common/services/Services';
import { UnicodeService } from 'common/services/UnicodeService';
import { UnicodeCharProperties, UnicodeCharWidth } from '../../../src/common/services/Services';
import { UnicodeService } from '../../../src/common/services/UnicodeService';
import * as UC from './third-party/UnicodeProperties';
export class UnicodeGraphemeProvider implements IUnicodeVersionProvider {
@@ -11,21 +11,14 @@
"sourceMap": true,
"removeComments": true,
"strict": true,
"paths": {
"common/*": [
"../../../src/common/*"
],
"@xterm/addon-unicode-graphemes": [
"../typings/addon-unicode-graphemes.d.ts"
]
},
"types": [
"../../../node_modules/@types/mocha"
]
},
"include": [
"./**/*",
"../../../typings/xterm.d.ts"
"../../../typings/xterm.d.ts",
"../typings/addon-unicode-graphemes.d.ts"
],
"references": [
{
@@ -3,20 +3,12 @@
"module": "commonjs",
"target": "ESNext",
"lib": [
"es2021",
"es2021"
],
"rootDir": ".",
"outDir": "../out-test",
"sourceMap": true,
"removeComments": true,
"paths": {
"common/*": [
"../../../src/common/*"
],
"browser/*": [
"../../../src/browser/*"
]
},
"strict": true,
"types": [
"../../../node_modules/@types/node"
+9 -3
View File
@@ -2,8 +2,14 @@
"files": [],
"include": [],
"references": [
{ "path": "./src" },
{ "path": "./test" },
{ "path": "./benchmark" }
{
"path": "./src"
},
{
"path": "./test"
},
{
"path": "./benchmark"
}
]
}
+2 -2
View File
@@ -4,8 +4,8 @@
*/
import { IUnicodeVersionProvider } from '@xterm/xterm';
import { UnicodeCharProperties, UnicodeCharWidth } from 'common/services/Services';
import { UnicodeService } from 'common/services/UnicodeService';
import { UnicodeCharProperties, UnicodeCharWidth } from '../../../src/common/services/Services';
import { UnicodeService } from '../../../src/common/services/UnicodeService';
const BMP_COMBINING = [
[0x0300, 0x036F], [0x0483, 0x0489], [0x0591, 0x05BD],
+2 -12
View File
@@ -11,24 +11,14 @@
"sourceMap": true,
"removeComments": true,
"strict": true,
"paths": {
"common/*": [
"../../../src/common/*"
],
"@xterm/addon-unicode11": [
"../typings/addon-unicode11.d.ts"
],
"*": [
"./*"
]
},
"types": [
"../../../node_modules/@types/mocha"
]
},
"include": [
"./**/*",
"../../../typings/xterm.d.ts"
"../../../typings/xterm.d.ts",
"../typings/addon-unicode11.d.ts"
],
"references": [
{
+1 -12
View File
@@ -3,23 +3,12 @@
"module": "commonjs",
"target": "ESNext",
"lib": [
"es2021",
"es2021"
],
"rootDir": ".",
"outDir": "../out-test",
"sourceMap": true,
"removeComments": true,
"paths": {
"common/*": [
"../../../src/common/*"
],
"browser/*": [
"../../../src/browser/*"
],
"*": [
"./*"
]
},
"strict": true,
"types": [
"../../../node_modules/@types/node"
+6 -2
View File
@@ -2,7 +2,11 @@
"files": [],
"include": [],
"references": [
{ "path": "./src" },
{ "path": "./test" }
{
"path": "./src"
},
{
"path": "./test"
}
]
}
+3 -7
View File
@@ -14,15 +14,11 @@
"strict": true,
"types": [
"../../../node_modules/@types/mocha"
],
"paths": {
"@xterm/addon-web-fonts": [
"../typings/addon-web-fonts.d.ts"
]
}
]
},
"include": [
"./**/*",
"../../../typings/xterm.d.ts"
"../../../typings/xterm.d.ts",
"../typings/addon-web-fonts.d.ts"
]
}
@@ -78,22 +78,7 @@ 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);
`);
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' }]);
}
deepStrictEqual(await getDocumentFonts(), [{ family: 'Kongtext', status: 'unloaded' }]);
// broken case: webfont in ctor without addon usage
await ctx.page.evaluate(`
@@ -101,20 +86,15 @@ test.describe('WebFontsAddon', () => {
window.helperTerm.open(term.element);
`);
fonts = await getDocumentFonts();
// safari might load the font, firefox & chrome dont
// safari loads the font, firefox & chrome dont
if (browser.browserType().name() === 'webkit') {
try {
deepStrictEqual(fonts, [{ family: 'Kongtext', status: 'loading' }]);
deepStrictEqual(await getDocumentFonts(), [{ family: 'Kongtext', status: 'loading' }]);
} catch (e) {
try {
deepStrictEqual(fonts, [{ family: 'Kongtext', status: 'loaded' }]);
} catch (e) {
deepStrictEqual(fonts, [{ family: 'Kongtext', status: 'unloaded' }]);
}
deepStrictEqual(await getDocumentFonts(), [{ family: 'Kongtext', status: 'loaded' }]);
}
} else {
deepStrictEqual(fonts, [{ family: 'Kongtext', status: 'unloaded' }]);
deepStrictEqual(await getDocumentFonts(), [{ family: 'Kongtext', status: 'unloaded' }]);
}
// good case: addon fixes layout for webfont in ctor
+1 -12
View File
@@ -3,23 +3,12 @@
"module": "commonjs",
"target": "es2021",
"lib": [
"es2021",
"es2021"
],
"rootDir": ".",
"outDir": "../out-test",
"sourceMap": true,
"removeComments": true,
"paths": {
"common/*": [
"../../../src/common/*"
],
"browser/*": [
"../../../src/browser/*"
],
"*": [
"./*"
]
},
"strict": true,
"types": [
"../../../node_modules/@types/node"
+6 -2
View File
@@ -2,7 +2,11 @@
"files": [],
"include": [],
"references": [
{ "path": "./src" },
{ "path": "./test" }
{
"path": "./src"
},
{
"path": "./test"
}
]
}
@@ -113,8 +113,8 @@ export class LinkComputer {
let line: IBufferLine | undefined;
let topIdx = lineIndex;
let bottomIdx = lineIndex;
let length;
let content;
let length = 0;
let content = '';
const lines: string[] = [];
if ((line = terminal.buffer.active.getLine(lineIndex))) {
+3 -7
View File
@@ -13,15 +13,11 @@
"strict": true,
"types": [
"../../../node_modules/@types/mocha"
],
"paths": {
"@xterm/addon-web-links": [
"../typings/addon-web-links.d.ts"
]
}
]
},
"include": [
"./**/*",
"../../../typings/xterm.d.ts"
"../../../typings/xterm.d.ts",
"../typings/addon-web-links.d.ts"
]
}
+1 -12
View File
@@ -3,23 +3,12 @@
"module": "commonjs",
"target": "es2021",
"lib": [
"es2021",
"es2021"
],
"rootDir": ".",
"outDir": "../out-test",
"sourceMap": true,
"removeComments": true,
"paths": {
"common/*": [
"../../../src/common/*"
],
"browser/*": [
"../../../src/browser/*"
],
"*": [
"./*"
]
},
"strict": true,
"types": [
"../../../node_modules/@types/node"
+6 -2
View File
@@ -2,7 +2,11 @@
"files": [],
"include": [],
"references": [
{ "path": "./src" },
{ "path": "./test" }
{
"path": "./src"
},
{
"path": "./test"
}
]
}
+8 -8
View File
@@ -1,12 +1,12 @@
import { ISelectionRenderModel } from 'browser/renderer/shared/Types';
import { ICoreBrowserService, IThemeService } from 'browser/services/Services';
import { ReadonlyColorSet } from 'browser/Types';
import { Attributes, BgFlags, ExtFlags, FgFlags, NULL_CELL_CODE, UnderlineStyle } from 'common/buffer/Constants';
import { IDecorationService, IOptionsService } from 'common/services/Services';
import { ICellData } from 'common/buffer/Types';
import { ISelectionRenderModel } from '../../../src/browser/renderer/shared/Types';
import { ICoreBrowserService, IThemeService } from '../../../src/browser/services/Services';
import { ReadonlyColorSet } from '../../../src/browser/Types';
import { Attributes, BgFlags, ExtFlags, FgFlags, NULL_CELL_CODE, UnderlineStyle } from '../../../src/common/buffer/Constants';
import { IDecorationService, IOptionsService } from '../../../src/common/services/Services';
import { ICellData } from '../../../src/common/Types';
import { Terminal } from '@xterm/xterm';
import { rgba } from 'common/Color';
import { treatGlyphAsBackgroundColor } from 'browser/renderer/shared/RendererUtils';
import { rgba } from '../../../src/common/Color';
import { treatGlyphAsBackgroundColor } from '../../../src/browser/renderer/shared/RendererUtils';
import { blockPatternCodepoints } from './customGlyphs/CustomGlyphDefinitions';
// Work variables to avoid garbage collection
+2 -2
View File
@@ -5,10 +5,10 @@
import { TextureAtlas } from './TextureAtlas';
import { ITerminalOptions, Terminal } from '@xterm/xterm';
import { ITerminal, ReadonlyColorSet } from 'browser/Types';
import { ITerminal, ReadonlyColorSet } from '../../../src/browser/Types';
import { ICharAtlasConfig, ITextureAtlas } from './Types';
import { generateConfig, configEquals } from './CharAtlasUtils';
import type { ILogService } from 'common/services/Services';
import type { ILogService } from '../../../src/common/services/Services';
interface ITextureAtlasCacheEntry {
atlas: ITextureAtlas;
@@ -6,8 +6,8 @@
import { assert } from 'chai';
import { configEquals } from './CharAtlasUtils';
import { ICharAtlasConfig } from './Types';
import { NULL_COLOR } from 'common/Color';
import { IColor } from 'common/Types';
import { NULL_COLOR } from '../../../src/common/Color';
import { IColor } from '../../../src/common/Types';
function createTestConfig(overrides: Partial<ICharAtlasConfig> = {}): ICharAtlasConfig {
const color: IColor = { css: '#ffffff', rgba: 0xffffffff };
+3 -3
View File
@@ -4,10 +4,10 @@
*/
import { ICharAtlasConfig } from './Types';
import { Attributes } from 'common/buffer/Constants';
import { Attributes } from '../../../src/common/buffer/Constants';
import { ITerminalOptions } from '@xterm/xterm';
import { IColorSet, ReadonlyColorSet } from 'browser/Types';
import { NULL_COLOR } from 'common/Color';
import { IColorSet, ReadonlyColorSet } from '../../../src/browser/Types';
import { NULL_COLOR } from '../../../src/common/Color';
export function generateConfig(deviceCellWidth: number, deviceCellHeight: number, deviceCharWidth: number, deviceCharHeight: number, options: Required<ITerminalOptions>, colors: ReadonlyColorSet, devicePixelRatio: number, deviceMaxTextureSize: number, customGlyphs: boolean = true): ICharAtlasConfig {
// null out some fields that don't matter
+1 -1
View File
@@ -3,7 +3,7 @@
* @license MIT
*/
import { isFirefox, isLegacyEdge } from 'common/Platform';
import { isFirefox, isLegacyEdge } from '../../../src/common/Platform';
export const DIM_OPACITY = 0.5;
// The text baseline is set conditionally by browser. Using 'ideographic' for Firefox or Legacy Edge
@@ -3,8 +3,8 @@
* @license MIT
*/
import { RendererConstants } from 'browser/renderer/shared/Constants';
import { ICoreBrowserService } from 'browser/services/Services';
import { RendererConstants } from '../../../src/browser/renderer/shared/Constants';
import { ICoreBrowserService } from '../../../src/browser/services/Services';
const enum Constants {
/**
@@ -3,7 +3,7 @@
* @license MIT
*/
import { toDisposable, IDisposable } from 'common/Lifecycle';
import { toDisposable, IDisposable } from '../../../src/common/Lifecycle';
export function observeDevicePixelDimensions(element: HTMLElement, parentWindow: Window & typeof globalThis, callback: (deviceWidth: number, deviceHeight: number) => void): IDisposable {
// Observe any resizes to the element and extract the actual pixel size of the element if the
+5 -5
View File
@@ -3,14 +3,14 @@
* @license MIT
*/
import { TextureAtlas } from './TextureAtlas';
import { IRenderDimensions } from 'browser/renderer/shared/Types';
import { NULL_CELL_CODE } from 'common/buffer/Constants';
import { Disposable, toDisposable } from 'common/Lifecycle';
import { IRenderDimensions } from '../../../src/browser/renderer/shared/Types';
import { NULL_CELL_CODE } from '../../../src/common/buffer/Constants';
import { Disposable, toDisposable } from '../../../src/common/Lifecycle';
import { Terminal } from '@xterm/xterm';
import { IRenderModel, IWebGL2RenderingContext, IWebGLVertexArrayObject, type IRasterizedGlyph, type ITextureAtlas } from './Types';
import { createProgram, GLTexture, PROJECTION_MATRIX } from './WebglUtils';
import type { ILogService, IOptionsService } from 'common/services/Services';
import { allowRescaling, throwIfFalsy } from 'browser/renderer/shared/RendererUtils';
import type { ILogService, IOptionsService } from '../../../src/common/services/Services';
import { allowRescaling, throwIfFalsy } from '../../../src/browser/renderer/shared/RendererUtils';
interface IVertices {
attributes: Float32Array;
+8 -8
View File
@@ -3,18 +3,18 @@
* @license MIT
*/
import { IRenderDimensions } from 'browser/renderer/shared/Types';
import { IThemeService } from 'browser/services/Services';
import { ReadonlyColorSet } from 'browser/Types';
import { Attributes, FgFlags } from 'common/buffer/Constants';
import { Disposable, toDisposable } from 'common/Lifecycle';
import { IColor } from 'common/Types';
import { IRenderDimensions } from '../../../src/browser/renderer/shared/Types';
import { IThemeService } from '../../../src/browser/services/Services';
import { ReadonlyColorSet } from '../../../src/browser/Types';
import { Attributes, FgFlags } from '../../../src/common/buffer/Constants';
import { Disposable, toDisposable } from '../../../src/common/Lifecycle';
import { IColor } from '../../../src/common/Types';
import { Terminal } from '@xterm/xterm';
import { RenderModelConstants } from './RenderModel';
import { IRenderModel, IWebGL2RenderingContext, IWebGLVertexArrayObject } from './Types';
import { createProgram, expandFloat32Array, PROJECTION_MATRIX } from './WebglUtils';
import { throwIfFalsy } from 'browser/renderer/shared/RendererUtils';
import type { ILogService } from 'common/services/Services';
import { throwIfFalsy } from '../../../src/browser/renderer/shared/RendererUtils';
import type { ILogService } from '../../../src/common/services/Services';
const enum VertexAttribLocations {
POSITION = 0,
+2 -2
View File
@@ -4,8 +4,8 @@
*/
import { ICursorRenderModel, IRenderModel } from './Types';
import { ISelectionRenderModel } from 'browser/renderer/shared/Types';
import { createSelectionRenderModel } from 'browser/renderer/shared/SelectionRenderModel';
import { ISelectionRenderModel } from '../../../src/browser/renderer/shared/Types';
import { createSelectionRenderModel } from '../../../src/browser/renderer/shared/SelectionRenderModel';
export const enum RenderModelConstants {
INDICIES_PER_CELL = 4,
+11 -11
View File
@@ -3,19 +3,19 @@
* @license MIT
*/
import { IColorContrastCache } from 'browser/Types';
import { IColorContrastCache } from '../../../src/browser/Types';
import { DIM_OPACITY, TEXT_BASELINE } from './Constants';
import { tryDrawCustomGlyph } from './customGlyphs/CustomGlyphRasterizer';
import { computeNextVariantOffset, treatGlyphAsBackgroundColor, isPowerlineGlyph, isRestrictedPowerlineGlyph, throwIfFalsy } from 'browser/renderer/shared/RendererUtils';
import { computeNextVariantOffset, treatGlyphAsBackgroundColor, isPowerlineGlyph, isRestrictedPowerlineGlyph, throwIfFalsy } from '../../../src/browser/renderer/shared/RendererUtils';
import { IBoundingBox, ICharAtlasConfig, IRasterizedGlyph, ITextureAtlas } from './Types';
import { NULL_COLOR, channels, color, rgba } from 'common/Color';
import { FourKeyMap } from 'common/MultiKeyMap';
import { IdleTaskQueue } from 'common/TaskQueue';
import { IColor } from 'common/Types';
import { AttributeData } from 'common/buffer/AttributeData';
import { Attributes, DEFAULT_COLOR, DEFAULT_EXT, UnderlineStyle } from 'common/buffer/Constants';
import { ILogService, IUnicodeService } from 'common/services/Services';
import { Emitter } from 'common/Event';
import { NULL_COLOR, channels, color, rgba } from '../../../src/common/Color';
import { FourKeyMap } from '../../../src/common/MultiKeyMap';
import { IdleTaskQueue } from '../../../src/common/TaskQueue';
import { IColor } from '../../../src/common/Types';
import { AttributeData } from '../../../src/common/buffer/AttributeData';
import { Attributes, DEFAULT_COLOR, DEFAULT_EXT, UnderlineStyle } from '../../../src/common/buffer/Constants';
import { ILogService, IUnicodeService } from '../../../src/common/services/Services';
import { Emitter } from '../../../src/common/Event';
/**
* A shared object which is used to draw nothing for a particular cell.
@@ -1125,7 +1125,7 @@ function clearColor(imageData: ImageData, bg: IColor, fg: IColor, enableThreshol
// were covered (fg=#8ae234, bg=#c4a000).
const threshold = Math.floor((Math.abs(r - fgR) + Math.abs(g - fgG) + Math.abs(b - fgB)) / 12);
// Set alpha channel of relevant pixels to 0
// Set alpha channel of relevent pixels to 0
let isEmpty = true;
for (let offset = 0; offset < imageData.data.length; offset += 4) {
// Check exact match
+4 -4
View File
@@ -4,10 +4,10 @@
*/
import { FontWeight } from '@xterm/xterm';
import { IColorSet } from 'browser/Types';
import { ISelectionRenderModel } from 'browser/renderer/shared/Types';
import { CursorInactiveStyle, CursorStyle, type IDisposable } from 'common/Types';
import type { IEvent } from 'common/Event';
import { IColorSet } from '../../../src/browser/Types';
import { ISelectionRenderModel } from '../../../src/browser/renderer/shared/Types';
import { CursorInactiveStyle, CursorStyle, type IDisposable } from '../../../src/common/Types';
import type { IEvent } from '../../../src/common/Event';
export interface IRenderModel {
cells: Uint32Array;
+7 -7
View File
@@ -5,14 +5,14 @@
import type { ITerminalAddon, Terminal } from '@xterm/xterm';
import type { IWebglAddonOptions, WebglAddon as IWebglApi } from '@xterm/addon-webgl';
import { ICharacterJoinerService, ICharSizeService, ICoreBrowserService, IRenderService, IThemeService } from 'browser/services/Services';
import { ITerminal } from 'browser/Types';
import { Disposable, toDisposable } from 'common/Lifecycle';
import { getSafariVersion, isSafari } from 'common/Platform';
import { ICoreService, IDecorationService, ILogService, IOptionsService } from 'common/services/Services';
import { ICharacterJoinerService, ICharSizeService, ICoreBrowserService, IRenderService, IThemeService } from '../../../src/browser/services/Services';
import { ITerminal } from '../../../src/browser/Types';
import { Disposable, toDisposable } from '../../../src/common/Lifecycle';
import { getSafariVersion, isSafari } from '../../../src/common/Platform';
import { ICoreService, IDecorationService, ILogService, IOptionsService } from '../../../src/common/services/Services';
import { IWebGL2RenderingContext } from './Types';
import { WebglRenderer } from './WebglRenderer';
import { Emitter, EventUtils } from 'common/Event';
import { Emitter, EventUtils } from '../../../src/common/Event';
export class WebglAddon extends Disposable implements ITerminalAddon, IWebglApi {
private _terminal?: Terminal;
@@ -40,7 +40,7 @@ export class WebglAddon extends Disposable implements ITerminalAddon, IWebglApi
};
const gl = document.createElement('canvas').getContext('webgl2', contextAttributes) as IWebGL2RenderingContext;
if (!gl) {
throw new Error('WebGL2 is only supported on Safari 16 and above');
throw new Error('Webgl2 is only supported on Safari 16 and above');
}
}
super();
+17 -17
View File
@@ -3,19 +3,19 @@
* @license MIT
*/
import { ITerminal } from 'browser/Types';
import { ITerminal } from '../../../src/browser/Types';
import { CellColorResolver } from './CellColorResolver';
import { acquireTextureAtlas, removeTerminalFromCache } from './CharAtlasCache';
import { CursorBlinkStateManager } from './CursorBlinkStateManager';
import { observeDevicePixelDimensions } from './DevicePixelObserver';
import { IRenderDimensions, IRenderer, IRequestRedrawEvent } from 'browser/renderer/shared/Types';
import { ICharSizeService, ICharacterJoinerService, ICoreBrowserService, IThemeService } from 'browser/services/Services';
import { CharData, IBufferLine, ICellData } from 'common/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';
import { TextBlinkStateManager } from 'browser/renderer/shared/TextBlinkStateManager';
import { ICoreService, IDecorationService, ILogService, IOptionsService } from 'common/services/Services';
import { IRenderDimensions, IRenderer, IRequestRedrawEvent } from '../../../src/browser/renderer/shared/Types';
import { ICharSizeService, ICharacterJoinerService, ICoreBrowserService, IThemeService } from '../../../src/browser/services/Services';
import { CharData, IBufferLine, ICellData } from '../../../src/common/Types';
import { AttributeData } from '../../../src/common/buffer/AttributeData';
import { CellData } from '../../../src/common/buffer/CellData';
import { Attributes, Content, FgFlags, NULL_CELL_CHAR, NULL_CELL_CODE } from '../../../src/common/buffer/Constants';
import { TextBlinkStateManager } from '../../../src/browser/renderer/shared/TextBlinkStateManager';
import { ICoreService, IDecorationService, ILogService, IOptionsService } from '../../../src/common/services/Services';
import { Terminal } from '@xterm/xterm';
import { GlyphRenderer } from './GlyphRenderer';
import { RectangleRenderer } from './RectangleRenderer';
@@ -23,10 +23,10 @@ import { COMBINED_CHAR_BIT_MASK, RenderModel, RenderModelConstants } from './Ren
import { IWebGL2RenderingContext, type ITextureAtlas } from './Types';
import { LinkRenderLayer } from './renderLayer/LinkRenderLayer';
import { IRenderLayer } from './renderLayer/Types';
import { Emitter, EventUtils } from 'common/Event';
import { addDisposableListener } from 'browser/Dom';
import { combinedDisposable, Disposable, MutableDisposable, toDisposable } from 'common/Lifecycle';
import { createRenderDimensions } from 'browser/renderer/shared/RendererUtils';
import { Emitter, EventUtils } from '../../../src/common/Event';
import { addDisposableListener } from '../../../src/browser/Dom';
import { combinedDisposable, Disposable, MutableDisposable, toDisposable } from '../../../src/common/Lifecycle';
import { createRenderDimensions } from '../../../src/browser/renderer/shared/RendererUtils';
const enum Constants {
MERGE_RETRY_LIMIT = 32
@@ -427,8 +427,8 @@ export class WebglRenderer extends Disposable implements IRenderer {
let line: IBufferLine;
let joinedRanges: [number, number][];
let isJoined: boolean;
let skipJoinedCheckUntilX: number;
let isValidJoinRange: boolean;
let skipJoinedCheckUntilX: number = 0;
let isValidJoinRange: boolean = true;
let lastCharX: number;
let range: [number, number];
let isCursorRow: boolean;
@@ -664,10 +664,10 @@ export class WebglRenderer extends Disposable implements IRenderer {
this.dimensions.device.canvas.height = this._terminal.rows * this.dimensions.device.cell.height;
this.dimensions.device.canvas.width = this._terminal.cols * this.dimensions.device.cell.width;
// The size of the canvas on the page. It's important that this rounds to nearest integer
// The the size of the canvas on the page. It's important that this rounds to nearest integer
// and not ceils as browsers often have floating point precision issues where
// `window.devicePixelRatio` ends up being something like `1.100000023841858` for example, when
// it's actually 1.1. Ceiling may cause blurriness as the backing canvas image is 1 pixel too
// it's actually 1.1. Ceiling may causes blurriness as the backing canvas image is 1 pixel too
// large for the canvas element size.
this.dimensions.css.canvas.height = Math.round(this.dimensions.device.canvas.height / this._devicePixelRatio);
this.dimensions.css.canvas.width = Math.round(this.dimensions.device.canvas.width / this._devicePixelRatio);

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