From 114c66c86ca836ccddb65ffb559dbce20711665a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 28 Feb 2026 03:15:03 +0000 Subject: [PATCH 01/11] Bump minimatch from 3.1.2 to 3.1.5 Bumps [minimatch](https://github.com/isaacs/minimatch) from 3.1.2 to 3.1.5. - [Changelog](https://github.com/isaacs/minimatch/blob/main/changelog.md) - [Commits](https://github.com/isaacs/minimatch/compare/v3.1.2...v3.1.5) --- updated-dependencies: - dependency-name: minimatch dependency-version: 3.1.5 dependency-type: indirect ... Signed-off-by: dependabot[bot] --- package-lock.json | 65 +++++++++++++++++++++++++++-------------------- 1 file changed, 37 insertions(+), 28 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9c83261b2..2df1786d3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1108,10 +1108,11 @@ } }, "node_modules/@eslint/config-array/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, + "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -1186,10 +1187,11 @@ } }, "node_modules/@eslint/eslintrc/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, + "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -3869,10 +3871,11 @@ } }, "node_modules/eslint/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, + "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -4558,10 +4561,11 @@ "dev": true }, "node_modules/glob/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", + "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", "dev": true, + "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" }, @@ -5804,12 +5808,13 @@ } }, "node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", "dev": true, + "license": "ISC", "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^2.0.2" }, "engines": { "node": ">=16 || 14 >=14.17" @@ -5875,10 +5880,11 @@ } }, "node_modules/mocha/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", + "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", "dev": true, + "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" }, @@ -6090,10 +6096,11 @@ } }, "node_modules/nyc/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, + "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -6905,10 +6912,11 @@ } }, "node_modules/rimraf/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, + "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -7618,10 +7626,11 @@ } }, "node_modules/test-exclude/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, + "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, From a2cb36e6dd26e25478de855920bcd55ad1a76aa4 Mon Sep 17 00:00:00 2001 From: Kain Date: Mon, 2 Mar 2026 04:23:54 +0100 Subject: [PATCH 02/11] Add WooTTY project to README Added WooTTY to the list of projects using xterm.js. --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index d001f1606..706414050 100644 --- a/README.md +++ b/README.md @@ -229,6 +229,7 @@ Xterm.js is used in many world-class applications to provide great terminal expe - [**ecmaOS**](https://ecmaos.sh): A kernel and suite of applications tying modern web technologies into a browser-based operating system. - [**LabEx**](https://labex.io): Interactive learning platform with hands-on labs and xterm.js-based online terminals, focused on learn-by-doing approach. - [**EmuDevz**](https://afska.github.io/emudevz): A free coding game where players learn how to build an emulator from scratch. +- [**WooTTY**](https://github.com/icoretech/wootty): Flawless browser terminal for real operators. - [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. From d6c872bb2c7c62c343ad795fdba0f494f60c27cd Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Tue, 3 Mar 2026 12:19:14 -0800 Subject: [PATCH 03/11] add onImageAdded to api --- addons/addon-image/src/ImageAddon.ts | 5 +++++ addons/addon-image/src/ImageStorage.ts | 2 ++ addons/addon-image/test/ImageAddon.test.ts | 12 ++++++++++++ addons/addon-image/test/KittyGraphics.test.ts | 13 +++++++++++++ addons/addon-image/typings/addon-image.d.ts | 7 ++++++- 5 files changed, 38 insertions(+), 1 deletion(-) diff --git a/addons/addon-image/src/ImageAddon.ts b/addons/addon-image/src/ImageAddon.ts index fb3368f40..7af0e519c 100644 --- a/addons/addon-image/src/ImageAddon.ts +++ b/addons/addon-image/src/ImageAddon.ts @@ -5,6 +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 { IIPHandler } from './IIPHandler'; import { ImageRenderer } from './ImageRenderer'; import { ImageStorage, CELL_SIZE_DEFAULT } from './ImageStorage'; @@ -62,6 +63,8 @@ export class ImageAddon implements ITerminalAddon, IImageApi { private _disposables: IDisposable[] = []; private _terminal: ITerminalExt | undefined; private _handlers: Map = new Map(); + private readonly _onImageAdded = new Emitter(); + public readonly onImageAdded: IEvent = this._onImageAdded.event; constructor(opts?: Partial) { this._opts = Object.assign({}, DEFAULT_OPTIONS, opts); @@ -74,6 +77,7 @@ export class ImageAddon implements ITerminalAddon, IImageApi { } this._disposables.length = 0; this._handlers.clear(); + this._onImageAdded.dispose(); } private _disposeLater(...args: IDisposable[]): void { @@ -88,6 +92,7 @@ export class ImageAddon implements ITerminalAddon, IImageApi { // internal data structures this._renderer = new ImageRenderer(terminal); this._storage = new ImageStorage(terminal, this._renderer, this._opts); + this._storage.onImageAdded = () => this._onImageAdded.fire(); // enable size reports if (this._opts.enableSizeReports) { diff --git a/addons/addon-image/src/ImageStorage.ts b/addons/addon-image/src/ImageStorage.ts index 2809c0fbd..8c89a16b0 100644 --- a/addons/addon-image/src/ImageStorage.ts +++ b/addons/addon-image/src/ImageStorage.ts @@ -124,6 +124,7 @@ export class ImageStorage implements IDisposable { private _pixelLimit: number = 2500000; private _viewportMetrics: { cols: number, rows: number }; + public onImageAdded: (() => void) | undefined; public onImageDeleted: ((storageId: number) => void) | undefined; constructor( @@ -336,6 +337,7 @@ export class ImageStorage implements IDisposable { // finally add the image this._images.set(imageId, imgSpec); + this.onImageAdded?.(); return imageId; } diff --git a/addons/addon-image/test/ImageAddon.test.ts b/addons/addon-image/test/ImageAddon.test.ts index d6178ff50..67769204f 100644 --- a/addons/addon-image/test/ImageAddon.test.ts +++ b/addons/addon-image/test/ImageAddon.test.ts @@ -196,6 +196,18 @@ test.describe('ImageAddon', () => { }); test.describe('image lifecycle & eviction', () => { + test('onImageAdded fires for each image', async () => { + await ctx.page.evaluate(` + window._imageAddedCount = 0; + window.imageAddon.onImageAdded(() => { window._imageAddedCount++; }); + `); + await ctx.proxy.write(SIXEL_SEQ_0); + await pollFor(ctx.page, 'window._imageAddedCount', 1); + await ctx.proxy.write(SIXEL_SEQ_0); + await pollFor(ctx.page, 'window._imageAddedCount', 2); + await ctx.proxy.write(SIXEL_SEQ_0); + await pollFor(ctx.page, 'window._imageAddedCount', 3); + }); test('delete image once scrolled off', async () => { await ctx.proxy.write(SIXEL_SEQ_0); pollFor(ctx.page, 'window.imageAddon._storage._images.size', 1); diff --git a/addons/addon-image/test/KittyGraphics.test.ts b/addons/addon-image/test/KittyGraphics.test.ts index b28c3422a..315538aa1 100644 --- a/addons/addon-image/test/KittyGraphics.test.ts +++ b/addons/addon-image/test/KittyGraphics.test.ts @@ -2159,6 +2159,19 @@ test.describe('Kitty Graphics Protocol', () => { strictEqual(await ctx.page.evaluate(`window.imageAddon._storage._images.has(${oldStorageId})`), false); }); }); + + test.describe('onImageAdded callback', () => { + test('onImageAdded fires for each kitty image', async () => { + await ctx.page.evaluate(` + window._imageAddedCount = 0; + window.imageAddon.onImageAdded(() => { window._imageAddedCount++; }); + `); + await ctx.proxy.write(`\x1b_Ga=T,f=100;${KITTY_BLACK_1X1_BASE64}\x1b\\`); + await pollFor(ctx.page, 'window._imageAddedCount', 1); + await ctx.proxy.write(`\x1b_Ga=T,f=100;${KITTY_RGB_3X1_BASE64}\x1b\\`); + await pollFor(ctx.page, 'window._imageAddedCount', 2); + }); + }); }); /** diff --git a/addons/addon-image/typings/addon-image.d.ts b/addons/addon-image/typings/addon-image.d.ts index 856063e3e..a3fc9e1c8 100644 --- a/addons/addon-image/typings/addon-image.d.ts +++ b/addons/addon-image/typings/addon-image.d.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { Terminal, ITerminalAddon } from '@xterm/xterm'; +import { Terminal, ITerminalAddon, IEvent } from '@xterm/xterm'; declare module '@xterm/addon-image' { export interface IImageAddonOptions { @@ -116,6 +116,11 @@ declare module '@xterm/addon-image' { */ public showPlaceholder: boolean; + /** + * Event fired whenever a new image is added to storage. + */ + public readonly onImageAdded: IEvent; + /** * Get original image canvas at buffer position. */ From f5e81afba76761912e4fa1f28f75c75624226764 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Thu, 5 Mar 2026 16:46:03 -0800 Subject: [PATCH 04/11] Fix ^C not working to end for newer fish + kitty keyboard protocol --- src/common/InputHandler.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/InputHandler.ts b/src/common/InputHandler.ts index 1997d9f11..327eb2ec6 100644 --- a/src/common/InputHandler.ts +++ b/src/common/InputHandler.ts @@ -3570,7 +3570,7 @@ export class InputHandler extends Disposable implements IInputHandler { return true; } const flags = params.params[0] || 0; - const mode = params.params[1] || 1; + const mode = params.length > 1 ? (params.params[1] || 1) : 1; const state = this._coreService.kittyKeyboard; switch (mode) { From 7ec41471da52dae5686f82bfca612bcea6fd2ef2 Mon Sep 17 00:00:00 2001 From: ScottMorse Date: Fri, 6 Mar 2026 00:10:32 -0700 Subject: [PATCH 05/11] Fix deprecated references of xterm-headless to @xterm/headless --- README.md | 2 +- headless/README.md | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index d001f1606..d8d3fb144 100644 --- a/README.md +++ b/README.md @@ -94,7 +94,7 @@ Since xterm.js is typically implemented as a developer tool, generally only mode ### Node.js Support -We also publish [`xterm-headless`](https://www.npmjs.com/package/xterm-headless) which is a stripped down version of xterm.js that runs headless in Node.js. An example use case for this is to keep track of a terminal's state where the process is running and using the [serialize addon](https://www.npmjs.com/package/@xterm/addon-serialize) so it can get all state restored upon reconnection. +We also publish [`@xterm/headless`](https://www.npmjs.com/package/@xterm/headless) which is a stripped down version of xterm.js that runs headless in Node.js. An example use case for this is to keep track of a terminal's state where the process is running and using the [serialize addon](https://www.npmjs.com/package/@xterm/addon-serialize) so it can get all state restored upon reconnection. ## API diff --git a/headless/README.md b/headless/README.md index 205fd79c5..fe98e12fd 100644 --- a/headless/README.md +++ b/headless/README.md @@ -2,17 +2,17 @@ ⚠ This package is experimental -`xterm-headless` is a headless terminal that can be run in node.js. This is useful in combination with the frontend [`xterm`](https://www.npmjs.com/package/xterm) for example to keep track of a terminal's state on a remote server where the process is hosted. +`@xterm/headless` is a headless terminal that can be run in node.js. This is useful in combination with the frontend [`xterm`](https://www.npmjs.com/package/xterm) for example to keep track of a terminal's state on a remote server where the process is hosted. ## Getting Started First, you need to install the module, we ship exclusively through npm, so you need that installed and then add xterm.js as a dependency by running: ```sh -npm install xterm-headless +npm install @xterm/headless ``` -Then import as you would a regular node package. The recommended way to load `xterm-headless` is with TypeScript and the ES6 module syntax: +Then import as you would a regular node package. The recommended way to load `@xterm/headless` is with TypeScript and the ES6 module syntax: ```javascript import { Terminal } from '@xterm/headless'; @@ -20,12 +20,12 @@ import { Terminal } from '@xterm/headless'; ## API -The full API for `xterm-headless` is contained within the [TypeScript declaration file](https://github.com/xtermjs/xterm.js/blob/master/typings/xterm-headless.d.ts), use the branch/tag picker in GitHub (`w`) to navigate to the correct version of the API. +The full API for `@xterm/headless` is contained within the [TypeScript declaration file](https://github.com/xtermjs/xterm.js/blob/master/typings/xterm-headless.d.ts), use the branch/tag picker in GitHub (`w`) to navigate to the correct version of the API. Note that some APIs are marked *experimental*, these are added to enable experimentation with new ideas without committing to support it like a normal [semver](https://semver.org/) API. Note that these APIs can change radically between versions, so be sure to read release notes if you plan on using experimental APIs. ### Addons -Addons in `xterm-headless` work the [same as in `xterm`](https://github.com/xtermjs/xterm.js/blob/master/README.md#addons) with the one caveat being that the addon needs to be packaged for node.js and not use any DOM APIs. +Addons in `@xterm/headless` work the [same as in `xterm`](https://github.com/xtermjs/xterm.js/blob/master/README.md#addons) with the one caveat being that the addon needs to be packaged for node.js and not use any DOM APIs. Currently no official addons are packaged on npm. From f7a353cd341866efb122d825f5cd6edd555e0fd5 Mon Sep 17 00:00:00 2001 From: ScottMorse Date: Fri, 6 Mar 2026 00:14:20 -0700 Subject: [PATCH 06/11] Fix npm link to deprecated xterm package to @xterm/xterm --- headless/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/headless/README.md b/headless/README.md index fe98e12fd..cf9169ec9 100644 --- a/headless/README.md +++ b/headless/README.md @@ -2,7 +2,7 @@ ⚠ This package is experimental -`@xterm/headless` is a headless terminal that can be run in node.js. This is useful in combination with the frontend [`xterm`](https://www.npmjs.com/package/xterm) for example to keep track of a terminal's state on a remote server where the process is hosted. +`@xterm/headless` is a headless terminal that can be run in node.js. This is useful in combination with the frontend [`xterm`](https://www.npmjs.com/package/@xterm/xterm) for example to keep track of a terminal's state on a remote server where the process is hosted. ## Getting Started From 163868ff601c0fe5d160ed6518f76d6653aafa14 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Sat, 7 Mar 2026 23:32:02 -0800 Subject: [PATCH 07/11] Remove redundant bitmask in RGB->RGBA conversion --- addons/addon-image/src/kitty/KittyGraphicsHandler.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/addons/addon-image/src/kitty/KittyGraphicsHandler.ts b/addons/addon-image/src/kitty/KittyGraphicsHandler.ts index 2e43accb4..7964fe9c1 100644 --- a/addons/addon-image/src/kitty/KittyGraphicsHandler.ts +++ b/addons/addon-image/src/kitty/KittyGraphicsHandler.ts @@ -752,9 +752,9 @@ export class KittyGraphicsHandler implements IApcHandler, IResetHandler, IDispos const b1 = src32[srcOffset++]; const b2 = src32[srcOffset++]; // Little-endian: pixel bytes are [R,G,B] → uint32 ABGR layout - dst32[dstOffset++] = (b0 & 0x00FFFFFF) | 0xFF000000; - dst32[dstOffset++] = ((b0 >>> 24) | (b1 << 8)) & 0x00FFFFFF | 0xFF000000; - dst32[dstOffset++] = ((b1 >>> 16) | (b2 << 16)) & 0x00FFFFFF | 0xFF000000; + dst32[dstOffset++] = b0 | 0xFF000000; + dst32[dstOffset++] = ((b0 >>> 24) | (b1 << 8)) | 0xFF000000; + dst32[dstOffset++] = ((b1 >>> 16) | (b2 << 16)) | 0xFF000000; dst32[dstOffset++] = (b2 >>> 8) | 0xFF000000; } From 0cea95d3b3f65737115456ead2e05e2c45700c49 Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Sun, 8 Mar 2026 20:33:01 -0700 Subject: [PATCH 08/11] Make code more readable --- addons/addon-image/src/kitty/KittyGraphicsHandler.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/addons/addon-image/src/kitty/KittyGraphicsHandler.ts b/addons/addon-image/src/kitty/KittyGraphicsHandler.ts index 7964fe9c1..17cd5ed9f 100644 --- a/addons/addon-image/src/kitty/KittyGraphicsHandler.ts +++ b/addons/addon-image/src/kitty/KittyGraphicsHandler.ts @@ -752,10 +752,10 @@ export class KittyGraphicsHandler implements IApcHandler, IResetHandler, IDispos const b1 = src32[srcOffset++]; const b2 = src32[srcOffset++]; // Little-endian: pixel bytes are [R,G,B] → uint32 ABGR layout - dst32[dstOffset++] = b0 | 0xFF000000; - dst32[dstOffset++] = ((b0 >>> 24) | (b1 << 8)) | 0xFF000000; - dst32[dstOffset++] = ((b1 >>> 16) | (b2 << 16)) | 0xFF000000; - dst32[dstOffset++] = (b2 >>> 8) | 0xFF000000; + dst32[dstOffset++] = 0xFF000000 | b0; + dst32[dstOffset++] = 0xFF000000 | (b0 >>> 24) | (b1 << 8); + dst32[dstOffset++] = 0xFF000000 | (b1 >>> 16) | (b2 << 16); + dst32[dstOffset++] = 0xFF000000 | (b2 >>> 8); } // Handle remaining 1–3 pixels From b2553c17b4e074f2029d9a302555a9a53a56d77d Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 9 Mar 2026 12:42:38 +0900 Subject: [PATCH 09/11] Clarify indexes in CharData and IColorRGB Part of #5740 --- src/common/Types.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/common/Types.ts b/src/common/Types.ts index a46f47b2a..a4040ab55 100644 --- a/src/common/Types.ts +++ b/src/common/Types.ts @@ -101,13 +101,13 @@ export interface ICharset { [key: string]: string | undefined; } -export type CharData = [number, string, number, number]; +export type CharData = [attrIndex: number, charIndex: string, width: number, code: number]; export interface IColor { readonly css: string; readonly rgba: number; // 32-bit int with rgba in each byte } -export type IColorRGB = [number, number, number]; +export type IColorRGB = [red: number, green: number, blue: number]; export interface IExtendedAttrs { ext: number; From df5393ec45714736aea308d6ed29d597429f53d4 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 9 Mar 2026 12:54:27 +0900 Subject: [PATCH 10/11] Use helper function and const for cell data tests --- src/common/Types.ts | 2 +- src/common/buffer/BufferLine.test.ts | 350 ++++++++++++++------------- 2 files changed, 179 insertions(+), 173 deletions(-) diff --git a/src/common/Types.ts b/src/common/Types.ts index a4040ab55..7ceda21d2 100644 --- a/src/common/Types.ts +++ b/src/common/Types.ts @@ -101,7 +101,7 @@ export interface ICharset { [key: string]: string | undefined; } -export type CharData = [attrIndex: number, charIndex: string, width: number, code: number]; +export type CharData = [attr: number, char: string, width: number, code: number]; export interface IColor { readonly css: string; diff --git a/src/common/buffer/BufferLine.test.ts b/src/common/buffer/BufferLine.test.ts index 9d08c2bef..eaceaa45f 100644 --- a/src/common/buffer/BufferLine.test.ts +++ b/src/common/buffer/BufferLine.test.ts @@ -24,6 +24,12 @@ class TestBufferLine extends BufferLine { } } +function createCellData(attr: number, char: string, width: number): CellData { + return CellData.fromCharData([attr, char, width, char.length === 0 ? 0 : char.charCodeAt(0)]); +} + +const NULL_CELL_DATA = Object.freeze(createCellData(DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH)); + describe('AttributeData', () => { describe('extended attributes', () => { it('hasExtendedAttrs', () => { @@ -173,78 +179,78 @@ describe('BufferLine', function(): void { assert.equal(line.length, 10); assert.deepEqual(line.loadCell(0, new CellData()).getAsCharData(), [0, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]); assert.equal(line.isWrapped, true); - line = new TestBufferLine(10, CellData.fromCharData([123, 'a', 456, 'a'.charCodeAt(0)]), true); + line = new TestBufferLine(10, createCellData(123, 'a', 456), true); assert.equal(line.length, 10); assert.deepEqual(line.loadCell(0, new CellData()).getAsCharData(), [123, 'a', 456, 'a'.charCodeAt(0)]); assert.equal(line.isWrapped, true); }); it('insertCells', function(): void { const line = new TestBufferLine(3); - line.setCell(0, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)])); - line.setCell(1, CellData.fromCharData([2, 'b', 0, 'b'.charCodeAt(0)])); - line.setCell(2, CellData.fromCharData([3, 'c', 0, 'c'.charCodeAt(0)])); - line.insertCells(1, 3, CellData.fromCharData([4, 'd', 0, 'd'.charCodeAt(0)])); + line.setCell(0, createCellData(1, 'a', 1)); + line.setCell(1, createCellData(2, 'b', 1)); + line.setCell(2, createCellData(3, 'c', 1)); + line.insertCells(1, 3, createCellData(4, 'd', 1)); assert.deepEqual(line.toArray(), [ - [1, 'a', 0, 'a'.charCodeAt(0)], - [4, 'd', 0, 'd'.charCodeAt(0)], - [4, 'd', 0, 'd'.charCodeAt(0)] + [1, 'a', 1, 'a'.charCodeAt(0)], + [4, 'd', 1, 'd'.charCodeAt(0)], + [4, 'd', 1, 'd'.charCodeAt(0)] ]); }); it('deleteCells', function(): void { const line = new TestBufferLine(5); - line.setCell(0, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)])); - line.setCell(1, CellData.fromCharData([2, 'b', 0, 'b'.charCodeAt(0)])); - line.setCell(2, CellData.fromCharData([3, 'c', 0, 'c'.charCodeAt(0)])); - line.setCell(3, CellData.fromCharData([4, 'd', 0, 'd'.charCodeAt(0)])); - line.setCell(4, CellData.fromCharData([5, 'e', 0, 'e'.charCodeAt(0)])); - line.deleteCells(1, 2, CellData.fromCharData([6, 'f', 0, 'f'.charCodeAt(0)])); + line.setCell(0, createCellData(1, 'a', 1)); + line.setCell(1, createCellData(2, 'b', 1)); + line.setCell(2, createCellData(3, 'c', 1)); + line.setCell(3, createCellData(4, 'd', 1)); + line.setCell(4, createCellData(5, 'e', 1)); + line.deleteCells(1, 2, createCellData(6, 'f', 1)); assert.deepEqual(line.toArray(), [ - [1, 'a', 0, 'a'.charCodeAt(0)], - [4, 'd', 0, 'd'.charCodeAt(0)], - [5, 'e', 0, 'e'.charCodeAt(0)], - [6, 'f', 0, 'f'.charCodeAt(0)], - [6, 'f', 0, 'f'.charCodeAt(0)] + [1, 'a', 1, 'a'.charCodeAt(0)], + [4, 'd', 1, 'd'.charCodeAt(0)], + [5, 'e', 1, 'e'.charCodeAt(0)], + [6, 'f', 1, 'f'.charCodeAt(0)], + [6, 'f', 1, 'f'.charCodeAt(0)] ]); }); it('replaceCells', function(): void { const line = new TestBufferLine(5); - line.setCell(0, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)])); - line.setCell(1, CellData.fromCharData([2, 'b', 0, 'b'.charCodeAt(0)])); - line.setCell(2, CellData.fromCharData([3, 'c', 0, 'c'.charCodeAt(0)])); - line.setCell(3, CellData.fromCharData([4, 'd', 0, 'd'.charCodeAt(0)])); - line.setCell(4, CellData.fromCharData([5, 'e', 0, 'e'.charCodeAt(0)])); - line.replaceCells(2, 4, CellData.fromCharData([6, 'f', 0, 'f'.charCodeAt(0)])); + line.setCell(0, createCellData(1, 'a', 1)); + line.setCell(1, createCellData(2, 'b', 1)); + line.setCell(2, createCellData(3, 'c', 1)); + line.setCell(3, createCellData(4, 'd', 1)); + line.setCell(4, createCellData(5, 'e', 1)); + line.replaceCells(2, 4, createCellData(6, 'f', 1)); assert.deepEqual(line.toArray(), [ - [1, 'a', 0, 'a'.charCodeAt(0)], - [2, 'b', 0, 'b'.charCodeAt(0)], - [6, 'f', 0, 'f'.charCodeAt(0)], - [6, 'f', 0, 'f'.charCodeAt(0)], - [5, 'e', 0, 'e'.charCodeAt(0)] + [1, 'a', 1, 'a'.charCodeAt(0)], + [2, 'b', 1, 'b'.charCodeAt(0)], + [6, 'f', 1, 'f'.charCodeAt(0)], + [6, 'f', 1, 'f'.charCodeAt(0)], + [5, 'e', 1, 'e'.charCodeAt(0)] ]); }); it('fill', function(): void { const line = new TestBufferLine(5); - line.setCell(0, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)])); - line.setCell(1, CellData.fromCharData([2, 'b', 0, 'b'.charCodeAt(0)])); - line.setCell(2, CellData.fromCharData([3, 'c', 0, 'c'.charCodeAt(0)])); - line.setCell(3, CellData.fromCharData([4, 'd', 0, 'd'.charCodeAt(0)])); - line.setCell(4, CellData.fromCharData([5, 'e', 0, 'e'.charCodeAt(0)])); - line.fill(CellData.fromCharData([123, 'z', 0, 'z'.charCodeAt(0)])); + line.setCell(0, createCellData(1, 'a', 1)); + line.setCell(1, createCellData(2, 'b', 1)); + line.setCell(2, createCellData(3, 'c', 1)); + line.setCell(3, createCellData(4, 'd', 1)); + line.setCell(4, createCellData(5, 'e', 1)); + line.fill(createCellData(123, 'z', 1)); assert.deepEqual(line.toArray(), [ - [123, 'z', 0, 'z'.charCodeAt(0)], - [123, 'z', 0, 'z'.charCodeAt(0)], - [123, 'z', 0, 'z'.charCodeAt(0)], - [123, 'z', 0, 'z'.charCodeAt(0)], - [123, 'z', 0, 'z'.charCodeAt(0)] + [123, 'z', 1, 'z'.charCodeAt(0)], + [123, 'z', 1, 'z'.charCodeAt(0)], + [123, 'z', 1, 'z'.charCodeAt(0)], + [123, 'z', 1, 'z'.charCodeAt(0)], + [123, 'z', 1, 'z'.charCodeAt(0)] ]); }); it('clone', function(): void { const line = new TestBufferLine(5, undefined, true); - line.setCell(0, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)])); - line.setCell(1, CellData.fromCharData([2, 'b', 0, 'b'.charCodeAt(0)])); - line.setCell(2, CellData.fromCharData([3, 'c', 0, 'c'.charCodeAt(0)])); - line.setCell(3, CellData.fromCharData([4, 'd', 0, 'd'.charCodeAt(0)])); - line.setCell(4, CellData.fromCharData([5, 'e', 0, 'e'.charCodeAt(0)])); + line.setCell(0, createCellData(1, 'a', 1)); + line.setCell(1, createCellData(2, 'b', 1)); + line.setCell(2, createCellData(3, 'c', 1)); + line.setCell(3, createCellData(4, 'd', 1)); + line.setCell(4, createCellData(5, 'e', 1)); const line2 = line.clone(); assert.deepEqual(TestBufferLine.prototype.toArray.apply(line2), line.toArray()); assert.equal(line2.length, line.length); @@ -252,12 +258,12 @@ describe('BufferLine', function(): void { }); it('copyFrom', function(): void { const line = new TestBufferLine(5); - line.setCell(0, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)])); - line.setCell(1, CellData.fromCharData([2, 'b', 0, 'b'.charCodeAt(0)])); - line.setCell(2, CellData.fromCharData([3, 'c', 0, 'c'.charCodeAt(0)])); - line.setCell(3, CellData.fromCharData([4, 'd', 0, 'd'.charCodeAt(0)])); - line.setCell(4, CellData.fromCharData([5, 'e', 0, 'e'.charCodeAt(0)])); - const line2 = new TestBufferLine(5, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]), true); + line.setCell(0, createCellData(1, 'a', 1)); + line.setCell(1, createCellData(2, 'b', 1)); + line.setCell(2, createCellData(3, 'c', 1)); + line.setCell(3, createCellData(4, 'd', 1)); + line.setCell(4, createCellData(5, 'e', 1)); + const line2 = new TestBufferLine(5, createCellData(1, 'a', 1), true); line2.copyFrom(line); assert.deepEqual(line2.toArray(), line.toArray()); assert.equal(line2.length, line.length); @@ -267,9 +273,9 @@ describe('BufferLine', function(): void { // CHAR_DATA_CODE_INDEX resembles current behavior in InputHandler.print // --> set code to the last charCodeAt value of the string // Note: needs to be fixed once the string pointer is in place - const line = new TestBufferLine(2, CellData.fromCharData([1, 'e\u0301', 0, '\u0301'.charCodeAt(0)])); - assert.deepEqual(line.toArray(), [[1, 'e\u0301', 0, '\u0301'.charCodeAt(0)], [1, 'e\u0301', 0, '\u0301'.charCodeAt(0)]]); - const line2 = new TestBufferLine(5, CellData.fromCharData([1, 'a', 0, '\u0301'.charCodeAt(0)]), true); + const line = new TestBufferLine(2, createCellData(1, 'e\u0301', 1)); + assert.deepEqual(line.toArray(), [[1, 'e\u0301', 1, '\u0301'.charCodeAt(0)], [1, 'e\u0301', 1, '\u0301'.charCodeAt(0)]]); + const line2 = new TestBufferLine(5, createCellData(1, 'a', 1), true); line2.copyFrom(line); assert.deepEqual(line2.toArray(), line.toArray()); const line3 = line.clone(); @@ -277,72 +283,72 @@ describe('BufferLine', function(): void { }); describe('resize', function(): void { it('enlarge(false)', function(): void { - const line = new TestBufferLine(5, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]), false); - line.resize(10, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)])); - assert.deepEqual(line.toArray(), (Array(10) as any).fill([1, 'a', 0, 'a'.charCodeAt(0)])); + const line = new TestBufferLine(5, createCellData(1, 'a', 1), false); + line.resize(10, createCellData(1, 'a', 1)); + assert.deepEqual(line.toArray(), (Array(10) as any).fill([1, 'a', 1, 'a'.charCodeAt(0)])); }); it('enlarge(true)', function(): void { - const line = new TestBufferLine(5, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]), false); - line.resize(10, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)])); - assert.deepEqual(line.toArray(), (Array(10) as any).fill([1, 'a', 0, 'a'.charCodeAt(0)])); + const line = new TestBufferLine(5, createCellData(1, 'a', 1), false); + line.resize(10, createCellData(1, 'a', 1)); + assert.deepEqual(line.toArray(), (Array(10) as any).fill([1, 'a', 1, 'a'.charCodeAt(0)])); }); it('shrink(true) - should apply new size', function(): void { - const line = new TestBufferLine(10, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]), false); - line.resize(5, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)])); - assert.deepEqual(line.toArray(), (Array(5) as any).fill([1, 'a', 0, 'a'.charCodeAt(0)])); + const line = new TestBufferLine(10, createCellData(1, 'a', 1), false); + line.resize(5, createCellData(1, 'a', 1)); + assert.deepEqual(line.toArray(), (Array(5) as any).fill([1, 'a', 1, 'a'.charCodeAt(0)])); }); it('shrink to 0 length', function(): void { - const line = new TestBufferLine(10, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]), false); - line.resize(0, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)])); - assert.deepEqual(line.toArray(), (Array(0) as any).fill([1, 'a', 0, 'a'.charCodeAt(0)])); + const line = new TestBufferLine(10, createCellData(1, 'a', 1), false); + line.resize(0, createCellData(1, 'a', 1)); + assert.deepEqual(line.toArray(), (Array(0) as any).fill([1, 'a', 1, 'a'.charCodeAt(0)])); }); it('should remove combining data on replaced cells after shrinking then enlarging', () => { - const line = new TestBufferLine(10, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]), false); + const line = new TestBufferLine(10, createCellData(1, 'a', 1), false); line.set(2, [ 0, '😁', 1, '😁'.charCodeAt(0) ]); line.set(9, [ 0, '😁', 1, '😁'.charCodeAt(0) ]); assert.equal(line.translateToString(), 'aa😁aaaaaa😁'); assert.equal(Object.keys(line.combined).length, 2); - line.resize(5, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)])); + line.resize(5, createCellData(1, 'a', 1)); assert.equal(line.translateToString(), 'aa😁aa'); - line.resize(10, CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)])); + line.resize(10, createCellData(1, 'a', 1)); assert.equal(line.translateToString(), 'aa😁aaaaaaa'); assert.equal(Object.keys(line.combined).length, 1); }); }); describe('getTrimLength', function(): void { it('empty line', function(): void { - const line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]), false); + const line = new TestBufferLine(10, NULL_CELL_DATA, false); assert.equal(line.getTrimmedLength(), 0); }); it('ASCII', function(): void { - const line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]), false); - line.setCell(0, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)])); - line.setCell(2, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)])); + const line = new TestBufferLine(10, NULL_CELL_DATA, false); + line.setCell(0, createCellData(1, 'a', 1)); + line.setCell(2, createCellData(1, 'a', 1)); assert.equal(line.getTrimmedLength(), 3); }); it('surrogate', function(): void { - const line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]), false); - line.setCell(0, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)])); - line.setCell(2, CellData.fromCharData([1, '𝄞', 1, '𝄞'.charCodeAt(0)])); + const line = new TestBufferLine(10, NULL_CELL_DATA, false); + line.setCell(0, createCellData(1, 'a', 1)); + line.setCell(2, createCellData(1, '𝄞', 1)); assert.equal(line.getTrimmedLength(), 3); }); it('combining', function(): void { - const line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]), false); - line.setCell(0, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)])); - line.setCell(2, CellData.fromCharData([1, 'e\u0301', 1, '\u0301'.charCodeAt(0)])); + const line = new TestBufferLine(10, NULL_CELL_DATA, false); + line.setCell(0, createCellData(1, 'a', 1)); + line.setCell(2, createCellData(1, 'e\u0301', 1)); assert.equal(line.getTrimmedLength(), 3); }); it('fullwidth', function(): void { - const line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]), false); - line.setCell(0, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)])); - line.setCell(2, CellData.fromCharData([1, '1', 2, '1'.charCodeAt(0)])); - line.setCell(3, CellData.fromCharData([0, '', 0, 0])); + const line = new TestBufferLine(10, NULL_CELL_DATA, false); + line.setCell(0, createCellData(1, 'a', 1)); + line.setCell(2, createCellData(1, '1', 2)); + line.setCell(3, createCellData(0, '', 0)); assert.equal(line.getTrimmedLength(), 4); // also counts null cell after fullwidth }); }); describe('translateToString with and w\'o trimming', function(): void { it('empty line', function(): void { - const line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]), false); + const line = new TestBufferLine(10, NULL_CELL_DATA, false); const columns: number[] = []; assert.equal(line.translateToString(false, undefined, undefined, columns), ' '); assert.deepEqual(columns, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); @@ -351,11 +357,11 @@ describe('BufferLine', function(): void { }); it('ASCII', function(): void { const columns: number[] = []; - const line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]), false); - line.setCell(0, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)])); - line.setCell(2, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)])); - line.setCell(4, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)])); - line.setCell(5, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)])); + const line = new TestBufferLine(10, NULL_CELL_DATA, false); + line.setCell(0, createCellData(1, 'a', 1)); + line.setCell(2, createCellData(1, 'a', 1)); + line.setCell(4, createCellData(1, 'a', 1)); + line.setCell(5, createCellData(1, 'a', 1)); assert.equal(line.translateToString(false, undefined, undefined, columns), 'a a aa '); assert.deepEqual(columns, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); assert.equal(line.translateToString(true, undefined, undefined, columns), 'a a aa'); @@ -372,11 +378,11 @@ describe('BufferLine', function(): void { }); it('surrogate', function(): void { const columns: number[] = []; - const line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]), false); - line.setCell(0, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)])); - line.setCell(2, CellData.fromCharData([1, '𝄞', 1, '𝄞'.charCodeAt(0)])); - line.setCell(4, CellData.fromCharData([1, '𝄞', 1, '𝄞'.charCodeAt(0)])); - line.setCell(5, CellData.fromCharData([1, '𝄞', 1, '𝄞'.charCodeAt(0)])); + const line = new TestBufferLine(10, NULL_CELL_DATA, false); + line.setCell(0, createCellData(1, 'a', 1)); + line.setCell(2, createCellData(1, '𝄞', 1)); + line.setCell(4, createCellData(1, '𝄞', 1)); + line.setCell(5, createCellData(1, '𝄞', 1)); assert.equal(line.translateToString(false, undefined, undefined, columns), 'a 𝄞 𝄞𝄞 '); assert.deepEqual(columns, [0, 1, 2, 2, 3, 4, 4, 5, 5, 6, 7, 8, 9, 10]); assert.equal(line.translateToString(true, undefined, undefined, columns), 'a 𝄞 𝄞𝄞'); @@ -392,11 +398,11 @@ describe('BufferLine', function(): void { }); it('combining', function(): void { const columns: number[] = []; - const line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]), false); - line.setCell(0, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)])); - line.setCell(2, CellData.fromCharData([1, 'e\u0301', 1, '\u0301'.charCodeAt(0)])); - line.setCell(4, CellData.fromCharData([1, 'e\u0301', 1, '\u0301'.charCodeAt(0)])); - line.setCell(5, CellData.fromCharData([1, 'e\u0301', 1, '\u0301'.charCodeAt(0)])); + const line = new TestBufferLine(10, NULL_CELL_DATA, false); + line.setCell(0, createCellData(1, 'a', 1)); + line.setCell(2, createCellData(1, 'e\u0301', 1)); + line.setCell(4, createCellData(1, 'e\u0301', 1)); + line.setCell(5, createCellData(1, 'e\u0301', 1)); assert.equal(line.translateToString(false, undefined, undefined, columns), 'a e\u0301 e\u0301e\u0301 '); assert.deepEqual(columns, [0, 1, 2, 2, 3, 4, 4, 5, 5, 6, 7, 8, 9, 10]); assert.equal(line.translateToString(true, undefined, undefined, columns), 'a e\u0301 e\u0301e\u0301'); @@ -412,14 +418,14 @@ describe('BufferLine', function(): void { }); it('fullwidth', function(): void { const columns: number[] = []; - const line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]), false); - line.setCell(0, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)])); - line.setCell(2, CellData.fromCharData([1, '1', 2, '1'.charCodeAt(0)])); - line.setCell(3, CellData.fromCharData([0, '', 0, 0])); - line.setCell(5, CellData.fromCharData([1, '1', 2, '1'.charCodeAt(0)])); - line.setCell(6, CellData.fromCharData([0, '', 0, 0])); - line.setCell(7, CellData.fromCharData([1, '1', 2, '1'.charCodeAt(0)])); - line.setCell(8, CellData.fromCharData([0, '', 0, 0])); + const line = new TestBufferLine(10, NULL_CELL_DATA, false); + line.setCell(0, createCellData(1, 'a', 1)); + line.setCell(2, createCellData(1, '1', 2)); + line.setCell(3, createCellData(0, '', 0)); + line.setCell(5, createCellData(1, '1', 2)); + line.setCell(6, createCellData(0, '', 0)); + line.setCell(7, createCellData(1, '1', 2)); + line.setCell(8, createCellData(0, '', 0)); assert.equal(line.translateToString(false, undefined, undefined, columns), 'a 1 11 '); assert.deepEqual(columns, [0, 1, 2, 4, 5, 7, 9, 10]); assert.equal(line.translateToString(true, undefined, undefined, columns), 'a 1 11'); @@ -441,12 +447,12 @@ describe('BufferLine', function(): void { }); it('space at end', function(): void { const columns: number[] = []; - const line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]), false); - line.setCell(0, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)])); - line.setCell(2, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)])); - line.setCell(4, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)])); - line.setCell(5, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)])); - line.setCell(6, CellData.fromCharData([1, ' ', 1, ' '.charCodeAt(0)])); + const line = new TestBufferLine(10, NULL_CELL_DATA, false); + line.setCell(0, createCellData(1, 'a', 1)); + line.setCell(2, createCellData(1, 'a', 1)); + line.setCell(4, createCellData(1, 'a', 1)); + line.setCell(5, createCellData(1, 'a', 1)); + line.setCell(6, createCellData(1, ' ', 1)); assert.equal(line.translateToString(false, undefined, undefined, columns), 'a a aa '); assert.deepEqual(columns, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); assert.equal(line.translateToString(true, undefined, undefined, columns), 'a a aa '); @@ -457,7 +463,7 @@ describe('BufferLine', function(): void { // sanity check - broken line with invalid out of bound null width cells // this can atm happen with deleting/inserting chars in inputhandler by "breaking" // fullwidth pairs --> needs to be fixed after settling BufferLine impl - const line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, 0, NULL_CELL_CODE]), false); + const line = new TestBufferLine(10, NULL_CELL_DATA, false); assert.equal(line.translateToString(false, undefined, undefined, columns), ' '); assert.deepEqual(columns, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); assert.equal(line.translateToString(true, undefined, undefined, columns), ''); @@ -465,15 +471,15 @@ describe('BufferLine', function(): void { }); it('should work with endCol=0', () => { const columns: number[] = []; - const line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, 0, NULL_CELL_CODE]), false); - line.setCell(0, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)])); + const line = new TestBufferLine(10, NULL_CELL_DATA, false); + line.setCell(0, createCellData(1, 'a', 1)); assert.equal(line.translateToString(true, 0, 0, columns), ''); assert.deepEqual(columns, [0]); }); }); describe('addCharToCell', () => { it('should set width to 1 for empty cell', () => { - const line = new TestBufferLine(3, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]), false); + const line = new TestBufferLine(3, NULL_CELL_DATA, false); line.addCodepointToCell(0, '\u0301'.charCodeAt(0), 0); const cell = line.loadCell(0, new CellData()); // chars contains single combining char @@ -483,7 +489,7 @@ describe('BufferLine', function(): void { assert.equal(cell.isCombined(), 0); }); it('should add char to combining string in cell', () => { - const line = new TestBufferLine(3, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]), false); + const line = new TestBufferLine(3, NULL_CELL_DATA, false); const cell = line .loadCell(0, new CellData()); cell.setFromCharData([123, 'e\u0301', 1, 'e\u0301'.charCodeAt(1)]); line.setCell(0, cell); @@ -496,7 +502,7 @@ describe('BufferLine', function(): void { assert.equal(cell.isCombined(), Content.IS_COMBINED_MASK); }); it('should create combining string on taken cell', () => { - const line = new TestBufferLine(3, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]), false); + const line = new TestBufferLine(3, NULL_CELL_DATA, false); const cell = line .loadCell(0, new CellData()); cell.setFromCharData([123, 'e', 1, 'e'.charCodeAt(1)]); line.setCell(0, cell); @@ -511,98 +517,98 @@ describe('BufferLine', function(): void { }); describe('correct fullwidth handling', () => { function populate(line: BufferLine): void { - const cell = CellData.fromCharData([1, '¥', 2, '¥'.charCodeAt(0)]); + const cell = createCellData(1, '¥', 2); for (let i = 0; i < line.length; i += 2) { line.setCell(i, cell); } } it('insert - wide char at pos', () => { - const line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, 0, NULL_CELL_CODE]), false); + const line = new TestBufferLine(10, NULL_CELL_DATA, false); populate(line); - line.insertCells(9, 1, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)])); + line.insertCells(9, 1, createCellData(1, 'a', 1)); assert.equal(line.translateToString(), '¥¥¥¥ a'); - line.insertCells(8, 1, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)])); + line.insertCells(8, 1, createCellData(1, 'a', 1)); assert.equal(line.translateToString(), '¥¥¥¥a '); - line.insertCells(1, 1, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)])); + line.insertCells(1, 1, createCellData(1, 'a', 1)); assert.equal(line.translateToString(), ' a ¥¥¥a'); }); it('insert - wide char at end', () => { - const line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, 0, NULL_CELL_CODE]), false); + const line = new TestBufferLine(10, NULL_CELL_DATA, false); populate(line); - line.insertCells(0, 3, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)])); + line.insertCells(0, 3, createCellData(1, 'a', 1)); assert.equal(line.translateToString(), 'aaa¥¥¥ '); - line.insertCells(4, 1, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)])); + line.insertCells(4, 1, createCellData(1, 'a', 1)); assert.equal(line.translateToString(), 'aaa a ¥¥'); - line.insertCells(4, 1, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)])); + line.insertCells(4, 1, createCellData(1, 'a', 1)); assert.equal(line.translateToString(), 'aaa aa ¥ '); }); it('delete', () => { - const line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, 0, NULL_CELL_CODE]), false); + const line = new TestBufferLine(10, NULL_CELL_DATA, false); populate(line); - line.deleteCells(0, 1, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)])); + line.deleteCells(0, 1, createCellData(1, 'a', 1)); assert.equal(line.translateToString(), ' ¥¥¥¥a'); - line.deleteCells(5, 2, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)])); + line.deleteCells(5, 2, createCellData(1, 'a', 1)); assert.equal(line.translateToString(), ' ¥¥¥aaa'); - line.deleteCells(0, 2, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)])); + line.deleteCells(0, 2, createCellData(1, 'a', 1)); assert.equal(line.translateToString(), ' ¥¥aaaaa'); }); it('replace - start at 0', () => { - let line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, 0, NULL_CELL_CODE]), false); + let line = new TestBufferLine(10, NULL_CELL_DATA, false); populate(line); - line.replaceCells(0, 1, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)])); + line.replaceCells(0, 1, createCellData(1, 'a', 1)); assert.equal(line.translateToString(), 'a ¥¥¥¥'); - line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, 0, NULL_CELL_CODE]), false); + line = new TestBufferLine(10, NULL_CELL_DATA, false); populate(line); - line.replaceCells(0, 2, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)])); + line.replaceCells(0, 2, createCellData(1, 'a', 1)); assert.equal(line.translateToString(), 'aa¥¥¥¥'); - line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, 0, NULL_CELL_CODE]), false); + line = new TestBufferLine(10, NULL_CELL_DATA, false); populate(line); - line.replaceCells(0, 3, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)])); + line.replaceCells(0, 3, createCellData(1, 'a', 1)); assert.equal(line.translateToString(), 'aaa ¥¥¥'); - line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, 0, NULL_CELL_CODE]), false); + line = new TestBufferLine(10, NULL_CELL_DATA, false); populate(line); - line.replaceCells(0, 8, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)])); + line.replaceCells(0, 8, createCellData(1, 'a', 1)); assert.equal(line.translateToString(), 'aaaaaaaa¥'); - line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, 0, NULL_CELL_CODE]), false); + line = new TestBufferLine(10, NULL_CELL_DATA, false); populate(line); - line.replaceCells(0, 9, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)])); + line.replaceCells(0, 9, createCellData(1, 'a', 1)); assert.equal(line.translateToString(), 'aaaaaaaaa '); - line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, 0, NULL_CELL_CODE]), false); + line = new TestBufferLine(10, NULL_CELL_DATA, false); populate(line); - line.replaceCells(0, 10, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)])); + line.replaceCells(0, 10, createCellData(1, 'a', 1)); assert.equal(line.translateToString(), 'aaaaaaaaaa'); }); it('replace - start at 1', () => { - let line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, 0, NULL_CELL_CODE]), false); + let line = new TestBufferLine(10, NULL_CELL_DATA, false); populate(line); - line.replaceCells(1, 2, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)])); + line.replaceCells(1, 2, createCellData(1, 'a', 1)); assert.equal(line.translateToString(), ' a¥¥¥¥'); - line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, 0, NULL_CELL_CODE]), false); + line = new TestBufferLine(10, NULL_CELL_DATA, false); populate(line); - line.replaceCells(1, 3, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)])); + line.replaceCells(1, 3, createCellData(1, 'a', 1)); assert.equal(line.translateToString(), ' aa ¥¥¥'); - line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, 0, NULL_CELL_CODE]), false); + line = new TestBufferLine(10, NULL_CELL_DATA, false); populate(line); - line.replaceCells(1, 4, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)])); + line.replaceCells(1, 4, createCellData(1, 'a', 1)); assert.equal(line.translateToString(), ' aaa¥¥¥'); - line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, 0, NULL_CELL_CODE]), false); + line = new TestBufferLine(10, NULL_CELL_DATA, false); populate(line); - line.replaceCells(1, 8, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)])); + line.replaceCells(1, 8, createCellData(1, 'a', 1)); assert.equal(line.translateToString(), ' aaaaaaa¥'); - line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, 0, NULL_CELL_CODE]), false); + line = new TestBufferLine(10, NULL_CELL_DATA, false); populate(line); - line.replaceCells(1, 9, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)])); + line.replaceCells(1, 9, createCellData(1, 'a', 1)); assert.equal(line.translateToString(), ' aaaaaaaa '); - line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, 0, NULL_CELL_CODE]), false); + line = new TestBufferLine(10, NULL_CELL_DATA, false); populate(line); - line.replaceCells(1, 10, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)])); + line.replaceCells(1, 10, createCellData(1, 'a', 1)); assert.equal(line.translateToString(), ' aaaaaaaaa'); }); }); describe('extended attributes', () => { it('setCells', function(): void { const line = new TestBufferLine(5); - const cell = CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]); + const cell = createCellData(1, 'a', 1); // no eAttrs line.setCell(0, cell); @@ -612,7 +618,7 @@ describe('BufferLine', function(): void { line.setCell(1, cell); // same eAttr, different codepoint - cell.content = 65; // 'A' + cell.content = createCellData(1, 'A', 1).content; line.setCell(2, cell); // different eAttr @@ -625,11 +631,11 @@ describe('BufferLine', function(): void { line.setCell(4, cell); assert.deepEqual(line.toArray(), [ - [1, 'a', 0, 'a'.charCodeAt(0)], - [1, 'a', 0, 'a'.charCodeAt(0)], - [1, 'A', 0, 'A'.charCodeAt(0)], - [1, 'A', 0, 'A'.charCodeAt(0)], - [1, 'A', 0, 'A'.charCodeAt(0)] + [1, 'a', 1, 'a'.charCodeAt(0)], + [1, 'a', 1, 'a'.charCodeAt(0)], + [1, 'A', 1, 'A'.charCodeAt(0)], + [1, 'A', 1, 'A'.charCodeAt(0)], + [1, 'A', 1, 'A'.charCodeAt(0)] ]); assert.equal((line as any)._extendedAttrs[0], undefined); assert.equal((line as any)._extendedAttrs[1].underlineStyle, UnderlineStyle.CURLY); @@ -643,7 +649,7 @@ describe('BufferLine', function(): void { }); it('loadCell', () => { const line = new TestBufferLine(5); - const cell = CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]); + const cell = createCellData(1, 'a', 1); // no eAttrs line.setCell(0, cell); @@ -686,7 +692,7 @@ describe('BufferLine', function(): void { }); it('fill', () => { const line = new TestBufferLine(3); - const cell = CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]); + const cell = createCellData(1, 'a', 1); cell.extended.underlineStyle = UnderlineStyle.CURLY; cell.bg |= BgFlags.HAS_EXTENDED; line.fill(cell); @@ -696,7 +702,7 @@ describe('BufferLine', function(): void { }); it('insertCells', () => { const line = new TestBufferLine(5); - const cell = CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]); + const cell = createCellData(1, 'a', 1); cell.extended.underlineStyle = UnderlineStyle.CURLY; cell.bg |= BgFlags.HAS_EXTENDED; line.insertCells(1, 3, cell); @@ -714,7 +720,7 @@ describe('BufferLine', function(): void { }); it('deleteCells', () => { const line = new TestBufferLine(5); - const fillCell = CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]); + const fillCell = createCellData(1, 'a', 1); fillCell.extended.underlineStyle = UnderlineStyle.CURLY; fillCell.bg |= BgFlags.HAS_EXTENDED; line.fill(fillCell); @@ -729,7 +735,7 @@ describe('BufferLine', function(): void { }); it('replaceCells', () => { const line = new TestBufferLine(5); - const fillCell = CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]); + const fillCell = createCellData(1, 'a', 1); fillCell.extended.underlineStyle = UnderlineStyle.CURLY; fillCell.bg |= BgFlags.HAS_EXTENDED; line.fill(fillCell); @@ -744,7 +750,7 @@ describe('BufferLine', function(): void { }); it('clone', () => { const line = new TestBufferLine(5); - const cell = CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]); + const cell = createCellData(1, 'a', 1); // no eAttrs line.setCell(0, cell); @@ -775,7 +781,7 @@ describe('BufferLine', function(): void { }); it('copyFrom', () => { const initial = new TestBufferLine(5); - const cell = CellData.fromCharData([1, 'a', 0, 'a'.charCodeAt(0)]); + const cell = createCellData(1, 'a', 1); // no eAttrs initial.setCell(0, cell); @@ -798,7 +804,7 @@ describe('BufferLine', function(): void { initial.setCell(4, cell); const line = new TestBufferLine(5); - line.fill(CellData.fromCharData([1, 'b', 0, 'b'.charCodeAt(0)])); + line.fill(createCellData(1, 'b', 1)); line.copyFrom(initial); assert.equal((line as any)._extendedAttrs[0], (initial as any)._extendedAttrs[0]); assert.equal((line as any)._extendedAttrs[1], (initial as any)._extendedAttrs[1]); From 671425bf5a6530ce664f68dc2298108364db1374 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 9 Mar 2026 13:03:00 +0900 Subject: [PATCH 11/11] Use helpers in other tests --- src/browser/Terminal.test.ts | 64 +++++----- .../dom/DomRendererRowFactory.test.ts | 110 +++++++++--------- .../services/CharacterJoinerService.test.ts | 16 +-- src/browser/services/SelectionService.test.ts | 6 +- src/common/TestUtils.test.ts | 8 ++ src/common/buffer/Buffer.test.ts | 36 +++--- src/common/buffer/BufferLine.test.ts | 7 +- 7 files changed, 125 insertions(+), 122 deletions(-) diff --git a/src/browser/Terminal.test.ts b/src/browser/Terminal.test.ts index ba2ce6dd5..c651c248a 100644 --- a/src/browser/Terminal.test.ts +++ b/src/browser/Terminal.test.ts @@ -8,7 +8,7 @@ import type { IBrowser } from 'browser/Types'; import { assert } from 'chai'; import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine'; import { CellData } from 'common/buffer/CellData'; -import { MockUnicodeService } from 'common/TestUtils.test'; +import { MockUnicodeService, createCellData } from 'common/TestUtils.test'; import { IMarker } from 'common/Types'; const INIT_COLS = 80; @@ -440,8 +440,8 @@ describe('Terminal', () => { describe('scroll() function', () => { describe('when scrollback > 0', () => { it('should create a new line and scroll', () => { - term.buffer.lines.get(0)!.setCell(0, CellData.fromCharData([0, 'a', 0, 'a'.charCodeAt(0)])); - term.buffer.lines.get(INIT_ROWS - 1)!.setCell(0, CellData.fromCharData([0, 'b', 0, 'b'.charCodeAt(0)])); + term.buffer.lines.get(0)!.setCell(0, createCellData(0, 'a', 0)); + term.buffer.lines.get(INIT_ROWS - 1)!.setCell(0, createCellData(0, 'b', 0)); term.buffer.y = INIT_ROWS - 1; // Move cursor to last line term.scroll(DEFAULT_ATTR_DATA.clone()); assert.equal(term.buffer.lines.length, INIT_ROWS + 1); @@ -451,9 +451,9 @@ describe('Terminal', () => { }); it('should properly scroll inside a scroll region (scrollTop set)', () => { - term.buffer.lines.get(0)!.setCell(0, CellData.fromCharData([0, 'a', 0, 'a'.charCodeAt(0)])); - term.buffer.lines.get(1)!.setCell(0, CellData.fromCharData([0, 'b', 0, 'b'.charCodeAt(0)])); - term.buffer.lines.get(2)!.setCell(0, CellData.fromCharData([0, 'c', 0, 'c'.charCodeAt(0)])); + term.buffer.lines.get(0)!.setCell(0, createCellData(0, 'a', 0)); + term.buffer.lines.get(1)!.setCell(0, createCellData(0, 'b', 0)); + term.buffer.lines.get(2)!.setCell(0, createCellData(0, 'c', 0)); term.buffer.y = INIT_ROWS - 1; // Move cursor to last line term.buffer.scrollTop = 1; term.scroll(DEFAULT_ATTR_DATA.clone()); @@ -463,11 +463,11 @@ describe('Terminal', () => { }); it('should properly scroll inside a scroll region (scrollBottom set)', () => { - term.buffer.lines.get(0)!.setCell(0, CellData.fromCharData([0, 'a', 0, 'a'.charCodeAt(0)])); - term.buffer.lines.get(1)!.setCell(0, CellData.fromCharData([0, 'b', 0, 'b'.charCodeAt(0)])); - term.buffer.lines.get(2)!.setCell(0, CellData.fromCharData([0, 'c', 0, 'c'.charCodeAt(0)])); - term.buffer.lines.get(3)!.setCell(0, CellData.fromCharData([0, 'd', 0, 'd'.charCodeAt(0)])); - term.buffer.lines.get(4)!.setCell(0, CellData.fromCharData([0, 'e', 0, 'e'.charCodeAt(0)])); + term.buffer.lines.get(0)!.setCell(0, createCellData(0, 'a', 0)); + term.buffer.lines.get(1)!.setCell(0, createCellData(0, 'b', 0)); + term.buffer.lines.get(2)!.setCell(0, createCellData(0, 'c', 0)); + term.buffer.lines.get(3)!.setCell(0, createCellData(0, 'd', 0)); + term.buffer.lines.get(4)!.setCell(0, createCellData(0, 'e', 0)); term.buffer.y = 3; term.buffer.scrollBottom = 3; term.scroll(DEFAULT_ATTR_DATA.clone()); @@ -481,11 +481,11 @@ describe('Terminal', () => { }); it('should properly scroll inside a scroll region (scrollTop and scrollBottom set)', () => { - term.buffer.lines.get(0)!.setCell(0, CellData.fromCharData([0, 'a', 0, 'a'.charCodeAt(0)])); - term.buffer.lines.get(1)!.setCell(0, CellData.fromCharData([0, 'b', 0, 'b'.charCodeAt(0)])); - term.buffer.lines.get(2)!.setCell(0, CellData.fromCharData([0, 'c', 0, 'c'.charCodeAt(0)])); - term.buffer.lines.get(3)!.setCell(0, CellData.fromCharData([0, 'd', 0, 'd'.charCodeAt(0)])); - term.buffer.lines.get(4)!.setCell(0, CellData.fromCharData([0, 'e', 0, 'e'.charCodeAt(0)])); + term.buffer.lines.get(0)!.setCell(0, createCellData(0, 'a', 0)); + term.buffer.lines.get(1)!.setCell(0, createCellData(0, 'b', 0)); + term.buffer.lines.get(2)!.setCell(0, createCellData(0, 'c', 0)); + term.buffer.lines.get(3)!.setCell(0, createCellData(0, 'd', 0)); + term.buffer.lines.get(4)!.setCell(0, createCellData(0, 'e', 0)); term.buffer.y = INIT_ROWS - 1; // Move cursor to last line term.buffer.scrollTop = 1; term.buffer.scrollBottom = 3; @@ -506,9 +506,9 @@ describe('Terminal', () => { }); it('should create a new line and shift everything up', () => { - term.buffer.lines.get(0)!.setCell(0, CellData.fromCharData([0, 'a', 0, 'a'.charCodeAt(0)])); - term.buffer.lines.get(1)!.setCell(0, CellData.fromCharData([0, 'b', 0, 'b'.charCodeAt(0)])); - term.buffer.lines.get(INIT_ROWS - 1)!.setCell(0, CellData.fromCharData([0, 'c', 0, 'c'.charCodeAt(0)])); + term.buffer.lines.get(0)!.setCell(0, createCellData(0, 'a', 0)); + term.buffer.lines.get(1)!.setCell(0, createCellData(0, 'b', 0)); + term.buffer.lines.get(INIT_ROWS - 1)!.setCell(0, createCellData(0, 'c', 0)); term.buffer.y = INIT_ROWS - 1; // Move cursor to last line assert.equal(term.buffer.lines.length, INIT_ROWS); term.scroll(DEFAULT_ATTR_DATA.clone()); @@ -521,9 +521,9 @@ describe('Terminal', () => { }); it('should properly scroll inside a scroll region (scrollTop set)', () => { - term.buffer.lines.get(0)!.setCell(0, CellData.fromCharData([0, 'a', 0, 'a'.charCodeAt(0)])); - term.buffer.lines.get(1)!.setCell(0, CellData.fromCharData([0, 'b', 0, 'b'.charCodeAt(0)])); - term.buffer.lines.get(2)!.setCell(0, CellData.fromCharData([0, 'c', 0, 'c'.charCodeAt(0)])); + term.buffer.lines.get(0)!.setCell(0, createCellData(0, 'a', 0)); + term.buffer.lines.get(1)!.setCell(0, createCellData(0, 'b', 0)); + term.buffer.lines.get(2)!.setCell(0, createCellData(0, 'c', 0)); term.buffer.y = INIT_ROWS - 1; // Move cursor to last line term.buffer.scrollTop = 1; term.scroll(DEFAULT_ATTR_DATA.clone()); @@ -533,11 +533,11 @@ describe('Terminal', () => { }); it('should properly scroll inside a scroll region (scrollBottom set)', () => { - term.buffer.lines.get(0)!.setCell(0, CellData.fromCharData([0, 'a', 0, 'a'.charCodeAt(0)])); - term.buffer.lines.get(1)!.setCell(0, CellData.fromCharData([0, 'b', 0, 'b'.charCodeAt(0)])); - term.buffer.lines.get(2)!.setCell(0, CellData.fromCharData([0, 'c', 0, 'c'.charCodeAt(0)])); - term.buffer.lines.get(3)!.setCell(0, CellData.fromCharData([0, 'd', 0, 'd'.charCodeAt(0)])); - term.buffer.lines.get(4)!.setCell(0, CellData.fromCharData([0, 'e', 0, 'e'.charCodeAt(0)])); + term.buffer.lines.get(0)!.setCell(0, createCellData(0, 'a', 0)); + term.buffer.lines.get(1)!.setCell(0, createCellData(0, 'b', 0)); + term.buffer.lines.get(2)!.setCell(0, createCellData(0, 'c', 0)); + term.buffer.lines.get(3)!.setCell(0, createCellData(0, 'd', 0)); + term.buffer.lines.get(4)!.setCell(0, createCellData(0, 'e', 0)); term.buffer.y = 3; term.buffer.scrollBottom = 3; term.scroll(DEFAULT_ATTR_DATA.clone()); @@ -550,11 +550,11 @@ describe('Terminal', () => { }); it('should properly scroll inside a scroll region (scrollTop and scrollBottom set)', () => { - term.buffer.lines.get(0)!.setCell(0, CellData.fromCharData([0, 'a', 0, 'a'.charCodeAt(0)])); - term.buffer.lines.get(1)!.setCell(0, CellData.fromCharData([0, 'b', 0, 'b'.charCodeAt(0)])); - term.buffer.lines.get(2)!.setCell(0, CellData.fromCharData([0, 'c', 0, 'c'.charCodeAt(0)])); - term.buffer.lines.get(3)!.setCell(0, CellData.fromCharData([0, 'd', 0, 'd'.charCodeAt(0)])); - term.buffer.lines.get(4)!.setCell(0, CellData.fromCharData([0, 'e', 0, 'e'.charCodeAt(0)])); + term.buffer.lines.get(0)!.setCell(0, createCellData(0, 'a', 0)); + term.buffer.lines.get(1)!.setCell(0, createCellData(0, 'b', 0)); + term.buffer.lines.get(2)!.setCell(0, createCellData(0, 'c', 0)); + term.buffer.lines.get(3)!.setCell(0, createCellData(0, 'd', 0)); + term.buffer.lines.get(4)!.setCell(0, createCellData(0, 'e', 0)); term.buffer.y = INIT_ROWS - 1; // Move cursor to last line term.buffer.scrollTop = 1; term.buffer.scrollBottom = 3; diff --git a/src/browser/renderer/dom/DomRendererRowFactory.test.ts b/src/browser/renderer/dom/DomRendererRowFactory.test.ts index b6f6eedca..05fc8000c 100644 --- a/src/browser/renderer/dom/DomRendererRowFactory.test.ts +++ b/src/browser/renderer/dom/DomRendererRowFactory.test.ts @@ -10,7 +10,7 @@ import { NULL_CELL_CODE, NULL_CELL_WIDTH, NULL_CELL_CHAR, DEFAULT_ATTR, FgFlags, import { BufferLine, DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine'; import { IBufferLine } from 'common/Types'; import { CellData } from 'common/buffer/CellData'; -import { MockCoreService, MockDecorationService, MockOptionsService } from 'common/TestUtils.test'; +import { MockCoreService, MockDecorationService, MockOptionsService, createCellData, NULL_CELL_DATA } from 'common/TestUtils.test'; import { MockCharacterJoinerService, MockCoreBrowserService, MockThemeService } from 'browser/TestUtils.test'; import { TestWidthCache } from 'browser/renderer/dom/WidthCache.test'; @@ -48,9 +48,9 @@ describe('DomRendererRowFactory', () => { it('should set correct attributes for double width characters', () => { widthCache.setWidths({ '語': 10 }); - lineData.setCell(0, CellData.fromCharData([DEFAULT_ATTR, '語', 2, '語'.charCodeAt(0)])); + lineData.setCell(0, createCellData(DEFAULT_ATTR, '語', 2)); // There should be no element for the following "empty" cell - lineData.setCell(1, CellData.fromCharData([DEFAULT_ATTR, '', 0, 0])); + lineData.setCell(1, createCellData(DEFAULT_ATTR, '', 0)); const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, true, 5, widthCache, -1, -1); assert.equal(extractHtml(spans), '語' @@ -117,7 +117,7 @@ describe('DomRendererRowFactory', () => { describe('attributes', () => { it('should add class for bold', () => { - const cell = CellData.fromCharData([0, 'a', 1, 'a'.charCodeAt(0)]); + const cell = createCellData(0, 'a', 1); cell.fg = DEFAULT_ATTR_DATA.fg | FgFlags.BOLD; lineData.setCell(0, cell); const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, true, 5, widthCache, -1, -1); @@ -127,7 +127,7 @@ describe('DomRendererRowFactory', () => { }); it('should add class for italic', () => { - const cell = CellData.fromCharData([0, 'a', 1, 'a'.charCodeAt(0)]); + const cell = createCellData(0, 'a', 1); cell.bg = DEFAULT_ATTR_DATA.bg | BgFlags.ITALIC; lineData.setCell(0, cell); const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, true, 5, widthCache, -1, -1); @@ -137,7 +137,7 @@ describe('DomRendererRowFactory', () => { }); it('should add class for dim', () => { - const cell = CellData.fromCharData([0, 'a', 1, 'a'.charCodeAt(0)]); + const cell = createCellData(0, 'a', 1); cell.bg = DEFAULT_ATTR_DATA.bg | BgFlags.DIM; lineData.setCell(0, cell); const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, true, 5, widthCache, -1, -1); @@ -148,7 +148,7 @@ describe('DomRendererRowFactory', () => { describe('underline', () => { it('should add class for straight underline style', () => { - const cell = CellData.fromCharData([0, 'a', 1, 'a'.charCodeAt(0)]); + const cell = createCellData(0, 'a', 1); cell.fg = DEFAULT_ATTR_DATA.fg | FgFlags.UNDERLINE; cell.bg = DEFAULT_ATTR_DATA.bg | BgFlags.HAS_EXTENDED; cell.extended.underlineStyle = UnderlineStyle.SINGLE; @@ -159,7 +159,7 @@ describe('DomRendererRowFactory', () => { ); }); it('should add class for double underline style', () => { - const cell = CellData.fromCharData([0, 'a', 1, 'a'.charCodeAt(0)]); + const cell = createCellData(0, 'a', 1); cell.fg = DEFAULT_ATTR_DATA.fg | FgFlags.UNDERLINE; cell.bg = DEFAULT_ATTR_DATA.bg | BgFlags.HAS_EXTENDED; cell.extended.underlineStyle = UnderlineStyle.DOUBLE; @@ -170,7 +170,7 @@ describe('DomRendererRowFactory', () => { ); }); it('should add class for curly underline style', () => { - const cell = CellData.fromCharData([0, 'a', 1, 'a'.charCodeAt(0)]); + const cell = createCellData(0, 'a', 1); cell.fg = DEFAULT_ATTR_DATA.fg | FgFlags.UNDERLINE; cell.bg = DEFAULT_ATTR_DATA.bg | BgFlags.HAS_EXTENDED; cell.extended.underlineStyle = UnderlineStyle.CURLY; @@ -181,7 +181,7 @@ describe('DomRendererRowFactory', () => { ); }); it('should add class for double dotted style', () => { - const cell = CellData.fromCharData([0, 'a', 1, 'a'.charCodeAt(0)]); + const cell = createCellData(0, 'a', 1); cell.fg = DEFAULT_ATTR_DATA.fg | FgFlags.UNDERLINE; cell.bg = DEFAULT_ATTR_DATA.bg | BgFlags.HAS_EXTENDED; cell.extended.underlineStyle = UnderlineStyle.DOTTED; @@ -192,7 +192,7 @@ describe('DomRendererRowFactory', () => { ); }); it('should add class for dashed underline style', () => { - const cell = CellData.fromCharData([0, 'a', 1, 'a'.charCodeAt(0)]); + const cell = createCellData(0, 'a', 1); cell.fg = DEFAULT_ATTR_DATA.fg | FgFlags.UNDERLINE; cell.bg = DEFAULT_ATTR_DATA.bg | BgFlags.HAS_EXTENDED; cell.extended.underlineStyle = UnderlineStyle.DASHED; @@ -205,7 +205,7 @@ describe('DomRendererRowFactory', () => { }); it('should add class for overline', () => { - const cell = CellData.fromCharData([0, 'a', 1, 'a'.charCodeAt(0)]); + const cell = createCellData(0, 'a', 1); cell.bg = DEFAULT_ATTR_DATA.bg | BgFlags.OVERLINE; lineData.setCell(0, cell); const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, true, 5, widthCache, -1, -1); @@ -215,7 +215,7 @@ describe('DomRendererRowFactory', () => { }); it('should add class for strikethrough', () => { - const cell = CellData.fromCharData([0, 'a', 1, 'a'.charCodeAt(0)]); + const cell = createCellData(0, 'a', 1); cell.fg = DEFAULT_ATTR_DATA.fg | FgFlags.STRIKETHROUGH; lineData.setCell(0, cell); const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, true, 5, widthCache, -1, -1); @@ -225,7 +225,7 @@ describe('DomRendererRowFactory', () => { }); it('should hide blinking text when blink is off', () => { - const cell = CellData.fromCharData([0, 'a', 1, 'a'.charCodeAt(0)]); + const cell = createCellData(0, 'a', 1); cell.fg = DEFAULT_ATTR_DATA.fg | FgFlags.BLINK | FgFlags.UNDERLINE; cell.bg = DEFAULT_ATTR_DATA.bg | BgFlags.HAS_EXTENDED; cell.extended.underlineStyle = UnderlineStyle.SINGLE; @@ -241,7 +241,7 @@ describe('DomRendererRowFactory', () => { }); it('should add classes for 256 foreground colors', () => { - const cell = CellData.fromCharData([0, 'a', 1, 'a'.charCodeAt(0)]); + const cell = createCellData(0, 'a', 1); cell.fg |= Attributes.CM_P256; for (let i = 0; i < 256; i++) { cell.fg &= ~Attributes.PCOLOR_MASK; @@ -255,7 +255,7 @@ describe('DomRendererRowFactory', () => { }); it('should add classes for 256 background colors', () => { - const cell = CellData.fromCharData([0, 'a', 1, 'a'.charCodeAt(0)]); + const cell = createCellData(0, 'a', 1); cell.bg |= Attributes.CM_P256; for (let i = 0; i < 256; i++) { cell.bg &= ~Attributes.PCOLOR_MASK; @@ -269,7 +269,7 @@ describe('DomRendererRowFactory', () => { }); it('should correctly invert colors', () => { - const cell = CellData.fromCharData([0, 'a', 1, 'a'.charCodeAt(0)]); + const cell = createCellData(0, 'a', 1); cell.fg |= Attributes.CM_P16 | 2 | FgFlags.INVERSE; cell.bg |= Attributes.CM_P16 | 1; lineData.setCell(0, cell); @@ -280,7 +280,7 @@ describe('DomRendererRowFactory', () => { }); it('should correctly invert default fg color', () => { - const cell = CellData.fromCharData([0, 'a', 1, 'a'.charCodeAt(0)]); + const cell = createCellData(0, 'a', 1); cell.fg |= FgFlags.INVERSE; cell.bg |= Attributes.CM_P16 | 1; lineData.setCell(0, cell); @@ -291,7 +291,7 @@ describe('DomRendererRowFactory', () => { }); it('should correctly invert default bg color', () => { - const cell = CellData.fromCharData([0, 'a', 1, 'a'.charCodeAt(0)]); + const cell = createCellData(0, 'a', 1); cell.fg |= Attributes.CM_P16 | 1 | FgFlags.INVERSE; lineData.setCell(0, cell); const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, true, 5, widthCache, -1, -1); @@ -301,7 +301,7 @@ describe('DomRendererRowFactory', () => { }); it('should turn bold fg text bright', () => { - const cell = CellData.fromCharData([0, 'a', 1, 'a'.charCodeAt(0)]); + const cell = createCellData(0, 'a', 1); cell.fg |= FgFlags.BOLD | Attributes.CM_P16; for (let i = 0; i < 8; i++) { cell.fg &= ~Attributes.PCOLOR_MASK; @@ -315,7 +315,7 @@ describe('DomRendererRowFactory', () => { }); it('should set style attribute for RBG', () => { - const cell = CellData.fromCharData([0, 'a', 1, 'a'.charCodeAt(0)]); + const cell = createCellData(0, 'a', 1); cell.fg |= Attributes.CM_RGB | 1 << 16 | 2 << 8 | 3; cell.bg |= Attributes.CM_RGB | 4 << 16 | 5 << 8 | 6; lineData.setCell(0, cell); @@ -326,7 +326,7 @@ describe('DomRendererRowFactory', () => { }); it('should correctly invert RGB colors', () => { - const cell = CellData.fromCharData([0, 'a', 1, 'a'.charCodeAt(0)]); + const cell = createCellData(0, 'a', 1); cell.fg |= Attributes.CM_RGB | 1 << 16 | 2 << 8 | 3 | FgFlags.INVERSE; cell.bg |= Attributes.CM_RGB | 4 << 16 | 5 << 8 | 6; lineData.setCell(0, cell); @@ -339,8 +339,8 @@ describe('DomRendererRowFactory', () => { describe('selectionForeground', () => { it('should force selected cells with content to be rendered above the background', () => { - lineData.setCell(0, CellData.fromCharData([DEFAULT_ATTR, 'a', 1, 'a'.charCodeAt(0)])); - lineData.setCell(1, CellData.fromCharData([DEFAULT_ATTR, 'b', 1, 'b'.charCodeAt(0)])); + lineData.setCell(0, createCellData(DEFAULT_ATTR, 'a', 1)); + lineData.setCell(1, createCellData(DEFAULT_ATTR, 'b', 1)); rowFactory.handleSelectionChanged([1, 0], [2, 0], false); const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, true, 5, widthCache, -1, -1); assert.equal(extractHtml(spans), @@ -348,7 +348,7 @@ describe('DomRendererRowFactory', () => { ); }); it('should force whitespace cells to be rendered above the background', () => { - lineData.setCell(1, CellData.fromCharData([DEFAULT_ATTR, 'a', 1, 'a'.charCodeAt(0)])); + lineData.setCell(1, createCellData(DEFAULT_ATTR, 'a', 1)); rowFactory.handleSelectionChanged([0, 0], [2, 0], false); const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, true, 5, widthCache, -1, -1); assert.equal(extractHtml(spans), @@ -374,9 +374,9 @@ describe('DomRendererRowFactory', () => { }); it('can merge codepoints for equal spacing', () => { - lineData.setCell(0, CellData.fromCharData([DEFAULT_ATTR, 'a', 1, 'a'.charCodeAt(0)])); - lineData.setCell(1, CellData.fromCharData([DEFAULT_ATTR, 'b', 1, 'b'.charCodeAt(0)])); - lineData.setCell(2, CellData.fromCharData([DEFAULT_ATTR, 'c', 1, 'c'.charCodeAt(0)])); + lineData.setCell(0, createCellData(DEFAULT_ATTR, 'a', 1)); + lineData.setCell(1, createCellData(DEFAULT_ATTR, 'b', 1)); + lineData.setCell(2, createCellData(DEFAULT_ATTR, 'c', 1)); const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, true, 5, widthCache, -1, -1); assert.equal(extractHtml(spans), 'abc' @@ -385,9 +385,9 @@ describe('DomRendererRowFactory', () => { it('should not merge codepoints with different spacing', () => { widthCache.setWidths({ '€': 2 }); - lineData.setCell(0, CellData.fromCharData([DEFAULT_ATTR, 'a', 1, 'a'.charCodeAt(0)])); - lineData.setCell(1, CellData.fromCharData([DEFAULT_ATTR, '€', 1, '€'.charCodeAt(0)])); - lineData.setCell(2, CellData.fromCharData([DEFAULT_ATTR, 'c', 1, 'c'.charCodeAt(0)])); + lineData.setCell(0, createCellData(DEFAULT_ATTR, 'a', 1)); + lineData.setCell(1, createCellData(DEFAULT_ATTR, '€', 1)); + lineData.setCell(2, createCellData(DEFAULT_ATTR, 'c', 1)); const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, true, 5, widthCache, -1, -1); assert.equal(extractHtml(spans), 'a€c' @@ -395,9 +395,9 @@ describe('DomRendererRowFactory', () => { }); it('should not merge on FG change', () => { - const aColor1 = CellData.fromCharData([DEFAULT_ATTR, 'a', 1, 'a'.charCodeAt(0)]); + const aColor1 = createCellData(DEFAULT_ATTR, 'a', 1); aColor1.fg |= Attributes.CM_P16 | 1; - const bColor2 = CellData.fromCharData([DEFAULT_ATTR, 'b', 1, 'b'.charCodeAt(0)]); + const bColor2 = createCellData(DEFAULT_ATTR, 'b', 1); bColor2.fg |= Attributes.CM_P16 | 2; lineData.setCell(0, aColor1); lineData.setCell(1, aColor1); @@ -410,11 +410,11 @@ describe('DomRendererRowFactory', () => { }); it('should not merge cursor cell', () => { - lineData.setCell(0, CellData.fromCharData([DEFAULT_ATTR, 'a', 1, 'a'.charCodeAt(0)])); - lineData.setCell(1, CellData.fromCharData([DEFAULT_ATTR, 'a', 1, 'a'.charCodeAt(0)])); - lineData.setCell(2, CellData.fromCharData([DEFAULT_ATTR, 'X', 1, 'X'.charCodeAt(0)])); - lineData.setCell(3, CellData.fromCharData([DEFAULT_ATTR, 'b', 1, 'b'.charCodeAt(0)])); - lineData.setCell(4, CellData.fromCharData([DEFAULT_ATTR, 'b', 1, 'b'.charCodeAt(0)])); + lineData.setCell(0, createCellData(DEFAULT_ATTR, 'a', 1)); + lineData.setCell(1, createCellData(DEFAULT_ATTR, 'a', 1)); + lineData.setCell(2, createCellData(DEFAULT_ATTR, 'X', 1)); + lineData.setCell(3, createCellData(DEFAULT_ATTR, 'b', 1)); + lineData.setCell(4, createCellData(DEFAULT_ATTR, 'b', 1)); const spans = rowFactory.createRow(lineData, 0, true, undefined, undefined, 2, false, true, 5, widthCache, -1, -1); assert.equal(extractHtml(spans), 'aaXbb' @@ -453,7 +453,7 @@ describe('DomRendererRowFactory', () => { assert.equal(extractHtml(spans), ' ' ); - lineData.setCell(4, CellData.fromCharData([DEFAULT_ATTR, 'a', 1, 'a'.charCodeAt(0)])); + lineData.setCell(4, createCellData(DEFAULT_ATTR, 'a', 1)); spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, true, 5, widthCache, -1, -1); assert.equal(extractHtml(spans), ' a' @@ -462,9 +462,9 @@ describe('DomRendererRowFactory', () => { it('should apply correct positive or negative spacing', () => { widthCache.setWidths({ '€': 2, '語': 10, '𝄞': 7 }); // €: too small (+3px), 語: exact, 𝄞: too wide (-2px) - lineData.setCell(0, CellData.fromCharData([DEFAULT_ATTR, 'a', 1, 'a'.charCodeAt(0)])); - lineData.setCell(1, CellData.fromCharData([DEFAULT_ATTR, '€', 1, '€'.charCodeAt(0)])); - lineData.setCell(2, CellData.fromCharData([DEFAULT_ATTR, 'c', 1, 'c'.charCodeAt(0)])); + lineData.setCell(0, createCellData(DEFAULT_ATTR, 'a', 1)); + lineData.setCell(1, createCellData(DEFAULT_ATTR, '€', 1)); + lineData.setCell(2, createCellData(DEFAULT_ATTR, 'c', 1)); lineData.setCell(3, CellData.fromCharData([DEFAULT_ATTR, '語', 2, 'c'.charCodeAt(0)])); lineData.setCell(4, CellData.fromCharData([DEFAULT_ATTR, '𝄞', 1, 'c'.charCodeAt(0)])); const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, true, 5, widthCache, -1, -1); @@ -474,13 +474,13 @@ describe('DomRendererRowFactory', () => { }); it('should not merge across link borders', () => { - lineData.setCell(0, CellData.fromCharData([DEFAULT_ATTR, 'a', 1, 'a'.charCodeAt(0)])); - lineData.setCell(1, CellData.fromCharData([DEFAULT_ATTR, 'a', 1, 'a'.charCodeAt(0)])); - lineData.setCell(2, CellData.fromCharData([DEFAULT_ATTR, 'x', 1, 'x'.charCodeAt(0)])); - lineData.setCell(3, CellData.fromCharData([DEFAULT_ATTR, 'x', 1, 'x'.charCodeAt(0)])); - lineData.setCell(4, CellData.fromCharData([DEFAULT_ATTR, 'x', 1, 'x'.charCodeAt(0)])); - lineData.setCell(5, CellData.fromCharData([DEFAULT_ATTR, 'b', 1, 'b'.charCodeAt(0)])); - lineData.setCell(6, CellData.fromCharData([DEFAULT_ATTR, 'b', 1, 'b'.charCodeAt(0)])); + lineData.setCell(0, createCellData(DEFAULT_ATTR, 'a', 1)); + lineData.setCell(1, createCellData(DEFAULT_ATTR, 'a', 1)); + lineData.setCell(2, createCellData(DEFAULT_ATTR, 'x', 1)); + lineData.setCell(3, createCellData(DEFAULT_ATTR, 'x', 1)); + lineData.setCell(4, createCellData(DEFAULT_ATTR, 'x', 1)); + lineData.setCell(5, createCellData(DEFAULT_ATTR, 'b', 1)); + lineData.setCell(6, createCellData(DEFAULT_ATTR, 'b', 1)); const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, true, 5, widthCache, 2, 4); assert.equal(extractHtml(spans), 'aaxxxbb' @@ -488,10 +488,10 @@ describe('DomRendererRowFactory', () => { }); it('empty cells included in link underline', () => { - lineData.setCell(0, CellData.fromCharData([DEFAULT_ATTR, 'a', 1, 'a'.charCodeAt(0)])); - lineData.setCell(1, CellData.fromCharData([DEFAULT_ATTR, 'a', 1, 'a'.charCodeAt(0)])); - lineData.setCell(2, CellData.fromCharData([DEFAULT_ATTR, 'x', 1, 'x'.charCodeAt(0)])); - lineData.setCell(4, CellData.fromCharData([DEFAULT_ATTR, 'x', 1, 'x'.charCodeAt(0)])); + lineData.setCell(0, createCellData(DEFAULT_ATTR, 'a', 1)); + lineData.setCell(1, createCellData(DEFAULT_ATTR, 'a', 1)); + lineData.setCell(2, createCellData(DEFAULT_ATTR, 'x', 1)); + lineData.setCell(4, createCellData(DEFAULT_ATTR, 'x', 1)); const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, true, 5, widthCache, 2, 4); assert.equal(extractHtml(spans), 'aax x' @@ -500,7 +500,7 @@ describe('DomRendererRowFactory', () => { it('link range gets capped to actual line borders', () => { for (let i = 0; i < 10; ++i) { - lineData.setCell(i, CellData.fromCharData([DEFAULT_ATTR, 'a', 1, 'a'.charCodeAt(0)])); + lineData.setCell(i, createCellData(DEFAULT_ATTR, 'a', 1)); } const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, true, 5, widthCache, -100, 100); assert.equal(extractHtml(spans), @@ -519,7 +519,7 @@ describe('DomRendererRowFactory', () => { function createEmptyLineData(cols: number): IBufferLine { const lineData = new BufferLine(cols); for (let i = 0; i < cols; i++) { - lineData.setCell(i, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE])); + lineData.setCell(i, NULL_CELL_DATA); } return lineData; } diff --git a/src/browser/services/CharacterJoinerService.test.ts b/src/browser/services/CharacterJoinerService.test.ts index 6b5326d93..a0ccc67d6 100644 --- a/src/browser/services/CharacterJoinerService.test.ts +++ b/src/browser/services/CharacterJoinerService.test.ts @@ -9,7 +9,7 @@ import { CharacterJoinerService } from 'browser/services/CharacterJoinerService' import { BufferLine } from 'common/buffer/BufferLine'; import { IBufferLine } from 'common/Types'; import { CellData } from 'common/buffer/CellData'; -import { MockBufferService } from 'common/TestUtils.test'; +import { MockBufferService, createCellData } from 'common/TestUtils.test'; describe('CharacterJoinerService', () => { let service: ICharacterJoinerService; @@ -25,17 +25,17 @@ describe('CharacterJoinerService', () => { lines.set(4, new BufferLine(0)); lines.set(5, lineData([['a', 0x11111111], [' -> b -> c -> '], ['d', 0x22222222]])); const line6 = lineData([['wi']]); - line6.resize(line6.length + 1, CellData.fromCharData([0, '¥', 2, '¥'.charCodeAt(0)])); - line6.resize(line6.length + 1, CellData.fromCharData([0, '', 0, 0])); + line6.resize(line6.length + 1, createCellData(0, '¥', 2)); + line6.resize(line6.length + 1, createCellData(0, '', 0)); let sub = lineData([['deemo']]); let oldSize = line6.length; - line6.resize(oldSize + sub.length, CellData.fromCharData([0, '', 0, 0])); + line6.resize(oldSize + sub.length, createCellData(0, '', 0)); for (let i = 0; i < sub.length; ++i) line6.setCell(i + oldSize, sub.loadCell(i, new CellData())); line6.resize(line6.length + 1, CellData.fromCharData([0, '\xf0\x9f\x98\x81', 1, 128513])); - line6.resize(line6.length + 1, CellData.fromCharData([0, ' ', 1, ' '.charCodeAt(0)])); + line6.resize(line6.length + 1, createCellData(0, ' ', 1)); sub = lineData([['jiabc']]); oldSize = line6.length; - line6.resize(oldSize + sub.length, CellData.fromCharData([0, '', 0, 0])); + line6.resize(oldSize + sub.length, createCellData(0, '', 0)); for (let i = 0; i < sub.length; ++i) line6.setCell(i + oldSize, sub.loadCell(i, new CellData())); lines.set(6, line6); @@ -272,8 +272,8 @@ function lineData(data: IPartialLineData[]): IBufferLine { const line = data[i][0]; const attr = (data[i][1] || 0) as number; const offset = tline.length; - tline.resize(tline.length + line.split('').length, CellData.fromCharData([0, '', 0, 0])); - line.split('').map((char, idx) => tline.setCell(idx + offset, CellData.fromCharData([attr, char, 1, char.charCodeAt(0)]))); + tline.resize(tline.length + line.split('').length, createCellData(0, '', 0)); + line.split('').map((char, idx) => tline.setCell(idx + offset, createCellData(attr, char, 1))); } return tline; } diff --git a/src/browser/services/SelectionService.test.ts b/src/browser/services/SelectionService.test.ts index 67158def0..8deedb751 100644 --- a/src/browser/services/SelectionService.test.ts +++ b/src/browser/services/SelectionService.test.ts @@ -7,7 +7,7 @@ import { assert } from 'chai'; import { SelectionService, SelectionMode } from './SelectionService'; import { SelectionModel } from 'browser/selection/SelectionModel'; import { IBufferLine } from 'common/Types'; -import { MockBufferService, MockOptionsService, MockCoreService } from 'common/TestUtils.test'; +import { MockBufferService, MockOptionsService, MockCoreService, createCellData } from 'common/TestUtils.test'; import { BufferLine } from 'common/buffer/BufferLine'; import { IBufferService, IOptionsService } from 'common/services/Services'; import { MockCoreBrowserService, MockMouseService, MockRenderService } from 'browser/TestUtils.test'; @@ -57,14 +57,14 @@ describe('SelectionService', () => { function stringToRow(text: string): IBufferLine { const result = new BufferLine(text.length); for (let i = 0; i < text.length; i++) { - result.setCell(i, CellData.fromCharData([0, text.charAt(i), 1, text.charCodeAt(i)])); + result.setCell(i, createCellData(0, text.charAt(i), 1)); } return result; } function stringArrayToRow(chars: string[]): IBufferLine { const line = new BufferLine(chars.length); - chars.map((c, idx) => line.setCell(idx, CellData.fromCharData([0, c, 1, c.charCodeAt(0)]))); + chars.map((c, idx) => line.setCell(idx, createCellData(0, c, 1))); return line; } diff --git a/src/common/TestUtils.test.ts b/src/common/TestUtils.test.ts index 6e6f0b642..0f982b302 100644 --- a/src/common/TestUtils.test.ts +++ b/src/common/TestUtils.test.ts @@ -13,6 +13,14 @@ import { IDecPrivateModes, ICoreMouseEvent, CoreMouseEventType, ICharset, IModes import { UnicodeV6 } from 'common/input/UnicodeV6'; import { IDecorationOptions, IDecoration } from '@xterm/xterm'; import { Emitter, type IEvent } from 'common/Event'; +import { CellData } from 'common/buffer/CellData'; +import { DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH } from 'common/buffer/Constants'; + +export function createCellData(attr: number, char: string, width: number): CellData { + return CellData.fromCharData([attr, char, width, char.length === 0 ? 0 : char.charCodeAt(0)]); +} + +export const NULL_CELL_DATA = Object.freeze(createCellData(DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH)); export class MockBufferService implements IBufferService { public serviceBrand: any; diff --git a/src/common/buffer/Buffer.test.ts b/src/common/buffer/Buffer.test.ts index f9e3e1b14..9c013c5de 100644 --- a/src/common/buffer/Buffer.test.ts +++ b/src/common/buffer/Buffer.test.ts @@ -6,7 +6,7 @@ import { assert } from 'chai'; import { Buffer } from 'common/buffer/Buffer'; import { CircularList } from 'common/CircularList'; -import { MockOptionsService, MockBufferService, MockLogService } from 'common/TestUtils.test'; +import { MockOptionsService, MockBufferService, MockLogService, createCellData } from 'common/TestUtils.test'; import { BufferLine, DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine'; import { CellData } from 'common/buffer/CellData'; import { ExtendedAttrs } from 'common/buffer/AttributeData'; @@ -1105,10 +1105,10 @@ describe('Buffer', () => { describe ('translateBufferLineToString', () => { it('should handle selecting a section of ascii text', () => { const line = new BufferLine(4); - line.setCell(0, CellData.fromCharData([ 0, 'a', 1, 'a'.charCodeAt(0)])); - line.setCell(1, CellData.fromCharData([ 0, 'b', 1, 'b'.charCodeAt(0)])); - line.setCell(2, CellData.fromCharData([ 0, 'c', 1, 'c'.charCodeAt(0)])); - line.setCell(3, CellData.fromCharData([ 0, 'd', 1, 'd'.charCodeAt(0)])); + line.setCell(0, createCellData(0, 'a', 1)); + line.setCell(1, createCellData(0, 'b', 1)); + line.setCell(2, createCellData(0, 'c', 1)); + line.setCell(3, createCellData(0, 'd', 1)); buffer.lines.set(0, line); const str = buffer.translateBufferLineToString(0, true, 0, 2); @@ -1117,9 +1117,9 @@ describe('Buffer', () => { it('should handle a cut-off double width character by including it', () => { const line = new BufferLine(3); - line.setCell(0, CellData.fromCharData([ 0, '語', 2, 35486 ])); - line.setCell(1, CellData.fromCharData([ 0, '', 0, 0])); - line.setCell(2, CellData.fromCharData([ 0, 'a', 1, 'a'.charCodeAt(0)])); + line.setCell(0, createCellData(0, '語', 2)); + line.setCell(1, createCellData(0, '', 0)); + line.setCell(2, createCellData(0, 'a', 1)); buffer.lines.set(0, line); const str1 = buffer.translateBufferLineToString(0, true, 0, 1); @@ -1128,9 +1128,9 @@ describe('Buffer', () => { it('should handle a zero width character in the middle of the string by not including it', () => { const line = new BufferLine(3); - line.setCell(0, CellData.fromCharData([ 0, '語', 2, '語'.charCodeAt(0) ])); - line.setCell(1, CellData.fromCharData([ 0, '', 0, 0])); - line.setCell(2, CellData.fromCharData([ 0, 'a', 1, 'a'.charCodeAt(0)])); + line.setCell(0, createCellData(0, '語', 2)); + line.setCell(1, createCellData(0, '', 0)); + line.setCell(2, createCellData(0, 'a', 1)); buffer.lines.set(0, line); const str0 = buffer.translateBufferLineToString(0, true, 0, 1); @@ -1145,8 +1145,8 @@ describe('Buffer', () => { it('should handle single width emojis', () => { const line = new BufferLine(2); - line.setCell(0, CellData.fromCharData([ 0, '😁', 1, '😁'.charCodeAt(0) ])); - line.setCell(1, CellData.fromCharData([ 0, 'a', 1, 'a'.charCodeAt(0)])); + line.setCell(0, createCellData(0, '😁', 1)); + line.setCell(1, createCellData(0, 'a', 1)); buffer.lines.set(0, line); const str1 = buffer.translateBufferLineToString(0, true, 0, 1); @@ -1158,8 +1158,8 @@ describe('Buffer', () => { it('should handle double width emojis', () => { const line = new BufferLine(2); - line.setCell(0, CellData.fromCharData([ 0, '😁', 2, '😁'.charCodeAt(0) ])); - line.setCell(1, CellData.fromCharData([ 0, '', 0, 0])); + line.setCell(0, createCellData(0, '😁', 2)); + line.setCell(1, createCellData(0, '', 0)); buffer.lines.set(0, line); const str1 = buffer.translateBufferLineToString(0, true, 0, 1); @@ -1169,9 +1169,9 @@ describe('Buffer', () => { assert.equal(str2, '😁'); const line2 = new BufferLine(3); - line2.setCell(0, CellData.fromCharData([ 0, '😁', 2, '😁'.charCodeAt(0) ])); - line2.setCell(1, CellData.fromCharData([ 0, '', 0, 0])); - line2.setCell(2, CellData.fromCharData([ 0, 'a', 1, 'a'.charCodeAt(0)])); + line2.setCell(0, createCellData(0, '😁', 2)); + line2.setCell(1, createCellData(0, '', 0)); + line2.setCell(2, createCellData(0, 'a', 1)); buffer.lines.set(0, line2); const str3 = buffer.translateBufferLineToString(0, true, 0, 3); diff --git a/src/common/buffer/BufferLine.test.ts b/src/common/buffer/BufferLine.test.ts index eaceaa45f..100b730d9 100644 --- a/src/common/buffer/BufferLine.test.ts +++ b/src/common/buffer/BufferLine.test.ts @@ -8,6 +8,7 @@ import { CellData } from 'common/buffer/CellData'; import { CharData, IBufferLine } from '../Types'; import { assert } from 'chai'; import { AttributeData } from 'common/buffer/AttributeData'; +import { createCellData, NULL_CELL_DATA } from 'common/TestUtils.test'; class TestBufferLine extends BufferLine { @@ -24,12 +25,6 @@ class TestBufferLine extends BufferLine { } } -function createCellData(attr: number, char: string, width: number): CellData { - return CellData.fromCharData([attr, char, width, char.length === 0 ? 0 : char.charCodeAt(0)]); -} - -const NULL_CELL_DATA = Object.freeze(createCellData(DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH)); - describe('AttributeData', () => { describe('extended attributes', () => { it('hasExtendedAttrs', () => {