Compare commits

..
Author SHA1 Message Date
Jörg Breitbart 2f49f321c1 remove layout stuff 2026-05-18 21:15:29 +02:00
Jörg Breitbart c0007301c1 multiple changes:
- layout detector
- browser helper to create layout maps
- DE, ES, GR, RU, US maps
2026-05-16 18:08:10 +02:00
Jörg Breitbart 0d8996395c fix AZERTY, layout map generator 2026-05-14 21:05:39 +02:00
Jörg Breitbart e24b043ab2 add DE_QWERTZ, FR_AZERTY and US_QWERTY layouts 2026-05-14 19:27:50 +02:00
Jörg Breitbart 6f15b0f916 modifier capslock, numlock & shift 2026-05-14 00:43:45 +02:00
Jörg Breitbart 33eb1dd45e add modifier keys 2026-05-13 20:16:33 +02:00
Jörg Breitbart 0ca8107055 german qwertz mode 1 no modifier mapping 2026-05-13 17:30:15 +02:00
Jörg Breitbart d844070855 draft of keytester 2026-05-12 15:35:54 +02:00
266 changed files with 3793 additions and 2501 deletions
-4
View File
@@ -22,10 +22,6 @@ build/
yarn.lock
test-results/
# Tracing/debug files
.cursor/nal-trace/
.cursor/*.log
# Keep bundled code out of Git
dist/
demo/dist/
-1
View File
@@ -253,7 +253,6 @@ Xterm.js is used in many world-class applications to provide great terminal expe
- [**SSH Connection Manager**](https://github.com/Amtrend/ssh-manager): Modern web-based SSH terminal and SFTP manager with AES-256 encryption, PWA support, and session management.
- [**WooTTY**](https://github.com/icoretech/wootty): Flawless browser terminal for real operators.
- [**Orca**](https://github.com/stablyai/orca): Agentic development environment for 100x builders.
- [**4bit**](https://ciembor.github.io/4bit): Terminal Color Scheme Designer.
- [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.
+7 -3
View File
@@ -13,11 +13,15 @@
"strict": true,
"types": [
"../../../node_modules/@types/mocha"
]
],
"paths": {
"@xterm/addon-attach": [
"../typings/addon-attach.d.ts"
]
}
},
"include": [
"./**/*",
"../../../typings/xterm.d.ts",
"../typings/addon-attach.d.ts"
"../../../typings/xterm.d.ts"
]
}
+12 -1
View File
@@ -3,12 +3,23 @@
"module": "commonjs",
"target": "ESNext",
"lib": [
"es2021"
"es2021",
],
"rootDir": ".",
"outDir": "../out-test",
"sourceMap": true,
"removeComments": true,
"paths": {
"common/*": [
"../../../src/common/*"
],
"browser/*": [
"../../../src/browser/*"
],
"*": [
"./*"
]
},
"strict": true,
"types": [
"../../../node_modules/@types/node"
+2 -6
View File
@@ -2,11 +2,7 @@
"files": [],
"include": [],
"references": [
{
"path": "./src"
},
{
"path": "./test"
}
{ "path": "./src" },
{ "path": "./test" }
]
}
+10 -3
View File
@@ -13,12 +13,19 @@
"strict": true,
"types": [
"../../../node_modules/@types/mocha"
]
],
"paths": {
"browser/*": [
"../../../src/browser/*"
],
"@xterm/addon-clipboard": [
"../typings/addon-clipboard.d.ts"
]
}
},
"include": [
"./**/*",
"../../../typings/xterm.d.ts",
"../typings/addon-clipboard.d.ts"
"../../../typings/xterm.d.ts"
],
"references": [
{
+12 -1
View File
@@ -3,12 +3,23 @@
"module": "commonjs",
"target": "ESNext",
"lib": [
"es2021"
"es2021",
],
"rootDir": ".",
"outDir": "../out-test",
"sourceMap": true,
"removeComments": true,
"paths": {
"common/*": [
"../../../src/common/*"
],
"browser/*": [
"../../../src/browser/*"
],
"*": [
"./*"
]
},
"strict": true,
"types": [
"../../../node_modules/@types/node"
+2 -6
View File
@@ -2,11 +2,7 @@
"files": [],
"include": [],
"references": [
{
"path": "./src"
},
{
"path": "./test"
}
{ "path": "./src" },
{ "path": "./test" }
]
}
+1 -1
View File
@@ -5,7 +5,7 @@
import type { Terminal, ITerminalAddon, IRenderDimensions } from '@xterm/xterm';
import type { FitAddon as IFitApi } from '@xterm/addon-fit';
import { ViewportConstants } from '../../../src/browser/shared/Constants';
import { ViewportConstants } from 'browser/shared/Constants';
interface ITerminalDimensions {
/**
+10 -3
View File
@@ -13,12 +13,19 @@
"strict": true,
"types": [
"../../../node_modules/@types/mocha"
]
],
"paths": {
"browser/*": [
"../../../src/browser/*"
],
"@xterm/addon-fit": [
"../typings/addon-fit.d.ts"
]
}
},
"include": [
"./**/*",
"../../../typings/xterm.d.ts",
"../typings/addon-fit.d.ts"
"../../../typings/xterm.d.ts"
],
"references": [
{
+12 -1
View File
@@ -3,12 +3,23 @@
"module": "commonjs",
"target": "ESNext",
"lib": [
"es2021"
"es2021",
],
"rootDir": ".",
"outDir": "../out-test",
"sourceMap": true,
"removeComments": true,
"paths": {
"common/*": [
"../../../src/common/*"
],
"browser/*": [
"../../../src/browser/*"
],
"*": [
"./*"
]
},
"strict": true,
"types": [
"../../../node_modules/@types/node"
+2 -6
View File
@@ -2,11 +2,7 @@
"files": [],
"include": [],
"references": [
{
"path": "./src"
},
{
"path": "./test"
}
{ "path": "./src" },
{ "path": "./test" }
]
}
+1 -1
View File
@@ -5,7 +5,7 @@
import type { ITerminalAddon, IDisposable } from '@xterm/xterm';
import type { ImageAddon as IImageApi } from '@xterm/addon-image';
import { Emitter, type IEvent } from '../../../src/common/Event';
import { Emitter, type IEvent } from 'common/Event';
import { IIPHandler } from './IIPHandler';
import { ImageRenderer } from './ImageRenderer';
import { ImageStorage, CELL_SIZE_DEFAULT } from './ImageStorage';
+1 -1
View File
@@ -6,7 +6,7 @@
import { toRGBA8888 } from 'sixel/lib/Colors';
import { IDisposable } from '@xterm/xterm';
import { ICellSize, ImageLayer, ITerminalExt, IImageSpec, IRenderDimensions, IRenderService } from './Types';
import { Disposable, MutableDisposable, toDisposable } from '../../../src/common/Lifecycle';
import { Disposable, MutableDisposable, toDisposable } from 'common/Lifecycle';
const enum Constants {
PLACEHOLDER_LENGTH = 4096,
+1 -1
View File
@@ -91,7 +91,7 @@ export class SixelHandler implements IDcsHandler, IResetHandler {
const height = this._dec.height;
// partial fix for https://github.com/jerch/xterm-addon-image/issues/37
if (!width || !height) {
if (!width || ! height) {
if (height) {
this._storage.advanceCursor(height);
}
+7 -7
View File
@@ -6,13 +6,13 @@
import { IDisposable, IMarker, Terminal } from '@xterm/xterm';
// private imports from base repo we build against
import { Attributes, BgFlags, Content, ExtFlags, UnderlineStyle } from '../../../src/common/buffer/Constants';
import type { AttributeData } from '../../../src/common/buffer/AttributeData';
import type { IParams, IDcsHandler, IOscHandler, IApcHandler, IEscapeSequenceParser } from '../../../src/common/parser/Types';
import type { IBufferLine, IExtendedAttrs, IInputHandler } from '../../../src/common/Types';
import type { ITerminal, ReadonlyColorSet } from '../../../src/browser/Types';
import type { IRenderDimensions } from '../../../src/browser/renderer/shared/Types';
import type { ICoreBrowserService, IRenderService, IThemeService } from '../../../src/browser/services/Services';
import { Attributes, BgFlags, Content, ExtFlags, UnderlineStyle } from 'common/buffer/Constants';
import type { AttributeData } from 'common/buffer/AttributeData';
import type { IParams, IDcsHandler, IOscHandler, IApcHandler, IEscapeSequenceParser } from 'common/parser/Types';
import type { IBufferLine, IExtendedAttrs, IInputHandler } from 'common/Types';
import type { ITerminal, ReadonlyColorSet } from 'browser/Types';
import type { IRenderDimensions } from 'browser/renderer/shared/Types';
import type { ICoreBrowserService, IRenderService, IThemeService } from 'browser/services/Services';
export const enum Cell {
CONTENT = 0, // codepoint and wcwidth information (enum Content)
@@ -533,8 +533,8 @@ export class KittyGraphicsHandler implements IApcHandler, IResetHandler, IDispos
private _sendResponse(id: number, message: string, quiet: number, placementId?: number): void {
const isOk = message === 'OK';
if (isOk && quiet >= 1) return;
if (!isOk && quiet >= 2) return;
if (isOk && quiet === 1) return;
if (!isOk && quiet === 2) return;
const pPart = placementId ? `,p=${placementId}` : '';
const response = `\x1b_Gi=${id}${pPart};${message}\x1b\\`;
+10 -9
View File
@@ -10,19 +10,20 @@
"preserveWatchOutput": true,
"types": [
"../../../node_modules/@types/mocha"
]
],
"paths": {
"browser/*": [ "../../../src/browser/*" ],
"common/*": [ "../../../src/common/*" ],
"@xterm/addon-image": [ "../typings/addon-image.d.ts" ],
"*": [ "./*" ]
}
},
"include": [
"./**/*",
"../../../typings/xterm.d.ts",
"../typings/addon-image.d.ts"
"../../../typings/xterm.d.ts"
],
"references": [
{
"path": "../../../src/browser"
},
{
"path": "../../../src/common"
}
{ "path": "../../../src/browser" },
{ "path": "../../../src/common" }
]
}
@@ -617,18 +617,6 @@ test.describe('Kitty Graphics Protocol', () => {
strictEqual(await ctx.page.evaluate('window.kittyGotResponse'), false);
});
test('suppresses OK response when q=2', async () => {
await ctx.page.evaluate(() => {
(window as any).kittyGotResponse = false;
(window as any).term.onData(() => { (window as any).kittyGotResponse = true; });
});
await ctx.proxy.write(`\x1b_Gi=81,a=q,q=2,f=100;${KITTY_BLACK_1X1_BASE64}\x1b\\`);
await timeout(100);
strictEqual(await ctx.page.evaluate('window.kittyGotResponse'), false);
});
test('suppresses error response when q=2', async () => {
await ctx.page.evaluate(() => {
(window as any).kittyGotResponse = false;
@@ -809,30 +797,6 @@ test.describe('Kitty Graphics Protocol', () => {
strictEqual(await ctx.page.evaluate('window.kittyGotResponse'), false);
});
test('a=t OK suppressed by q=2', async () => {
await ctx.page.evaluate(() => {
(window as any).kittyGotResponse = false;
(window as any).term.onData(() => { (window as any).kittyGotResponse = true; });
});
await ctx.proxy.write(`\x1b_Gi=181,a=t,q=2,f=100;${KITTY_BLACK_1X1_BASE64}\x1b\\`);
await timeout(100);
strictEqual(await ctx.page.evaluate('window.kittyGotResponse'), false);
});
test('a=T OK suppressed by q=2', async () => {
await ctx.page.evaluate(() => {
(window as any).kittyGotResponse = false;
(window as any).term.onData(() => { (window as any).kittyGotResponse = true; });
});
await ctx.proxy.write(`\x1b_Gi=191,a=T,q=2,f=100;${KITTY_BLACK_1X1_BASE64}\x1b\\`);
await timeout(100);
strictEqual(await ctx.page.evaluate('window.kittyGotResponse'), false);
});
});
test.describe('Transmission medium rejection', () => {
@@ -1182,22 +1146,6 @@ test.describe('Kitty Graphics Protocol', () => {
strictEqual(await getImageStorageLength(), 1);
});
test('OK response suppressed by q=2', async () => {
await ctx.proxy.write(`\x1b_Ga=t,f=100,i=221;${KITTY_BLACK_1X1_BASE64}\x1b\\`);
await timeout(100);
await ctx.page.evaluate(() => {
(window as any).kittyGotResponse = false;
(window as any).term.onData(() => { (window as any).kittyGotResponse = true; });
});
await ctx.proxy.write(`\x1b_Ga=p,i=221,q=2\x1b\\`);
await timeout(100);
strictEqual(await ctx.page.evaluate('window.kittyGotResponse'), false);
strictEqual(await getImageStorageLength(), 1);
});
test('ENOENT error suppressed by q=2', async () => {
await ctx.page.evaluate(() => {
(window as any).kittyGotResponse = false;
+12 -1
View File
@@ -3,12 +3,23 @@
"module": "commonjs",
"target": "ESNext",
"lib": [
"es2021"
"es2021",
],
"rootDir": ".",
"outDir": "../out-test",
"sourceMap": true,
"removeComments": true,
"paths": {
"common/*": [
"../../../src/common/*"
],
"browser/*": [
"../../../src/browser/*"
],
"*": [
"./*"
]
},
"strict": true,
"types": [
"../../../node_modules/@types/node"
+2 -6
View File
@@ -2,11 +2,7 @@
"files": [],
"include": [],
"references": [
{
"path": "./src"
},
{
"path": "./test"
}
{ "path": "./src" },
{ "path": "./test" }
]
}
+2 -2
View File
@@ -20,7 +20,7 @@
"package": "webpack",
"pretest": "npm run build",
"test": "nyc mocha out/**/*.test.js",
"prepublishOnly": "npm run package"
"prepublish": "npm run package"
},
"keywords": [
"font",
@@ -37,7 +37,7 @@
"devDependencies": {
"@types/lru-cache": "^5.1.0",
"@types/opentype.js": "^0.7.0",
"axios": "^1.15.2",
"axios": "^1.15.0",
"font-finder": "^1.1.0",
"mkdirp": "0.5.5",
"yauzl": "^3.2.1"
+8 -3
View File
@@ -10,12 +10,17 @@
"preserveWatchOutput": true,
"types": [
"../../../node_modules/@types/mocha",
// HACK: src shouldn't use node types but it's needed for index.test.ts
"../../../node_modules/@types/node"
]
],
"paths": {
"@xterm/addon-ligatures" : [
"../typings/addon-ligatures.d.ts"
]
}
},
"include": [
"./**/*",
"../../../typings/xterm.d.ts",
"../typings/addon-ligatures.d.ts"
"../../../typings/xterm.d.ts"
]
}
@@ -10,6 +10,11 @@
"sourceMap": true,
"removeComments": true,
"strict": true,
"paths": {
"*": [
"./*"
]
},
"types": [
"../../../node_modules/@types/mocha",
"../../../node_modules/@types/node"
+2 -6
View File
@@ -2,11 +2,7 @@
"files": [],
"include": [],
"references": [
{
"path": "./src"
},
{
"path": "./test"
}
{ "path": "./src" },
{ "path": "./test" }
]
}
+1 -1
View File
@@ -5,7 +5,7 @@
import type { Terminal, ITerminalAddon, IDisposable } from '@xterm/xterm';
import type { ProgressAddon as IProgressApi, IProgressState } from '@xterm/addon-progress';
import type { Emitter, IEvent } from '../../../src/common/Event';
import type { Emitter, IEvent } from 'common/Event';
const enum ProgressType {
+13 -3
View File
@@ -13,12 +13,22 @@
"strict": true,
"types": [
"../../../node_modules/@types/mocha"
]
],
"paths": {
"common/*": [
"../../../src/common/*"
],
"browser/*": [
"../../../src/browser/*"
],
"@xterm/addon-progress": [
"../typings/addon-progress.d.ts"
]
}
},
"include": [
"./**/*",
"../../../typings/xterm.d.ts",
"../typings/addon-progress.d.ts"
"../../../typings/xterm.d.ts"
],
"references": [
{
+12 -1
View File
@@ -3,12 +3,23 @@
"module": "commonjs",
"target": "ESNext",
"lib": [
"es2021"
"es2021",
],
"rootDir": ".",
"outDir": "../out-test",
"sourceMap": true,
"removeComments": true,
"paths": {
"common/*": [
"../../../src/common/*"
],
"browser/*": [
"../../../src/browser/*"
],
"*": [
"./*"
]
},
"strict": true,
"types": [
"../../../node_modules/@types/node"
+2 -6
View File
@@ -2,11 +2,7 @@
"files": [],
"include": [],
"references": [
{
"path": "./src"
},
{
"path": "./test"
}
{ "path": "./src" },
{ "path": "./test" }
]
}
+1 -1
View File
@@ -5,7 +5,7 @@
import type { Terminal, IDisposable, IDecoration } from '@xterm/xterm';
import type { ISearchDecorationOptions } from '@xterm/addon-search';
import { dispose, Disposable, toDisposable } from '../../../src/common/Lifecycle';
import { dispose, Disposable, toDisposable } from 'common/Lifecycle';
import type { ISearchResult } from './SearchEngine';
/**
+3 -3
View File
@@ -5,9 +5,9 @@
import type { Terminal, IDisposable, ITerminalAddon } from '@xterm/xterm';
import type { SearchAddon as ISearchApi, ISearchOptions, ISearchAddonOptions, ISearchResultChangeEvent, ISearchDecorationOptions } from '@xterm/addon-search';
import { Emitter, type IEvent } from '../../../src/common/Event';
import { Disposable, MutableDisposable, toDisposable } from '../../../src/common/Lifecycle';
import { disposableTimeout } from '../../../src/common/Async';
import { Emitter, type IEvent } from 'common/Event';
import { Disposable, MutableDisposable, toDisposable } from 'common/Lifecycle';
import { disposableTimeout } from 'common/Async';
import { SearchLineCache } from './SearchLineCache';
import { SearchState } from './SearchState';
import { SearchEngine, type ISearchResult } from './SearchEngine';
+2 -2
View File
@@ -5,9 +5,9 @@
import { assert } from 'chai';
import { SearchEngine } from './SearchEngine';
import { SearchLineCache } from './SearchLineCache';
import { Terminal } from '../../../src/browser/public/Terminal';
import { Terminal } from 'browser/public/Terminal';
import type { ISearchOptions } from '@xterm/addon-search';
import { DisposableStore } from '../../../src/common/Lifecycle';
import { DisposableStore } from 'common/Lifecycle';
function writeP(terminal: Terminal, data: string): Promise<void> {
return new Promise(r => terminal.write(data, r));
@@ -4,8 +4,8 @@
*/
import { assert } from 'chai';
import { SearchLineCache, LineCacheEntry } from './SearchLineCache';
import { Terminal } from '../../../src/browser/public/Terminal';
import { timeout } from '../../../src/common/Async';
import { Terminal } from 'browser/public/Terminal';
import { timeout } from 'common/Async';
function writeP(terminal: Terminal, data: string): Promise<void> {
return new Promise(r => terminal.write(data, r));
+2 -2
View File
@@ -4,8 +4,8 @@
*/
import type { Terminal } from '@xterm/xterm';
import { combinedDisposable, Disposable, MutableDisposable, toDisposable } from '../../../src/common/Lifecycle';
import { disposableTimeout } from '../../../src/common/Async';
import { combinedDisposable, Disposable, MutableDisposable, toDisposable } from 'common/Lifecycle';
import { disposableTimeout } from 'common/Async';
export type LineCacheEntry = [
/**
@@ -5,8 +5,8 @@
import type { ISearchResultChangeEvent } from '@xterm/addon-search';
import type { IDisposable } from '@xterm/xterm';
import { Emitter, type IEvent } from '../../../src/common/Event';
import { Disposable } from '../../../src/common/Lifecycle';
import { Emitter, type IEvent } from 'common/Event';
import { Disposable } from 'common/Lifecycle';
import type { ISearchResult } from './SearchEngine';
/**
+14 -4
View File
@@ -4,7 +4,7 @@
"target": "es2021",
"lib": [
"dom",
"es2021"
"es2021",
],
"rootDir": ".",
"outDir": "../out",
@@ -13,12 +13,22 @@
"strict": true,
"types": [
"../../../node_modules/@types/mocha"
]
],
"paths": {
"common/*": [
"../../../src/common/*"
],
"browser/*": [
"../../../src/browser/*"
],
"@xterm/addon-search" : [
"../typings/addon-search.d.ts"
]
}
},
"include": [
"./**/*",
"../../../typings/xterm.d.ts",
"../typings/addon-search.d.ts"
"../../../typings/xterm.d.ts"
],
"references": [
{
+12 -1
View File
@@ -3,12 +3,23 @@
"module": "commonjs",
"target": "ESNext",
"lib": [
"es2021"
"es2021",
],
"rootDir": ".",
"outDir": "../out-test",
"sourceMap": true,
"removeComments": true,
"paths": {
"common/*": [
"../../../src/common/*"
],
"browser/*": [
"../../../src/browser/*"
],
"*": [
"./*"
]
},
"strict": true,
"types": [
"../../../node_modules/@types/node"
+2 -6
View File
@@ -2,11 +2,7 @@
"files": [],
"include": [],
"references": [
{
"path": "./src"
},
{
"path": "./test"
}
{ "path": "./src" },
{ "path": "./test" }
]
}
@@ -6,9 +6,9 @@
import { perfContext, before, ThroughputRuntimeCase } from 'xterm-benchmark';
import { spawn } from 'node-pty';
import { Utf8ToUtf32, stringFromCodePoint } from '../../../src/common/input/TextDecoder';
import { Terminal } from '../../../src/browser/public/Terminal';
import { SerializeAddon } from '../src/SerializeAddon';
import { Utf8ToUtf32, stringFromCodePoint } from 'common/input/TextDecoder';
import { Terminal } from 'browser/public/Terminal';
import { SerializeAddon } from 'SerializeAddon';
class TestTerminal extends Terminal {
public writeSync(data: string): void {
+15 -19
View File
@@ -6,28 +6,24 @@
],
"rootDir": "..",
"outDir": "../out-benchmark",
"types": [
"../../../node_modules/@types/node"
],
"types": ["../../../node_modules/@types/node"],
"moduleResolution": "node",
"strict": false,
"target": "es2021",
"module": "commonjs"
},
"include": [
"../**/*",
"../../../typings/xterm.d.ts"
],
"exclude": [
"../../../**/*test.ts",
"../../**/*api.ts"
],
"references": [
{
"path": "../../../src/common"
},
{
"path": "../../../src/browser"
"module": "commonjs",
"paths": {
"common/*": ["../../../src/common/*"],
"browser/*": ["../../../src/browser/*"],
"SerializeAddon": ["../src/SerializeAddon"],
"@xterm/addon-serialize": [
"../typings/addon-serialize.d.ts"
]
}
},
"include": ["../**/*", "../../../typings/xterm.d.ts"],
"exclude": ["../../../**/*test.ts", "../../**/*api.ts"],
"references": [
{ "path": "../../../src/common" },
{ "path": "../../../src/browser" }
]
}
@@ -6,10 +6,10 @@
import jsdom = require('jsdom');
import { assert } from 'chai';
import { SerializeAddon } from './SerializeAddon';
import { Terminal } from '../../../src/browser/public/Terminal';
import { SelectionModel } from '../../../src/browser/selection/SelectionModel';
import { IBufferService } from '../../../src/common/services/Services';
import { ThemeService } from '../../../src/browser/services/ThemeService';
import { Terminal } from 'browser/public/Terminal';
import { SelectionModel } from 'browser/selection/SelectionModel';
import { IBufferService } from 'common/services/Services';
import { ThemeService } from 'browser/services/ThemeService';
function sgr(...seq: string[]): string {
return `\x1b[${seq.join(';')}m`;
+3 -3
View File
@@ -7,9 +7,9 @@
import type { IBuffer, IBufferCell, IBufferRange, ITerminalAddon, Terminal } from '@xterm/xterm';
import type { IHTMLSerializeOptions, SerializeAddon as ISerializeApi, ISerializeOptions, ISerializeRange } from '@xterm/addon-serialize';
import { IAttributeData, IColor } from '../../../src/common/Types';
import { DEFAULT_ANSI_COLORS } from '../../../src/browser/Types';
import { UnderlineStyle } from '../../../src/common/buffer/Constants';
import { IAttributeData, IColor } from 'common/Types';
import { DEFAULT_ANSI_COLORS } from 'browser/Types';
import { UnderlineStyle } from 'common/buffer/Constants';
function constrain(value: number, low: number, high: number): number {
return Math.max(low, Math.min(value, high));
+12 -2
View File
@@ -10,6 +10,17 @@
"outDir": "../out",
"sourceMap": true,
"removeComments": true,
"paths": {
"common/*": [
"../../../src/common/*"
],
"browser/*": [
"../../../src/browser/*"
],
"@xterm/addon-serialize": [
"../typings/addon-serialize.d.ts"
]
},
"strict": true,
"skipLibCheck": true,
"types": [
@@ -18,8 +29,7 @@
},
"include": [
"./**/*",
"../../../typings/xterm.d.ts",
"../typings/addon-serialize.d.ts"
"../../../typings/xterm.d.ts"
],
"references": [
{
+9 -1
View File
@@ -3,12 +3,20 @@
"module": "commonjs",
"target": "ESNext",
"lib": [
"es2021"
"es2021",
],
"rootDir": ".",
"outDir": "../out-test",
"sourceMap": true,
"removeComments": true,
"paths": {
"common/*": [
"../../../src/common/*"
],
"browser/*": [
"../../../src/browser/*"
]
},
"strict": true,
"types": [
"../../../node_modules/@types/node"
+3 -9
View File
@@ -2,14 +2,8 @@
"files": [],
"include": [],
"references": [
{
"path": "./src"
},
{
"path": "./test"
},
{
"path": "./benchmark"
}
{ "path": "./src" },
{ "path": "./test" },
{ "path": "./benchmark" }
]
}
@@ -6,9 +6,9 @@
import { perfContext, before, ThroughputRuntimeCase } from 'xterm-benchmark';
import { spawn } from 'node-pty';
import { Utf8ToUtf32, stringFromCodePoint } from '../../../src/common/input/TextDecoder';
import { CoreBrowserTerminal } from '../../../src/browser/CoreBrowserTerminal';
import { UnicodeGraphemeProvider } from '../src/UnicodeGraphemeProvider';
import { Utf8ToUtf32, stringFromCodePoint } from 'common/input/TextDecoder';
import { CoreBrowserTerminal } from 'browser/CoreBrowserTerminal';
import { UnicodeGraphemeProvider } from 'UnicodeGraphemeProvider';
function fakedAddonLoad(terminal: any): void {
@@ -1,33 +1,26 @@
{
"compilerOptions": {
"lib": [
"dom",
"es6"
],
"lib": ["dom", "es6"],
"rootDir": "..",
"outDir": "../out-benchmark",
"types": [
"../../../node_modules/@types/node"
],
"types": ["../../../node_modules/@types/node"],
"moduleResolution": "node",
"strict": false,
"target": "es2015",
"module": "commonjs"
},
"include": [
"../**/*",
"../../../typings/xterm.d.ts"
],
"exclude": [
"../../../**/*test.ts",
"../../**/*api.ts"
],
"references": [
{
"path": "../../../src/common"
},
{
"path": "../../../src/browser"
"module": "commonjs",
"paths": {
"common/*": ["../../../src/common/*"],
"browser/*": ["../../../src/browser/*"],
"UnicodeGraphemeProvider": ["../src/UnicodeGraphemeProvider"],
"@xterm/addon-unicode-graphemes": [
"../typings/addon-unicode-graphemes.d.ts"
]
}
},
"include": ["../**/*", "../../../typings/xterm.d.ts"],
"exclude": ["../../../**/*test.ts", "../../**/*api.ts"],
"references": [
{ "path": "../../../src/common" },
{ "path": "../../../src/browser" }
]
}
@@ -4,8 +4,8 @@
*/
import { IUnicodeVersionProvider } from '@xterm/xterm';
import { UnicodeCharProperties, UnicodeCharWidth } from '../../../src/common/services/Services';
import { UnicodeService } from '../../../src/common/services/UnicodeService';
import { UnicodeCharProperties, UnicodeCharWidth } from 'common/services/Services';
import { UnicodeService } from 'common/services/UnicodeService';
import * as UC from './third-party/UnicodeProperties';
export class UnicodeGraphemeProvider implements IUnicodeVersionProvider {
@@ -11,14 +11,21 @@
"sourceMap": true,
"removeComments": true,
"strict": true,
"paths": {
"common/*": [
"../../../src/common/*"
],
"@xterm/addon-unicode-graphemes": [
"../typings/addon-unicode-graphemes.d.ts"
]
},
"types": [
"../../../node_modules/@types/mocha"
]
},
"include": [
"./**/*",
"../../../typings/xterm.d.ts",
"../typings/addon-unicode-graphemes.d.ts"
"../../../typings/xterm.d.ts"
],
"references": [
{
@@ -3,12 +3,20 @@
"module": "commonjs",
"target": "ESNext",
"lib": [
"es2021"
"es2021",
],
"rootDir": ".",
"outDir": "../out-test",
"sourceMap": true,
"removeComments": true,
"paths": {
"common/*": [
"../../../src/common/*"
],
"browser/*": [
"../../../src/browser/*"
]
},
"strict": true,
"types": [
"../../../node_modules/@types/node"
+3 -9
View File
@@ -2,14 +2,8 @@
"files": [],
"include": [],
"references": [
{
"path": "./src"
},
{
"path": "./test"
},
{
"path": "./benchmark"
}
{ "path": "./src" },
{ "path": "./test" },
{ "path": "./benchmark" }
]
}
+2 -2
View File
@@ -4,8 +4,8 @@
*/
import { IUnicodeVersionProvider } from '@xterm/xterm';
import { UnicodeCharProperties, UnicodeCharWidth } from '../../../src/common/services/Services';
import { UnicodeService } from '../../../src/common/services/UnicodeService';
import { UnicodeCharProperties, UnicodeCharWidth } from 'common/services/Services';
import { UnicodeService } from 'common/services/UnicodeService';
const BMP_COMBINING = [
[0x0300, 0x036F], [0x0483, 0x0489], [0x0591, 0x05BD],
+12 -2
View File
@@ -11,14 +11,24 @@
"sourceMap": true,
"removeComments": true,
"strict": true,
"paths": {
"common/*": [
"../../../src/common/*"
],
"@xterm/addon-unicode11": [
"../typings/addon-unicode11.d.ts"
],
"*": [
"./*"
]
},
"types": [
"../../../node_modules/@types/mocha"
]
},
"include": [
"./**/*",
"../../../typings/xterm.d.ts",
"../typings/addon-unicode11.d.ts"
"../../../typings/xterm.d.ts"
],
"references": [
{
+12 -1
View File
@@ -3,12 +3,23 @@
"module": "commonjs",
"target": "ESNext",
"lib": [
"es2021"
"es2021",
],
"rootDir": ".",
"outDir": "../out-test",
"sourceMap": true,
"removeComments": true,
"paths": {
"common/*": [
"../../../src/common/*"
],
"browser/*": [
"../../../src/browser/*"
],
"*": [
"./*"
]
},
"strict": true,
"types": [
"../../../node_modules/@types/node"
+2 -6
View File
@@ -2,11 +2,7 @@
"files": [],
"include": [],
"references": [
{
"path": "./src"
},
{
"path": "./test"
}
{ "path": "./src" },
{ "path": "./test" }
]
}
+7 -3
View File
@@ -14,11 +14,15 @@
"strict": true,
"types": [
"../../../node_modules/@types/mocha"
]
],
"paths": {
"@xterm/addon-web-fonts": [
"../typings/addon-web-fonts.d.ts"
]
}
},
"include": [
"./**/*",
"../../../typings/xterm.d.ts",
"../typings/addon-web-fonts.d.ts"
"../../../typings/xterm.d.ts"
]
}
+13 -1
View File
@@ -3,12 +3,24 @@
"module": "commonjs",
"target": "es2021",
"lib": [
"es2021"
"es2021",
],
// "downlevelIteration": true,
"rootDir": ".",
"outDir": "../out-test",
"sourceMap": true,
"removeComments": true,
"paths": {
"common/*": [
"../../../src/common/*"
],
"browser/*": [
"../../../src/browser/*"
],
"*": [
"./*"
]
},
"strict": true,
"types": [
"../../../node_modules/@types/node"
+2 -6
View File
@@ -2,11 +2,7 @@
"files": [],
"include": [],
"references": [
{
"path": "./src"
},
{
"path": "./test"
}
{ "path": "./src" },
{ "path": "./test" }
]
}
+7 -3
View File
@@ -13,11 +13,15 @@
"strict": true,
"types": [
"../../../node_modules/@types/mocha"
]
],
"paths": {
"@xterm/addon-web-links": [
"../typings/addon-web-links.d.ts"
]
}
},
"include": [
"./**/*",
"../../../typings/xterm.d.ts",
"../typings/addon-web-links.d.ts"
"../../../typings/xterm.d.ts"
]
}
+13 -1
View File
@@ -3,12 +3,24 @@
"module": "commonjs",
"target": "es2021",
"lib": [
"es2021"
"es2021",
],
// "downlevelIteration": true,
"rootDir": ".",
"outDir": "../out-test",
"sourceMap": true,
"removeComments": true,
"paths": {
"common/*": [
"../../../src/common/*"
],
"browser/*": [
"../../../src/browser/*"
],
"*": [
"./*"
]
},
"strict": true,
"types": [
"../../../node_modules/@types/node"
+2 -6
View File
@@ -2,11 +2,7 @@
"files": [],
"include": [],
"references": [
{
"path": "./src"
},
{
"path": "./test"
}
{ "path": "./src" },
{ "path": "./test" }
]
}
+8 -8
View File
@@ -1,12 +1,12 @@
import { ISelectionRenderModel } from '../../../src/browser/renderer/shared/Types';
import { ICoreBrowserService, IThemeService } from '../../../src/browser/services/Services';
import { ReadonlyColorSet } from '../../../src/browser/Types';
import { Attributes, BgFlags, ExtFlags, FgFlags, NULL_CELL_CODE, UnderlineStyle } from '../../../src/common/buffer/Constants';
import { IDecorationService, IOptionsService } from '../../../src/common/services/Services';
import { ICellData } from '../../../src/common/Types';
import { ISelectionRenderModel } from 'browser/renderer/shared/Types';
import { ICoreBrowserService, IThemeService } from 'browser/services/Services';
import { ReadonlyColorSet } from 'browser/Types';
import { Attributes, BgFlags, ExtFlags, FgFlags, NULL_CELL_CODE, UnderlineStyle } from 'common/buffer/Constants';
import { IDecorationService, IOptionsService } from 'common/services/Services';
import { ICellData } from 'common/Types';
import { Terminal } from '@xterm/xterm';
import { rgba } from '../../../src/common/Color';
import { treatGlyphAsBackgroundColor } from '../../../src/browser/renderer/shared/RendererUtils';
import { rgba } from 'common/Color';
import { treatGlyphAsBackgroundColor } from 'browser/renderer/shared/RendererUtils';
import { blockPatternCodepoints } from './customGlyphs/CustomGlyphDefinitions';
// Work variables to avoid garbage collection
+2 -2
View File
@@ -5,10 +5,10 @@
import { TextureAtlas } from './TextureAtlas';
import { ITerminalOptions, Terminal } from '@xterm/xterm';
import { ITerminal, ReadonlyColorSet } from '../../../src/browser/Types';
import { ITerminal, ReadonlyColorSet } from 'browser/Types';
import { ICharAtlasConfig, ITextureAtlas } from './Types';
import { generateConfig, configEquals } from './CharAtlasUtils';
import type { ILogService } from '../../../src/common/services/Services';
import type { ILogService } from 'common/services/Services';
interface ITextureAtlasCacheEntry {
atlas: ITextureAtlas;
@@ -1,87 +0,0 @@
/**
* Copyright (c) 2026 The xterm.js authors. All rights reserved.
* @license MIT
*/
import { assert } from 'chai';
import { configEquals } from './CharAtlasUtils';
import { ICharAtlasConfig } from './Types';
import { NULL_COLOR } from '../../../src/common/Color';
import { IColor } from '../../../src/common/Types';
function createTestConfig(overrides: Partial<ICharAtlasConfig> = {}): ICharAtlasConfig {
const color: IColor = { css: '#ffffff', rgba: 0xffffffff };
const contrastCache = {
clear: () => {},
setCss: () => {},
getCss: () => undefined,
setColor: () => {},
getColor: () => undefined
};
const colors = {
foreground: color,
background: color,
cursor: NULL_COLOR,
cursorAccent: NULL_COLOR,
selectionForeground: undefined,
selectionBackgroundTransparent: NULL_COLOR,
selectionBackgroundOpaque: NULL_COLOR,
selectionInactiveBackgroundTransparent: NULL_COLOR,
selectionInactiveBackgroundOpaque: NULL_COLOR,
overviewRulerBorder: NULL_COLOR,
scrollbarSliderBackground: NULL_COLOR,
scrollbarSliderHoverBackground: NULL_COLOR,
scrollbarSliderActiveBackground: NULL_COLOR,
ansi: new Array(256).fill(color),
contrastCache,
halfContrastCache: contrastCache
};
return {
customGlyphs: true,
devicePixelRatio: 1,
deviceMaxTextureSize: 4096,
letterSpacing: 0,
lineHeight: 1,
fontSize: 15,
fontFamily: 'monospace',
fontWeight: 'normal',
fontWeightBold: 'bold',
deviceCellWidth: 10,
deviceCellHeight: 20,
deviceCharWidth: 8,
deviceCharHeight: 16,
allowTransparency: false,
drawBoldTextInBrightColors: true,
minimumContrastRatio: 1,
colors,
...overrides
};
}
describe('CharAtlasUtils', () => {
describe('configEquals', () => {
it('should return true for identical configs', () => {
const a = createTestConfig();
const b = createTestConfig();
assert.ok(configEquals(a, b));
});
it('should return false when deviceMaxTextureSize differs', () => {
const a = createTestConfig();
const b = createTestConfig({ deviceMaxTextureSize: 8192 });
assert.ok(!configEquals(a, b));
});
it('should return false when deviceCellWidth differs', () => {
const a = createTestConfig();
const b = createTestConfig({ deviceCellWidth: 11 });
assert.ok(!configEquals(a, b));
});
it('should return false when deviceCellHeight differs', () => {
const a = createTestConfig();
const b = createTestConfig({ deviceCellHeight: 21 });
assert.ok(!configEquals(a, b));
});
});
});
+3 -6
View File
@@ -4,10 +4,10 @@
*/
import { ICharAtlasConfig } from './Types';
import { Attributes } from '../../../src/common/buffer/Constants';
import { Attributes } from 'common/buffer/Constants';
import { ITerminalOptions } from '@xterm/xterm';
import { IColorSet, ReadonlyColorSet } from '../../../src/browser/Types';
import { NULL_COLOR } from '../../../src/common/Color';
import { IColorSet, ReadonlyColorSet } from 'browser/Types';
import { NULL_COLOR } from 'common/Color';
export function generateConfig(deviceCellWidth: number, deviceCellHeight: number, deviceCharWidth: number, deviceCharHeight: number, options: Required<ITerminalOptions>, colors: ReadonlyColorSet, devicePixelRatio: number, deviceMaxTextureSize: number, customGlyphs: boolean = true): ICharAtlasConfig {
// null out some fields that don't matter
@@ -59,7 +59,6 @@ export function configEquals(a: ICharAtlasConfig, b: ICharAtlasConfig): boolean
}
}
return a.devicePixelRatio === b.devicePixelRatio &&
a.deviceMaxTextureSize === b.deviceMaxTextureSize &&
a.customGlyphs === b.customGlyphs &&
a.lineHeight === b.lineHeight &&
a.letterSpacing === b.letterSpacing &&
@@ -68,8 +67,6 @@ export function configEquals(a: ICharAtlasConfig, b: ICharAtlasConfig): boolean
a.fontWeight === b.fontWeight &&
a.fontWeightBold === b.fontWeightBold &&
a.allowTransparency === b.allowTransparency &&
a.deviceCellWidth === b.deviceCellWidth &&
a.deviceCellHeight === b.deviceCellHeight &&
a.deviceCharWidth === b.deviceCharWidth &&
a.deviceCharHeight === b.deviceCharHeight &&
a.drawBoldTextInBrightColors === b.drawBoldTextInBrightColors &&
+1 -1
View File
@@ -3,7 +3,7 @@
* @license MIT
*/
import { isFirefox, isLegacyEdge } from '../../../src/common/Platform';
import { isFirefox, isLegacyEdge } from 'common/Platform';
export const DIM_OPACITY = 0.5;
// The text baseline is set conditionally by browser. Using 'ideographic' for Firefox or Legacy Edge
@@ -3,8 +3,8 @@
* @license MIT
*/
import { RendererConstants } from '../../../src/browser/renderer/shared/Constants';
import { ICoreBrowserService } from '../../../src/browser/services/Services';
import { RendererConstants } from 'browser/renderer/shared/Constants';
import { ICoreBrowserService } from 'browser/services/Services';
const enum Constants {
/**
@@ -3,7 +3,7 @@
* @license MIT
*/
import { toDisposable, IDisposable } from '../../../src/common/Lifecycle';
import { toDisposable, IDisposable } from 'common/Lifecycle';
export function observeDevicePixelDimensions(element: HTMLElement, parentWindow: Window & typeof globalThis, callback: (deviceWidth: number, deviceHeight: number) => void): IDisposable {
// Observe any resizes to the element and extract the actual pixel size of the element if the
+10 -18
View File
@@ -3,14 +3,14 @@
* @license MIT
*/
import { TextureAtlas } from './TextureAtlas';
import { IRenderDimensions } from '../../../src/browser/renderer/shared/Types';
import { NULL_CELL_CODE } from '../../../src/common/buffer/Constants';
import { Disposable, toDisposable } from '../../../src/common/Lifecycle';
import { IRenderDimensions } from 'browser/renderer/shared/Types';
import { NULL_CELL_CODE } from 'common/buffer/Constants';
import { Disposable, toDisposable } from 'common/Lifecycle';
import { Terminal } from '@xterm/xterm';
import { IRenderModel, IWebGL2RenderingContext, IWebGLVertexArrayObject, type IRasterizedGlyph, type ITextureAtlas } from './Types';
import { createProgram, GLTexture, PROJECTION_MATRIX } from './WebglUtils';
import type { ILogService, IOptionsService } from '../../../src/common/services/Services';
import { allowRescaling, throwIfFalsy } from '../../../src/browser/renderer/shared/RendererUtils';
import type { IOptionsService } from 'common/services/Services';
import { allowRescaling, throwIfFalsy } from 'browser/renderer/shared/RendererUtils';
interface IVertices {
attributes: Float32Array;
@@ -114,8 +114,7 @@ export class GlyphRenderer extends Disposable {
private readonly _terminal: Terminal,
private readonly _gl: IWebGL2RenderingContext,
private _dimensions: IRenderDimensions,
private readonly _optionsService: IOptionsService,
private readonly _logService: ILogService
private readonly _optionsService: IOptionsService
) {
super();
@@ -128,7 +127,7 @@ export class GlyphRenderer extends Disposable {
TextureAtlas.maxTextureSize = throwIfFalsy(gl.getParameter(gl.MAX_TEXTURE_SIZE) as number | null);
}
this._program = throwIfFalsy(createProgram(gl, vertexShaderSource, createFragmentShaderSource(TextureAtlas.maxAtlasPages), this._logService));
this._program = throwIfFalsy(createProgram(gl, vertexShaderSource, createFragmentShaderSource(TextureAtlas.maxAtlasPages)));
this._register(toDisposable(() => gl.deleteProgram(this._program)));
// Uniform locations
@@ -137,9 +136,8 @@ export class GlyphRenderer extends Disposable {
this._textureLocation = throwIfFalsy(gl.getUniformLocation(this._program, 'u_texture'));
// Create and set the vertex array object
const vertexArrayObject = this._vertexArrayObject = gl.createVertexArray();
this._register(toDisposable(() => gl.deleteVertexArray(vertexArrayObject)));
gl.bindVertexArray(vertexArrayObject);
this._vertexArrayObject = gl.createVertexArray();
gl.bindVertexArray(this._vertexArrayObject);
// Setup a_unitquad, this defines the 4 vertices of a rectangle
const unitQuadVertices = new Float32Array([0, 0, 1, 0, 0, 1, 1, 1]);
@@ -360,9 +358,7 @@ export class GlyphRenderer extends Disposable {
gl.bindBuffer(gl.ARRAY_BUFFER, this._attributesBuffer);
gl.bufferData(gl.ARRAY_BUFFER, activeBuffer.subarray(0, bufferLength), gl.STREAM_DRAW);
// Bind the atlas page texture if they have changed. AtlasPage.version is globally
// monotonic, so a page object swap at the same index (which happens after a page merge)
// is detected by the same comparison.
// Bind the atlas page texture if they have changed
for (let i = 0; i < this._atlas.pages.length; i++) {
if (this._atlas.pages[i].version !== this._atlasTextures[i].version) {
this._bindAtlasPageTexture(gl, this._atlas, i);
@@ -375,10 +371,6 @@ export class GlyphRenderer extends Disposable {
public setAtlas(atlas: ITextureAtlas): void {
this._atlas = atlas;
this.invalidateAtlasTextures();
}
public invalidateAtlasTextures(): void {
for (const glTexture of this._atlasTextures) {
glTexture.version = -1;
}
+13 -17
View File
@@ -3,18 +3,17 @@
* @license MIT
*/
import { IRenderDimensions } from '../../../src/browser/renderer/shared/Types';
import { IThemeService } from '../../../src/browser/services/Services';
import { ReadonlyColorSet } from '../../../src/browser/Types';
import { Attributes, FgFlags } from '../../../src/common/buffer/Constants';
import { Disposable, toDisposable } from '../../../src/common/Lifecycle';
import { IColor } from '../../../src/common/Types';
import { IRenderDimensions } from 'browser/renderer/shared/Types';
import { IThemeService } from 'browser/services/Services';
import { ReadonlyColorSet } from 'browser/Types';
import { Attributes, FgFlags } from 'common/buffer/Constants';
import { Disposable, toDisposable } from 'common/Lifecycle';
import { IColor } from 'common/Types';
import { Terminal } from '@xterm/xterm';
import { RenderModelConstants } from './RenderModel';
import { IRenderModel, IWebGL2RenderingContext, IWebGLVertexArrayObject } from './Types';
import { createProgram, expandFloat32Array, PROJECTION_MATRIX } from './WebglUtils';
import { throwIfFalsy } from '../../../src/browser/renderer/shared/RendererUtils';
import type { ILogService } from '../../../src/common/services/Services';
import { throwIfFalsy } from 'browser/renderer/shared/RendererUtils';
const enum VertexAttribLocations {
POSITION = 0,
@@ -90,23 +89,21 @@ export class RectangleRenderer extends Disposable {
private _terminal: Terminal,
private _gl: IWebGL2RenderingContext,
private _dimensions: IRenderDimensions,
private readonly _themeService: IThemeService,
private readonly _logService: ILogService
private readonly _themeService: IThemeService
) {
super();
const gl = this._gl;
this._program = throwIfFalsy(createProgram(gl, vertexShaderSource, fragmentShaderSource, this._logService));
this._program = throwIfFalsy(createProgram(gl, vertexShaderSource, fragmentShaderSource));
this._register(toDisposable(() => gl.deleteProgram(this._program)));
// Uniform locations
this._projectionLocation = throwIfFalsy(gl.getUniformLocation(this._program, 'u_projection'));
// Create and set the vertex array object
const vertexArrayObject = this._vertexArrayObject = gl.createVertexArray();
this._register(toDisposable(() => gl.deleteVertexArray(vertexArrayObject)));
gl.bindVertexArray(vertexArrayObject);
this._vertexArrayObject = gl.createVertexArray();
gl.bindVertexArray(this._vertexArrayObject);
// Setup a_unitquad, this defines the 4 vertices of a rectangle
const unitQuadVertices = new Float32Array([0, 0, 1, 0, 0, 1, 1, 1]);
@@ -339,9 +336,8 @@ export class RectangleRenderer extends Disposable {
}
}
if (vertices.attributes.length < offset + INDICES_PER_RECTANGLE) {
// +1 for the viewport-clear rectangle at offset 0.
vertices.attributes = expandFloat32Array(vertices.attributes, (this._terminal.rows * this._terminal.cols + 1) * INDICES_PER_RECTANGLE);
if (vertices.attributes.length < offset + 4) {
vertices.attributes = expandFloat32Array(vertices.attributes, this._terminal.rows * this._terminal.cols * INDICES_PER_RECTANGLE);
}
$x1 = startX * this._dimensions.device.cell.width;
$y1 = y * this._dimensions.device.cell.height;
+2 -2
View File
@@ -4,8 +4,8 @@
*/
import { ICursorRenderModel, IRenderModel } from './Types';
import { ISelectionRenderModel } from '../../../src/browser/renderer/shared/Types';
import { createSelectionRenderModel } from '../../../src/browser/renderer/shared/SelectionRenderModel';
import { ISelectionRenderModel } from 'browser/renderer/shared/Types';
import { createSelectionRenderModel } from 'browser/renderer/shared/SelectionRenderModel';
export const enum RenderModelConstants {
INDICIES_PER_CELL = 4,
+18 -25
View File
@@ -3,19 +3,19 @@
* @license MIT
*/
import { IColorContrastCache } from '../../../src/browser/Types';
import { IColorContrastCache } from 'browser/Types';
import { DIM_OPACITY, TEXT_BASELINE } from './Constants';
import { tryDrawCustomGlyph } from './customGlyphs/CustomGlyphRasterizer';
import { computeNextVariantOffset, treatGlyphAsBackgroundColor, isPowerlineGlyph, isRestrictedPowerlineGlyph, throwIfFalsy } from '../../../src/browser/renderer/shared/RendererUtils';
import { computeNextVariantOffset, treatGlyphAsBackgroundColor, isPowerlineGlyph, isRestrictedPowerlineGlyph, throwIfFalsy } from 'browser/renderer/shared/RendererUtils';
import { IBoundingBox, ICharAtlasConfig, IRasterizedGlyph, ITextureAtlas } from './Types';
import { NULL_COLOR, channels, color, rgba } from '../../../src/common/Color';
import { FourKeyMap } from '../../../src/common/MultiKeyMap';
import { IdleTaskQueue } from '../../../src/common/TaskQueue';
import { IColor } from '../../../src/common/Types';
import { AttributeData } from '../../../src/common/buffer/AttributeData';
import { Attributes, DEFAULT_COLOR, DEFAULT_EXT, UnderlineStyle } from '../../../src/common/buffer/Constants';
import { ILogService, IUnicodeService } from '../../../src/common/services/Services';
import { Emitter } from '../../../src/common/Event';
import { NULL_COLOR, channels, color, rgba } from 'common/Color';
import { FourKeyMap } from 'common/MultiKeyMap';
import { IdleTaskQueue } from 'common/TaskQueue';
import { IColor } from 'common/Types';
import { AttributeData } from 'common/buffer/AttributeData';
import { Attributes, DEFAULT_COLOR, DEFAULT_EXT, UnderlineStyle } from 'common/buffer/Constants';
import { ILogService, IUnicodeService } from 'common/services/Services';
import { Emitter } from 'common/Event';
/**
* A shared object which is used to draw nothing for a particular cell.
@@ -109,7 +109,6 @@ export class TextureAtlas implements ITextureAtlas {
page.canvas.remove();
}
this._onAddTextureAtlasCanvas.dispose();
this._onRemoveTextureAtlasCanvas.dispose();
}
public warmUp(): void {
@@ -134,9 +133,7 @@ export class TextureAtlas implements ITextureAtlas {
private _requestClearModel = false;
public beginFrame(): boolean {
const result = this._requestClearModel;
this._requestClearModel = false;
return result;
return this._requestClearModel;
}
public clearTexture(): void {
@@ -196,7 +193,7 @@ export class TextureAtlas implements ITextureAtlas {
// Merge into the new page
const mergedPage = this._mergePages(mergingPages, mergedPageIndex);
mergedPage.version = ++AtlasPage.nextVersion;
mergedPage.version++;
// Delete the pages, shifting glyph texture pages as needed
for (let i = sortedMergingPagesIndexes.length - 1; i >= 0; i--) {
@@ -254,7 +251,7 @@ export class TextureAtlas implements ITextureAtlas {
for (const g of adjustingPage.glyphs) {
g.texturePage--;
}
adjustingPage.version = ++AtlasPage.nextVersion;
adjustingPage.version++;
}
}
@@ -530,7 +527,7 @@ export class TextureAtlas implements ITextureAtlas {
let customGlyph = false;
if (this._config.customGlyphs !== false) {
const variantOffset = this._workAttributeData.getUnderlineVariantOffset();
customGlyph = tryDrawCustomGlyph(this._tmpCtx, chars, padding, padding, this._config.deviceCellWidth, this._config.deviceCellHeight, this._config.deviceCharWidth, this._config.deviceCharHeight, this._config.fontSize, this._config.devicePixelRatio, this._logService, backgroundColor.css, variantOffset);
customGlyph = tryDrawCustomGlyph(this._tmpCtx, chars, padding, padding, this._config.deviceCellWidth, this._config.deviceCellHeight, this._config.deviceCharWidth, this._config.deviceCharHeight, this._config.fontSize, this._config.devicePixelRatio, backgroundColor.css, variantOffset);
}
// Whether to clear pixels based on a threshold difference between the glyph color and the
@@ -940,7 +937,7 @@ export class TextureAtlas implements ITextureAtlas {
rasterizedGlyph.size.y
);
activePage.addGlyph(rasterizedGlyph);
activePage.version = ++AtlasPage.nextVersion;
activePage.version++;
return rasterizedGlyph;
}
@@ -1050,13 +1047,9 @@ class AtlasPage {
}
/**
* Monotonically increasing across all atlas pages globally. Used to detect when the texture
* unit at a given index needs to be re-uploaded — both for content changes within the same
* page and for a page object swap at the same index (which happens after a page merge,
* where a per-page counter could coincide with the previously-bound page's value).
* Used to check whether the canvas of the atlas page has changed.
*/
public static nextVersion: number = 0;
public version = ++AtlasPage.nextVersion;
public version = 0;
// Texture atlas current positioning data. The texture packing strategy used is to fill from
// left-to-right and top-to-bottom. When the glyph being written is less than half of the current
@@ -1099,7 +1092,7 @@ class AtlasPage {
this.currentRow.y = 0;
this.currentRow.height = 0;
this.fixedRows.length = 0;
this.version = ++AtlasPage.nextVersion;
this.version++;
}
}
+4 -5
View File
@@ -4,10 +4,10 @@
*/
import { FontWeight } from '@xterm/xterm';
import { IColorSet } from '../../../src/browser/Types';
import { ISelectionRenderModel } from '../../../src/browser/renderer/shared/Types';
import { CursorInactiveStyle, CursorStyle, type IDisposable } from '../../../src/common/Types';
import type { IEvent } from '../../../src/common/Event';
import { IColorSet } from 'browser/Types';
import { ISelectionRenderModel } from 'browser/renderer/shared/Types';
import { CursorInactiveStyle, CursorStyle, type IDisposable } from 'common/Types';
import type { IEvent } from 'common/Event';
export interface IRenderModel {
cells: Uint32Array;
@@ -28,7 +28,6 @@ export interface ICursorRenderModel {
export interface IWebGL2RenderingContext extends WebGLRenderingContext {
vertexAttribDivisor(index: number, divisor: number): void;
createVertexArray(): IWebGLVertexArrayObject;
deleteVertexArray(vao: IWebGLVertexArrayObject): void;
bindVertexArray(vao: IWebGLVertexArrayObject): void;
drawElementsInstanced(mode: number, count: number, type: number, offset: number, instanceCount: number): void;
}
+6 -8
View File
@@ -5,14 +5,14 @@
import type { ITerminalAddon, Terminal } from '@xterm/xterm';
import type { IWebglAddonOptions, WebglAddon as IWebglApi } from '@xterm/addon-webgl';
import { ICharacterJoinerService, ICharSizeService, ICoreBrowserService, IRenderService, IThemeService } from '../../../src/browser/services/Services';
import { ITerminal } from '../../../src/browser/Types';
import { Disposable, toDisposable } from '../../../src/common/Lifecycle';
import { getSafariVersion, isSafari } from '../../../src/common/Platform';
import { ICoreService, IDecorationService, ILogService, IOptionsService } from '../../../src/common/services/Services';
import { ICharacterJoinerService, ICharSizeService, ICoreBrowserService, IRenderService, IThemeService } from 'browser/services/Services';
import { ITerminal } from 'browser/Types';
import { Disposable, toDisposable } from 'common/Lifecycle';
import { getSafariVersion, isSafari } from 'common/Platform';
import { ICoreService, IDecorationService, IOptionsService } from 'common/services/Services';
import { IWebGL2RenderingContext } from './Types';
import { WebglRenderer } from './WebglRenderer';
import { Emitter, EventUtils } from '../../../src/common/Event';
import { Emitter, EventUtils } from 'common/Event';
export class WebglAddon extends Disposable implements ITerminalAddon, IWebglApi {
private _terminal?: Terminal;
@@ -65,7 +65,6 @@ export class WebglAddon extends Disposable implements ITerminalAddon, IWebglApi
const charSizeService: ICharSizeService = unsafeCore._charSizeService;
const coreBrowserService: ICoreBrowserService = unsafeCore._coreBrowserService;
const decorationService: IDecorationService = unsafeCore._decorationService;
const logService: ILogService = unsafeCore._logService;
const themeService: IThemeService = unsafeCore._themeService;
this._renderer = this._register(new WebglRenderer(
@@ -75,7 +74,6 @@ export class WebglAddon extends Disposable implements ITerminalAddon, IWebglApi
coreBrowserService,
coreService,
decorationService,
logService,
optionsService,
themeService,
this._customGlyphs,
+18 -38
View File
@@ -3,19 +3,19 @@
* @license MIT
*/
import { ITerminal } from '../../../src/browser/Types';
import { ITerminal } from 'browser/Types';
import { CellColorResolver } from './CellColorResolver';
import { acquireTextureAtlas, removeTerminalFromCache } from './CharAtlasCache';
import { CursorBlinkStateManager } from './CursorBlinkStateManager';
import { observeDevicePixelDimensions } from './DevicePixelObserver';
import { IRenderDimensions, IRenderer, IRequestRedrawEvent } from '../../../src/browser/renderer/shared/Types';
import { ICharSizeService, ICharacterJoinerService, ICoreBrowserService, IThemeService } from '../../../src/browser/services/Services';
import { CharData, IBufferLine, ICellData } from '../../../src/common/Types';
import { AttributeData } from '../../../src/common/buffer/AttributeData';
import { CellData } from '../../../src/common/buffer/CellData';
import { Attributes, Content, FgFlags, NULL_CELL_CHAR, NULL_CELL_CODE } from '../../../src/common/buffer/Constants';
import { TextBlinkStateManager } from '../../../src/browser/renderer/shared/TextBlinkStateManager';
import { ICoreService, IDecorationService, ILogService, IOptionsService } from '../../../src/common/services/Services';
import { IRenderDimensions, IRenderer, IRequestRedrawEvent } from 'browser/renderer/shared/Types';
import { ICharSizeService, ICharacterJoinerService, ICoreBrowserService, IThemeService } from 'browser/services/Services';
import { CharData, IBufferLine, ICellData } from 'common/Types';
import { AttributeData } from 'common/buffer/AttributeData';
import { CellData } from 'common/buffer/CellData';
import { Attributes, Content, FgFlags, NULL_CELL_CHAR, NULL_CELL_CODE } from 'common/buffer/Constants';
import { TextBlinkStateManager } from 'browser/renderer/shared/TextBlinkStateManager';
import { ICoreService, IDecorationService, IOptionsService } from 'common/services/Services';
import { Terminal } from '@xterm/xterm';
import { GlyphRenderer } from './GlyphRenderer';
import { RectangleRenderer } from './RectangleRenderer';
@@ -23,14 +23,10 @@ import { COMBINED_CHAR_BIT_MASK, RenderModel, RenderModelConstants } from './Ren
import { IWebGL2RenderingContext, type ITextureAtlas } from './Types';
import { LinkRenderLayer } from './renderLayer/LinkRenderLayer';
import { IRenderLayer } from './renderLayer/Types';
import { Emitter, EventUtils } from '../../../src/common/Event';
import { addDisposableListener } from '../../../src/browser/Dom';
import { combinedDisposable, Disposable, MutableDisposable, toDisposable } from '../../../src/common/Lifecycle';
import { createRenderDimensions } from '../../../src/browser/renderer/shared/RendererUtils';
const enum Constants {
MERGE_RETRY_LIMIT = 32
}
import { Emitter, EventUtils } from 'common/Event';
import { addDisposableListener } from 'browser/Dom';
import { combinedDisposable, Disposable, MutableDisposable, toDisposable } from 'common/Lifecycle';
import { createRenderDimensions } from 'browser/renderer/shared/RendererUtils';
export class WebglRenderer extends Disposable implements IRenderer {
private _renderLayers: IRenderLayer[];
@@ -77,7 +73,6 @@ export class WebglRenderer extends Disposable implements IRenderer {
private readonly _coreBrowserService: ICoreBrowserService,
private readonly _coreService: ICoreService,
private readonly _decorationService: IDecorationService,
private readonly _logService: ILogService,
private readonly _optionsService: IOptionsService,
private readonly _themeService: IThemeService,
private readonly _customGlyphs: boolean = true,
@@ -123,19 +118,19 @@ export class WebglRenderer extends Disposable implements IRenderer {
this._deviceMaxTextureSize = this._gl.getParameter(this._gl.MAX_TEXTURE_SIZE);
this._register(addDisposableListener(this._canvas, 'webglcontextlost', (e) => {
this._logService.debug('webglcontextlost event received');
console.log('webglcontextlost event received');
// Prevent the default behavior in order to enable WebGL context restoration.
e.preventDefault();
// Wait a few seconds to see if the 'webglcontextrestored' event is fired.
// If not, dispatch the onContextLoss notification to observers.
this._contextRestorationTimeout = setTimeout(() => {
this._contextRestorationTimeout = undefined;
this._logService.warn('webgl context not restored; firing onContextLoss');
console.warn('webgl context not restored; firing onContextLoss');
this._onContextLoss.fire(e);
}, 3000 /* ms */);
}));
this._register(addDisposableListener(this._canvas, 'webglcontextrestored', (e) => {
this._logService.warn('webglcontextrestored event received');
console.warn('webglcontextrestored event received');
clearTimeout(this._contextRestorationTimeout);
this._contextRestorationTimeout = undefined;
// The texture atlas and glyph renderer must be fully reinitialized
@@ -159,8 +154,6 @@ export class WebglRenderer extends Disposable implements IRenderer {
this._isAttached = this._core.screenElement!.isConnected;
this._register(toDisposable(() => {
clearTimeout(this._contextRestorationTimeout);
this._contextRestorationTimeout = undefined;
for (const l of this._renderLayers) {
l.dispose();
}
@@ -277,8 +270,8 @@ export class WebglRenderer extends Disposable implements IRenderer {
* Initializes members dependent on WebGL context state.
*/
private _initializeWebGLState(): [RectangleRenderer, GlyphRenderer] {
this._rectangleRenderer.value = new RectangleRenderer(this._terminal, this._gl, this.dimensions, this._themeService, this._logService);
this._glyphRenderer.value = new GlyphRenderer(this._terminal, this._gl, this.dimensions, this._optionsService, this._logService);
this._rectangleRenderer.value = new RectangleRenderer(this._terminal, this._gl, this.dimensions, this._themeService);
this._glyphRenderer.value = new GlyphRenderer(this._terminal, this._gl, this.dimensions, this._optionsService);
// Update dimensions and acquire char atlas
this.handleCharSizeChanged();
@@ -382,19 +375,6 @@ export class WebglRenderer extends Disposable implements IRenderer {
this._updateModel(start, end);
}
// A mid-update atlas page merge invalidates vertex data and may not bump the host
// page's version, so re-run the update and force a full texture rebind.
let merged = false;
let mergeRetries = 0;
while (this._charAtlas && this._glyphRenderer.value.beginFrame() && mergeRetries++ < Constants.MERGE_RETRY_LIMIT) {
merged = true;
this._clearModel(true);
this._updateModel(0, this._terminal.rows - 1);
}
if (merged) {
this._glyphRenderer.value.invalidateAtlasTextures();
}
// Render
this._rectangleRenderer.value.renderBackgrounds();
this._glyphRenderer.value.render(this._model);
+7 -8
View File
@@ -3,8 +3,7 @@
* @license MIT
*/
import { throwIfFalsy } from '../../../src/browser/renderer/shared/RendererUtils';
import type { ILogService } from '../../../src/common/services/Services';
import { throwIfFalsy } from 'browser/renderer/shared/RendererUtils';
/**
* A matrix that when multiplies will translate 0-1 coordinates (left to right,
@@ -17,21 +16,21 @@ export const PROJECTION_MATRIX = new Float32Array([
-1, 1, 0, 1
]);
export function createProgram(gl: WebGLRenderingContext, vertexSource: string, fragmentSource: string, logService: ILogService): WebGLProgram | undefined {
export function createProgram(gl: WebGLRenderingContext, vertexSource: string, fragmentSource: string): WebGLProgram | undefined {
const program = throwIfFalsy(gl.createProgram());
gl.attachShader(program, throwIfFalsy(createShader(gl, gl.VERTEX_SHADER, vertexSource, logService)));
gl.attachShader(program, throwIfFalsy(createShader(gl, gl.FRAGMENT_SHADER, fragmentSource, logService)));
gl.attachShader(program, throwIfFalsy(createShader(gl, gl.VERTEX_SHADER, vertexSource)));
gl.attachShader(program, throwIfFalsy(createShader(gl, gl.FRAGMENT_SHADER, fragmentSource)));
gl.linkProgram(program);
const success = gl.getProgramParameter(program, gl.LINK_STATUS);
if (success) {
return program;
}
logService.error(gl.getProgramInfoLog(program));
console.error(gl.getProgramInfoLog(program));
gl.deleteProgram(program);
}
export function createShader(gl: WebGLRenderingContext, type: number, source: string, logService: ILogService): WebGLShader | undefined {
export function createShader(gl: WebGLRenderingContext, type: number, source: string): WebGLShader | undefined {
const shader = throwIfFalsy(gl.createShader(type));
gl.shaderSource(shader, source);
gl.compileShader(shader);
@@ -40,7 +39,7 @@ export function createShader(gl: WebGLRenderingContext, type: number, source: st
return shader;
}
logService.error(gl.getShaderInfoLog(shader));
console.error(gl.getShaderInfoLog(shader));
gl.deleteShader(shader);
}
@@ -3,8 +3,7 @@
* @license MIT
*/
import { throwIfFalsy } from '../../../../src/browser/renderer/shared/RendererUtils';
import type { ILogService } from '../../../../src/common/services/Services';
import { throwIfFalsy } from 'browser/renderer/shared/RendererUtils';
import { customGlyphDefinitions } from './CustomGlyphDefinitions';
import { CustomGlyphDefinitionType, CustomGlyphScaleType, CustomGlyphVectorType, type CustomGlyphDefinitionPart, type CustomGlyphPathDrawFunctionDefinition, type CustomGlyphPatternDefinition, type ICustomGlyphSolidOctantBlockVector, type ICustomGlyphVectorShape } from './Types';
@@ -23,7 +22,6 @@ export function tryDrawCustomGlyph(
deviceCharHeight: number,
fontSize: number,
devicePixelRatio: number,
logService: ILogService,
backgroundColor?: string,
variantOffset: number = 0
): boolean {
@@ -32,7 +30,7 @@ export function tryDrawCustomGlyph(
// Normalize to array for uniform handling
const parts = Array.isArray(unifiedCharDefinition) ? unifiedCharDefinition : [unifiedCharDefinition];
for (const part of parts) {
drawDefinitionPart(ctx, part, xOffset, yOffset, deviceCellWidth, deviceCellHeight, deviceCharWidth, deviceCharHeight, fontSize, devicePixelRatio, logService, backgroundColor, variantOffset);
drawDefinitionPart(ctx, part, xOffset, yOffset, deviceCellWidth, deviceCellHeight, deviceCharWidth, deviceCharHeight, fontSize, devicePixelRatio, backgroundColor, variantOffset);
}
return true;
}
@@ -51,7 +49,6 @@ function drawDefinitionPart(
deviceCharHeight: number,
fontSize: number,
devicePixelRatio: number,
logService: ILogService,
backgroundColor?: string,
variantOffset: number = 0
): void {
@@ -82,7 +79,7 @@ function drawDefinitionPart(
drawPatternChar(ctx, part.data, drawXOffset, drawYOffset, drawWidth, drawHeight, variantOffset);
break;
case CustomGlyphDefinitionType.PATH_FUNCTION:
drawPathFunctionCharacter(ctx, part.data, drawXOffset, drawYOffset, drawWidth, drawHeight, devicePixelRatio, logService, part.strokeWidth);
drawPathFunctionCharacter(ctx, part.data, drawXOffset, drawYOffset, drawWidth, drawHeight, devicePixelRatio, part.strokeWidth);
break;
case CustomGlyphDefinitionType.PATH:
drawPathDefinitionCharacter(ctx, part.data, drawXOffset, drawYOffset, drawWidth, drawHeight, devicePixelRatio, part.strokeWidth);
@@ -91,7 +88,7 @@ function drawDefinitionPart(
drawPathNegativeDefinitionCharacter(ctx, part.data, drawXOffset, drawYOffset, drawWidth, drawHeight, devicePixelRatio, backgroundColor);
break;
case CustomGlyphDefinitionType.VECTOR_SHAPE:
drawVectorShape(ctx, part.data, drawXOffset, drawYOffset, drawWidth, drawHeight, fontSize, devicePixelRatio, logService);
drawVectorShape(ctx, part.data, drawXOffset, drawYOffset, drawWidth, drawHeight, fontSize, devicePixelRatio);
break;
case CustomGlyphDefinitionType.BRAILLE:
drawBrailleCharacter(ctx, part.data, drawXOffset, drawYOffset, drawWidth, drawHeight);
@@ -513,7 +510,6 @@ function drawPathFunctionCharacter(
deviceCellWidth: number,
deviceCellHeight: number,
devicePixelRatio: number,
logService: ILogService,
strokeWidth?: number
): void {
ctx.save();
@@ -540,7 +536,7 @@ function drawPathFunctionCharacter(
}
const f = svgToCanvasInstructionMap[type];
if (!f) {
logService.error(`Could not find drawing instructions for "${type}"`);
console.error(`Could not find drawing instructions for "${type}"`);
continue;
}
const args: string[] = instruction.substring(1).split(',');
@@ -602,8 +598,7 @@ function drawVectorShape(
deviceCellWidth: number,
deviceCellHeight: number,
fontSize: number,
devicePixelRatio: number,
logService: ILogService
devicePixelRatio: number
): void {
// Clip the cell to make sure drawing doesn't occur beyond bounds
const clipRegion = new Path2D();
@@ -624,7 +619,7 @@ function drawVectorShape(
}
const f = svgToCanvasInstructionMap[type];
if (!f) {
logService.error(`Could not find drawing instructions for "${type}"`);
console.error(`Could not find drawing instructions for "${type}"`);
continue;
}
const args: string[] = instruction.substring(1).split(',');
@@ -3,16 +3,16 @@
* @license MIT
*/
import { ReadonlyColorSet } from '../../../../src/browser/Types';
import { ReadonlyColorSet } from 'browser/Types';
import { acquireTextureAtlas } from '../CharAtlasCache';
import { IRenderDimensions } from '../../../../src/browser/renderer/shared/Types';
import { ICoreBrowserService, IThemeService } from '../../../../src/browser/services/Services';
import { Disposable, toDisposable } from '../../../../src/common/Lifecycle';
import { CellData } from '../../../../src/common/buffer/CellData';
import { IOptionsService } from '../../../../src/common/services/Services';
import { IRenderDimensions } from 'browser/renderer/shared/Types';
import { ICoreBrowserService, IThemeService } from 'browser/services/Services';
import { Disposable, toDisposable } from 'common/Lifecycle';
import { CellData } from 'common/buffer/CellData';
import { IOptionsService } from 'common/services/Services';
import { Terminal } from '@xterm/xterm';
import { IRenderLayer } from './Types';
import { throwIfFalsy } from '../../../../src/browser/renderer/shared/RendererUtils';
import { throwIfFalsy } from 'browser/renderer/shared/RendererUtils';
import { TEXT_BASELINE } from '../Constants';
import type { ITextureAtlas } from '../Types';
@@ -4,11 +4,11 @@
*/
import { is256Color } from '../CharAtlasUtils';
import { INVERTED_DEFAULT_COLOR } from '../../../../src/browser/renderer/shared/Constants';
import { IRenderDimensions } from '../../../../src/browser/renderer/shared/Types';
import { ICoreBrowserService, IThemeService } from '../../../../src/browser/services/Services';
import { ILinkifier2, ILinkifierEvent } from '../../../../src/browser/Types';
import { IOptionsService } from '../../../../src/common/services/Services';
import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/shared/Constants';
import { IRenderDimensions } from 'browser/renderer/shared/Types';
import { ICoreBrowserService, IThemeService } from 'browser/services/Services';
import { ILinkifier2, ILinkifierEvent } from 'browser/Types';
import { IOptionsService } from 'common/services/Services';
import { Terminal } from '@xterm/xterm';
import { BaseRenderLayer } from './BaseRenderLayer';
+1 -1
View File
@@ -4,7 +4,7 @@
*/
import { IDisposable, Terminal } from '@xterm/xterm';
import { IRenderDimensions } from '../../../../src/browser/renderer/shared/Types';
import { IRenderDimensions } from 'browser/renderer/shared/Types';
export interface IRenderLayer extends IDisposable {
/**
+15 -2
View File
@@ -10,6 +10,20 @@
"outDir": "../out",
"sourceMap": true,
"removeComments": true,
"paths": {
"common/*": [
"../../../src/common/*"
],
"browser/*": [
"../../../src/browser/*"
],
"@xterm/addon-webgl": [
"../typings/addon-webgl.d.ts"
],
"*": [
"./*"
]
},
"strict": true,
"downlevelIteration": true,
"experimentalDecorators": true,
@@ -19,8 +33,7 @@
},
"include": [
"./**/*",
"../../../typings/xterm.d.ts",
"../typings/addon-webgl.d.ts"
"../../../typings/xterm.d.ts"
],
"references": [
{
+12 -1
View File
@@ -3,12 +3,23 @@
"module": "commonjs",
"target": "ESNext",
"lib": [
"es2021"
"es2021",
],
"rootDir": ".",
"outDir": "../out-test",
"sourceMap": true,
"removeComments": true,
"paths": {
"common/*": [
"../../../src/common/*"
],
"browser/*": [
"../../../src/browser/*"
],
"*": [
"./*"
]
},
"strict": true,
"types": [
"../../../node_modules/@types/node",
+2 -6
View File
@@ -2,11 +2,7 @@
"files": [],
"include": [],
"references": [
{
"path": "./src"
},
{
"path": "./test"
}
{ "path": "./src" },
{ "path": "./test" }
]
}
+3 -10
View File
@@ -107,20 +107,13 @@ if (config.addon) {
};
outConfig = {
...outConfig,
bundle: true,
platform: 'node',
packages: 'external',
entryPoints: [`addons/addon-${config.addon}/src/**/*.ts`],
outdir: `addons/addon-${config.addon}/out-esbuild/`,
// Public API stays external; src/common|browser are bundled from source.
external: ['@xterm/xterm', '@xterm/headless', '@xterm/addon-*'],
outdir: `addons/addon-${config.addon}/out-esbuild/`
};
// Playwright suites only (not run by unit tests); keep unbundled.
outTestConfig = {
format: 'cjs',
sourcemap: true,
...outConfig,
entryPoints: [`addons/addon-${config.addon}/test/**/*.ts`],
outdir: `addons/addon-${config.addon}/out-esbuild-test/`,
outdir: `addons/addon-${config.addon}/out-esbuild-test/`
};
if (config.addon === 'ligatures') {
+4 -15
View File
@@ -7,30 +7,19 @@ import { argv } from "process";
/** @type {{cp: import("child_process").ChildProcessByStdio<any, any, any>, name: string}[]} */
const jobs = [];
// Core job must finish before addons (addon tests resolve src/* via out-esbuild).
const xtermJob = createJob('xterm', []);
const xtermCode = await new Promise(r => {
xtermJob.cp.on('exit', code => {
log(`Finished \x1b[32m${xtermJob.name}\x1b[0m${code ? ' \x1b[31mwith errors\x1b[0m' : ''}`);
r(code ?? 1);
});
});
if (xtermCode !== 0) {
process.exit(xtermCode);
}
// Core job
jobs.push(createJob('xterm', []));
// Addon jobs
const addons = (await readdir('addons')).filter(e => e.startsWith('addon-')).map(e => e.replace('addon-', ''));
/** @type {{cp: import("child_process").ChildProcessByStdio<any, any, any>, name: string}[]} */
const addonJobs = [];
for (const addon of addons) {
addonJobs.push(createJob(`xterm-addon-${addon}`, [`--addon=${addon}`]));
jobs.push(createJob(`xterm-addon-${addon}`, [`--addon=${addon}`]));
}
// Demo job - This requires the others to be built so it's not included when building all
// jobs.push(createJob('demo-client', [`--demo-client`]));
await Promise.all(addonJobs.map(job => {
await Promise.all(jobs.map((job, i) => {
return new Promise(r => {
job.cp.on('exit', code => {
log(`Finished \x1b[32m${job.name}\x1b[0m${code ? ' \x1b[31mwith errors\x1b[0m' : ''}`);
+196
View File
@@ -0,0 +1,196 @@
from termios import tcgetattr, tcsetattr, TCSADRAIN, TIOCGWINSZ, TCSAFLUSH
from tty import setcbreak, setraw
import os
import sys
from contextlib import contextmanager
from select import select
from time import sleep
from json import dumps, loads
from typing import Optional, Tuple, Union
class TerminalContext:
def __init__(self, fd: int, close_fd=False) -> None:
if not os.isatty(fd):
raise TypeError('fd is not a terminal')
self.close_fd = close_fd
self.fd = fd
self.is_cbreak = False
self.is_raw = False
self._initial_attr = self.termios_attributes
@classmethod
def from_cterm(cls):
fd = os.open(os.ctermid(), os.O_RDWR)
return TerminalContext(fd, True)
def close(self) -> None:
tcsetattr(self.fd, TCSADRAIN, self._initial_attr)
if self.close_fd:
os.close(self.fd)
@property
def termios_attributes(self) -> list:
return tcgetattr(self.fd)
@property
def ttyname(self) -> str:
return os.ttyname(self.fd)
@contextmanager
def cbreak_mode(self):
"""
Enter cbreak mode context.
"""
if self.is_cbreak:
yield
return
tattr = self.termios_attributes
try:
setcbreak(self.fd, TCSADRAIN)
self.is_cbreak = True
yield
finally:
tcsetattr(self.fd, TCSADRAIN, tattr)
self.is_cbreak = False
@contextmanager
def raw_mode(self):
"""
Enter cbreak mode context.
"""
if self.is_raw:
yield
return
tattr = self.termios_attributes
try:
setraw(self.fd, TCSADRAIN)
self.is_raw = True
yield
finally:
tcsetattr(self.fd, TCSADRAIN, tattr)
self.is_raw = False
@contextmanager
def custom_state(self, undo=None):
"""
Enter custom terminal state, that needs to to be undone by ``undo``.
Useful, if you want to apply a custom terminal state and
have to make sure, that it gets properly reset to previous state.
"""
try:
yield
finally:
if undo:
undo()
def write(self, data: Union[str, bytes]) -> None:
"""
Write string or bytes directly to the terminal.
"""
if isinstance(data, str):
data = data.encode('utf-8')
sent = os.write(self.fd, data)
while sent:
data = data[sent:]
sent = os.write(self.fd, data)
def read(self, amount: int = 1024, timeout: Optional[float] = None) -> bytes:
"""
Blocking read from the terminal.
If nothing was sent from the terminal within ``timeout``,
empty bytes are returned.
"""
can_read, _, _ = select([self.fd], [], [], timeout)
if can_read:
return os.read(self.fd, amount)
return b''
@contextmanager
def cterminal_context():
t = TerminalContext.from_cterm()
try:
yield t
finally:
t.close()
def extract_events(data: list[str]):
if len(data) < 5:
raise Exception('not enough reports')
types = set(data)
if len(types) == 1:
return {'PRESS': data[0], 'REPEAT': data[0], 'RELEASE': None}
if len(types) == 2:
last = data.pop()
if last != data[0] and len(set(data)) == 1:
return {'PRESS': data[0], 'REPEAT': data[0], 'RELEASE': last}
raise Exception('weird reports, 2 types')
if len(types) == 3:
first = data.pop(0)
last = data.pop()
if first != last and first != data[0] and last != data[0]:
return {'PRESS': first, 'REPEAT': data[0], 'RELEASE': last}
raise Exception('weird reports, 3 types')
raise Exception('more than 3 types')
def query(term: TerminalContext, mode: int):
with term.custom_state(undo=lambda:term.write('\x1b[<u')):
term.write(f'\x1b[>{mode}u')
term.read(timeout=.1)
print('PRESS (within 5s) and HOLD (for 5s)\r')
data: list[bytes] = []
cur = term.read(timeout=5)
while cur:
data.append(cur)
cur = term.read(timeout=.5)
if len(data) > 5:
print('RELEASE\r', end='')
try:
return extract_events([b.decode('utf-8') for b in data])
except Exception as e:
print('Error:', e, '\r')
print(data, '\r')
term.read(timeout=.1)
def save(filedata: dict[str, dict[str, str]], filename, entry, mode, events):
try:
filedata[entry][mode] = events
except KeyError:
filedata[entry] = {mode: events}
with open(filename, 'w') as f:
f.write(dumps(filedata, indent=2))
def main():
mode = 1
filedata = {}
print(sys.argv)
if len(sys.argv) != 3:
print('ERROR: not enough arguments')
print('Usage: python kitty_keytester.py <mode> <json-file>')
return
mode = int(sys.argv[1])
filename = sys.argv[2]
if os.path.exists(filename):
with open(filename) as f:
filedata = loads(f.read())
with cterminal_context() as term:
while True:
with term.raw_mode():
events = query(term, mode)
if events:
print(events)
entry = input('Entry: ')
if entry:
save(filedata, filename, entry, str(mode), events)
cont = input('Continue? (y)')
if cont not in ['y', '']:
break
if __name__ == '__main__':
main()
+1 -1
View File
@@ -8,7 +8,7 @@ const path = require('path');
const COVERAGE_LINES_THRESHOLD = 40;
// Add `out-esbuild` to NODE_PATH for any remaining non-relative requires in test bundles.
// Add `out` to the NODE_PATH so absolute paths can be resolved.
const env = { ...process.env };
env.NODE_PATH = path.resolve(__dirname, '../out-esbuild');
@@ -6,7 +6,7 @@
import { BaseWindow } from './baseWindow';
import type { Terminal, ITheme } from '@xterm/xterm';
import type { IControlWindow } from '../controlBar';
import type { AddonCollection } from '../../types';
import type { AddonCollection } from 'types';
const xtermjsTheme: ITheme = Object.freeze({
foreground: '#F8F8F8',
@@ -252,11 +252,6 @@ export class OptionsWindow extends BaseWindow implements IControlWindow {
input.value = 'auto';
this._handlers.updateTerminalSize();
}
const screenElement = document.querySelector('.xterm-screen');
if (screenElement instanceof HTMLElement) {
screenElement.style.outline = this._autoResize ? ''
: 'solid thin red';
}
} else if (o === 'theme') {
value = this._getTheme();
}
+1 -1
View File
@@ -7,7 +7,7 @@ import { writeUnicodeTable } from '../../unicodeTable';
import type { IControlWindow } from '../controlBar';
import { BaseWindow } from './baseWindow';
import type { IDisposable, Terminal } from '@xterm/xterm';
import type { AddonCollection } from '../../types';
import type { AddonCollection } from 'types';
export class TestWindow extends BaseWindow implements IControlWindow {
public readonly id = 'test';
+17 -1
View File
@@ -6,7 +6,23 @@
"rootDir": ".",
"sourceMap": true,
"strict": true,
"skipLibCheck": true
"skipLibCheck": true,
"paths": {
"@xterm/addon-attach": ["../../addons/addon-attach"],
"@xterm/addon-clipboard": ["../../addons/addon-clipboard"],
"@xterm/addon-fit": ["../../addons/addon-fit"],
"@xterm/addon-image": ["../../addons/addon-image"],
"@xterm/addon-progress": ["../../addons/addon-progress"],
"@xterm/addon-search": ["../../addons/addon-search"],
"@xterm/addon-serialize": ["../../addons/addon-serialize"],
"@xterm/addon-web-fonts": ["../../addons/addon-web-fonts"],
"@xterm/addon-web-links": ["../../addons/addon-web-links"],
"@xterm/addon-webgl": ["../../addons/addon-webgl"],
"@xterm/addon-unicode11": ["../../addons/addon-unicode11"],
"@xterm/addon-unicode-graphemes": ["../../addons/addon-unicode-graphemes"],
"@xterm/addon-ligatures": ["../../addons/addon-ligatures"],
"*": ["./*"]
}
},
"include": [
"./**/*",
+1 -1
View File
@@ -9,6 +9,6 @@
"skipLibCheck": true
},
"include": [
"./**/*"
"./**/*",
]
}
+2 -6
View File
@@ -1,11 +1,7 @@
{
"references": [
{
"path": "./client"
},
{
"path": "./server"
}
{ "path": "./client" },
{ "path": "./server" }
],
"files": []
}
-2
View File
@@ -43,7 +43,6 @@ export default tseslint.config(
singleline: { delimiter: 'comma', requireLast: false }
}],
'@stylistic/type-annotation-spacing': 'warn',
'@stylistic/space-unary-ops': 'warn',
'@typescript-eslint/array-type': ['warn', { default: 'array', readonly: 'generic' }],
'@typescript-eslint/consistent-type-assertions': 'warn',
@@ -86,7 +85,6 @@ export default tseslint.config(
'eqeqeq': ['warn', 'always'],
'jsdoc/check-alignment': 'warn',
'jsdoc/check-param-names': 'warn',
'jsdoc/multiline-blocks': 'warn',
'jsdoc/no-multi-asterisks': 'warn',
'keyword-spacing': 'warn',
'max-len': ['warn', {
@@ -0,0 +1,373 @@
{
"Escape +CapsLock": {
"1": {
"PRESS": "\u001b[27;65u",
"REPEAT": "\u001b[27;65u",
"RELEASE": null
}
},
"F1 +CapsLock": {
"1": {
"PRESS": "\u001b[1;65P",
"REPEAT": "\u001b[1;65P",
"RELEASE": null
}
},
"Insert +CapsLock": {
"1": {
"PRESS": "\u001b[2;65~",
"REPEAT": "\u001b[2;65~",
"RELEASE": null
}
},
"Delete +CapsLock": {
"1": {
"PRESS": "\u001b[3;65~",
"REPEAT": "\u001b[3;65~",
"RELEASE": null
}
},
"PageUp +CapsLock": {
"1": {
"PRESS": "\u001b[5;65~",
"REPEAT": "\u001b[5;65~",
"RELEASE": null
}
},
"Dead (Backquote) +CapsLock": {
"1": {
"PRESS": "^",
"REPEAT": "^",
"RELEASE": null
}
},
"1 (Digit1) +CapsLock": {
"1": {
"PRESS": "1",
"REPEAT": "1",
"RELEASE": null
}
},
"\u00df (Minus) +CapsLock": {
"1": {
"PRESS": "\u1e9e",
"REPEAT": "\u1e9e",
"RELEASE": null
}
},
"Dead (Equal) +CapsLock": {
"1": {
"PRESS": "\u00b4",
"REPEAT": "\u00b4",
"RELEASE": null
}
},
"Backspace +CapsLock": {
"1": {
"PRESS": "\u007f",
"REPEAT": "\u007f",
"RELEASE": null
}
},
"Tab +CapsLock": {
"1": {
"PRESS": "\t",
"REPEAT": "\t",
"RELEASE": null
}
},
"Q (KeyQ) +CapsLock": {
"1": {
"PRESS": "Q",
"REPEAT": "Q",
"RELEASE": null
}
},
"\u00dc (BracketLeft) +CapsLock": {
"1": {
"PRESS": "\u00dc",
"REPEAT": "\u00dc",
"RELEASE": null
}
},
"+ (BracketRight) +CapsLock": {
"1": {
"PRESS": "+",
"REPEAT": "+",
"RELEASE": null
}
},
"Enter +CapsLock": {
"1": {
"PRESS": "\r",
"REPEAT": "\r",
"RELEASE": null
}
},
"\u00d6 (Semicolon) +CapsLock": {
"1": {
"PRESS": "\u00d6",
"REPEAT": "\u00d6",
"RELEASE": null
}
},
"\u00c4 (Quote) +CapsLock": {
"1": {
"PRESS": "\u00c4",
"REPEAT": "\u00c4",
"RELEASE": null
}
},
"# (Bashslash) +CapsLock": {
"1": {
"PRESS": "#",
"REPEAT": "#",
"RELEASE": null
}
},
"< (IntlBackslash) +CapsLock": {
"1": {
"PRESS": "<",
"REPEAT": "<",
"RELEASE": null
}
},
", (Comma) +CapsLock": {
"1": {
"PRESS": ",",
"REPEAT": ",",
"RELEASE": null
}
},
". (Period) +CapsLock": {
"1": {
"PRESS": ".",
"REPEAT": ".",
"RELEASE": null
}
},
"- (Slash) +CapsLock": {
"1": {
"PRESS": "-",
"REPEAT": "-",
"RELEASE": null
}
},
"ArrowUp +CapsLock": {
"1": {
"PRESS": "\u001b[1;65A",
"REPEAT": "\u001b[1;65A",
"RELEASE": null
}
},
"PageDown +CapsLock": {
"1": {
"PRESS": "\u001b[6;65~",
"REPEAT": "\u001b[6;65~",
"RELEASE": null
}
},
"Home +CapsLock": {
"1": {
"PRESS": "\u001b[1;65H",
"REPEAT": "\u001b[1;65H",
"RELEASE": null
}
},
"End +CapsLock": {
"1": {
"PRESS": "\u001b[1;65F",
"REPEAT": "\u001b[1;65F",
"RELEASE": null
}
},
"+ (NumpadAdd) +CapsLock": {
"1": {
"PRESS": "+",
"REPEAT": "+",
"RELEASE": null
}
},
"Enter (NumpadEnter) +CapsLock": {
"1": {
"PRESS": "\u001b[57414;65u",
"REPEAT": "\u001b[57414;65u",
"RELEASE": null
}
},
"Insert (Numpad0) +CapsLock": {
"1": {
"PRESS": "\u001b[57425;65u",
"REPEAT": "\u001b[57425;65u",
"RELEASE": null
}
},
"\u0000 (NumpadDecimal) +CapsLock": {
"1": {
"PRESS": "\u001b[57426;65u",
"REPEAT": "\u001b[57426;65u",
"RELEASE": null
}
},
"Numlock +CapsLock": {
"1": {
"PRESS": null,
"REPEAT": null,
"RELEASE": null
}
},
"CapsLock +CapsLock": {
"1": {
"PRESS": null,
"REPEAT": null,
"RELEASE": null
}
},
"Shift (ShiftLeft) +CapsLock": {
"1": {
"PRESS": null,
"REPEAT": null,
"RELEASE": null
}
},
"Shift (ShiftRight) +CapsLock": {
"1": {
"PRESS": null,
"REPEAT": null,
"RELEASE": null
}
},
"Control (ControlLeft) +CapsLock": {
"1": {
"PRESS": null,
"REPEAT": null,
"RELEASE": null
}
},
"Meta (MetaLeft) +CapsLock": {
"1": {
"PRESS": null,
"REPEAT": null,
"RELEASE": null
}
},
"Alt (AltLeft) +CapsLock": {
"1": {
"PRESS": null,
"REPEAT": null,
"RELEASE": null
}
},
"AltGraph (AltRight) +CapsLock": {
"1": {
"PRESS": null,
"REPEAT": null,
"RELEASE": null
}
},
"Control (ControlRight) +CapsLock": {
"1": {
"PRESS": null,
"REPEAT": null,
"RELEASE": null
}
},
"/ (NumpadDivide) +NumLock": {
"1": {
"PRESS": "/",
"REPEAT": "/",
"RELEASE": null
}
},
"* (NumpadMultiply) +NumLock": {
"1": {
"PRESS": "*",
"REPEAT": "*",
"RELEASE": null
}
},
"- (NumpadSubtract) +NumLock": {
"1": {
"PRESS": "-",
"REPEAT": "-",
"RELEASE": null
}
},
"0 (Numpad0) +NumLock": {
"1": {
"PRESS": "0",
"REPEAT": "0",
"RELEASE": null
}
},
"1 (Numpad1) +NumLock": {
"1": {
"PRESS": "1",
"REPEAT": "1",
"RELEASE": null
}
},
"2 (Numpad2) +NumLock": {
"1": {
"PRESS": "2",
"REPEAT": "2",
"RELEASE": null
}
},
"3 (Numpad3) +NumLock": {
"1": {
"PRESS": "3",
"REPEAT": "3",
"RELEASE": null
}
},
"4 (Numpad4) +NumLock": {
"1": {
"PRESS": "4",
"REPEAT": "4",
"RELEASE": null
}
},
"5 (Numpad5) +NumLock": {
"1": {
"PRESS": "5",
"REPEAT": "5",
"RELEASE": null
}
},
"6 (Numpad6) +NumLock": {
"1": {
"PRESS": "6",
"REPEAT": "6",
"RELEASE": null
}
},
"7 (Numpad7) +NumLock": {
"1": {
"PRESS": "7",
"REPEAT": "7",
"RELEASE": null
}
},
"8 (Numpad8) +NumLock": {
"1": {
"PRESS": "8",
"REPEAT": "8",
"RELEASE": null
}
},
"9 (Numpad9) +NumLock": {
"1": {
"PRESS": "9",
"REPEAT": "9",
"RELEASE": null
}
},
", (NumpadDecimal) +NumLock": {
"1": {
"PRESS": ",",
"REPEAT": ",",
"RELEASE": null
}
}
}
@@ -0,0 +1,702 @@
{
"Escape": {
"1": {
"PRESS": "\u001b[27u",
"REPEAT": "\u001b[27u",
"RELEASE": null
}
},
"F1": {
"1": {
"PRESS": "\u001b[P",
"REPEAT": "\u001b[P",
"RELEASE": null
}
},
"F2": {
"1": {
"PRESS": "\u001b[Q",
"REPEAT": "\u001b[Q",
"RELEASE": null
}
},
"F3": {
"1": {
"PRESS": "\u001b[13~",
"REPEAT": "\u001b[13~",
"RELEASE": null
}
},
"F4": {
"1": {
"PRESS": "\u001b[S",
"REPEAT": "\u001b[S",
"RELEASE": null
}
},
"F5": {
"1": {
"PRESS": "\u001b[15~",
"REPEAT": "\u001b[15~",
"RELEASE": null
}
},
"F6": {
"1": {
"PRESS": "\u001b[17~",
"REPEAT": "\u001b[17~",
"RELEASE": null
}
},
"F7": {
"1": {
"PRESS": "\u001b[18~",
"REPEAT": "\u001b[18~",
"RELEASE": null
}
},
"F8": {
"1": {
"PRESS": "\u001b[19~",
"REPEAT": "\u001b[19~",
"RELEASE": null
}
},
"F9": {
"1": {
"PRESS": "\u001b[20~",
"REPEAT": "\u001b[20~",
"RELEASE": null
}
},
"F10": {
"1": {
"PRESS": "\u001b[21~",
"REPEAT": "\u001b[21~",
"RELEASE": null
}
},
"F11": {
"1": {
"PRESS": "\u001b[23~",
"REPEAT": "\u001b[23~",
"RELEASE": null
}
},
"F12": {
"1": {
"PRESS": "\u001b[24~",
"REPEAT": "\u001b[24~",
"RELEASE": null
}
},
"Insert": {
"1": {
"PRESS": "\u001b[2~",
"REPEAT": "\u001b[2~",
"RELEASE": null
}
},
"Delete": {
"1": {
"PRESS": "\u001b[3~",
"REPEAT": "\u001b[3~",
"RELEASE": null
}
},
"PageUp": {
"1": {
"PRESS": "\u001b[5~",
"REPEAT": "\u001b[5~",
"RELEASE": null
}
},
"PageDown": {
"1": {
"PRESS": "\u001b[6~",
"REPEAT": "\u001b[6~",
"RELEASE": null
}
},
"Home": {
"1": {
"PRESS": "\u001b[H",
"REPEAT": "\u001b[H",
"RELEASE": null
}
},
"End": {
"1": {
"PRESS": "\u001b[F",
"REPEAT": "\u001b[F",
"RELEASE": null
}
},
"Dead (Backquote)": {
"1": {
"PRESS": "^",
"REPEAT": "^",
"RELEASE": null
}
},
"1 (Digit1)": {
"1": {
"PRESS": "1",
"REPEAT": "1",
"RELEASE": null
}
},
"2 (Digit2)": {
"1": {
"PRESS": "2",
"REPEAT": "2",
"RELEASE": null
}
},
"3 (Digit3)": {
"1": {
"PRESS": "3",
"REPEAT": "3",
"RELEASE": null
}
},
"4 (Digit4)": {
"1": {
"PRESS": "4",
"REPEAT": "4",
"RELEASE": null
}
},
"5 (Digit5)": {
"1": {
"PRESS": "5",
"REPEAT": "5",
"RELEASE": null
}
},
"6 (Digit6)": {
"1": {
"PRESS": "6",
"REPEAT": "6",
"RELEASE": null
}
},
"7 (Digit7)": {
"1": {
"PRESS": "7",
"REPEAT": "7",
"RELEASE": null
}
},
"8 (Digit8)": {
"1": {
"PRESS": "8",
"REPEAT": "8",
"RELEASE": null
}
},
"9 (Digit9)": {
"1": {
"PRESS": "9",
"REPEAT": "9",
"RELEASE": null
}
},
"0 (Digit0)": {
"1": {
"PRESS": "0",
"REPEAT": "0",
"RELEASE": null
}
},
"\u00df (Minus)": {
"1": {
"PRESS": "\u00df",
"REPEAT": "\u00df",
"RELEASE": null
}
},
"Dead (Equal)": {
"1": {
"PRESS": "\u00b4",
"REPEAT": "\u00b4",
"RELEASE": null
}
},
"Backspace": {
"1": {
"PRESS": "\u007f",
"REPEAT": "\u007f",
"RELEASE": null
}
},
"Tab": {
"1": {
"PRESS": "\t",
"REPEAT": "\t",
"RELEASE": null
}
},
"q (KeyQ)": {
"1": {
"PRESS": "q",
"REPEAT": "q",
"RELEASE": null
}
},
"w (KeyW)": {
"1": {
"PRESS": "w",
"REPEAT": "w",
"RELEASE": null
}
},
"e (KeyE)": {
"1": {
"PRESS": "e",
"REPEAT": "e",
"RELEASE": null
}
},
"r (KeyR)": {
"1": {
"PRESS": "r",
"REPEAT": "r",
"RELEASE": null
}
},
"t (KeyT)": {
"1": {
"PRESS": "t",
"REPEAT": "t",
"RELEASE": null
}
},
"z (KeyY)": {
"1": {
"PRESS": "z",
"REPEAT": "z",
"RELEASE": null
}
},
"u (KeyU)": {
"1": {
"PRESS": "u",
"REPEAT": "u",
"RELEASE": null
}
},
"i (KeyI)": {
"1": {
"PRESS": "i",
"REPEAT": "i",
"RELEASE": null
}
},
"o (KeyO)": {
"1": {
"PRESS": "o",
"REPEAT": "o",
"RELEASE": null
}
},
"p (KeyP)": {
"1": {
"PRESS": "p",
"REPEAT": "p",
"RELEASE": null
}
},
"\u00fc (BracketLeft)": {
"1": {
"PRESS": "\u00fc",
"REPEAT": "\u00fc",
"RELEASE": null
}
},
"+ (BracketRight)": {
"1": {
"PRESS": "+",
"REPEAT": "+",
"RELEASE": null
}
},
"Enter": {
"1": {
"PRESS": "\r",
"REPEAT": "\r",
"RELEASE": null
}
},
"a (KeyA)": {
"1": {
"PRESS": "a",
"REPEAT": "a",
"RELEASE": null
}
},
"s (KeyS)": {
"1": {
"PRESS": "s",
"REPEAT": "s",
"RELEASE": null
}
},
"d (KeyD)": {
"1": {
"PRESS": "d",
"REPEAT": "d",
"RELEASE": null
}
},
"f (KeyF)": {
"1": {
"PRESS": "f",
"REPEAT": "f",
"RELEASE": null
}
},
"g (KeyG)": {
"1": {
"PRESS": "g",
"REPEAT": "g",
"RELEASE": null
}
},
"h (KeyH)": {
"1": {
"PRESS": "h",
"REPEAT": "h",
"RELEASE": null
}
},
"j (KeyJ)": {
"1": {
"PRESS": "j",
"REPEAT": "j",
"RELEASE": null
}
},
"k (KeyK)": {
"1": {
"PRESS": "k",
"REPEAT": "k",
"RELEASE": null
}
},
"l (KeyL)": {
"1": {
"PRESS": "l",
"REPEAT": "l",
"RELEASE": null
}
},
"\u00f6 (Semicolon)": {
"1": {
"PRESS": "\u00f6",
"REPEAT": "\u00f6",
"RELEASE": null
}
},
"\u00e4 (Quote)": {
"1": {
"PRESS": "\u00e4",
"REPEAT": "\u00e4",
"RELEASE": null
}
},
"# (Backslash)": {
"1": {
"PRESS": "#",
"REPEAT": "#",
"RELEASE": null
}
},
"< (IntlBackslash)": {
"1": {
"PRESS": "<",
"REPEAT": "<",
"RELEASE": null
}
},
"y (KeyZ)": {
"1": {
"PRESS": "y",
"REPEAT": "y",
"RELEASE": null
}
},
"x (KeyX)": {
"1": {
"PRESS": "x",
"REPEAT": "x",
"RELEASE": null
}
},
"c (KeyC)": {
"1": {
"PRESS": "c",
"REPEAT": "c",
"RELEASE": null
}
},
"v (KeyV)": {
"1": {
"PRESS": "v",
"REPEAT": "v",
"RELEASE": null
}
},
"b (KeyB)": {
"1": {
"PRESS": "b",
"REPEAT": "b",
"RELEASE": null
}
},
"n (KeyN)": {
"1": {
"PRESS": "n",
"REPEAT": "n",
"RELEASE": null
}
},
"m (KeyM)": {
"1": {
"PRESS": "m",
"REPEAT": "m",
"RELEASE": null
}
},
", (Comma)": {
"1": {
"PRESS": ",",
"REPEAT": ",",
"RELEASE": null
}
},
". (Period)": {
"1": {
"PRESS": ".",
"REPEAT": ".",
"RELEASE": null
}
},
"- (Slash)": {
"1": {
"PRESS": "-",
"REPEAT": "-",
"RELEASE": null
}
},
"ArrowLeft": {
"1": {
"PRESS": "\u001b[D",
"REPEAT": "\u001b[D",
"RELEASE": null
}
},
"ArrowUp": {
"1": {
"PRESS": "\u001b[A",
"REPEAT": "\u001b[A",
"RELEASE": null
}
},
"ArrowRight": {
"1": {
"PRESS": "\u001b[C",
"REPEAT": "\u001b[C",
"RELEASE": null
}
},
"ArrowDown": {
"1": {
"PRESS": "\u001b[B",
"REPEAT": "\u001b[B",
"RELEASE": null
}
},
"/ (NumpadDivide)": {
"1": {
"PRESS": "/",
"REPEAT": "/",
"RELEASE": null
}
},
"* (NumpadMultiply)": {
"1": {
"PRESS": "*",
"REPEAT": "*",
"RELEASE": null
}
},
"- (NumpadSubtract)": {
"1": {
"PRESS": "-",
"REPEAT": "-",
"RELEASE": null
}
},
"Home (Numpad7)": {
"1": {
"PRESS": "\u001b[57423u",
"REPEAT": "\u001b[57423u",
"RELEASE": null
}
},
"ArrowUp (Numpad8)": {
"1": {
"PRESS": "\u001b[57419u",
"REPEAT": "\u001b[57419u",
"RELEASE": null
}
},
"PageUp (Numpad9)": {
"1": {
"PRESS": "\u001b[57421u",
"REPEAT": "\u001b[57421u",
"RELEASE": null
}
},
"+ (NumpadAdd)": {
"1": {
"PRESS": "+",
"REPEAT": "+",
"RELEASE": null
}
},
"ArrowLeft (Numpad4)": {
"1": {
"PRESS": "\u001b[57417u",
"REPEAT": "\u001b[57417u",
"RELEASE": null
}
},
"Clear (Numpad5)": {
"1": {
"PRESS": "\u001b[E",
"REPEAT": "\u001b[E",
"RELEASE": null
}
},
"ArrowRight (Numpad6)": {
"1": {
"PRESS": "\u001b[57418u",
"REPEAT": "\u001b[57418u",
"RELEASE": null
}
},
"End (Numpad1)": {
"1": {
"PRESS": "\u001b[57424u",
"REPEAT": "\u001b[57424u",
"RELEASE": null
}
},
"ArrowDown (Numpad2)": {
"1": {
"PRESS": "\u001b[57420u",
"REPEAT": "\u001b[57420u",
"RELEASE": null
}
},
"PageDown (Numpad3)": {
"1": {
"PRESS": "\u001b[57422u",
"REPEAT": "\u001b[57422u",
"RELEASE": null
}
},
"Enter (NumpadEnter)": {
"1": {
"PRESS": "\u001b[57414u",
"REPEAT": "\u001b[57414u",
"RELEASE": null
}
},
"Insert (Numpad0)": {
"1": {
"PRESS": "\u001b[57425u",
"REPEAT": "\u001b[57425u",
"RELEASE": null
}
},
"\u0000 (NumpadDecimal)": {
"1": {
"PRESS": "\u001b[57426u",
"REPEAT": "\u001b[57426u",
"RELEASE": null
}
},
"Numlock": {
"1": {
"PRESS": null,
"REPEAT": null,
"RELEASE": null
}
},
"CapsLock": {
"1": {
"PRESS": null,
"REPEAT": null,
"RELEASE": null
}
},
"Shift (ShiftLeft)": {
"1": {
"PRESS": null,
"REPEAT": null,
"RELEASE": null
}
},
"Shift (ShiftRight)": {
"1": {
"PRESS": null,
"REPEAT": null,
"RELEASE": null
}
},
"Control (ControlLeft)": {
"1": {
"PRESS": null,
"REPEAT": null,
"RELEASE": null
}
},
"Meta (MetaLeft)": {
"1": {
"PRESS": null,
"REPEAT": null,
"RELEASE": null
}
},
"Alt (AltLeft)": {
"1": {
"PRESS": null,
"REPEAT": null,
"RELEASE": null
}
},
" (Space)": {
"1": {
"PRESS": " ",
"REPEAT": " ",
"RELEASE": null
}
},
"AltGraph (AltRight)": {
"1": {
"PRESS": null,
"REPEAT": null,
"RELEASE": null
}
},
"Control (ControlRight)": {
"1": {
"PRESS": null,
"REPEAT": null,
"RELEASE": null
}
}
}
@@ -0,0 +1,331 @@
{
"Escape +Shift": {
"1": {
"PRESS": "\u001b[27;2u",
"REPEAT": "\u001b[27;2u",
"RELEASE": null
}
},
"F1 +Shift": {
"1": {
"PRESS": "\u001b[1;2P",
"REPEAT": "\u001b[1;2P",
"RELEASE": null
}
},
"Delete +Shift": {
"1": {
"PRESS": "\u001b[3;2~",
"REPEAT": "\u001b[3;2~",
"RELEASE": null
}
},
"PageUp +Shift": {
"1": {
"PRESS": "\u001b[5;2~",
"REPEAT": "\u001b[5;2~",
"RELEASE": null
}
},
"Dead (Backquote) +Shift": {
"1": {
"PRESS": "\u00b0",
"REPEAT": "\u00b0",
"RELEASE": null
}
},
"! (Digit1) +Shift": {
"1": {
"PRESS": "!",
"REPEAT": "!",
"RELEASE": null
}
},
"\" (Digit2) +Shift": {
"1": {
"PRESS": "\"",
"REPEAT": "\"",
"RELEASE": null
}
},
"\u00a7 (Digit3) +Shift": {
"1": {
"PRESS": "\u00a7",
"REPEAT": "\u00a7",
"RELEASE": null
}
},
"$ (Digit4) +Shifft": {
"1": {
"PRESS": "$",
"REPEAT": "$",
"RELEASE": null
}
},
"% (Digit5) +Shift": {
"1": {
"PRESS": "%",
"REPEAT": "%",
"RELEASE": null
}
},
"& (Digit6) +Shift": {
"1": {
"PRESS": "&",
"REPEAT": "&",
"RELEASE": null
}
},
"/ (Digit7) +Shift": {
"1": {
"PRESS": "/",
"REPEAT": "/",
"RELEASE": null
}
},
"( (Digit8) +Shift": {
"1": {
"PRESS": "(",
"REPEAT": "(",
"RELEASE": null
}
},
") (Digit9) +Shift": {
"1": {
"PRESS": ")",
"REPEAT": ")",
"RELEASE": null
}
},
"= (Digit0) +Shift": {
"1": {
"PRESS": "=",
"REPEAT": "=",
"RELEASE": null
}
},
"? (Minus) +Shift": {
"1": {
"PRESS": "?",
"REPEAT": "?",
"RELEASE": null
}
},
"Backspace +Shift": {
"1": {
"PRESS": "\u001b[127;2u",
"REPEAT": "\u001b[127;2u",
"RELEASE": null
}
},
"Tab +Shift": {
"1": {
"PRESS": "\u001b[9;2u",
"REPEAT": "\u001b[9;2u",
"RELEASE": null
}
},
"Q (KeyQ) +Shift": {
"1": {
"PRESS": "Q",
"REPEAT": "Q",
"RELEASE": null
}
},
"\u00dc (BracketLeft) +Shift": {
"1": {
"PRESS": "\u00dc",
"REPEAT": "\u00dc",
"RELEASE": null
}
},
"* (BracketRight) +Shift": {
"1": {
"PRESS": "*",
"REPEAT": "*",
"RELEASE": null
}
},
"Enter +Shift": {
"1": {
"PRESS": "\u001b[13;2u",
"REPEAT": "\u001b[13;2u",
"RELEASE": null
}
},
"\u00d6 (Semicolon) +Shift": {
"1": {
"PRESS": "\u00d6",
"REPEAT": "\u00d6",
"RELEASE": null
}
},
"\u00c4 (Quote) +Shift": {
"1": {
"PRESS": "\u00c4",
"REPEAT": "\u00c4",
"RELEASE": null
}
},
"' (Backslash) +Shift": {
"1": {
"PRESS": "'",
"REPEAT": "'",
"RELEASE": null
}
},
"> (IntlBackslash) +Shift": {
"1": {
"PRESS": ">",
"REPEAT": ">",
"RELEASE": null
}
},
"; (Comma) +Shift": {
"1": {
"PRESS": ";",
"REPEAT": ";",
"RELEASE": null
}
},
": (Period) +Shift": {
"1": {
"PRESS": ":",
"REPEAT": ":",
"RELEASE": null
}
},
"_ (Slash) +Shift": {
"1": {
"PRESS": "_",
"REPEAT": "_",
"RELEASE": null
}
},
"ArrowUp +Shift": {
"1": {
"PRESS": "\u001b[1;2A",
"REPEAT": "\u001b[1;2A",
"RELEASE": null
}
},
"PageDown +Shift": {
"1": {
"PRESS": "\u001b[6;2~",
"REPEAT": "\u001b[6;2~",
"RELEASE": null
}
},
"Home +Shift": {
"1": {
"PRESS": "\u001b[1;2H",
"REPEAT": "\u001b[1;2H",
"RELEASE": null
}
},
"Home (Numpad7) +Shift": {
"1": {
"PRESS": "\u001b[57423;2u",
"REPEAT": "\u001b[57423;2u",
"RELEASE": null
}
},
"ArrowUp (Numapd8) +Shift": {
"1": {
"PRESS": "\u001b[57419;2u",
"REPEAT": "\u001b[57419;2u",
"RELEASE": null
}
},
"Clear (Numpad5) +Shift": {
"1": {
"PRESS": "\u001b[1;2E",
"REPEAT": "\u001b[1;2E",
"RELEASE": null
}
},
"Enter (NumpadEnter) +Shift": {
"1": {
"PRESS": "\u001b[57414;2u",
"REPEAT": "\u001b[57414;2u",
"RELEASE": null
}
},
"Insert (Numpad0) +Shift": {
"1": {
"PRESS": "\u001b[57425;2u",
"REPEAT": "\u001b[57425;2u",
"RELEASE": null
}
},
"\u0000 (NumpadDecimal) +Shift": {
"1": {
"PRESS": "\u001b[57426;2u",
"REPEAT": "\u001b[57426;2u",
"RELEASE": null
}
},
"Numlock +Shift": {
"1": {
"PRESS": null,
"REPEAT": null,
"RELEASE": null
}
},
"CapsLock +Shift": {
"1": {
"PRESS": null,
"REPEAT": null,
"RELEASE": null
}
},
"Shift (ShiftRight) +Shift": {
"1": {
"PRESS": null,
"REPEAT": null,
"RELEASE": null
}
},
"Control (ControlLeft) +Shift": {
"1": {
"PRESS": null,
"REPEAT": null,
"RELEASE": null
}
},
"Meta (MetaLeft) +Shift": {
"1": {
"PRESS": null,
"REPEAT": null,
"RELEASE": null
}
},
"Alt (AltLeft) +Shift": {
"1": {
"PRESS": null,
"REPEAT": null,
"RELEASE": null
}
},
" (Space) +Shift": {
"1": {
"PRESS": " ",
"REPEAT": " ",
"RELEASE": null
}
},
"AltGraph (AltRight) +Shift": {
"1": {
"PRESS": null,
"REPEAT": null,
"RELEASE": null
}
},
"Control (ControlRight) +Shift": {
"1": {
"PRESS": null,
"REPEAT": null,
"RELEASE": null
}
}
}
+7 -5
View File
@@ -3,13 +3,15 @@
"description": "A headless terminal component that runs in Node.js",
"version": "6.0.0",
"main": "lib-headless/xterm-headless.js",
"module": "lib/xterm.mjs",
"module": "lib-headless/xterm-headless.mjs",
"types": "typings/xterm-headless.d.ts",
"exports": {
"types": "./typings/xterm-headless.d.ts",
"import": "./lib-headless/xterm-headless.mjs",
"require": "./lib-headless/xterm-headless.js"
},
"repository": "https://github.com/xtermjs/xterm.js",
"license": "MIT",
"workspaces": [
"addons/*"
],
"keywords": [
"cli",
"command-line",
@@ -25,4 +27,4 @@
"webgl",
"xterm"
]
}
}
-39
View File
@@ -1,39 +0,0 @@
/**
* Smoke test: @xterm/headless loads and runs in Node.js (require).
*/
const { strictEqual } = require('node:assert');
const { Terminal } = require('./lib-headless/xterm-headless.js');
function writeSync(term, data) {
return new Promise(resolve => term.write(data, resolve));
}
function lineEquals(term, index, text) {
strictEqual(term.buffer.active.getLine(index).translateToString(true), text);
}
(async () => {
const term = new Terminal({ cols: 80, rows: 24, allowProposedApi: true });
strictEqual(term.cols, 80);
strictEqual(term.rows, 24);
await writeSync(term, 'hello from node');
lineEquals(term, 0, 'hello from node');
await writeSync(term, 'foo');
await writeSync(term, 'bar');
await writeSync(term, '文');
lineEquals(term, 0, 'hello from nodefoobar文');
term.resize(60, 15);
strictEqual(term.cols, 60);
strictEqual(term.rows, 15);
term.dispose();
strictEqual(term._core._store.isDisposed, true);
console.log('headless package (CJS): load, write, buffer, resize, dispose OK');
})().catch(err => {
console.error(err);
process.exit(1);
});
-29
View File
@@ -1,29 +0,0 @@
/**
* Smoke test: @xterm/headless loads and runs in Node.js (ESM + CJS).
*/
import { createRequire } from 'node:module';
import { strictEqual } from 'node:assert';
const require = createRequire(import.meta.url);
function writeSync(term, data) {
return new Promise(resolve => term.write(data, resolve));
}
function lineEquals(term, index, text) {
strictEqual(term.buffer.active.getLine(index).translateToString(true), text);
}
const { Terminal: TerminalEsm } = await import('./lib-headless/xterm-headless.mjs');
const termEsm = new TerminalEsm({ cols: 80, rows: 24 });
await writeSync(termEsm, 'esm ok');
lineEquals(termEsm, 0, 'esm ok');
termEsm.dispose();
const { Terminal: TerminalCjs } = require('./lib-headless/xterm-headless.js');
const termCjs = new TerminalCjs({ cols: 40, rows: 10 });
await writeSync(termCjs, 'cjs ok');
lineEquals(termCjs, 0, 'cjs ok');
termCjs.dispose();
console.log('headless package: ESM and CJS load, write, and buffer read OK');
+706 -9
View File
File diff suppressed because it is too large Load Diff

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