@svelterm/core 0.1.0 → 0.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +465 -0
- package/README.md +42 -29
- package/dist/src/cli/build.d.ts +13 -0
- package/dist/src/cli/build.js +119 -0
- package/dist/src/cli/bundle.d.ts +25 -0
- package/dist/src/cli/bundle.js +61 -0
- package/dist/src/cli/dev.d.ts +10 -0
- package/dist/src/cli/dev.js +152 -0
- package/dist/src/cli/devtools.d.ts +9 -0
- package/dist/src/cli/devtools.js +47 -0
- package/dist/src/cli/init.d.ts +8 -0
- package/dist/src/cli/init.js +153 -0
- package/dist/src/cli/main.d.ts +9 -0
- package/dist/src/cli/main.js +52 -0
- package/dist/src/cli/svt-bin.d.ts +2 -0
- package/dist/src/cli/svt-bin.js +6 -0
- package/dist/src/cli/svt.d.ts +14 -0
- package/dist/src/cli/svt.js +76 -0
- package/dist/src/components/text-buffer.js +8 -5
- package/dist/src/css/animation-runner.d.ts +15 -6
- package/dist/src/css/animation-runner.js +80 -29
- package/dist/src/css/animation.d.ts +12 -0
- package/dist/src/css/animation.js +21 -0
- package/dist/src/css/calc.js +4 -3
- package/dist/src/css/color.d.ts +19 -0
- package/dist/src/css/color.js +371 -62
- package/dist/src/css/compute.d.ts +31 -4
- package/dist/src/css/compute.js +273 -34
- package/dist/src/css/defaults.d.ts +1 -1
- package/dist/src/css/defaults.js +9 -0
- package/dist/src/css/easing.d.ts +9 -0
- package/dist/src/css/easing.js +95 -0
- package/dist/src/css/incremental.d.ts +1 -1
- package/dist/src/css/incremental.js +2 -2
- package/dist/src/css/interpolate.d.ts +13 -0
- package/dist/src/css/interpolate.js +41 -0
- package/dist/src/css/parser.js +59 -3
- package/dist/src/css/pseudo-elements.d.ts +9 -0
- package/dist/src/css/pseudo-elements.js +97 -0
- package/dist/src/css/selector.d.ts +17 -2
- package/dist/src/css/selector.js +128 -13
- package/dist/src/css/specificity.js +17 -6
- package/dist/src/css/values.d.ts +6 -1
- package/dist/src/css/values.js +13 -6
- package/dist/src/debug/context.d.ts +13 -0
- package/dist/src/debug/context.js +11 -0
- package/dist/src/debug/css.d.ts +12 -0
- package/dist/src/debug/css.js +28 -0
- package/dist/src/debug/dom.d.ts +17 -0
- package/dist/src/debug/dom.js +92 -0
- package/dist/src/devtools/DevTools.compiled.js +327 -0
- package/dist/src/devtools/DevTools.css.js +1 -0
- package/dist/src/devtools/client.d.ts +36 -0
- package/dist/src/devtools/client.js +76 -0
- package/dist/src/framelog.d.ts +54 -0
- package/dist/src/framelog.js +99 -0
- package/dist/src/headless.js +12 -4
- package/dist/src/index.d.ts +66 -3
- package/dist/src/index.js +610 -81
- package/dist/src/input/checkable.d.ts +8 -0
- package/dist/src/input/checkable.js +66 -0
- package/dist/src/input/details.d.ts +6 -0
- package/dist/src/input/details.js +34 -0
- package/dist/src/input/focus.d.ts +6 -0
- package/dist/src/input/focus.js +27 -9
- package/dist/src/input/keyboard.d.ts +2 -2
- package/dist/src/input/keyboard.js +32 -5
- package/dist/src/input/label.d.ts +8 -0
- package/dist/src/input/label.js +53 -0
- package/dist/src/input/modal.d.ts +9 -0
- package/dist/src/input/modal.js +28 -0
- package/dist/src/input/mouse.d.ts +2 -2
- package/dist/src/input/mouse.js +15 -2
- package/dist/src/input/select.d.ts +12 -0
- package/dist/src/input/select.js +63 -0
- package/dist/src/input/selection.d.ts +48 -0
- package/dist/src/input/selection.js +150 -0
- package/dist/src/layout/engine.d.ts +2 -0
- package/dist/src/layout/engine.js +1092 -142
- package/dist/src/layout/flex.js +4 -4
- package/dist/src/layout/size.js +3 -2
- package/dist/src/layout/text.d.ts +3 -2
- package/dist/src/layout/text.js +96 -17
- package/dist/src/layout/unicode.d.ts +20 -0
- package/dist/src/layout/unicode.js +121 -0
- package/dist/src/render/animation-clock.d.ts +57 -0
- package/dist/src/render/animation-clock.js +221 -0
- package/dist/src/render/ansi-text.d.ts +26 -0
- package/dist/src/render/ansi-text.js +131 -0
- package/dist/src/render/ansi.d.ts +18 -0
- package/dist/src/render/ansi.js +64 -19
- package/dist/src/render/border.js +166 -17
- package/dist/src/render/buffer.d.ts +1 -0
- package/dist/src/render/buffer.js +5 -2
- package/dist/src/render/clock.d.ts +35 -0
- package/dist/src/render/clock.js +67 -0
- package/dist/src/render/color-depth.d.ts +8 -0
- package/dist/src/render/color-depth.js +59 -0
- package/dist/src/render/context.d.ts +1 -0
- package/dist/src/render/context.js +17 -21
- package/dist/src/render/cursor-emit.d.ts +18 -0
- package/dist/src/render/cursor-emit.js +50 -0
- package/dist/src/render/diff.d.ts +12 -0
- package/dist/src/render/diff.js +120 -0
- package/dist/src/render/generation.d.ts +9 -0
- package/dist/src/render/generation.js +14 -0
- package/dist/src/render/graphics-layer.d.ts +27 -0
- package/dist/src/render/graphics-layer.js +86 -0
- package/dist/src/render/image.d.ts +27 -0
- package/dist/src/render/image.js +113 -0
- package/dist/src/render/incremental-paint.d.ts +7 -3
- package/dist/src/render/incremental-paint.js +52 -79
- package/dist/src/render/inline.d.ts +59 -0
- package/dist/src/render/inline.js +219 -0
- package/dist/src/render/kitty-graphics.d.ts +24 -0
- package/dist/src/render/kitty-graphics.js +58 -0
- package/dist/src/render/paint-text.js +68 -22
- package/dist/src/render/paint.d.ts +8 -1
- package/dist/src/render/paint.js +358 -31
- package/dist/src/render/png.d.ts +13 -0
- package/dist/src/render/png.js +145 -0
- package/dist/src/render/scrollbar.d.ts +8 -2
- package/dist/src/render/scrollbar.js +71 -14
- package/dist/src/render/snapshot.js +3 -1
- package/dist/src/renderer/default.d.ts +7 -0
- package/dist/src/renderer/default.js +11 -0
- package/dist/src/renderer/index.d.ts +8 -2
- package/dist/src/renderer/index.js +4 -2
- package/dist/src/renderer/node.d.ts +109 -0
- package/dist/src/renderer/node.js +165 -1
- package/dist/src/terminal/capabilities.d.ts +33 -0
- package/dist/src/terminal/capabilities.js +66 -0
- package/dist/src/terminal/clipboard.d.ts +9 -0
- package/dist/src/terminal/clipboard.js +39 -0
- package/dist/src/terminal/io.d.ts +82 -0
- package/dist/src/terminal/io.js +155 -0
- package/dist/src/terminal/screen.d.ts +3 -10
- package/dist/src/terminal/screen.js +5 -28
- package/dist/src/terminal/stdin-router.d.ts +8 -5
- package/dist/src/terminal/stdin-router.js +22 -11
- package/dist/src/utils/node-map.d.ts +24 -0
- package/dist/src/utils/node-map.js +75 -0
- package/dist/src/vite/config.d.ts +62 -0
- package/dist/src/vite/config.js +191 -0
- package/docs/compatibility.md +67 -0
- package/docs/debug/devtools.md +40 -0
- package/docs/debug/svt.md +50 -0
- package/docs/distribution.md +106 -0
- package/docs/elements.md +120 -0
- package/docs/getting-started.md +177 -0
- package/docs/guide/css.md +187 -0
- package/docs/guide/input.md +143 -0
- package/docs/guide/layout.md +171 -0
- package/docs/guide/theming.md +94 -0
- package/docs/how-it-works.md +115 -0
- package/docs/inline-mode.md +77 -0
- package/docs/layout.md +112 -0
- package/docs/motion.md +91 -0
- package/docs/reference/README.md +65 -0
- package/docs/reference/css/properties/border-corner.md +82 -0
- package/docs/reference/css/properties/border-style.md +168 -0
- package/docs/reference.md +227 -0
- package/docs/selectors.md +80 -0
- package/docs/terminal-css.md +149 -0
- package/docs/terminals.md +83 -0
- package/package.json +28 -7
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Clipboard writes. OSC 52 travels in-band (works over ssh and through
|
|
3
|
+
* multiplexers that pass it on); a platform tool runs as well when one
|
|
4
|
+
* exists, covering terminals with OSC 52 disabled. Failures are silent —
|
|
5
|
+
* copying is best-effort by nature here.
|
|
6
|
+
*/
|
|
7
|
+
export function osc52Copy(text) {
|
|
8
|
+
const base64 = typeof Buffer !== 'undefined'
|
|
9
|
+
? Buffer.from(text, 'utf8').toString('base64')
|
|
10
|
+
: btoa(String.fromCharCode(...new TextEncoder().encode(text)));
|
|
11
|
+
return `\x1b]52;c;${base64}\x07`;
|
|
12
|
+
}
|
|
13
|
+
/** The platform clipboard command, if this platform has a common one. */
|
|
14
|
+
function platformCopyCommand() {
|
|
15
|
+
if (typeof process === 'undefined')
|
|
16
|
+
return null;
|
|
17
|
+
switch (process.platform) {
|
|
18
|
+
case 'darwin': return { command: 'pbcopy', args: [] };
|
|
19
|
+
case 'linux':
|
|
20
|
+
return process.env.WAYLAND_DISPLAY
|
|
21
|
+
? { command: 'wl-copy', args: [] }
|
|
22
|
+
: { command: 'xclip', args: ['-selection', 'clipboard'] };
|
|
23
|
+
case 'win32': return { command: 'clip', args: [] };
|
|
24
|
+
default: return null;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
/** Write text to the clipboard: OSC 52 through `write`, plus a platform tool. */
|
|
28
|
+
export function copyToClipboard(text, write) {
|
|
29
|
+
write(osc52Copy(text));
|
|
30
|
+
const tool = platformCopyCommand();
|
|
31
|
+
if (!tool)
|
|
32
|
+
return;
|
|
33
|
+
import('node:child_process').then(({ spawn }) => {
|
|
34
|
+
const child = spawn(tool.command, tool.args, { stdio: ['pipe', 'ignore', 'ignore'] });
|
|
35
|
+
child.on('error', () => { });
|
|
36
|
+
child.stdin.on('error', () => { });
|
|
37
|
+
child.stdin.end(text);
|
|
38
|
+
}).catch(() => { });
|
|
39
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Terminal IO interface — abstracts over the physical terminal.
|
|
3
|
+
*
|
|
4
|
+
* Two implementations:
|
|
5
|
+
* - ProcessIO: passthrough to process.stdout/stdin (Node.js terminal)
|
|
6
|
+
* - In-process IO: connects to a VT100 emulator in the same JS context (browser)
|
|
7
|
+
*/
|
|
8
|
+
export interface TerminalIO {
|
|
9
|
+
/** Write a string to the terminal output */
|
|
10
|
+
write(data: string): void;
|
|
11
|
+
/** Get current terminal dimensions */
|
|
12
|
+
getSize(): {
|
|
13
|
+
width: number;
|
|
14
|
+
height: number;
|
|
15
|
+
};
|
|
16
|
+
/** Subscribe to incoming data (keyboard, mouse, query responses) */
|
|
17
|
+
onData(callback: (data: Buffer) => void): void;
|
|
18
|
+
/** Subscribe to terminal resize */
|
|
19
|
+
onResize(callback: () => void): void;
|
|
20
|
+
/** Enable raw input mode (no echo, no line buffering) */
|
|
21
|
+
enableRawMode(): void;
|
|
22
|
+
/** Disable raw input mode */
|
|
23
|
+
disableRawMode(): void;
|
|
24
|
+
/** Clean up all listeners */
|
|
25
|
+
dispose(): void;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Passthrough to process.stdout/stdin — the default for Node.js terminal apps.
|
|
29
|
+
*
|
|
30
|
+
* When stdin is not a TTY — `curl app.mjs | node -` delivers the script
|
|
31
|
+
* itself on stdin — input falls back to the controlling terminal
|
|
32
|
+
* (`/dev/tty`) so the app stays interactive.
|
|
33
|
+
*/
|
|
34
|
+
export declare class ProcessIO implements TerminalIO {
|
|
35
|
+
private dataCallbacks;
|
|
36
|
+
private resizeCallbacks;
|
|
37
|
+
private input;
|
|
38
|
+
private onInputData;
|
|
39
|
+
private onStdoutResize;
|
|
40
|
+
private listening;
|
|
41
|
+
private rawModeWanted;
|
|
42
|
+
private disposed;
|
|
43
|
+
constructor();
|
|
44
|
+
private reopenControllingTerminal;
|
|
45
|
+
write(data: string): void;
|
|
46
|
+
getSize(): {
|
|
47
|
+
width: number;
|
|
48
|
+
height: number;
|
|
49
|
+
};
|
|
50
|
+
onData(callback: (data: Buffer) => void): void;
|
|
51
|
+
onResize(callback: () => void): void;
|
|
52
|
+
enableRawMode(): void;
|
|
53
|
+
disableRawMode(): void;
|
|
54
|
+
dispose(): void;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* In-process IO — connects svelterm to a consumer in the same JS context.
|
|
58
|
+
* Used for browser-based terminal rendering (svelterm → VT100 emulator).
|
|
59
|
+
*/
|
|
60
|
+
export declare class InProcessIO implements TerminalIO {
|
|
61
|
+
private dataCallbacks;
|
|
62
|
+
private resizeCallbacks;
|
|
63
|
+
private _width;
|
|
64
|
+
private _height;
|
|
65
|
+
/** Called when svelterm writes output — connect this to a VT100 emulator */
|
|
66
|
+
onOutput?: (data: string) => void;
|
|
67
|
+
constructor(width: number, height: number);
|
|
68
|
+
write(data: string): void;
|
|
69
|
+
getSize(): {
|
|
70
|
+
width: number;
|
|
71
|
+
height: number;
|
|
72
|
+
};
|
|
73
|
+
onData(callback: (data: Buffer) => void): void;
|
|
74
|
+
onResize(callback: () => void): void;
|
|
75
|
+
enableRawMode(): void;
|
|
76
|
+
disableRawMode(): void;
|
|
77
|
+
dispose(): void;
|
|
78
|
+
/** Feed input data (keyboard/mouse) into svelterm */
|
|
79
|
+
feedInput(data: string): void;
|
|
80
|
+
/** Notify svelterm of a resize */
|
|
81
|
+
setSize(width: number, height: number): void;
|
|
82
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Terminal IO interface — abstracts over the physical terminal.
|
|
3
|
+
*
|
|
4
|
+
* Two implementations:
|
|
5
|
+
* - ProcessIO: passthrough to process.stdout/stdin (Node.js terminal)
|
|
6
|
+
* - In-process IO: connects to a VT100 emulator in the same JS context (browser)
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Passthrough to process.stdout/stdin — the default for Node.js terminal apps.
|
|
10
|
+
*
|
|
11
|
+
* When stdin is not a TTY — `curl app.mjs | node -` delivers the script
|
|
12
|
+
* itself on stdin — input falls back to the controlling terminal
|
|
13
|
+
* (`/dev/tty`) so the app stays interactive.
|
|
14
|
+
*/
|
|
15
|
+
export class ProcessIO {
|
|
16
|
+
dataCallbacks = [];
|
|
17
|
+
resizeCallbacks = [];
|
|
18
|
+
input = process.stdin;
|
|
19
|
+
onInputData = (data) => {
|
|
20
|
+
for (const cb of this.dataCallbacks)
|
|
21
|
+
cb(data);
|
|
22
|
+
};
|
|
23
|
+
onStdoutResize = () => {
|
|
24
|
+
for (const cb of this.resizeCallbacks)
|
|
25
|
+
cb();
|
|
26
|
+
};
|
|
27
|
+
listening = false;
|
|
28
|
+
rawModeWanted = false;
|
|
29
|
+
disposed = false;
|
|
30
|
+
constructor() {
|
|
31
|
+
// With `node -` the script is read to EOF before execution, so no
|
|
32
|
+
// input can be lost while the async reopen is in flight.
|
|
33
|
+
if (!process.stdin.isTTY) {
|
|
34
|
+
void this.reopenControllingTerminal();
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
async reopenControllingTerminal() {
|
|
38
|
+
try {
|
|
39
|
+
const [fs, tty] = await Promise.all([import('node:fs'), import('node:tty')]);
|
|
40
|
+
const stream = new tty.ReadStream(fs.openSync('/dev/tty', 'r'));
|
|
41
|
+
if (this.disposed) {
|
|
42
|
+
stream.destroy();
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
const previous = this.input;
|
|
46
|
+
this.input = stream;
|
|
47
|
+
if (this.listening) {
|
|
48
|
+
previous.removeListener('data', this.onInputData);
|
|
49
|
+
stream.on('data', this.onInputData);
|
|
50
|
+
}
|
|
51
|
+
if (this.rawModeWanted && stream.isTTY) {
|
|
52
|
+
stream.setRawMode(true);
|
|
53
|
+
stream.resume();
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
// No controlling terminal (CI, Windows pipe) — stay on stdin.
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
write(data) {
|
|
61
|
+
process.stdout.write(data);
|
|
62
|
+
}
|
|
63
|
+
getSize() {
|
|
64
|
+
return {
|
|
65
|
+
width: Math.max(1, process.stdout.columns ?? 80),
|
|
66
|
+
height: Math.max(1, process.stdout.rows ?? 24),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
onData(callback) {
|
|
70
|
+
this.dataCallbacks.push(callback);
|
|
71
|
+
if (!this.listening) {
|
|
72
|
+
this.input.on('data', this.onInputData);
|
|
73
|
+
this.listening = true;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
onResize(callback) {
|
|
77
|
+
this.resizeCallbacks.push(callback);
|
|
78
|
+
process.stdout.on('resize', this.onStdoutResize);
|
|
79
|
+
}
|
|
80
|
+
enableRawMode() {
|
|
81
|
+
this.rawModeWanted = true;
|
|
82
|
+
if (this.input.isTTY) {
|
|
83
|
+
this.input.setRawMode(true);
|
|
84
|
+
this.input.resume();
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
disableRawMode() {
|
|
88
|
+
this.rawModeWanted = false;
|
|
89
|
+
if (this.input.isTTY) {
|
|
90
|
+
this.input.setRawMode(false);
|
|
91
|
+
this.input.pause();
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
dispose() {
|
|
95
|
+
this.disposed = true;
|
|
96
|
+
this.input.removeListener('data', this.onInputData);
|
|
97
|
+
process.stdout.removeListener('resize', this.onStdoutResize);
|
|
98
|
+
if (this.input !== process.stdin) {
|
|
99
|
+
this.input.destroy();
|
|
100
|
+
}
|
|
101
|
+
this.dataCallbacks = [];
|
|
102
|
+
this.resizeCallbacks = [];
|
|
103
|
+
this.listening = false;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* In-process IO — connects svelterm to a consumer in the same JS context.
|
|
108
|
+
* Used for browser-based terminal rendering (svelterm → VT100 emulator).
|
|
109
|
+
*/
|
|
110
|
+
export class InProcessIO {
|
|
111
|
+
dataCallbacks = [];
|
|
112
|
+
resizeCallbacks = [];
|
|
113
|
+
_width;
|
|
114
|
+
_height;
|
|
115
|
+
/** Called when svelterm writes output — connect this to a VT100 emulator */
|
|
116
|
+
onOutput;
|
|
117
|
+
constructor(width, height) {
|
|
118
|
+
this._width = width;
|
|
119
|
+
this._height = height;
|
|
120
|
+
}
|
|
121
|
+
write(data) {
|
|
122
|
+
this.onOutput?.(data);
|
|
123
|
+
}
|
|
124
|
+
getSize() {
|
|
125
|
+
return { width: this._width, height: this._height };
|
|
126
|
+
}
|
|
127
|
+
onData(callback) {
|
|
128
|
+
this.dataCallbacks.push(callback);
|
|
129
|
+
}
|
|
130
|
+
onResize(callback) {
|
|
131
|
+
this.resizeCallbacks.push(callback);
|
|
132
|
+
}
|
|
133
|
+
enableRawMode() { }
|
|
134
|
+
disableRawMode() { }
|
|
135
|
+
dispose() {
|
|
136
|
+
this.dataCallbacks = [];
|
|
137
|
+
this.resizeCallbacks = [];
|
|
138
|
+
this.onOutput = undefined;
|
|
139
|
+
}
|
|
140
|
+
/** Feed input data (keyboard/mouse) into svelterm */
|
|
141
|
+
feedInput(data) {
|
|
142
|
+
const buf = typeof Buffer !== 'undefined'
|
|
143
|
+
? Buffer.from(data)
|
|
144
|
+
: new TextEncoder().encode(data);
|
|
145
|
+
for (const cb of this.dataCallbacks)
|
|
146
|
+
cb(buf);
|
|
147
|
+
}
|
|
148
|
+
/** Notify svelterm of a resize */
|
|
149
|
+
setSize(width, height) {
|
|
150
|
+
this._width = width;
|
|
151
|
+
this._height = height;
|
|
152
|
+
for (const cb of this.resizeCallbacks)
|
|
153
|
+
cb();
|
|
154
|
+
}
|
|
155
|
+
}
|
|
@@ -1,10 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
}
|
|
5
|
-
export declare function getTerminalSize(): TerminalSize;
|
|
6
|
-
export declare function enterFullscreen(): void;
|
|
7
|
-
export declare function exitFullscreen(): void;
|
|
8
|
-
export declare function enableRawMode(): void;
|
|
9
|
-
export declare function disableRawMode(): void;
|
|
10
|
-
export declare function writeOutput(data: string): void;
|
|
1
|
+
import type { TerminalIO } from './io.js';
|
|
2
|
+
export declare function enterFullscreen(io: TerminalIO): void;
|
|
3
|
+
export declare function exitFullscreen(io: TerminalIO): void;
|
|
@@ -1,31 +1,8 @@
|
|
|
1
1
|
import * as ansi from '../render/ansi.js';
|
|
2
|
-
export function
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
height: Math.max(1, process.stdout.rows ?? 24),
|
|
6
|
-
};
|
|
2
|
+
export function enterFullscreen(io) {
|
|
3
|
+
io.write(ansi.enterAltScreen());
|
|
4
|
+
io.write(ansi.clearScreen());
|
|
7
5
|
}
|
|
8
|
-
export function
|
|
9
|
-
|
|
10
|
-
process.stdout.write(ansi.hideCursor());
|
|
11
|
-
process.stdout.write(ansi.clearScreen());
|
|
12
|
-
}
|
|
13
|
-
export function exitFullscreen() {
|
|
14
|
-
process.stdout.write(ansi.showCursor());
|
|
15
|
-
process.stdout.write(ansi.exitAltScreen());
|
|
16
|
-
}
|
|
17
|
-
export function enableRawMode() {
|
|
18
|
-
if (process.stdin.isTTY) {
|
|
19
|
-
process.stdin.setRawMode(true);
|
|
20
|
-
process.stdin.resume();
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
export function disableRawMode() {
|
|
24
|
-
if (process.stdin.isTTY) {
|
|
25
|
-
process.stdin.setRawMode(false);
|
|
26
|
-
process.stdin.pause();
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
export function writeOutput(data) {
|
|
30
|
-
process.stdout.write(ansi.beginSyncUpdate() + data + ansi.endSyncUpdate());
|
|
6
|
+
export function exitFullscreen(io) {
|
|
7
|
+
io.write(ansi.exitAltScreen());
|
|
31
8
|
}
|
|
@@ -1,27 +1,30 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Single point of control for
|
|
2
|
+
* Single point of control for terminal input.
|
|
3
3
|
*
|
|
4
|
-
* All
|
|
4
|
+
* All input data flows through here. Incoming bytes are classified and
|
|
5
5
|
* routed to the appropriate handler. Query-response interactions (OSC 11,
|
|
6
6
|
* DA1, etc.) are serialised — only one query is in-flight at a time.
|
|
7
7
|
*/
|
|
8
|
+
import type { TerminalIO } from './io.js';
|
|
8
9
|
export interface StdinHandlers {
|
|
9
|
-
onKey: (data: Buffer) => void;
|
|
10
|
-
onMouse: (data: Buffer) => void;
|
|
10
|
+
onKey: (data: Buffer | Uint8Array) => void;
|
|
11
|
+
onMouse: (data: Buffer | Uint8Array) => void;
|
|
11
12
|
onPaste: (text: string) => void;
|
|
12
13
|
}
|
|
13
14
|
export declare class StdinRouter {
|
|
15
|
+
private io;
|
|
14
16
|
private handlers;
|
|
15
17
|
private pendingQuery;
|
|
16
18
|
private queryQueue;
|
|
17
19
|
private pasteBuffer;
|
|
20
|
+
constructor(io: TerminalIO);
|
|
18
21
|
start(handlers: StdinHandlers): void;
|
|
19
22
|
stop(): void;
|
|
20
23
|
/**
|
|
21
24
|
* Send a query and wait for a matching response.
|
|
22
25
|
* Queries are serialised — queued if one is already in-flight.
|
|
23
26
|
*/
|
|
24
|
-
query(write: string, match: (data: string) => string | null, timeoutMs?: number): Promise<string | null>;
|
|
27
|
+
query(write: string, match: (data: string) => string | null, timeoutMs?: number, onSend?: () => void): Promise<string | null>;
|
|
25
28
|
private drainQueryQueue;
|
|
26
29
|
private onData;
|
|
27
30
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Single point of control for
|
|
2
|
+
* Single point of control for terminal input.
|
|
3
3
|
*
|
|
4
|
-
* All
|
|
4
|
+
* All input data flows through here. Incoming bytes are classified and
|
|
5
5
|
* routed to the appropriate handler. Query-response interactions (OSC 11,
|
|
6
6
|
* DA1, etc.) are serialised — only one query is in-flight at a time.
|
|
7
7
|
*/
|
|
@@ -10,16 +10,19 @@ const SGR_MOUSE_RE = /\x1b\[<\d+;\d+;\d+[Mm]/g;
|
|
|
10
10
|
const PASTE_START = '\x1b[200~';
|
|
11
11
|
const PASTE_END = '\x1b[201~';
|
|
12
12
|
export class StdinRouter {
|
|
13
|
+
io;
|
|
13
14
|
handlers = null;
|
|
14
15
|
pendingQuery = null;
|
|
15
16
|
queryQueue = [];
|
|
16
17
|
pasteBuffer = null;
|
|
18
|
+
constructor(io) {
|
|
19
|
+
this.io = io;
|
|
20
|
+
}
|
|
17
21
|
start(handlers) {
|
|
18
22
|
this.handlers = handlers;
|
|
19
|
-
|
|
23
|
+
this.io.onData(this.onData);
|
|
20
24
|
}
|
|
21
25
|
stop() {
|
|
22
|
-
process.stdin.removeListener('data', this.onData);
|
|
23
26
|
this.handlers = null;
|
|
24
27
|
if (this.pendingQuery) {
|
|
25
28
|
clearTimeout(this.pendingQuery.timer);
|
|
@@ -31,16 +34,16 @@ export class StdinRouter {
|
|
|
31
34
|
* Send a query and wait for a matching response.
|
|
32
35
|
* Queries are serialised — queued if one is already in-flight.
|
|
33
36
|
*/
|
|
34
|
-
query(write, match, timeoutMs = 200) {
|
|
37
|
+
query(write, match, timeoutMs = 200, onSend) {
|
|
35
38
|
return new Promise((resolve, reject) => {
|
|
36
|
-
this.queryQueue.push({ write, match, resolve: resolve, reject, timeoutMs });
|
|
39
|
+
this.queryQueue.push({ write, match, resolve: resolve, reject, timeoutMs, onSend });
|
|
37
40
|
this.drainQueryQueue();
|
|
38
41
|
});
|
|
39
42
|
}
|
|
40
43
|
drainQueryQueue() {
|
|
41
44
|
if (this.pendingQuery || this.queryQueue.length === 0)
|
|
42
45
|
return;
|
|
43
|
-
const { write, match, resolve, timeoutMs } = this.queryQueue.shift();
|
|
46
|
+
const { write, match, resolve, timeoutMs, onSend } = this.queryQueue.shift();
|
|
44
47
|
const timer = setTimeout(() => {
|
|
45
48
|
this.pendingQuery = null;
|
|
46
49
|
resolve(null);
|
|
@@ -56,10 +59,13 @@ export class StdinRouter {
|
|
|
56
59
|
},
|
|
57
60
|
timer,
|
|
58
61
|
};
|
|
59
|
-
|
|
62
|
+
onSend?.();
|
|
63
|
+
this.io.write(write);
|
|
60
64
|
}
|
|
61
65
|
onData = (data) => {
|
|
62
|
-
const str =
|
|
66
|
+
const str = typeof Buffer !== 'undefined' && Buffer.isBuffer(data)
|
|
67
|
+
? data.toString()
|
|
68
|
+
: new TextDecoder().decode(data);
|
|
63
69
|
// Check for pending query response first
|
|
64
70
|
if (this.pendingQuery) {
|
|
65
71
|
const result = this.pendingQuery.match(str);
|
|
@@ -99,7 +105,7 @@ export class StdinRouter {
|
|
|
99
105
|
const mouseMatches = [...str.matchAll(SGR_MOUSE_RE)];
|
|
100
106
|
if (mouseMatches.length > 0) {
|
|
101
107
|
for (const match of mouseMatches) {
|
|
102
|
-
this.handlers?.onMouse(
|
|
108
|
+
this.handlers?.onMouse(toBytes(match[0]));
|
|
103
109
|
}
|
|
104
110
|
// Check if there's non-mouse data remaining
|
|
105
111
|
let remaining = str;
|
|
@@ -107,7 +113,7 @@ export class StdinRouter {
|
|
|
107
113
|
remaining = remaining.replace(match[0], '');
|
|
108
114
|
}
|
|
109
115
|
if (remaining.length > 0 && remaining.trim().length > 0) {
|
|
110
|
-
this.handlers?.onKey(
|
|
116
|
+
this.handlers?.onKey(toBytes(remaining));
|
|
111
117
|
}
|
|
112
118
|
return;
|
|
113
119
|
}
|
|
@@ -131,3 +137,8 @@ export function parseOSC11Scheme(rgb) {
|
|
|
131
137
|
const luminance = 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
|
132
138
|
return luminance > 128 ? 'light' : 'dark';
|
|
133
139
|
}
|
|
140
|
+
function toBytes(str) {
|
|
141
|
+
if (typeof Buffer !== 'undefined')
|
|
142
|
+
return Buffer.from(str);
|
|
143
|
+
return new TextEncoder().encode(str);
|
|
144
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A Map-compatible container keyed by node IDs, backed by a sparse array
|
|
3
|
+
* for fast indexed access. Implements the full Map<number, T> interface
|
|
4
|
+
* so it's a drop-in replacement.
|
|
5
|
+
*
|
|
6
|
+
* ~10x faster than Map.get()/set() for integer keys.
|
|
7
|
+
*/
|
|
8
|
+
export declare class NodeMap<T> implements Map<number, T> {
|
|
9
|
+
private items;
|
|
10
|
+
private _size;
|
|
11
|
+
readonly [Symbol.toStringTag] = "NodeMap";
|
|
12
|
+
get(id: number): T | undefined;
|
|
13
|
+
set(id: number, value: T): this;
|
|
14
|
+
has(id: number): boolean;
|
|
15
|
+
delete(id: number): boolean;
|
|
16
|
+
get size(): number;
|
|
17
|
+
clear(): void;
|
|
18
|
+
clone(): NodeMap<T>;
|
|
19
|
+
forEach(fn: (value: T, key: number, map: Map<number, T>) => void): void;
|
|
20
|
+
entries(): MapIterator<[number, T]>;
|
|
21
|
+
values(): MapIterator<T>;
|
|
22
|
+
keys(): MapIterator<number>;
|
|
23
|
+
[Symbol.iterator](): MapIterator<[number, T]>;
|
|
24
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A Map-compatible container keyed by node IDs, backed by a sparse array
|
|
3
|
+
* for fast indexed access. Implements the full Map<number, T> interface
|
|
4
|
+
* so it's a drop-in replacement.
|
|
5
|
+
*
|
|
6
|
+
* ~10x faster than Map.get()/set() for integer keys.
|
|
7
|
+
*/
|
|
8
|
+
export class NodeMap {
|
|
9
|
+
items = [];
|
|
10
|
+
_size = 0;
|
|
11
|
+
[Symbol.toStringTag] = 'NodeMap';
|
|
12
|
+
get(id) {
|
|
13
|
+
return this.items[id];
|
|
14
|
+
}
|
|
15
|
+
set(id, value) {
|
|
16
|
+
if (this.items[id] === undefined)
|
|
17
|
+
this._size++;
|
|
18
|
+
this.items[id] = value;
|
|
19
|
+
return this;
|
|
20
|
+
}
|
|
21
|
+
has(id) {
|
|
22
|
+
return this.items[id] !== undefined;
|
|
23
|
+
}
|
|
24
|
+
delete(id) {
|
|
25
|
+
if (this.items[id] !== undefined) {
|
|
26
|
+
this.items[id] = undefined;
|
|
27
|
+
this._size--;
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
get size() {
|
|
33
|
+
return this._size;
|
|
34
|
+
}
|
|
35
|
+
clear() {
|
|
36
|
+
this.items = [];
|
|
37
|
+
this._size = 0;
|
|
38
|
+
}
|
|
39
|
+
clone() {
|
|
40
|
+
const copy = new NodeMap();
|
|
41
|
+
copy.items = this.items.slice();
|
|
42
|
+
copy._size = this._size;
|
|
43
|
+
return copy;
|
|
44
|
+
}
|
|
45
|
+
forEach(fn) {
|
|
46
|
+
for (let i = 0; i < this.items.length; i++) {
|
|
47
|
+
const v = this.items[i];
|
|
48
|
+
if (v !== undefined)
|
|
49
|
+
fn(v, i, this);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
*entries() {
|
|
53
|
+
for (let i = 0; i < this.items.length; i++) {
|
|
54
|
+
const v = this.items[i];
|
|
55
|
+
if (v !== undefined)
|
|
56
|
+
yield [i, v];
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
*values() {
|
|
60
|
+
for (let i = 0; i < this.items.length; i++) {
|
|
61
|
+
const v = this.items[i];
|
|
62
|
+
if (v !== undefined)
|
|
63
|
+
yield v;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
*keys() {
|
|
67
|
+
for (let i = 0; i < this.items.length; i++) {
|
|
68
|
+
if (this.items[i] !== undefined)
|
|
69
|
+
yield i;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
[Symbol.iterator]() {
|
|
73
|
+
return this.entries();
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vite configuration helpers for svelterm.
|
|
3
|
+
*
|
|
4
|
+
* Usage in vite.config.ts:
|
|
5
|
+
*
|
|
6
|
+
* import { svelte } from '@sveltejs/vite-plugin-svelte'
|
|
7
|
+
* import { svelterm } from '@svelterm/core/vite'
|
|
8
|
+
*
|
|
9
|
+
* export default defineConfig({
|
|
10
|
+
* plugins: [
|
|
11
|
+
* svelte(svelterm.svelteOptions()),
|
|
12
|
+
* ...svelterm.terminalServer(),
|
|
13
|
+
* ],
|
|
14
|
+
* environments: svelterm.environments(),
|
|
15
|
+
* })
|
|
16
|
+
*
|
|
17
|
+
* Then in another terminal:
|
|
18
|
+
* npx svelterm dev http://localhost:5173/src/App.svelte
|
|
19
|
+
*/
|
|
20
|
+
export interface SveltermConfig {
|
|
21
|
+
/** Custom renderer module specifier. Default: '@svelterm/core' */
|
|
22
|
+
renderer?: string;
|
|
23
|
+
/** Terminal entry component. Default: './App.svelte' */
|
|
24
|
+
entry?: string;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Returns svelte plugin options with dynamicCompileOptions for the
|
|
28
|
+
* terminal environment.
|
|
29
|
+
*/
|
|
30
|
+
export declare function svelteOptions(config?: SveltermConfig): {
|
|
31
|
+
dynamicCompileOptions({ environment }: any): {
|
|
32
|
+
generate: string;
|
|
33
|
+
css: string;
|
|
34
|
+
experimental: {
|
|
35
|
+
customRenderer: string;
|
|
36
|
+
};
|
|
37
|
+
} | undefined;
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Returns Vite environment configuration for the terminal environment.
|
|
41
|
+
*/
|
|
42
|
+
export declare function environments(): {
|
|
43
|
+
terminal: {
|
|
44
|
+
dev: {
|
|
45
|
+
createEnvironment(name: string, config: any): import("vite").RunnableDevEnvironment;
|
|
46
|
+
};
|
|
47
|
+
resolve: {
|
|
48
|
+
conditions: string[];
|
|
49
|
+
noExternal: string[];
|
|
50
|
+
};
|
|
51
|
+
};
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* Returns Vite plugins that bridge the terminal environment over
|
|
55
|
+
* WebSocket and serve CSS for the svelterm CLI.
|
|
56
|
+
*/
|
|
57
|
+
export declare function terminalServer(config?: SveltermConfig): any[];
|
|
58
|
+
export declare const svelterm: {
|
|
59
|
+
svelteOptions: typeof svelteOptions;
|
|
60
|
+
environments: typeof environments;
|
|
61
|
+
terminalServer: typeof terminalServer;
|
|
62
|
+
};
|