From a1b438cea6aec91726c526e263ea5d32079356b5 Mon Sep 17 00:00:00 2001 From: Asem Dreibati Date: Tue, 29 Oct 2024 10:31:57 +0300 Subject: [PATCH 01/42] Store the DI decorator's id in specific property (#5131). --- src/common/services/InstantiationService.ts | 2 +- src/common/services/ServiceRegistry.ts | 2 +- src/common/services/Services.ts | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/common/services/InstantiationService.ts b/src/common/services/InstantiationService.ts index 375e442d..7e769548 100644 --- a/src/common/services/InstantiationService.ts +++ b/src/common/services/InstantiationService.ts @@ -67,7 +67,7 @@ export class InstantiationService implements IInstantiationService { for (const dependency of serviceDependencies) { const service = this._services.get(dependency.id); if (!service) { - throw new Error(`[createInstance] ${ctor.name} depends on UNKNOWN service ${dependency.id}.`); + throw new Error(`[createInstance] ${ctor.name} depends on UNKNOWN service ${dependency.id._id}.`); } serviceArgs.push(service); } diff --git a/src/common/services/ServiceRegistry.ts b/src/common/services/ServiceRegistry.ts index 6510fb8e..7d887bc6 100644 --- a/src/common/services/ServiceRegistry.ts +++ b/src/common/services/ServiceRegistry.ts @@ -33,7 +33,7 @@ export function createDecorator(id: string): IServiceIdentifier { storeServiceDependency(decorator, target, index); }; - decorator.toString = () => id; + decorator._id = id; serviceRegistry.set(id, decorator); return decorator; diff --git a/src/common/services/Services.ts b/src/common/services/Services.ts index 0ceff36c..9d5ca64b 100644 --- a/src/common/services/Services.ts +++ b/src/common/services/Services.ts @@ -124,6 +124,7 @@ export interface ICharsetService { export interface IServiceIdentifier { (...args: any[]): void; type: T; + _id: string; } export interface IBrandedService { From 952ac617617678a0b2314e261fbe3e78995e2eb7 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 1 Nov 2024 07:18:27 -0700 Subject: [PATCH 02/42] Only resolve ligatures externals in commonjs environment Part of microsoft/vscode#34103 --- addons/addon-ligatures/webpack.config.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/addons/addon-ligatures/webpack.config.js b/addons/addon-ligatures/webpack.config.js index f9e9f347..c43d0a17 100644 --- a/addons/addon-ligatures/webpack.config.js +++ b/addons/addon-ligatures/webpack.config.js @@ -31,10 +31,10 @@ module.exports = { }, mode: 'production', externals: { - 'fs': 'fs', - 'path': 'path', - 'stream': 'stream', - 'util': 'util' + 'fs': 'commonjs fs', + 'path': 'commonjs path', + 'stream': 'commonjs stream', + 'util': 'commonjs util' }, resolve: { // The ligature modules contains fallbacks for node environments, we never want to browserify them From 6684c406e89abd9b422177995880f030d73b536c Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 1 Nov 2024 07:19:33 -0700 Subject: [PATCH 03/42] Don't hide build folders This was getting too annoying needing to comment out these lines --- .vscode/settings.json | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 3bf1c691..9a9182c9 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -4,11 +4,7 @@ }, // Hide output files from the file explorer, comment this out to see the build output "files.exclude": { - "**/.nyc_output": true, - "**/lib": true, - "**/dist": true, - "**/out": true, - "**/out-*": true, + "**/.nyc_output": true }, "typescript.preferences.importModuleSpecifier": "non-relative", "typescript.preferences.quoteStyle": "single", From 3948482dc74518697a8c8c1df8b2f33341d32277 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 1 Nov 2024 07:20:10 -0700 Subject: [PATCH 04/42] Also remove associated comment --- .vscode/settings.json | 1 - 1 file changed, 1 deletion(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 9a9182c9..cf683d8a 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -2,7 +2,6 @@ "files.associations": { ".eslintrc.json.typings": "jsonc" }, - // Hide output files from the file explorer, comment this out to see the build output "files.exclude": { "**/.nyc_output": true }, From 77885ed63d2b88004e0272845f9a92a4e08308e0 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 4 Nov 2024 09:47:26 -0800 Subject: [PATCH 05/42] Set liga font feature when ligatures is enabled Fixes #5207 --- addons/addon-ligatures/src/LigaturesAddon.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/addons/addon-ligatures/src/LigaturesAddon.ts b/addons/addon-ligatures/src/LigaturesAddon.ts index cb589303..47fbc323 100644 --- a/addons/addon-ligatures/src/LigaturesAddon.ts +++ b/addons/addon-ligatures/src/LigaturesAddon.ts @@ -31,8 +31,12 @@ export class LigaturesAddon implements ITerminalAddon , ILigaturesApi { } public activate(terminal: Terminal): void { + if (!terminal.element) { + throw new Error('Cannot activate LigaturesAddon before open is called'); + } this._terminal = terminal; this._characterJoinerId = enableLigatures(terminal, this._fallbackLigatures); + terminal.element.style.fontFeatureSettings = '"liga" on, "calt" on'; } public dispose(): void { @@ -40,5 +44,8 @@ export class LigaturesAddon implements ITerminalAddon , ILigaturesApi { this._terminal?.deregisterCharacterJoiner(this._characterJoinerId); this._characterJoinerId = undefined; } + if (this._terminal?.element) { + this._terminal.element.style.fontFeatureSettings = ''; + } } } From 7fab293cd6f90d60d9ec7ff47cb514bbc2138850 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 4 Nov 2024 10:12:20 -0800 Subject: [PATCH 06/42] Ensure last ligature cell is updated Fixes #3288 Fixes #5206 Part of microsoft/vscode#233005 --- addons/addon-webgl/src/WebglRenderer.ts | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/addons/addon-webgl/src/WebglRenderer.ts b/addons/addon-webgl/src/WebglRenderer.ts index 48023eae..216b8404 100644 --- a/addons/addon-webgl/src/WebglRenderer.ts +++ b/addons/addon-webgl/src/WebglRenderer.ts @@ -314,14 +314,6 @@ export class WebglRenderer extends Disposable implements IRenderer { this._updateCursorBlink(); } - public registerCharacterJoiner(handler: (text: string) => [number, number][]): number { - return -1; - } - - public deregisterCharacterJoiner(joinerId: number): boolean { - return false; - } - public renderRows(start: number, end: number): void { if (!this._isAttached) { if (this._coreBrowserService.window.document.body.contains(this._core.screenElement!) && this._charSizeService.width && this._charSizeService.height) { @@ -510,14 +502,17 @@ export class WebglRenderer extends Disposable implements IRenderer { cell = this._workCell; // Null out non-first cells - for (x++; x < lastCharX; x++) { + for (x++; x <= lastCharX; x++) { j = ((y * terminal.cols) + x) * RENDER_MODEL_INDICIES_PER_CELL; this._glyphRenderer.value!.updateCell(x, y, NULL_CELL_CODE, 0, 0, 0, NULL_CELL_CHAR, 0, 0); this._model.cells[j] = NULL_CELL_CODE; + // Don't re-resolve the cell color since multi-colored ligature backgrounds are not + // supported this._model.cells[j + RENDER_MODEL_BG_OFFSET] = this._cellColorResolver.result.bg; this._model.cells[j + RENDER_MODEL_FG_OFFSET] = this._cellColorResolver.result.fg; this._model.cells[j + RENDER_MODEL_EXT_OFFSET] = this._cellColorResolver.result.ext; } + x--; // Go back to the previous update cell for next iteration } } } From 9c66ad11399cc362dcc94e7f131bbb0f9555ba72 Mon Sep 17 00:00:00 2001 From: JackieL Date: Tue, 19 Nov 2024 14:58:32 +0800 Subject: [PATCH 07/42] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 39b7b52e..5102c69d 100644 --- a/README.md +++ b/README.md @@ -226,6 +226,7 @@ Xterm.js is used in several world-class applications to provide great terminal e - [**OpenSFTP**](https://opensftp.com): Super beautiful SSH and SFTP integrated workspace client. - [**balena**](https://www.balena.io/): Balena is a full-stack solution for developing, deploying, updating, and troubleshooting IoT Edge devices. We use xterm.js to manage & debug devices on [balenaCloud](https://www.balena.io/cloud). - [**Filet Cloud**](https://github.com/fuglaro/filet-cloud): The lean and powerful personal cloud ⛅. +- [**LabEx**](https://labex.io): LabEx: Interactive learning platform with hands-on labs and xterm.js-based online terminals, focused on learn-by-doing approach. - [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. Note: Please add any new contributions to the end of the list only. From db621d7294866ed75fe0066b7bdab1df61c11f9a Mon Sep 17 00:00:00 2001 From: JackieL Date: Tue, 19 Nov 2024 14:59:53 +0800 Subject: [PATCH 08/42] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5102c69d..7883f870 100644 --- a/README.md +++ b/README.md @@ -226,7 +226,7 @@ Xterm.js is used in several world-class applications to provide great terminal e - [**OpenSFTP**](https://opensftp.com): Super beautiful SSH and SFTP integrated workspace client. - [**balena**](https://www.balena.io/): Balena is a full-stack solution for developing, deploying, updating, and troubleshooting IoT Edge devices. We use xterm.js to manage & debug devices on [balenaCloud](https://www.balena.io/cloud). - [**Filet Cloud**](https://github.com/fuglaro/filet-cloud): The lean and powerful personal cloud ⛅. -- [**LabEx**](https://labex.io): LabEx: Interactive learning platform with hands-on labs and xterm.js-based online terminals, focused on learn-by-doing approach. +- [**LabEx**](https://labex.io): Interactive learning platform with hands-on labs and xterm.js-based online terminals, focused on learn-by-doing approach. - [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. Note: Please add any new contributions to the end of the list only. From ae2622e5ce864a784b1faf43425106625a118a8e Mon Sep 17 00:00:00 2001 From: Jacob Bandes-Storch Date: Thu, 5 Dec 2024 18:00:18 -0800 Subject: [PATCH 09/42] Add reflowCursorLine option --- demo/client.ts | 21 +++++++++++++-------- demo/index.html | 1 + src/common/buffer/Buffer.ts | 15 +++++++++------ src/common/buffer/BufferReflow.ts | 15 +++++++++------ src/common/services/OptionsService.ts | 1 + src/common/services/Services.ts | 1 + typings/xterm-headless.d.ts | 6 ++++++ typings/xterm.d.ts | 6 ++++++ 8 files changed, 46 insertions(+), 20 deletions(-) diff --git a/demo/client.ts b/demo/client.ts index 49ba6743..6e0be22f 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -366,14 +366,19 @@ function createTerminal(): void { // Set terminal size again to set the specific dimensions on the demo updateTerminalSize(); - const res = await fetch('/terminals?cols=' + term.cols + '&rows=' + term.rows, { method: 'POST' }); - const processId = await res.text(); - pid = processId; - socketURL += processId; - socket = new WebSocket(socketURL); - socket.onopen = runRealTerminal; - socket.onclose = runFakeTerminal; - socket.onerror = runFakeTerminal; + const useRealTerminal = document.getElementById('use-real-terminal'); + if (useRealTerminal instanceof HTMLInputElement && !useRealTerminal.checked) { + runFakeTerminal(); + } else { + const res = await fetch('/terminals?cols=' + term.cols + '&rows=' + term.rows, { method: 'POST' }); + const processId = await res.text(); + pid = processId; + socketURL += processId; + socket = new WebSocket(socketURL); + socket.onopen = runRealTerminal; + socket.onclose = runFakeTerminal; + socket.onerror = runFakeTerminal; + } }, 0); } diff --git a/demo/index.html b/demo/index.html index 06451847..3d46f61c 100644 --- a/demo/index.html +++ b/demo/index.html @@ -81,6 +81,7 @@
Lifecycle
+
diff --git a/src/common/buffer/Buffer.ts b/src/common/buffer/Buffer.ts index d5e05731..dfdaefd3 100644 --- a/src/common/buffer/Buffer.ts +++ b/src/common/buffer/Buffer.ts @@ -315,7 +315,7 @@ export class Buffer implements IBuffer { } private _reflowLarger(newCols: number, newRows: number): void { - const toRemove: number[] = reflowLargerGetLinesToRemove(this.lines, this._cols, newCols, this.ybase + this.y, this.getNullCell(DEFAULT_ATTR_DATA)); + const toRemove: number[] = reflowLargerGetLinesToRemove(this.lines, this._cols, newCols, this.ybase + this.y, this.getNullCell(DEFAULT_ATTR_DATA), this._optionsService.rawOptions.reflowCursorLine); if (toRemove.length > 0) { const newLayoutResult = reflowLargerCreateNewLayout(this.lines, toRemove); reflowLargerApplyNewLayout(this.lines, newLayoutResult.layout); @@ -347,6 +347,7 @@ export class Buffer implements IBuffer { } private _reflowSmaller(newCols: number, newRows: number): void { + const reflowCursorLine = this._optionsService.rawOptions.reflowCursorLine; const nullCell = this.getNullCell(DEFAULT_ATTR_DATA); // Gather all BufferLines that need to be inserted into the Buffer here so that they can be // batched up and only committed once @@ -367,11 +368,13 @@ export class Buffer implements IBuffer { wrappedLines.unshift(nextLine); } - // If these lines contain the cursor don't touch them, the program will handle fixing up - // wrapped lines with the cursor - const absoluteY = this.ybase + this.y; - if (absoluteY >= y && absoluteY < y + wrappedLines.length) { - continue; + if (!reflowCursorLine) { + // If these lines contain the cursor don't touch them, the program will handle fixing up + // wrapped lines with the cursor + const absoluteY = this.ybase + this.y; + if (absoluteY >= y && absoluteY < y + wrappedLines.length) { + continue; + } } const lastLineLength = wrappedLines[wrappedLines.length - 1].getTrimmedLength(); diff --git a/src/common/buffer/BufferReflow.ts b/src/common/buffer/BufferReflow.ts index af1c6473..c127f3b0 100644 --- a/src/common/buffer/BufferReflow.ts +++ b/src/common/buffer/BufferReflow.ts @@ -20,8 +20,9 @@ export interface INewLayoutResult { * @param newCols The columns after resize. * @param bufferAbsoluteY The absolute y position of the cursor (baseY + cursorY). * @param nullCell The cell data to use when filling in empty cells. + * @param reflowCursorLine Whether to reflow the line containing the cursor. */ -export function reflowLargerGetLinesToRemove(lines: CircularList, oldCols: number, newCols: number, bufferAbsoluteY: number, nullCell: ICellData): number[] { +export function reflowLargerGetLinesToRemove(lines: CircularList, oldCols: number, newCols: number, bufferAbsoluteY: number, nullCell: ICellData,reflowCursorLine: boolean): number[] { // Gather all BufferLines that need to be removed from the Buffer here so that they can be // batched up and only committed once const toRemove: number[] = []; @@ -41,11 +42,13 @@ export function reflowLargerGetLinesToRemove(lines: CircularList, o nextLine = lines.get(++i) as BufferLine; } - // If these lines contain the cursor don't touch them, the program will handle fixing up wrapped - // lines with the cursor - if (bufferAbsoluteY >= y && bufferAbsoluteY < i) { - y += wrappedLines.length - 1; - continue; + if (!reflowCursorLine) { + // If these lines contain the cursor don't touch them, the program will handle fixing up + // wrapped lines with the cursor + if (bufferAbsoluteY >= y && bufferAbsoluteY < i) { + y += wrappedLines.length - 1; + continue; + } } // Copy buffer data to new locations diff --git a/src/common/services/OptionsService.ts b/src/common/services/OptionsService.ts index a757c179..4b8ca822 100644 --- a/src/common/services/OptionsService.ts +++ b/src/common/services/OptionsService.ts @@ -44,6 +44,7 @@ export const DEFAULT_OPTIONS: Readonly> = { allowTransparency: false, tabStopWidth: 8, theme: {}, + reflowCursorLine: false, rescaleOverlappingGlyphs: false, rightClickSelectsWord: isMac, windowOptions: {}, diff --git a/src/common/services/Services.ts b/src/common/services/Services.ts index 0ceff36c..0e5c5184 100644 --- a/src/common/services/Services.ts +++ b/src/common/services/Services.ts @@ -237,6 +237,7 @@ export interface ITerminalOptions { macOptionIsMeta?: boolean; macOptionClickForcesSelection?: boolean; minimumContrastRatio?: number; + reflowCursorLine?: boolean; rescaleOverlappingGlyphs?: boolean; rightClickSelectsWord?: boolean; rows?: number; diff --git a/typings/xterm-headless.d.ts b/typings/xterm-headless.d.ts index 3cbde44b..621b450b 100644 --- a/typings/xterm-headless.d.ts +++ b/typings/xterm-headless.d.ts @@ -142,6 +142,12 @@ declare module '@xterm/headless' { */ minimumContrastRatio?: number; + /** + * Whether to reflow the line containing the cursor when the terminal is resized. Defaults to + * false, because shells usually handle this themselves. + */ + reflowCursorLine?: boolean; + /** * Whether to rescale glyphs horizontally that are a single cell wide but * have glyphs that would overlap following cell(s). This typically happens diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index f9cf14f9..a3ebadb0 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -213,6 +213,12 @@ declare module '@xterm/xterm' { */ minimumContrastRatio?: number; + /** + * Whether to reflow the line containing the cursor when the terminal is resized. Defaults to + * false, because shells usually handle this themselves. + */ + reflowCursorLine?: boolean; + /** * Whether to rescale glyphs horizontally that are a single cell wide but * have glyphs that would overlap following cell(s). This typically happens From d7364faeee18768908df3fbc2664f8922e5ff7e8 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 12 Dec 2024 06:25:31 -0800 Subject: [PATCH 10/42] Update src/common/buffer/BufferReflow.ts --- src/common/buffer/BufferReflow.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/buffer/BufferReflow.ts b/src/common/buffer/BufferReflow.ts index c127f3b0..44aa0976 100644 --- a/src/common/buffer/BufferReflow.ts +++ b/src/common/buffer/BufferReflow.ts @@ -22,7 +22,7 @@ export interface INewLayoutResult { * @param nullCell The cell data to use when filling in empty cells. * @param reflowCursorLine Whether to reflow the line containing the cursor. */ -export function reflowLargerGetLinesToRemove(lines: CircularList, oldCols: number, newCols: number, bufferAbsoluteY: number, nullCell: ICellData,reflowCursorLine: boolean): number[] { +export function reflowLargerGetLinesToRemove(lines: CircularList, oldCols: number, newCols: number, bufferAbsoluteY: number, nullCell: ICellData, reflowCursorLine: boolean): number[] { // Gather all BufferLines that need to be removed from the Buffer here so that they can be // batched up and only committed once const toRemove: number[] = []; From 65899a9b50ced99098798b11354f0b5d3a4d8b25 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 12 Dec 2024 06:25:36 -0800 Subject: [PATCH 11/42] Update src/common/buffer/Buffer.ts --- src/common/buffer/Buffer.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/common/buffer/Buffer.ts b/src/common/buffer/Buffer.ts index dfdaefd3..81ab156b 100644 --- a/src/common/buffer/Buffer.ts +++ b/src/common/buffer/Buffer.ts @@ -315,7 +315,8 @@ export class Buffer implements IBuffer { } private _reflowLarger(newCols: number, newRows: number): void { - const toRemove: number[] = reflowLargerGetLinesToRemove(this.lines, this._cols, newCols, this.ybase + this.y, this.getNullCell(DEFAULT_ATTR_DATA), this._optionsService.rawOptions.reflowCursorLine); + const reflowCursorLine = this._optionsService.rawOptions.reflowCursorLine; + const toRemove: number[] = reflowLargerGetLinesToRemove(this.lines, this._cols, newCols, this.ybase + this.y, this.getNullCell(DEFAULT_ATTR_DATA), reflowCursorLine); if (toRemove.length > 0) { const newLayoutResult = reflowLargerCreateNewLayout(this.lines, toRemove); reflowLargerApplyNewLayout(this.lines, newLayoutResult.layout); From 0d673930721848418163df6f6da956d287d2b676 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 12 Dec 2024 06:30:17 -0800 Subject: [PATCH 12/42] Fix lint --- typings/xterm-headless.d.ts | 5 +++-- typings/xterm.d.ts | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/typings/xterm-headless.d.ts b/typings/xterm-headless.d.ts index 621b450b..1085db9b 100644 --- a/typings/xterm-headless.d.ts +++ b/typings/xterm-headless.d.ts @@ -143,8 +143,9 @@ declare module '@xterm/headless' { minimumContrastRatio?: number; /** - * Whether to reflow the line containing the cursor when the terminal is resized. Defaults to - * false, because shells usually handle this themselves. + * Whether to reflow the line containing the cursor when the terminal is + * resized. Defaults to false, because shells usually handle this + * themselves. */ reflowCursorLine?: boolean; diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index a3ebadb0..15a03327 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -214,8 +214,9 @@ declare module '@xterm/xterm' { minimumContrastRatio?: number; /** - * Whether to reflow the line containing the cursor when the terminal is resized. Defaults to - * false, because shells usually handle this themselves. + * Whether to reflow the line containing the cursor when the terminal is + * resized. Defaults to false, because shells usually handle this + * themselves. */ reflowCursorLine?: boolean; From bc7288a38908231a2f1642706d84307abe511c9c Mon Sep 17 00:00:00 2001 From: Jay Mathis Date: Thu, 12 Dec 2024 21:40:46 -0600 Subject: [PATCH 13/42] Update README.md - add ecmaOS as Use Case --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 39b7b52e..fc2a6cea 100644 --- a/README.md +++ b/README.md @@ -226,6 +226,7 @@ Xterm.js is used in several world-class applications to provide great terminal e - [**OpenSFTP**](https://opensftp.com): Super beautiful SSH and SFTP integrated workspace client. - [**balena**](https://www.balena.io/): Balena is a full-stack solution for developing, deploying, updating, and troubleshooting IoT Edge devices. We use xterm.js to manage & debug devices on [balenaCloud](https://www.balena.io/cloud). - [**Filet Cloud**](https://github.com/fuglaro/filet-cloud): The lean and powerful personal cloud ⛅. +- [**ecmaOS**](https://ecmaos.sh): A kernel and suite of applications tying modern web technologies into a browser-based operating system. - [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. Note: Please add any new contributions to the end of the list only. From 1978399259365a3d7e65b575068c58e0751e09f4 Mon Sep 17 00:00:00 2001 From: Jacob Bandes-Storch Date: Fri, 13 Dec 2024 13:57:17 -0800 Subject: [PATCH 14/42] Fix click event bug caused by DomRenderer replaceChildren behavior --- src/browser/renderer/dom/DomRenderer.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/browser/renderer/dom/DomRenderer.ts b/src/browser/renderer/dom/DomRenderer.ts index f6fda22e..2274d043 100644 --- a/src/browser/renderer/dom/DomRenderer.ts +++ b/src/browser/renderer/dom/DomRenderer.ts @@ -161,6 +161,10 @@ export class DomRenderer extends Disposable implements IRenderer { // Base CSS let styles = `${this._terminalSelector} .${ROW_CONTAINER_CLASS} {` + + // Disabling pointer events circumvents a browser behavior that prevents `click` events from + // being delivered if the target element is replaced during the click. This happened due to + // refresh() being called during the mousedown handler to start a selection. + ` pointer-events: none;` + ` color: ${colors.foreground.css};` + ` font-family: ${this._optionsService.rawOptions.fontFamily};` + ` font-size: ${this._optionsService.rawOptions.fontSize}px;` + From 9f9bb3c11c0d6d88c4d9d16ce70e9e1f3be73038 Mon Sep 17 00:00:00 2001 From: An Phi Date: Wed, 18 Dec 2024 01:26:47 -0500 Subject: [PATCH 15/42] bug: properly render the terminal when open() is called again --- src/browser/CoreBrowserTerminal.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/browser/CoreBrowserTerminal.ts b/src/browser/CoreBrowserTerminal.ts index 2e15b5f3..33ee94bc 100644 --- a/src/browser/CoreBrowserTerminal.ts +++ b/src/browser/CoreBrowserTerminal.ts @@ -400,7 +400,7 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal { } // If the terminal is already opened - if (this.element?.ownerDocument.defaultView && this._coreBrowserService) { + if (this.element?.ownerDocument.defaultView && this._coreBrowserService && this.element?.isConnected) { // Adjust the window if needed if (this.element.ownerDocument.defaultView !== this._coreBrowserService.window) { this._coreBrowserService.window = this.element.ownerDocument.defaultView; From b74ec267e2bcfd1c3582958cf7be29fff29cbd80 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 20 Dec 2024 07:37:12 -0800 Subject: [PATCH 16/42] Blend cursor with background to support alpha in webgl Fixes #5241 --- demo/client.ts | 4 ++-- src/browser/services/ThemeService.ts | 2 +- test/playwright/SharedRendererTests.ts | 8 ++++++++ 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/demo/client.ts b/demo/client.ts index 6e0be22f..02b9f7be 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -16,7 +16,7 @@ if ('WebAssembly' in window) { ImageAddon = imageAddon.ImageAddon; } -import { Terminal, ITerminalOptions, type IDisposable } from '@xterm/xterm'; +import { Terminal, ITerminalOptions, type IDisposable, type ITheme } from '@xterm/xterm'; import { AttachAddon } from '@xterm/addon-attach'; import { ClipboardAddon } from '@xterm/addon-clipboard'; import { FitAddon } from '@xterm/addon-fit'; @@ -131,7 +131,7 @@ const xtermjsTheme = { brightCyan: '#72F0FF', white: '#F8F8F8', brightWhite: '#FFFFFF' -}; +} satisfies ITheme; function setPadding(): void { term.element.style.padding = parseInt(paddingElement.value, 10).toString() + 'px'; addons.fit.instance.fit(); diff --git a/src/browser/services/ThemeService.ts b/src/browser/services/ThemeService.ts index 88ffd99d..0dc7fcbf 100644 --- a/src/browser/services/ThemeService.ts +++ b/src/browser/services/ThemeService.ts @@ -82,7 +82,7 @@ export class ThemeService extends Disposable implements IThemeService { const colors = this._colors; colors.foreground = parseColor(theme.foreground, DEFAULT_FOREGROUND); colors.background = parseColor(theme.background, DEFAULT_BACKGROUND); - colors.cursor = parseColor(theme.cursor, DEFAULT_CURSOR); + colors.cursor = color.blend(colors.background, parseColor(theme.cursor, DEFAULT_CURSOR)); colors.cursorAccent = parseColor(theme.cursorAccent, DEFAULT_CURSOR_ACCENT); colors.selectionBackgroundTransparent = parseColor(theme.selectionBackground, DEFAULT_SELECTION); colors.selectionBackgroundOpaque = color.blend(colors.background, colors.selectionBackgroundTransparent); diff --git a/test/playwright/SharedRendererTests.ts b/test/playwright/SharedRendererTests.ts index 4a1798ce..b0c5c499 100644 --- a/test/playwright/SharedRendererTests.ts +++ b/test/playwright/SharedRendererTests.ts @@ -1248,6 +1248,14 @@ export function injectSharedRendererTests(ctx: ISharedRendererTestContext): void await ctx.value.proxy.scrollLines(-2); await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [0, 0, 0, 255]); }); + test('#5241 cursor with alpha should blend color with background color', async () => { + const theme: ITheme = { + cursor: '#FF000080' + }; + await ctx.value.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`); + await ctx.value.proxy.focus(); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [128, 0, 0, 255]); + }); }); } From 3e998387fe61910b75e3e549c5d84e8e322c6978 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 20 Dec 2024 08:51:17 -0800 Subject: [PATCH 17/42] Blend cursorAccent with background too Fixes #5241 --- src/browser/services/ThemeService.ts | 2 +- test/playwright/SharedRendererTests.ts | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/browser/services/ThemeService.ts b/src/browser/services/ThemeService.ts index 0dc7fcbf..cd85e0ff 100644 --- a/src/browser/services/ThemeService.ts +++ b/src/browser/services/ThemeService.ts @@ -83,7 +83,7 @@ export class ThemeService extends Disposable implements IThemeService { colors.foreground = parseColor(theme.foreground, DEFAULT_FOREGROUND); colors.background = parseColor(theme.background, DEFAULT_BACKGROUND); colors.cursor = color.blend(colors.background, parseColor(theme.cursor, DEFAULT_CURSOR)); - colors.cursorAccent = parseColor(theme.cursorAccent, DEFAULT_CURSOR_ACCENT); + colors.cursorAccent = color.blend(colors.background, parseColor(theme.cursorAccent, DEFAULT_CURSOR_ACCENT)); colors.selectionBackgroundTransparent = parseColor(theme.selectionBackground, DEFAULT_SELECTION); colors.selectionBackgroundOpaque = color.blend(colors.background, colors.selectionBackgroundTransparent); colors.selectionInactiveBackgroundTransparent = parseColor(theme.selectionInactiveBackground, colors.selectionBackgroundTransparent); diff --git a/test/playwright/SharedRendererTests.ts b/test/playwright/SharedRendererTests.ts index b0c5c499..998d51a6 100644 --- a/test/playwright/SharedRendererTests.ts +++ b/test/playwright/SharedRendererTests.ts @@ -1256,6 +1256,16 @@ export function injectSharedRendererTests(ctx: ISharedRendererTestContext): void await ctx.value.proxy.focus(); await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [128, 0, 0, 255]); }); + test.only('#5241 cursorAccent with alpha should blend color with background color', async () => { + const theme: ITheme = { + cursorAccent: '#FF000080' + }; + await ctx.value.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`); + await ctx.value.proxy.focus(); + await ctx.value.proxy.write('■'); + await ctx.value.proxy.write('\x1b[1D'); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [128, 0, 0, 255]); + }); }); } From da22b0308ef6f53f62f5eb78b19fce735aa1a15f Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 20 Dec 2024 09:00:05 -0800 Subject: [PATCH 18/42] Make textarea readonly when disableStdin is set Fixes #5256 --- src/browser/CoreBrowserTerminal.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/browser/CoreBrowserTerminal.ts b/src/browser/CoreBrowserTerminal.ts index 2e15b5f3..58da9344 100644 --- a/src/browser/CoreBrowserTerminal.ts +++ b/src/browser/CoreBrowserTerminal.ts @@ -437,7 +437,7 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal { this.screenElement.appendChild(this._helperContainer); fragment.appendChild(this.screenElement); - this.textarea = this._document.createElement('textarea'); + const textarea = this.textarea = this._document.createElement('textarea'); this.textarea.classList.add('xterm-helper-textarea'); this.textarea.setAttribute('aria-label', Strings.promptLabel.get()); if (!Browser.isChromeOS) { @@ -449,6 +449,8 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal { this.textarea.setAttribute('autocapitalize', 'off'); this.textarea.setAttribute('spellcheck', 'false'); this.textarea.tabIndex = 0; + this._register(this.optionsService.onSpecificOptionChange('disableStdin', () => textarea.readOnly = this.optionsService.rawOptions.disableStdin)); + this.textarea.readOnly = this.optionsService.rawOptions.disableStdin; // Register the core browser service before the generic textarea handlers are registered so it // handles them first. Otherwise the renderers may use the wrong focus state. From 18c9eb196010ec082ad56adf7040ed2dca5fd391 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 20 Dec 2024 09:01:30 -0800 Subject: [PATCH 19/42] Remove .only --- test/playwright/SharedRendererTests.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/playwright/SharedRendererTests.ts b/test/playwright/SharedRendererTests.ts index 998d51a6..1d37b5c1 100644 --- a/test/playwright/SharedRendererTests.ts +++ b/test/playwright/SharedRendererTests.ts @@ -1256,7 +1256,7 @@ export function injectSharedRendererTests(ctx: ISharedRendererTestContext): void await ctx.value.proxy.focus(); await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [128, 0, 0, 255]); }); - test.only('#5241 cursorAccent with alpha should blend color with background color', async () => { + test('#5241 cursorAccent with alpha should blend color with background color', async () => { const theme: ITheme = { cursorAccent: '#FF000080' }; From fb2e96e1b0693809ea6221113506033b4184a41c Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 20 Dec 2024 10:21:06 -0800 Subject: [PATCH 20/42] Add test button for DECSCUSR Part of #3293 --- demo/client.ts | 44 ++++++++++++++++++++++++-------------------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/demo/client.ts b/demo/client.ts index 02b9f7be..ceb3fab1 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -1266,10 +1266,13 @@ function addVtButtons(): void { const element = document.createElement('button'); element.textContent = name; - writeCsi.split(''); - const prefix = writeCsi.length === 2 ? writeCsi[0] : ''; - const suffix = writeCsi[writeCsi.length - 1]; - element.addEventListener(`click`, () => term.write(csi(`${prefix}${inputs.map(e => e.value).join(';')}${suffix}`))); + const writeCsiSplit = writeCsi.split('|'); + const prefix = writeCsiSplit.length === 2 ? writeCsiSplit[0] : ''; + const suffix = writeCsiSplit[writeCsiSplit.length - 1]; + element.addEventListener(`click`, () => { + debugger; + term.write(csi(`${prefix}${inputs.map(e => e.value).join(';')}${suffix}`)); + }); const desc = document.createElement('span'); desc.textContent = description; @@ -1281,22 +1284,23 @@ function addVtButtons(): void { } const vtFragment = document.createDocumentFragment(); const buttonSpecs: { [key: string]: { label: string, description: string, paramCount?: number }} = { - A: { label: 'CUU ↑', description: 'Cursor Up Ps Times' }, - B: { label: 'CUD ↓', description: 'Cursor Down Ps Times' }, - C: { label: 'CUF →', description: 'Cursor Forward Ps Times' }, - D: { label: 'CUB ←', description: 'Cursor Backward Ps Times' }, - E: { label: 'CNL', description: 'Cursor Next Line Ps Times' }, - F: { label: 'CPL', description: 'Cursor Preceding Line Ps Times' }, - G: { label: 'CHA', description: 'Cursor Character Absolute' }, - H: { label: 'CUP', description: 'Cursor Position [row;column]', paramCount: 2 }, - I: { label: 'CHT', description: 'Cursor Forward Tabulation Ps tab stops' }, - J: { label: 'ED', description: 'Erase in Display' }, - '?J': { label: 'DECSED', description: 'Erase in Display' }, - K: { label: 'EL', description: 'Erase in Line' }, - '?K': { label: 'DECSEL', description: 'Erase in Line' }, - L: { label: 'IL', description: 'Insert Ps Line(s)' }, - M: { label: 'DL', description: 'Delete Ps Line(s)' }, - P: { label: 'DCH', description: 'Delete Ps Character(s)' } + A: { label: 'CUU ↑', description: 'Cursor Up Ps Times' }, + B: { label: 'CUD ↓', description: 'Cursor Down Ps Times' }, + C: { label: 'CUF →', description: 'Cursor Forward Ps Times' }, + D: { label: 'CUB ←', description: 'Cursor Backward Ps Times' }, + E: { label: 'CNL', description: 'Cursor Next Line Ps Times' }, + F: { label: 'CPL', description: 'Cursor Preceding Line Ps Times' }, + G: { label: 'CHA', description: 'Cursor Character Absolute' }, + H: { label: 'CUP', description: 'Cursor Position [row;column]', paramCount: 2 }, + I: { label: 'CHT', description: 'Cursor Forward Tabulation Ps tab stops' }, + J: { label: 'ED', description: 'Erase in Display' }, + '?|J': { label: 'DECSED', description: 'Erase in Display' }, + K: { label: 'EL', description: 'Erase in Line' }, + '?|K': { label: 'DECSEL', description: 'Erase in Line' }, + L: { label: 'IL', description: 'Insert Ps Line(s)' }, + M: { label: 'DL', description: 'Delete Ps Line(s)' }, + P: { label: 'DCH', description: 'Delete Ps Character(s)' }, + ' q': { label: 'DECSCUSR', description: 'Set Cursor Style', paramCount: 1 } }; for (const s of Object.keys(buttonSpecs)) { const spec = buttonSpecs[s]; From b579649a392d6e178cb08948ef888316b4b91e3a Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 20 Dec 2024 10:34:05 -0800 Subject: [PATCH 21/42] Revert to cursor options after DECSCUSR 0 Fixes #3293 --- addons/addon-webgl/src/WebglRenderer.ts | 11 ++++--- demo/client.ts | 5 +-- src/browser/renderer/dom/DomRenderer.ts | 7 ++-- src/common/InputHandler.ts | 44 +++++++++++++++---------- src/common/TestUtils.test.ts | 2 ++ src/common/Types.ts | 2 ++ src/common/services/CoreService.ts | 2 ++ 7 files changed, 43 insertions(+), 30 deletions(-) diff --git a/addons/addon-webgl/src/WebglRenderer.ts b/addons/addon-webgl/src/WebglRenderer.ts index 216b8404..6c270d12 100644 --- a/addons/addon-webgl/src/WebglRenderer.ts +++ b/addons/addon-webgl/src/WebglRenderer.ts @@ -354,7 +354,7 @@ export class WebglRenderer extends Disposable implements IRenderer { } private _updateCursorBlink(): void { - if (this._terminal.options.cursorBlink) { + if (this._coreService.decPrivateModes.cursorBlink ?? this._terminal.options.cursorBlink) { this._cursorBlinkStateManager.value = new CursorBlinkStateManager(() => { this._requestRedrawCursor(); }, this._coreBrowserService); @@ -387,6 +387,7 @@ export class WebglRenderer extends Disposable implements IRenderer { let j: number; start = clamp(start, terminal.rows - 1, 0); end = clamp(end, terminal.rows - 1, 0); + const cursorStyle = this._coreService.decPrivateModes.cursorStyle ?? terminal.options.cursorStyle ?? 'block'; const cursorY = this._terminal.buffer.active.baseY + this._terminal.buffer.active.cursorY; const viewportRelativeCursorY = cursorY - terminal.buffer.ydisp; @@ -450,8 +451,7 @@ export class WebglRenderer extends Disposable implements IRenderer { x: cursorX, y: viewportRelativeCursorY, width: cell.getWidth(), - style: this._coreBrowserService.isFocused ? - (terminal.options.cursorStyle || 'block') : terminal.options.cursorInactiveStyle, + style: this._coreBrowserService.isFocused ? cursorStyle : terminal.options.cursorInactiveStyle, cursorWidth: terminal.options.cursorWidth, dpr: this._devicePixelRatio }; @@ -459,9 +459,10 @@ export class WebglRenderer extends Disposable implements IRenderer { } if (x >= cursorX && x <= lastCursorX && ((this._coreBrowserService.isFocused && - (terminal.options.cursorStyle || 'block') === 'block') || + cursorStyle === 'block') || (this._coreBrowserService.isFocused === false && - terminal.options.cursorInactiveStyle === 'block'))) { + terminal.options.cursorInactiveStyle === 'block')) + ) { this._cellColorResolver.result.fg = Attributes.CM_RGB | (this._themeService.colors.cursorAccent.rgba >> 8 & Attributes.RGB_MASK); this._cellColorResolver.result.bg = diff --git a/demo/client.ts b/demo/client.ts index ceb3fab1..f7503210 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -1269,10 +1269,7 @@ function addVtButtons(): void { const writeCsiSplit = writeCsi.split('|'); const prefix = writeCsiSplit.length === 2 ? writeCsiSplit[0] : ''; const suffix = writeCsiSplit[writeCsiSplit.length - 1]; - element.addEventListener(`click`, () => { - debugger; - term.write(csi(`${prefix}${inputs.map(e => e.value).join(';')}${suffix}`)); - }); + element.addEventListener(`click`, () => term.write(csi(`${prefix}${inputs.map(e => e.value).join(';')}${suffix}`))); const desc = document.createElement('span'); desc.textContent = description; diff --git a/src/browser/renderer/dom/DomRenderer.ts b/src/browser/renderer/dom/DomRenderer.ts index f6fda22e..1de446c4 100644 --- a/src/browser/renderer/dom/DomRenderer.ts +++ b/src/browser/renderer/dom/DomRenderer.ts @@ -13,7 +13,7 @@ import { ICharSizeService, ICoreBrowserService, IThemeService } from 'browser/se import { ILinkifier2, ILinkifierEvent, ITerminal, ReadonlyColorSet } from 'browser/Types'; import { color } from 'common/Color'; import { Disposable, toDisposable } from 'vs/base/common/lifecycle'; -import { IBufferService, IInstantiationService, IOptionsService } from 'common/services/Services'; +import { IBufferService, ICoreService, IInstantiationService, IOptionsService } from 'common/services/Services'; import { Emitter } from 'vs/base/common/event'; @@ -59,6 +59,7 @@ export class DomRenderer extends Disposable implements IRenderer { @ICharSizeService private readonly _charSizeService: ICharSizeService, @IOptionsService private readonly _optionsService: IOptionsService, @IBufferService private readonly _bufferService: IBufferService, + @ICoreService private readonly _coreService: ICoreService, @ICoreBrowserService private readonly _coreBrowserService: ICoreBrowserService, @IThemeService private readonly _themeService: IThemeService ) { @@ -437,8 +438,8 @@ export class DomRenderer extends Disposable implements IRenderer { const buffer = this._bufferService.buffer; const cursorAbsoluteY = buffer.ybase + buffer.y; const cursorX = Math.min(buffer.x, this._bufferService.cols - 1); - const cursorBlink = this._optionsService.rawOptions.cursorBlink; - const cursorStyle = this._optionsService.rawOptions.cursorStyle; + const cursorBlink = this._coreService.decPrivateModes.cursorBlink ?? this._optionsService.rawOptions.cursorBlink; + const cursorStyle = this._coreService.decPrivateModes.cursorStyle ?? this._optionsService.rawOptions.cursorStyle; const cursorInactiveStyle = this._optionsService.rawOptions.cursorInactiveStyle; for (let y = start; y <= end; y++) { diff --git a/src/common/InputHandler.ts b/src/common/InputHandler.ts index b94d7855..e9b99138 100644 --- a/src/common/InputHandler.ts +++ b/src/common/InputHandler.ts @@ -2714,7 +2714,7 @@ export class InputHandler extends Disposable implements IInputHandler { /** * CSI Ps SP q Set cursor style (DECSCUSR, VT520). - * Ps = 0 -> blinking block. + * Ps = 0 -> reset to option. * Ps = 1 -> blinking block (default). * Ps = 2 -> steady block. * Ps = 3 -> blinking underline. @@ -2724,7 +2724,8 @@ export class InputHandler extends Disposable implements IInputHandler { * * @vt: #Y CSI DECSCUSR "Set Cursor Style" "CSI Ps SP q" "Set cursor style." * Supported cursor styles: - * - empty, 0 or 1: steady block + * - empty, 0: reset to option + * - 1: steady block * - 2: blink block * - 3: steady underline * - 4: blink underline @@ -2732,23 +2733,30 @@ export class InputHandler extends Disposable implements IInputHandler { * - 6: blink bar */ public setCursorStyle(params: IParams): boolean { - const param = params.params[0] || 1; - switch (param) { - case 1: - case 2: - this._optionsService.options.cursorStyle = 'block'; - break; - case 3: - case 4: - this._optionsService.options.cursorStyle = 'underline'; - break; - case 5: - case 6: - this._optionsService.options.cursorStyle = 'bar'; - break; + const param = params.params[0] ?? 1; + if (param === 0) { + this._coreService.decPrivateModes.cursorStyle = undefined; + this._coreService.decPrivateModes.cursorBlink = undefined; + } else { + switch (param) { + case 0: + break; + case 1: + case 2: + this._coreService.decPrivateModes.cursorStyle = 'block'; + break; + case 3: + case 4: + this._coreService.decPrivateModes.cursorStyle = 'underline'; + break; + case 5: + case 6: + this._coreService.decPrivateModes.cursorStyle = 'bar'; + break; + } + const isBlinking = param % 2 === 1; + this._coreService.decPrivateModes.cursorBlink = isBlinking; } - const isBlinking = param % 2 === 1; - this._optionsService.options.cursorBlink = isBlinking; return true; } diff --git a/src/common/TestUtils.test.ts b/src/common/TestUtils.test.ts index 8c9634db..127e1f24 100644 --- a/src/common/TestUtils.test.ts +++ b/src/common/TestUtils.test.ts @@ -89,6 +89,8 @@ export class MockCoreService implements ICoreService { applicationCursorKeys: false, applicationKeypad: false, bracketedPasteMode: false, + cursorBlink: undefined, + cursorStyle: undefined, origin: false, reverseWraparound: false, sendFocus: false, diff --git a/src/common/Types.ts b/src/common/Types.ts index 289aa1f6..c254d330 100644 --- a/src/common/Types.ts +++ b/src/common/Types.ts @@ -268,6 +268,8 @@ export interface IDecPrivateModes { applicationCursorKeys: boolean; applicationKeypad: boolean; bracketedPasteMode: boolean; + cursorBlink: boolean | undefined; + cursorStyle: CursorStyle | undefined; origin: boolean; reverseWraparound: boolean; sendFocus: boolean; diff --git a/src/common/services/CoreService.ts b/src/common/services/CoreService.ts index 9c41fc1a..5bee6356 100644 --- a/src/common/services/CoreService.ts +++ b/src/common/services/CoreService.ts @@ -17,6 +17,8 @@ const DEFAULT_DEC_PRIVATE_MODES: IDecPrivateModes = Object.freeze({ applicationCursorKeys: false, applicationKeypad: false, bracketedPasteMode: false, + cursorBlink: undefined, + cursorStyle: undefined, origin: false, reverseWraparound: false, sendFocus: false, From 82598e94a9cf766172c24b683a49020e5b749fc0 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 20 Dec 2024 10:36:32 -0800 Subject: [PATCH 22/42] Fix default param DECSCUSR handling --- src/common/InputHandler.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/common/InputHandler.ts b/src/common/InputHandler.ts index e9b99138..de8664e9 100644 --- a/src/common/InputHandler.ts +++ b/src/common/InputHandler.ts @@ -2733,14 +2733,12 @@ export class InputHandler extends Disposable implements IInputHandler { * - 6: blink bar */ public setCursorStyle(params: IParams): boolean { - const param = params.params[0] ?? 1; + const param = params.length === 0 ? 1 : params.params[0]; if (param === 0) { this._coreService.decPrivateModes.cursorStyle = undefined; this._coreService.decPrivateModes.cursorBlink = undefined; } else { switch (param) { - case 0: - break; case 1: case 2: this._coreService.decPrivateModes.cursorStyle = 'block'; From f824083324052ae0387008da999a1f8c3df404ae Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 20 Dec 2024 10:37:48 -0800 Subject: [PATCH 23/42] Correct @vt docs --- src/common/InputHandler.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/common/InputHandler.ts b/src/common/InputHandler.ts index de8664e9..8308e6de 100644 --- a/src/common/InputHandler.ts +++ b/src/common/InputHandler.ts @@ -2724,13 +2724,13 @@ export class InputHandler extends Disposable implements IInputHandler { * * @vt: #Y CSI DECSCUSR "Set Cursor Style" "CSI Ps SP q" "Set cursor style." * Supported cursor styles: - * - empty, 0: reset to option - * - 1: steady block - * - 2: blink block - * - 3: steady underline - * - 4: blink underline - * - 5: steady bar - * - 6: blink bar + * - 0: reset to option + * - empty, 1: blinking block + * - 2: steady block + * - 3: blinking underline + * - 4: steady underline + * - 5: blinking bar + * - 6: steady bar */ public setCursorStyle(params: IParams): boolean { const param = params.length === 0 ? 1 : params.params[0]; From d44f7818c4bfbcdeb006f4c16174462795052047 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 20 Dec 2024 10:43:38 -0800 Subject: [PATCH 24/42] Fix test to assert new behavior --- src/common/InputHandler.test.ts | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/common/InputHandler.test.ts b/src/common/InputHandler.test.ts index baae735c..1280417b 100644 --- a/src/common/InputHandler.test.ts +++ b/src/common/InputHandler.test.ts @@ -202,38 +202,38 @@ describe('InputHandler', () => { describe('setCursorStyle', () => { it('should call Terminal.setOption with correct params', () => { inputHandler.setCursorStyle(Params.fromArray([0])); - assert.equal(optionsService.options['cursorStyle'], 'block'); - assert.equal(optionsService.options['cursorBlink'], true); + assert.equal(coreService.decPrivateModes.cursorStyle, undefined); + assert.equal(coreService.decPrivateModes.cursorBlink, undefined); optionsService.options = clone(DEFAULT_OPTIONS); inputHandler.setCursorStyle(Params.fromArray([1])); - assert.equal(optionsService.options['cursorStyle'], 'block'); - assert.equal(optionsService.options['cursorBlink'], true); + assert.equal(coreService.decPrivateModes.cursorStyle, 'block'); + assert.equal(coreService.decPrivateModes.cursorBlink, true); optionsService.options = clone(DEFAULT_OPTIONS); inputHandler.setCursorStyle(Params.fromArray([2])); - assert.equal(optionsService.options['cursorStyle'], 'block'); - assert.equal(optionsService.options['cursorBlink'], false); + assert.equal(coreService.decPrivateModes.cursorStyle, 'block'); + assert.equal(coreService.decPrivateModes.cursorBlink, false); optionsService.options = clone(DEFAULT_OPTIONS); inputHandler.setCursorStyle(Params.fromArray([3])); - assert.equal(optionsService.options['cursorStyle'], 'underline'); - assert.equal(optionsService.options['cursorBlink'], true); + assert.equal(coreService.decPrivateModes.cursorStyle, 'underline'); + assert.equal(coreService.decPrivateModes.cursorBlink, true); optionsService.options = clone(DEFAULT_OPTIONS); inputHandler.setCursorStyle(Params.fromArray([4])); - assert.equal(optionsService.options['cursorStyle'], 'underline'); - assert.equal(optionsService.options['cursorBlink'], false); + assert.equal(coreService.decPrivateModes.cursorStyle, 'underline'); + assert.equal(coreService.decPrivateModes.cursorBlink, false); optionsService.options = clone(DEFAULT_OPTIONS); inputHandler.setCursorStyle(Params.fromArray([5])); - assert.equal(optionsService.options['cursorStyle'], 'bar'); - assert.equal(optionsService.options['cursorBlink'], true); + assert.equal(coreService.decPrivateModes.cursorStyle, 'bar'); + assert.equal(coreService.decPrivateModes.cursorBlink, true); optionsService.options = clone(DEFAULT_OPTIONS); inputHandler.setCursorStyle(Params.fromArray([6])); - assert.equal(optionsService.options['cursorStyle'], 'bar'); - assert.equal(optionsService.options['cursorBlink'], false); + assert.equal(coreService.decPrivateModes.cursorStyle, 'bar'); + assert.equal(coreService.decPrivateModes.cursorBlink, false); }); }); describe('setMode', () => { From ef67b42fbd80a584f3897c5614cda8bf13ffadc9 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 6 Jan 2025 09:59:00 -0800 Subject: [PATCH 25/42] Demo test button for common ligatures Part of #5231 --- demo/client.ts | 13 +++++++++++++ demo/index.html | 3 +++ 2 files changed, 16 insertions(+) diff --git a/demo/client.ts b/demo/client.ts index f7503210..7a1ac72b 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -240,6 +240,7 @@ if (document.location.pathname === '/test') { document.getElementById('add-decoration').addEventListener('click', addDecoration); document.getElementById('add-overview-ruler').addEventListener('click', addOverviewRuler); document.getElementById('decoration-stress-test').addEventListener('click', decorationStressTest); + document.getElementById('ligatures-test').addEventListener('click', ligaturesTest); document.getElementById('weblinks-test').addEventListener('click', testWeblinks); document.getElementById('bce').addEventListener('click', coloredErase); addVtButtons(); @@ -1307,6 +1308,18 @@ function addVtButtons(): void { document.querySelector('#vt-container').appendChild(vtFragment); } +function ligaturesTest(): void { + term.write([ + '', + '-<< -< -<- <-- <--- <<- <- -> ->> --> ---> ->- >- >>-', + '=<< =< =<= <== <=== <<= <= => =>> ==> ===> =>= >= >>=', + '<-> <--> <---> <----> <=> <==> <===> <====> :: ::: __', + '<~~ /> ~~> == != /= ~= <> === !== !=== =/= =!=', + '<: := *= *+ <* <*> *> <| <|> |> <. <.> .> +* =* =: :>', + '(* *) /* */ [| |] {| |} ++ +++ \/ /\ |- -| <---> <----> <=> <==> <===> <====> :: ::: __', '<~~ /> ~~> == != /= ~= <> === !== !=== =/= =!=', '<: := *= *+ <* <*> *> <| <|> |> <. <.> .> +* =* =: :>', - '(* *) /* */ [| |] {| |} ++ +++ \/ /\ |- -| ', '--->', '<==', '<===', '<<=', '<=', '=>', '=>>', '==>', '===>', '>=', '>>=', - '<->', '<-->', '<--->', '<---->', '<=>', '<==>', '<===>', '<====>', '-------->', - '<~~', '<~', '~>', '~~>', '::', ':::', '==', '!=', '===', '!==', - ':=', ':-', ':+', '<*', '<*>', '*>', '<|', '<|>', '|>', '+:', '-:', '=:', ':>', - '++', '+++', '', '<--->', '<---->', '<=>', '<==>', '<===>', '<====>', '::', ':::', + '<~~', '', '/>', '~~>', '==', '!=', '/=', '~=', '<>', '===', '!==', '!===', + '<:', ':=', '*=', '*+', '<*', '<*>', '*>', '<|', '<|>', '|>', '+*', '=*', '=:', ':>', + '/*', '*/', '+++', ' ---> * <== <=== <<= <= => =>> ==> ===> >= >>= - * <-> <--> <---> <----> <=> <==> <===> <====> --------> - * <~~ <~ ~> ~~> :: ::: == != === !== - * := :- :+ <* <*> *> <| <|> |> +: -: =: :> - * ++ +++ <---> <----> <=> <==> <===> <====> :: ::: + * <~~ /> ~~> == != /= ~= <> === !== !=== + * <: := *= *+ <* <*> *> <| <|> |> +* =* =: :> + * /* +++