Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
97a5069
Refactor so isWrapped isn't public writeable.
PerBothner Mar 31, 2026
1c58f1f
Merge branch 'master' into setWrapped
PerBothner Apr 6, 2026
e9ea22f
Fix MockBuffer.setWrapped.
PerBothner Apr 6, 2026
8773207
Merge branch 'master' into setWrapped
PerBothner Apr 25, 2026
a88bd17
Bump axios from 1.13.2 to 1.15.0
dependabot[bot] Apr 12, 2026
1d562d1
Fix image layer resize jitter
SergioChan Mar 11, 2026
7e7dbc9
Update ImageRenderer.ts
Tyriar Mar 12, 2026
8315000
add missing separator for test arguments
jerch Apr 28, 2026
46fcebb
fix web-fonts tests
jerch Apr 28, 2026
918c5f0
newer safari promotes different loading state
jerch Apr 28, 2026
8653029
remove broken test condition
jerch Apr 28, 2026
0780cfc
fix safari
jerch Apr 28, 2026
cf799ac
fix safari
jerch Apr 28, 2026
10d2b1d
fix safari
jerch Apr 28, 2026
1a06b92
disable wrong webgl tests
jerch Apr 28, 2026
9c5e1d7
disable firefox webgl
jerch Apr 28, 2026
26d1ddd
Bump follow-redirects from 1.15.11 to 1.16.0
dependabot[bot] Apr 28, 2026
673db7c
Make agent instructions harness agnostic
Tyriar May 1, 2026
2998cf7
Add Orca to real-world uses
nwparker Apr 29, 2026
5c0434f
fix #5849
jerch Apr 28, 2026
fe7611a
adjust image addon defaults
jerch Apr 26, 2026
b54f918
multiple changes in APC handling:
jerch Apr 24, 2026
7ecb43d
make linter happy
jerch Apr 24, 2026
91f816c
add state and transition tests
jerch Apr 24, 2026
b4c9024
basic read ahead optimization
jerch Apr 24, 2026
bdea328
APC perf test
jerch Apr 24, 2026
05b8c14
better read ahead condition
jerch Apr 24, 2026
ffd194a
make linter happy
jerch Apr 24, 2026
4dc3aff
register APC fallback handler
jerch Apr 25, 2026
648779a
handler registration tests, fix mulitple immediates
jerch Apr 25, 2026
658ce2b
APC async tests
jerch Apr 25, 2026
9088d1e
fix ApcParser tests
jerch Apr 25, 2026
6b00ba6
fix vt feature listing
jerch Apr 26, 2026
d8f504f
fix vt feature listing
jerch Apr 26, 2026
bf35a21
fix vt feature listing
jerch Apr 26, 2026
d99c173
fix vt feature listing
jerch Apr 26, 2026
d7129e5
qoi support for IIP
jerch Apr 26, 2026
7205b06
add iip resize & qoi tests
jerch Apr 29, 2026
553d0dc
activate tests for webkit
jerch Apr 29, 2026
31e2fe7
IIP resizing tests for QOI
jerch Apr 29, 2026
552b7a0
merge resize tests for PNG & QOI into one block
jerch Apr 29, 2026
eb3c7e7
Add instructions around MutableDisposables and const enums
Tyriar May 2, 2026
97cb26a
Convert a lot of top level consts to const enums
Tyriar May 2, 2026
5107158
Cache translateToString calls inside BufferLine
Tyriar May 1, 2026
ca70702
Fix test/startCol handling
Tyriar May 1, 2026
227ea0c
Fix integration test defaults
Tyriar May 1, 2026
8757f76
Support caching of trimRight
Tyriar May 2, 2026
17e7ec5
Store a boolean on whether the cache is trimmed
Tyriar May 2, 2026
d92a1a9
Move cache into Buffer owned object, use WeakRefs
Tyriar May 2, 2026
1b9c9c4
Cache/buffer lifecycle
Tyriar May 2, 2026
424722a
Convert cache to Disposable
Tyriar May 2, 2026
6adfde4
Avoid any in test
Tyriar May 2, 2026
36a9f5d
Avoid thrashing timeouts
Tyriar May 2, 2026
d027be4
Fix build, lint
Tyriar May 2, 2026
34dd3ca
Ignore cache keys in serialize addon
Tyriar May 2, 2026
874825e
Fix lint
Tyriar May 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
---
applyTo: '**/*.benchmark.ts'
name: benchmark
description: Run and author benchmark tests in xterm.js. Use when editing benchmark files, working in `*.benchmark.ts`, or when asked how to run the full benchmark suite, a single benchmark file, or a single benchmark case.
---
# Benchmark run instructions

# Benchmark Run Instructions

## Running Benchmarks

- Full suite: `npm run benchmark`
- Single benchmark file:
Expand All @@ -11,8 +15,14 @@ applyTo: '**/*.benchmark.ts'
- Use `-t` to get the path, then:
- `npm run benchmark -- -s "<path>" out-test/benchmark/Event.benchmark.js`

When writing instructions, use `RuntimeCase` to measure pure runtime in ms, use `ThroughputRuntimeCase` when measuring throughput in MB/s.
## Benchmark Case Selection

When writing benchmark instructions:

- Use `RuntimeCase` to measure pure runtime in ms.
- Use `ThroughputRuntimeCase` when measuring throughput in MB/s.

## Notes

Notes:
- Benchmarks run from built JS in `out-test/benchmark/*.benchmark.js`.
- Keep `NODE_PATH=./out` (handled by the npm script).
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
---
applyTo: '**/*.test.ts'
name: unit-test
description: Write and review xterm.js unit tests with project conventions. Use when editing `*.test.ts` files or when asked for test authoring guidelines.
---
When writing unit tests follow these rules:

# Unit Test Instructions

When writing unit tests, follow these rules:

- When writing unit tests for addons, always create a real xterm.js instance instead of mocking it.
- Prefer `assert.ok` over `assert.notStrictEqual` when checking something is undefined or not.
Expand Down
10 changes: 0 additions & 10 deletions .github/hooks/setupRepo.json

This file was deleted.

26 changes: 13 additions & 13 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -207,31 +207,31 @@ jobs:
- name: Build demo server
run: npm run esbuild-demo-server
- name: Integration tests (core) # Tests use 50% workers to reduce flakiness
run: npm run test-integration-${{ matrix.browser }} --workers=50% --forbid-only --suite=core
run: npm run test-integration-${{ matrix.browser }} -- --workers=50% --forbid-only --suite=core
- name: Integration tests (addon-attach)
run: npm run test-integration-${{ matrix.browser }} --workers=50% --forbid-only --suite=addon-attach
run: npm run test-integration-${{ matrix.browser }} -- --workers=50% --forbid-only --suite=addon-attach
- name: Integration tests (addon-clipboard)
run: npm run test-integration-${{ matrix.browser }} --workers=50% --forbid-only --suite=addon-clipboard
run: npm run test-integration-${{ matrix.browser }} -- --workers=50% --forbid-only --suite=addon-clipboard
- name: Integration tests (addon-fit)
run: npm run test-integration-${{ matrix.browser }} --workers=50% --forbid-only --suite=addon-fit
run: npm run test-integration-${{ matrix.browser }} -- --workers=50% --forbid-only --suite=addon-fit
- name: Integration tests (addon-image)
run: npm run test-integration-${{ matrix.browser }} --workers=50% --forbid-only --suite=addon-image
run: npm run test-integration-${{ matrix.browser }} -- --workers=50% --forbid-only --suite=addon-image
- name: Integration tests (addon-progress)
run: npm run test-integration-${{ matrix.browser }} --workers=50% --forbid-only --suite=addon-progress
run: npm run test-integration-${{ matrix.browser }} -- --workers=50% --forbid-only --suite=addon-progress
- name: Integration tests (addon-search)
run: npm run test-integration-${{ matrix.browser }} --workers=50% --forbid-only --suite=addon-search
run: npm run test-integration-${{ matrix.browser }} -- --workers=50% --forbid-only --suite=addon-search
- name: Integration tests (addon-serialize)
run: npm run test-integration-${{ matrix.browser }} --workers=50% --forbid-only --suite=addon-serialize
run: npm run test-integration-${{ matrix.browser }} -- --workers=50% --forbid-only --suite=addon-serialize
- name: Integration tests (addon-unicode-graphemes)
run: npm run test-integration-${{ matrix.browser }} --workers=50% --forbid-only --suite=addon-unicode-graphemes
run: npm run test-integration-${{ matrix.browser }} -- --workers=50% --forbid-only --suite=addon-unicode-graphemes
- name: Integration tests (addon-unicode11)
run: npm run test-integration-${{ matrix.browser }} --workers=50% --forbid-only --suite=addon-unicode11
run: npm run test-integration-${{ matrix.browser }} -- --workers=50% --forbid-only --suite=addon-unicode11
- name: Integration tests (addon-web-fonts)
run: npm run test-integration-${{ matrix.browser }} --workers=50% --forbid-only --suite=addon-web-fonts
run: npm run test-integration-${{ matrix.browser }} -- --workers=50% --forbid-only --suite=addon-web-fonts
- name: Integration tests (addon-web-links)
run: npm run test-integration-${{ matrix.browser }} --workers=50% --forbid-only --suite=addon-web-links
run: npm run test-integration-${{ matrix.browser }} -- --workers=50% --forbid-only --suite=addon-web-links
- name: Integration tests (addon-webgl)
run: npm run test-integration-${{ matrix.browser }} --workers=50% --forbid-only --suite=addon-webgl
run: npm run test-integration-${{ matrix.browser }} -- --workers=50% --forbid-only --suite=addon-webgl

release-dry-run:
needs: build
Expand Down
7 changes: 7 additions & 0 deletions .github/copilot-instructions.md → AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,13 @@ export class MyAddon implements ITerminalAddon {
- Proposed APIs require `allowProposedApi: true` option
- Constructor-only options (cols, rows) cannot be changed after instantiation

**Disposable Management**:
- When a disposable object can be replaced over time, prefer a registered `MutableDisposable` over manual dispose/reassign logic.
- Register it on the owning class (for example, `this._register(new MutableDisposable())`) and assign through `.value`; this automatically disposes the previous value and avoids accidentally leaking resources.

**TypeScript Constants**:
- Prefer `const enum` over top-level `const` declarations for primitive constants when appropriate, since values are inlined and avoid runtime property lookups.

**Testing Utilities**: Use `TestUtils.ts` helpers:
- `openTerminal(ctx, options)` for setup
- `pollFor(page, fn, expectedValue)` for async assertions
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,7 @@ Xterm.js is used in many world-class applications to provide great terminal expe
- [**EmuDevz**](https://afska.github.io/emudevz): A free coding game where players learn how to build an emulator from scratch.
- [**SSH Connection Manager**](https://github.com/Amtrend/ssh-manager): Modern web-based SSH terminal and SFTP manager with AES-256 encryption, PWA support, and session management.
- [**WooTTY**](https://github.com/icoretech/wootty): Flawless browser terminal for real operators.
- [**Orca**](https://github.com/stablyai/orca): Agentic development environment for 100x builders.
- [And much more...](https://github.com/xtermjs/xterm.js/network/dependents?package_id=UGFja2FnZS0xNjYzMjc4OQ%3D%3D)

Do you use xterm.js in your application as well? Please [open a Pull Request](https://github.com/sourcelair/xterm.js/pulls) to include it here. We would love to have it on our list. Please add any new contributions to the end of the list.
Expand Down
10 changes: 6 additions & 4 deletions addons/addon-fit/src/FitAddon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,10 @@ interface ITerminalDimensions {
cols: number;
}

const MINIMUM_COLS = 2;
const MINIMUM_ROWS = 1;
const enum Constants {
MINIMUM_COLS = 2,
MINIMUM_ROWS = 1
}

function getWindow(e: Node): Window {
if (e?.ownerDocument?.defaultView) {
Expand Down Expand Up @@ -86,8 +88,8 @@ export class FitAddon implements ITerminalAddon, IFitApi {
const availableHeight = parentElementHeight - elementPaddingVer;
const availableWidth = parentElementWidth - elementPaddingHor - scrollbarWidth;
const geometry = {
cols: Math.max(MINIMUM_COLS, Math.floor(availableWidth / dims.css.cell.width)),
rows: Math.max(MINIMUM_ROWS, Math.floor(availableHeight / dims.css.cell.height))
cols: Math.max(Constants.MINIMUM_COLS, Math.floor(availableWidth / dims.css.cell.width)),
rows: Math.max(Constants.MINIMUM_ROWS, Math.floor(availableHeight / dims.css.cell.height))
};
return geometry;
}
Expand Down
8 changes: 4 additions & 4 deletions addons/addon-image/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,14 @@ const customSettings: IImageAddonOptions = {
pixelLimit: 16777216, // max. pixel size of a single image
sixelSupport: true, // enable sixel support
sixelScrolling: true, // whether to scroll on image output
sixelPaletteLimit: 256, // initial sixel palette size
sixelSizeLimit: 25000000, // size limit of a single sixel sequence
sixelPaletteLimit: 4096, // initial sixel palette size
sixelSizeLimit: 33554432, // size limit of a single sixel sequence
storageLimit: 128, // FIFO storage limit in MB
showPlaceholder: true, // whether to show a placeholder for evicted images
iipSupport: true, // enable iTerm IIP support
iipSizeLimit: 20000000, // size limit of a single IIP sequence
iipSizeLimit: 33554432, // size limit of a single IIP sequence
kittySupport: true, // enable Kitty graphics support
kittySizeLimit: 20000000 // size limit of a single Kitty sequence
kittySizeLimit: 33554432 // size limit of a single Kitty sequence
}

// initialization
Expand Down
2 changes: 1 addition & 1 deletion addons/addon-image/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,6 @@
},
"devDependencies": {
"sixel": "^0.16.0",
"xterm-wasm-parts": "^0.3.0"
"xterm-wasm-parts": "^0.4.1"
}
}
43 changes: 23 additions & 20 deletions addons/addon-image/src/IIPHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { ImageRenderer } from './ImageRenderer';
import { IIPImageStorage } from './IIPImageStorage';
import { CELL_SIZE_DEFAULT } from './ImageStorage';
import Base64Decoder from 'xterm-wasm-parts/lib/base64/Base64Decoder.wasm';
import QoiDecoder from 'xterm-wasm-parts/lib/qoi/QoiDecoder.wasm';
import { HeaderParser, IHeaderFields, HeaderState } from './IIPHeaderParser';
import { imageType, UNSUPPORTED_TYPE } from './IIPMetrics';

Expand Down Expand Up @@ -36,6 +37,7 @@ export class IIPHandler implements IOscHandler, IResetHandler {
private _hp = new HeaderParser();
private _header: IHeaderFields = DEFAULT_HEADER;
private _dec: Base64Decoder;
private _qoiDec: QoiDecoder;
private _metrics = UNSUPPORTED_TYPE;

constructor(
Expand All @@ -47,6 +49,7 @@ export class IIPHandler implements IOscHandler, IResetHandler {
const maxEncodedBytes = Math.ceil(this._opts.iipSizeLimit * 4 / 3);
const initialBytes = Math.min(DecoderConst.INITIAL_DATA, maxEncodedBytes);
this._dec = new Base64Decoder(DecoderConst.KEEP_DATA, maxEncodedBytes, initialBytes);
this._qoiDec = new QoiDecoder(DecoderConst.KEEP_DATA);
}

public reset(): void {}
Expand Down Expand Up @@ -115,27 +118,27 @@ export class IIPHandler implements IOscHandler, IResetHandler {
return true;
}

// HACK: The types on Blob are too restrictive, this is a Uint8Array so the browser accepts it
const blob = new Blob([this._dec.data8 as Uint8Array<ArrayBuffer>], { type: this._metrics.mime });
this._dec.release();

if (!window.createImageBitmap) {
const url = URL.createObjectURL(blob);
const img = new Image();
return new Promise<boolean>(r => {
img.addEventListener('load', () => {
URL.revokeObjectURL(url);
const canvas = ImageRenderer.createCanvas(window.document, w, h);
canvas.getContext('2d')?.drawImage(img, 0, 0, w, h);
this._storage.addImage(canvas);
r(true);
});
img.src = url;
// sanity measure to avoid terminal blocking from dangling promise
// happens from corrupt data (onload never gets fired)
setTimeout(() => r(true), 1000);
});
let blob: Blob | ImageData;
if (this._metrics.mime === 'image/qoi') {
const data = this._qoiDec.decode(this._dec.data8);
blob = new ImageData(
new Uint8ClampedArray(data.buffer, data.byteOffset, data.byteLength),
this._qoiDec.width,
this._qoiDec.height
);
this._qoiDec.release();
if (w === this._qoiDec.width && h === this._qoiDec.height) {
// use fast-path if we don't need to rescale
this._dec.release();
const canvas = ImageRenderer.createCanvas(undefined, this._qoiDec.width, this._qoiDec.height);
canvas.getContext('2d')?.putImageData(blob, 0, 0);
this._storage.addImage(canvas);
return true;
}
} else {
blob = new Blob([this._dec.data8], { type: this._metrics.mime });
}
this._dec.release();
return createImageBitmap(blob, { resizeWidth: w, resizeHeight: h })
.then(bm => {
this._storage.addImage(bm);
Expand Down
10 changes: 9 additions & 1 deletion addons/addon-image/src/IIPMetrics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*/


export type ImageType = 'image/png' | 'image/jpeg' | 'image/gif' | 'unsupported' | '';
export type ImageType = 'image/png' | 'image/jpeg' | 'image/gif' | 'image/qoi' | 'unsupported' | '';

export interface IMetrics {
mime: ImageType;
Expand Down Expand Up @@ -45,6 +45,14 @@ export function imageType(d: Uint8Array): IMetrics {
height: d[9] << 8 | d[8]
};
}
// QOI: qoif
if (d32[0] === 0x66696F71) {
return {
mime: 'image/qoi',
width: d[4] << 24 | d[5] << 16 | d[6] << 8 | d[7],
height: d[8] << 24 | d[9] << 16 | d[10] << 8 | d[11]
};
}
return UNSUPPORTED_TYPE;
}

Expand Down
51 changes: 46 additions & 5 deletions addons/addon-image/src/ImageAddon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,61 @@ import { SixelImageStorage } from './SixelImageStorage';
import { IIPImageStorage } from './IIPImageStorage';
import { ITerminalExt, IImageAddonOptions, IResetHandler } from './Types';


/**
* Document VT features provided by this addon.
*
* @vt: #E[Supported via @xterm/addon-image.] DCS SIXEL "SIXEL Graphics" "DCS Ps ; Ps ; Ps ; q Pt ST" "Draw SIXEL image."
*
* Sixel support is provided by the addon @xterm/addon-image with these limitations:
* - immediate coloring (no shared palette, allows high color settings of `img2sixel`)
* - max. palette size of 4096 colors
* - max. pixel width of 16K
* - max. 25 MB per sixel sequence
* - VT340 cursor positioning (begin of last sixel data row)
*
* See [addon readme](https://github.com/xtermjs/xterm.js/tree/master/addons/addon-image) for more details.
*
*
* @vt: #E[Supported via @xterm/addon-image.] OSC 1337 "iTerm2 Commands" "OSC 1337 ; Pt BEL" "Custom iTerm2 commands."
*
* Only the inline image protocol (IIP) is supported by the addon @xterm/addon-image with
* the following limitations:
* - sequence:
* - format: `OSC 1337 ; File=inline=1 ; size=<unencoded size> ; ... : <base64 payload> BEL`
* - size param must be set and payload may not exceed CEIL(size * 4 / 3)
* - strict base64 handling as of RFC4648 §4 (standard alphabet, optional padding,
* no separator bytes allowed)
* - supported params: size, name, width, height, preserveAspectRatio
* - image formats: PNG, JPEG and GIF
* - no animation support (renders first image of a GIF)
* - no multipart support
* - VT340 cursor positioning (begin of last sixel data row)
*
* See [addon readme](https://github.com/xtermjs/xterm.js/tree/master/addons/addon-image)
* and [iTerm2 IIP docs](https://iterm2.com/documentation-images.html) for more details.
*
*
* @vt: #E[Supported via @xterm/addon-image.] APC KITTY_GRAPHICS "Kitty Graphics" "APC G Pt ST" "Kitty Graphics Protocol."
*
* Kitty graphics support is provided by the addon @xterm/addon-image.
* Note that while basic image output already works, this is still work in progress.
*/

// default values of addon ctor options
const DEFAULT_OPTIONS: IImageAddonOptions = {
enableSizeReports: true,
pixelLimit: 16777216, // limit to 4096 * 4096 pixels
sixelSupport: true,
sixelScrolling: true,
sixelPaletteLimit: 256,
sixelSizeLimit: 25000000,
sixelPaletteLimit: 4096,
sixelSizeLimit: 33554432,
storageLimit: 128,
showPlaceholder: true,
iipSupport: true,
iipSizeLimit: 20000000,
iipSizeLimit: 33554432,
kittySupport: true,
kittySizeLimit: 20000000
kittySizeLimit: 33554432
};

// max palette size supported by the sixel lib (compile time setting)
Expand Down Expand Up @@ -166,7 +207,7 @@ export class ImageAddon implements ITerminalAddon, IImageApi {
this._disposeLater(
kittyStorage,
kittyHandler,
terminal._core._inputHandler._parser.registerApcHandler(0x47, kittyHandler)
terminal._core._inputHandler._parser.registerApcHandler({ final: 'G' }, kittyHandler)
);
}
}
Expand Down
Loading