@cplieger/web-terminal-engine 5.2.0 → 6.0.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/README.md +121 -61
- package/jsr.json +1 -1
- package/package.json +7 -7
- package/src/connection.ts +1205 -1587
- package/src/index.ts +24 -6
- package/src/keyboard.ts +56 -133
- package/src/modes.ts +92 -166
- package/src/mouse.ts +380 -413
- package/src/render.ts +1894 -2342
- package/src/scroll.ts +225 -476
- package/src/status-stream.ts +5 -0
- package/src/store.ts +31 -1
- package/src/terminal.ts +135 -0
- package/src/toolbar.ts +29 -43
package/src/index.ts
CHANGED
|
@@ -1,14 +1,32 @@
|
|
|
1
1
|
export * from "./types.js";
|
|
2
2
|
export { LineStore } from "./store.js";
|
|
3
3
|
export type { WindowState, StoreChanges, StoreSnapshot } from "./store.js";
|
|
4
|
-
export
|
|
5
|
-
export type {
|
|
4
|
+
export { createTerminalEngine } from "./terminal.js";
|
|
5
|
+
export type { TerminalEngine, TerminalEngineOptions } from "./terminal.js";
|
|
6
|
+
export { createRenderer } from "./render.js";
|
|
7
|
+
export type { Renderer, RendererOptions, ViewMemory } from "./render.js";
|
|
8
|
+
export { createScrollController } from "./scroll.js";
|
|
9
|
+
export type { ScrollController, ScrollControllerOptions } from "./scroll.js";
|
|
10
|
+
export { createConnection, MAX_REPLAY_LINES, MAX_OUTBOX_BYTES } from "./connection.js";
|
|
11
|
+
export type { Connection, ConnectionOptions, ConnectionCallbacks } from "./connection.js";
|
|
12
|
+
export { createMouseController, encodeSGR } from "./mouse.js";
|
|
13
|
+
export type { MouseController, MouseControllerOptions, MouseInputHandler } from "./mouse.js";
|
|
14
|
+
export { createModeState, POWER_ON_MODES } from "./modes.js";
|
|
15
|
+
export type { ModeState, ModeSnapshot } from "./modes.js";
|
|
16
|
+
/**
|
|
17
|
+
* Key encoding, and nothing else: `mapKeyboardEvent` turns a `KeyboardEvent`
|
|
18
|
+
* into the bytes a PTY expects or a local action, and the paste helpers do the
|
|
19
|
+
* same for text. It touches no DOM and sends nothing; mode state arrives as an
|
|
20
|
+
* explicit argument (an engine's `modes` satisfies it).
|
|
21
|
+
*/
|
|
6
22
|
export * as keyboard from "./keyboard.js";
|
|
23
|
+
/**
|
|
24
|
+
* The on-screen mobile key toolbar, the one DOM widget in the engine:
|
|
25
|
+
* `bindMobileToolbar` wires an existing container's buttons to a send sink and
|
|
26
|
+
* a mode state and returns a controller owning sticky-Ctrl state and tear-down.
|
|
27
|
+
* A missing button is skipped rather than fatal.
|
|
28
|
+
*/
|
|
7
29
|
export * as toolbar from "./toolbar.js";
|
|
8
|
-
export * as scroll from "./scroll.js";
|
|
9
|
-
export * as modes from "./modes.js";
|
|
10
|
-
export * as mouse from "./mouse.js";
|
|
11
|
-
export * as connection from "./connection.js";
|
|
12
30
|
export { decodeWireBinary } from "./wire-binary.js";
|
|
13
31
|
export {
|
|
14
32
|
MIN_SUPPORTED_SERVER_WIRE_VERSION,
|
package/src/keyboard.ts
CHANGED
|
@@ -1,20 +1,9 @@
|
|
|
1
|
-
// Keyboard event → terminal byte sequence mapping.
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
// the host application get the keys they expect.
|
|
8
|
-
//
|
|
9
|
-
// Modifier encoding follows xterm.js / VT520 convention:
|
|
10
|
-
// 1=Shift, 2=Alt, 4=Ctrl, 8=Meta. Sum then +1 for CSI 1;{n}{letter}.
|
|
11
|
-
//
|
|
12
|
-
// Application cursor mode (DECCKM, CSI ?1h/l) is tracked client-side
|
|
13
|
-
// via modes.ts (announced by server in wireMsgModes). Server-side, the
|
|
14
|
-
// vt screen tracks both bracketed paste (?2004) and DECCKM (?1) and
|
|
15
|
-
// emits a modes frame whenever they change.
|
|
16
|
-
|
|
17
|
-
import { getKeyboardFlags, isBracketedPaste } from "./modes.js";
|
|
1
|
+
// Keyboard event → terminal byte sequence mapping, mirroring xterm.js's
|
|
2
|
+
// evaluateKeyboardEvent (src/common/input/Keyboard.ts) so vim, readline, Ink
|
|
3
|
+
// and the host application get the keys they expect. Modifier digit per the
|
|
4
|
+
// xterm/VT520 convention: 1=Shift, 2=Alt, 4=Ctrl, 8=Meta, summed then +1 for
|
|
5
|
+
// CSI 1;{n}{letter}. DECCKM and DECKPAM come from the injected mode state,
|
|
6
|
+
// which the server's modes frame keeps current.
|
|
18
7
|
|
|
19
8
|
/** Result of mapping a keyboard event. */
|
|
20
9
|
export type KeyboardResult =
|
|
@@ -26,10 +15,8 @@ export type KeyboardResult =
|
|
|
26
15
|
/**
|
|
27
16
|
* KeyboardModes is the mode state `mapKeyboardEvent` reads: DECCKM (application
|
|
28
17
|
* cursor keys), DECKPAM (application keypad), and the kitty keyboard
|
|
29
|
-
* progressive-enhancement flags.
|
|
30
|
-
*
|
|
31
|
-
* global state. The `modes` module namespace satisfies this structurally; a
|
|
32
|
-
* tabbed shell passes its active session's modes.
|
|
18
|
+
* progressive-enhancement flags. A `ModeState` satisfies it; a consumer passes
|
|
19
|
+
* the state of the terminal the key belongs to.
|
|
33
20
|
*/
|
|
34
21
|
export interface KeyboardModes {
|
|
35
22
|
isApplicationCursor: () => boolean;
|
|
@@ -59,17 +46,10 @@ type CursorKeyLetter = "A" | "B" | "C" | "D" | "H" | "F";
|
|
|
59
46
|
|
|
60
47
|
/**
|
|
61
48
|
* plainCursorKeySeq is THE encoding of an unmodified logical cursor-key press
|
|
62
|
-
* (arrows, Home, End)
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
* protocol supersedes cursor-key mode);
|
|
67
|
-
* - DECCKM (application cursor mode): the SS3 form (ESC O letter);
|
|
68
|
-
* - otherwise: the bare CSI form.
|
|
69
|
-
*
|
|
70
|
-
* Both mode bits are PARAMETERS (not read from the module-global mode state):
|
|
71
|
-
* mapKeyboardEvent honors an injected KeyboardModes, and a helper silently
|
|
72
|
-
* consulting the global would diverge from it in a tabbed shell.
|
|
49
|
+
* (arrows, Home, End), shared by the physical-key path and the mobile toolbar's
|
|
50
|
+
* arrow buttons so the two encoders cannot drift: the CSI form under kitty
|
|
51
|
+
* disambiguate (the protocol supersedes cursor-key mode), the SS3 form
|
|
52
|
+
* (ESC O letter) under DECCKM, the bare CSI form otherwise.
|
|
73
53
|
*/
|
|
74
54
|
export function plainCursorKeySeq(
|
|
75
55
|
letter: CursorKeyLetter,
|
|
@@ -94,40 +74,22 @@ export function plainEscapeSeq(kittyActive: boolean): string {
|
|
|
94
74
|
return kittyActive ? `${ESC}[27u` : ESC;
|
|
95
75
|
}
|
|
96
76
|
|
|
97
|
-
/**
|
|
98
|
-
|
|
99
|
-
* which reads the module-global active-session facade used by the toolbar.
|
|
100
|
-
* connection.setSession restores that facade from the target session's
|
|
101
|
-
* snapshot synchronously before switch-window input can be encoded. */
|
|
102
|
-
function kittyActiveIn(modes: KeyboardModes): boolean {
|
|
77
|
+
/** True when the kitty disambiguate flag is active in `modes`. */
|
|
78
|
+
export function kittyDisambiguateActive(modes: KeyboardModes): boolean {
|
|
103
79
|
return (modes.getKeyboardFlags() & KITTY_DISAMBIGUATE) !== 0;
|
|
104
80
|
}
|
|
105
81
|
|
|
106
|
-
//
|
|
107
|
-
//
|
|
108
|
-
//
|
|
109
|
-
//
|
|
110
|
-
//
|
|
111
|
-
// The parameter type is `CursorKeyLetter` rather than `string`, so "every
|
|
112
|
-
// letter reaching here honors DECCKM" is a compiler guarantee instead of a
|
|
113
|
-
// runtime net: all three call sites pass A/B/C/D from ARROW_LETTER, or the H/F
|
|
114
|
-
// literals.
|
|
115
|
-
//
|
|
116
|
-
// Application cursor mode (DECCKM, CSI ?1) is plumbed via modes.ts.
|
|
117
|
-
// When the application has set DECCKM, the modifier-less form switches
|
|
118
|
-
// from CSI to SS3 (ESC O letter) via plainCursorKeySeq; modifier-bearing
|
|
119
|
-
// forms stay on CSI because they have no SS3 equivalent.
|
|
82
|
+
// The keys that honor DECCKM (arrows, Home, End): bare CSI or SS3 unmodified,
|
|
83
|
+
// CSI 1;{mod}{letter} with modifiers, which have no SS3 equivalent. The
|
|
84
|
+
// `CursorKeyLetter` parameter type makes "every letter here honors DECCKM" a
|
|
85
|
+
// compiler guarantee; F1-F4's SS3/CSI pair is encoded at its own call site.
|
|
120
86
|
function csiLetter(letter: CursorKeyLetter, ev: KeyboardEvent, modes: KeyboardModes): string {
|
|
121
87
|
const m = modifiersDigit(ev);
|
|
122
88
|
if (m === 1) {
|
|
123
|
-
// `
|
|
124
|
-
//
|
|
125
|
-
//
|
|
126
|
-
|
|
127
|
-
// literal `false` would make this function structurally unable to express
|
|
128
|
-
// the kitty form — a later change to that early return would then
|
|
129
|
-
// mis-encode silently instead of failing to compile or reading wrong.
|
|
130
|
-
return plainCursorKeySeq(letter, kittyActiveIn(modes), modes.isApplicationCursor());
|
|
89
|
+
// Read from `modes` rather than hard-coded false (mapKeyboardEvent returns
|
|
90
|
+
// through encodeKittyDisambiguate first) so a change to that early return
|
|
91
|
+
// cannot mis-encode silently.
|
|
92
|
+
return plainCursorKeySeq(letter, kittyDisambiguateActive(modes), modes.isApplicationCursor());
|
|
131
93
|
}
|
|
132
94
|
return `${ESC}[1;${m}${letter}`;
|
|
133
95
|
}
|
|
@@ -199,18 +161,12 @@ const KEYPAD_SS3: Record<string, string | undefined> = {
|
|
|
199
161
|
};
|
|
200
162
|
|
|
201
163
|
// -- Kitty keyboard protocol (progressive enhancement) ----------------------
|
|
202
|
-
//
|
|
203
|
-
//
|
|
204
|
-
//
|
|
205
|
-
//
|
|
206
|
-
//
|
|
207
|
-
//
|
|
208
|
-
// and the encoder never has to emit them.
|
|
209
|
-
//
|
|
210
|
-
// Under disambiguate, text-producing keys still flow through the hidden
|
|
211
|
-
// textarea as text (this encoder returns "ignore" for them), matching both the
|
|
212
|
-
// spec (text keys stay text under 0x1) and our IME/composition model. Only
|
|
213
|
-
// Escape, ctrl/alt/meta combinations, and functional keys are re-encoded.
|
|
164
|
+
// Under an active kitty flag, keys that do NOT produce text are encoded per the
|
|
165
|
+
// kitty protocol so an app that enabled it (Codex via crossterm) gets
|
|
166
|
+
// unambiguous key events. Only the disambiguate flag (0x1) is honored; the
|
|
167
|
+
// server masks off 0x2/0x4/0x8/0x10, so the CSI ?u query reports only 0x1.
|
|
168
|
+
// Text-producing keys still flow through the hidden textarea as text (this
|
|
169
|
+
// encoder returns "ignore" for them), per the spec and the IME model.
|
|
214
170
|
|
|
215
171
|
/** Kitty progressive-enhancement flag bits (mirror vt/kitty.go). */
|
|
216
172
|
const KITTY_DISAMBIGUATE = 1;
|
|
@@ -339,17 +295,13 @@ export function kittyCtrlCharSeq(ch: string): string | null {
|
|
|
339
295
|
return `${ESC}[${cp};${shifted ? 6 : 5}u`; // 5 = ctrl, 6 = ctrl+shift
|
|
340
296
|
}
|
|
341
297
|
|
|
342
|
-
// Keypad NON-TEXT keys under disambiguate, keyed by ev.key (
|
|
343
|
-
//
|
|
344
|
-
//
|
|
345
|
-
//
|
|
346
|
-
//
|
|
347
|
-
//
|
|
348
|
-
//
|
|
349
|
-
// form: both kitty and crossterm decode `CSI 57427 u`, whereas the spec's
|
|
350
|
-
// alternate `CSI E` letter form is kitty-only (crossterm/ratatui can't parse it).
|
|
351
|
-
// The KP_ DIGIT/operator codes (57399-57416) are deliberately absent — they are
|
|
352
|
-
// a report-all (0x8) feature; under 0x1 a NumLock-on numpad digit is plain text.
|
|
298
|
+
// Keypad NON-TEXT keys under disambiguate, keyed by ev.key (NumLock flips a
|
|
299
|
+
// numpad key between its text digit, which stays text under 0x1, and these
|
|
300
|
+
// functions), with the spec's dedicated KP_* codes (57414-57427). NumpadEnter
|
|
301
|
+
// is KP_ENTER, a distinct key from the legacy-byte main Enter. KP_BEGIN is
|
|
302
|
+
// emitted in the `u` form because the spec's alternate `CSI E` letter form is
|
|
303
|
+
// kitty-only (crossterm/ratatui cannot parse it). The KP_ digit/operator codes
|
|
304
|
+
// (57399-57416) are a report-all (0x8) feature and are deliberately absent.
|
|
353
305
|
const KITTY_KEYPAD: Record<string, number | undefined> = {
|
|
354
306
|
ArrowLeft: 57417, // KP_LEFT
|
|
355
307
|
ArrowRight: 57418, // KP_RIGHT
|
|
@@ -490,28 +442,12 @@ function encodeKittyDisambiguate(ev: KeyboardEvent): KeyboardResult {
|
|
|
490
442
|
return { kind: "ignore" };
|
|
491
443
|
}
|
|
492
444
|
|
|
493
|
-
/** True when the kitty disambiguate flag is active in the GLOBAL
|
|
494
|
-
* active-session mode facade. Exported for the toolbar widget (toolbar.ts);
|
|
495
|
-
* connection.setSession synchronously restores the target session's cached
|
|
496
|
-
* snapshot before input resumes. Encoder paths in this module use
|
|
497
|
-
* kittyActiveIn (the injected-modes read) instead. */
|
|
498
|
-
export function kittyDisambiguateActive(): boolean {
|
|
499
|
-
return (getKeyboardFlags() & KITTY_DISAMBIGUATE) !== 0;
|
|
500
|
-
}
|
|
501
|
-
|
|
502
445
|
/**
|
|
503
|
-
* mapKeyboardEvent converts a KeyboardEvent into the terminal action
|
|
504
|
-
*
|
|
505
|
-
*
|
|
506
|
-
*
|
|
507
|
-
*
|
|
508
|
-
* Caller is responsible for ev.preventDefault() when the result is
|
|
509
|
-
* "send" or "scroll-*"; we don't call it here so the function stays
|
|
510
|
-
* pure and testable.
|
|
511
|
-
*
|
|
512
|
-
* `modes` supplies the active session's DECCKM/DECKPAM state; pass the `modes`
|
|
513
|
-
* module namespace for the single-terminal case or the active tab's modes in a
|
|
514
|
-
* tabbed shell.
|
|
446
|
+
* mapKeyboardEvent converts a KeyboardEvent into the terminal action to take,
|
|
447
|
+
* read against `modes`, the mode state of the terminal the key belongs to.
|
|
448
|
+
* Returns "ignore" for a bare modifier press or a key the browser should keep
|
|
449
|
+
* (Cmd+R). The caller calls `ev.preventDefault()` on "send" and "scroll-*";
|
|
450
|
+
* this function stays pure.
|
|
515
451
|
*/
|
|
516
452
|
export function mapKeyboardEvent(ev: KeyboardEvent, modes: KeyboardModes): KeyboardResult {
|
|
517
453
|
// Modifier-only presses (Shift, Ctrl, Alt, Meta) — no-op.
|
|
@@ -683,22 +619,10 @@ export function mapKeyboardEvent(ev: KeyboardEvent, modes: KeyboardModes): Keybo
|
|
|
683
619
|
}
|
|
684
620
|
|
|
685
621
|
/**
|
|
686
|
-
* ctrlByteFor returns the C0 control byte produced by Ctrl+`ch`, or
|
|
687
|
-
*
|
|
688
|
-
*
|
|
689
|
-
*
|
|
690
|
-
* ' ' (space) → \x00 (NUL — same as Ctrl+@)
|
|
691
|
-
* '@' → \x00 (NUL)
|
|
692
|
-
* '[' → \x1b (ESC)
|
|
693
|
-
* '\\' → \x1c (FS)
|
|
694
|
-
* ']' → \x1d (GS)
|
|
695
|
-
* '^' → \x1e (RS)
|
|
696
|
-
* '_' → \x1f (US)
|
|
697
|
-
* '?' → \x7f (DEL — also Ctrl+8 on US layouts via Shift+/)
|
|
698
|
-
*
|
|
699
|
-
* Anything else (multi-char strings, unmapped single chars) returns
|
|
700
|
-
* `null`. Used by `mapKeyboardEvent` for Ctrl+printable handling and
|
|
701
|
-
* by `bindMobileToolbar`'s sticky-Ctrl applier.
|
|
622
|
+
* ctrlByteFor returns the C0 control byte produced by Ctrl+`ch`, or `null`
|
|
623
|
+
* when the character has no Ctrl mapping: a-z (case-folded) give \x01..\x1a,
|
|
624
|
+
* space and '@' give NUL, '[' '\\' ']' '^' '_' give \x1b..\x1f, '?' gives DEL.
|
|
625
|
+
* A multi-character string or an unmapped character returns `null`.
|
|
702
626
|
*/
|
|
703
627
|
export function ctrlByteFor(ch: string): string | null {
|
|
704
628
|
if (ch.length !== 1) {
|
|
@@ -774,21 +698,20 @@ function numpadCodeToKey(code: string): string | undefined {
|
|
|
774
698
|
|
|
775
699
|
// -- Bracketed paste --------------------------------------------------------
|
|
776
700
|
|
|
701
|
+
/** The mode bit `bracketTextForPaste` reads; a `ModeState` satisfies it. */
|
|
702
|
+
export interface PasteModes {
|
|
703
|
+
isBracketedPaste(): boolean;
|
|
704
|
+
}
|
|
705
|
+
|
|
777
706
|
/**
|
|
778
|
-
* bracketTextForPaste wraps text in DEC 2004 bracketed-paste sentinels
|
|
779
|
-
*
|
|
780
|
-
*
|
|
781
|
-
*
|
|
782
|
-
*
|
|
783
|
-
* sync by the server's wireMsgModes wire frame.
|
|
784
|
-
*
|
|
785
|
-
* The ESC sanitisation defends against an attacker-controlled paste
|
|
786
|
-
* containing \x1b[201~ that would prematurely close the paste region
|
|
787
|
-
* and let the rest be interpreted as a command — only relevant when
|
|
788
|
-
* we are bracketing.
|
|
707
|
+
* bracketTextForPaste wraps text in DEC 2004 bracketed-paste sentinels when
|
|
708
|
+
* `modes` reports bracketed-paste mode (CSI ?2004h) enabled, and returns it
|
|
709
|
+
* unchanged otherwise. Embedded ESC bytes are replaced with U+241B first: an
|
|
710
|
+
* attacker-controlled paste containing \x1b[201~ would otherwise close the
|
|
711
|
+
* paste region early and have the rest interpreted as a command.
|
|
789
712
|
*/
|
|
790
|
-
export function bracketTextForPaste(text: string): string {
|
|
791
|
-
if (!isBracketedPaste()) {
|
|
713
|
+
export function bracketTextForPaste(text: string, modes: PasteModes): string {
|
|
714
|
+
if (!modes.isBracketedPaste()) {
|
|
792
715
|
return text;
|
|
793
716
|
}
|
|
794
717
|
// eslint-disable-next-line no-control-regex -- intentional: sanitising ESC bytes in pasted text
|
package/src/modes.ts
CHANGED
|
@@ -1,31 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
// mirrors the modes of whichever session the live socket serves: the
|
|
3
|
-
// connection module is the single writer (it applies every inbound modes
|
|
4
|
-
// frame via applySnapshot AND, in a tabbed shell, synchronously restores the
|
|
5
|
-
// target session's cached snapshot inside setSession — power-on defaults for
|
|
6
|
-
// a session never seen). Readers therefore never observe another session's
|
|
7
|
-
// modes after setSession returns; the only staleness left is the inherent
|
|
8
|
-
// one-frame lag against the session's own server state. Used by:
|
|
9
|
-
// - keyboard.ts arrow-key encoder: emits SS3 (ESC O letter) when
|
|
10
|
-
// applicationCursor is true, CSI (ESC [ letter) otherwise.
|
|
11
|
-
// - paste handling: wraps in \e[200~..\e[201~ only when
|
|
12
|
-
// bracketedPaste is on; otherwise sends raw text.
|
|
13
|
-
// - mouse.ts: encodes mouse events only when mouseMode > 0.
|
|
14
|
-
//
|
|
15
|
-
// Defaults: bracketedPaste starts true because most modern shells
|
|
16
|
-
// (bash 4.4+, zsh, fish) enable it immediately on startup via
|
|
17
|
-
// CSI ?2004h. Starting false would cause the first paste before the
|
|
18
|
-
// server's modes frame arrives to be sent un-bracketed, which shells
|
|
19
|
-
// interpret as typed input (potentially executing pasted commands
|
|
20
|
-
// character-by-character). applicationCursor starts false (normal
|
|
21
|
-
// mode) because that's the VT100 power-on default.
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* One session's complete DEC-mode mirror, as a typed value (P3). The
|
|
25
|
-
* connection module caches one per session and applies it via applySnapshot;
|
|
26
|
-
* the named-field shape (vs setModes' nine positional parameters) makes
|
|
27
|
-
* field-order drift impossible for production writers.
|
|
28
|
-
*/
|
|
1
|
+
/** One session's complete DEC-mode mirror, as a typed value. */
|
|
29
2
|
export interface ModeSnapshot {
|
|
30
3
|
bracketedPaste: boolean;
|
|
31
4
|
applicationCursor: boolean;
|
|
@@ -39,11 +12,12 @@ export interface ModeSnapshot {
|
|
|
39
12
|
}
|
|
40
13
|
|
|
41
14
|
/**
|
|
42
|
-
* The VT power-on mode state
|
|
43
|
-
*
|
|
44
|
-
*
|
|
15
|
+
* The VT power-on mode state, frozen: copy it (`{ ...POWER_ON_MODES }`) before
|
|
16
|
+
* changing a field. `bracketedPaste` starts true because modern shells enable
|
|
17
|
+
* DEC 2004 at startup, and a paste delivered un-bracketed before the server's
|
|
18
|
+
* first modes frame would be executed as typed input.
|
|
45
19
|
*/
|
|
46
|
-
export const POWER_ON_MODES: Readonly<ModeSnapshot> = {
|
|
20
|
+
export const POWER_ON_MODES: Readonly<ModeSnapshot> = Object.freeze({
|
|
47
21
|
bracketedPaste: true,
|
|
48
22
|
applicationCursor: false,
|
|
49
23
|
mouseSGR: false,
|
|
@@ -53,144 +27,96 @@ export const POWER_ON_MODES: Readonly<ModeSnapshot> = {
|
|
|
53
27
|
reverseVideo: false,
|
|
54
28
|
mousePixels: false,
|
|
55
29
|
keyboardFlags: 0,
|
|
56
|
-
};
|
|
30
|
+
});
|
|
57
31
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
*/
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
applicationCursor = appCursor;
|
|
88
|
-
if (mSGR !== undefined) {
|
|
89
|
-
mouseSGR = mSGR;
|
|
90
|
-
}
|
|
91
|
-
if (focus !== undefined) {
|
|
92
|
-
focusReporting = focus;
|
|
93
|
-
}
|
|
94
|
-
if (mMode !== undefined) {
|
|
95
|
-
mouseMode = mMode;
|
|
96
|
-
}
|
|
97
|
-
if (appKeypad !== undefined) {
|
|
98
|
-
applicationKeypad = appKeypad;
|
|
99
|
-
}
|
|
100
|
-
if (revVideo !== undefined) {
|
|
101
|
-
reverseVideo = revVideo;
|
|
102
|
-
}
|
|
103
|
-
if (mPixels !== undefined) {
|
|
104
|
-
mousePixels = mPixels;
|
|
105
|
-
}
|
|
106
|
-
if (kbdFlags !== undefined) {
|
|
107
|
-
keyboardFlags = kbdFlags;
|
|
108
|
-
}
|
|
32
|
+
/** The DEC private-mode state of one terminal instance. */
|
|
33
|
+
export interface ModeState {
|
|
34
|
+
/** True when the server has DEC 2004 (bracketed paste) enabled. */
|
|
35
|
+
isBracketedPaste(): boolean;
|
|
36
|
+
/** True when the server has DECCKM (application cursor keys) enabled. */
|
|
37
|
+
isApplicationCursor(): boolean;
|
|
38
|
+
/** Active mouse tracking mode (xterm DECSET): 0 = off, 1000 = normal, 1002 = button-event, 1003 = any-event. */
|
|
39
|
+
getMouseMode(): number;
|
|
40
|
+
/** True when the server has DEC 1006 (SGR mouse encoding) enabled. */
|
|
41
|
+
isMouseSGR(): boolean;
|
|
42
|
+
/** True when the server has DEC 1016 (SGR-pixels mouse) enabled: reports carry pixel coordinates. */
|
|
43
|
+
isMousePixels(): boolean;
|
|
44
|
+
/** True when the server has DEC 1004 (focus event reporting) enabled. */
|
|
45
|
+
isFocusReporting(): boolean;
|
|
46
|
+
/** True when the server has DECKPAM (application keypad) enabled. */
|
|
47
|
+
isApplicationKeypad(): boolean;
|
|
48
|
+
/** True when the server has DEC 5 (reverse video / DECSCNM) enabled. */
|
|
49
|
+
isReverseVideo(): boolean;
|
|
50
|
+
/**
|
|
51
|
+
* Kitty keyboard progressive-enhancement flags in effect (bit0 disambiguate,
|
|
52
|
+
* bit1 report-event-types, bit2 report-alternate-keys); 0 means legacy encoding.
|
|
53
|
+
*/
|
|
54
|
+
getKeyboardFlags(): number;
|
|
55
|
+
/** Replaces every mode bit with the snapshot's; no field is left at its previous value. */
|
|
56
|
+
applySnapshot(s: Readonly<ModeSnapshot>): void;
|
|
57
|
+
/** The current mode state as a snapshot value (a copy; safe to retain). */
|
|
58
|
+
snapshot(): ModeSnapshot;
|
|
59
|
+
/** Resets every field to `POWER_ON_MODES` and makes `applySnapshot` a no-op. */
|
|
60
|
+
dispose(): void;
|
|
109
61
|
}
|
|
110
62
|
|
|
111
|
-
/**
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
63
|
+
/** Creates independent mode state initialized from a copy of `initial` (default `POWER_ON_MODES`). */
|
|
64
|
+
export function createModeState(initial: Readonly<ModeSnapshot> = POWER_ON_MODES): ModeState {
|
|
65
|
+
let bracketedPaste = initial.bracketedPaste;
|
|
66
|
+
let applicationCursor = initial.applicationCursor;
|
|
67
|
+
let mouseSGR = initial.mouseSGR;
|
|
68
|
+
let focusReporting = initial.focusReporting;
|
|
69
|
+
let mouseMode = initial.mouseMode;
|
|
70
|
+
let applicationKeypad = initial.applicationKeypad;
|
|
71
|
+
let reverseVideo = initial.reverseVideo;
|
|
72
|
+
let mousePixels = initial.mousePixels;
|
|
73
|
+
let keyboardFlags = initial.keyboardFlags;
|
|
74
|
+
let disposed = false;
|
|
75
|
+
|
|
76
|
+
function assign(s: Readonly<ModeSnapshot>): void {
|
|
77
|
+
bracketedPaste = s.bracketedPaste;
|
|
78
|
+
applicationCursor = s.applicationCursor;
|
|
79
|
+
mouseSGR = s.mouseSGR;
|
|
80
|
+
focusReporting = s.focusReporting;
|
|
81
|
+
mouseMode = s.mouseMode;
|
|
82
|
+
applicationKeypad = s.applicationKeypad;
|
|
83
|
+
reverseVideo = s.reverseVideo;
|
|
84
|
+
mousePixels = s.mousePixels;
|
|
85
|
+
keyboardFlags = s.keyboardFlags;
|
|
86
|
+
}
|
|
128
87
|
|
|
129
|
-
/** The current mode state as a snapshot value (a copy; safe to retain). */
|
|
130
|
-
export function snapshot(): ModeSnapshot {
|
|
131
88
|
return {
|
|
132
|
-
bracketedPaste,
|
|
133
|
-
applicationCursor,
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
keyboardFlags,
|
|
89
|
+
isBracketedPaste: () => bracketedPaste,
|
|
90
|
+
isApplicationCursor: () => applicationCursor,
|
|
91
|
+
getMouseMode: () => mouseMode,
|
|
92
|
+
isMouseSGR: () => mouseSGR,
|
|
93
|
+
isMousePixels: () => mousePixels,
|
|
94
|
+
isFocusReporting: () => focusReporting,
|
|
95
|
+
isApplicationKeypad: () => applicationKeypad,
|
|
96
|
+
isReverseVideo: () => reverseVideo,
|
|
97
|
+
getKeyboardFlags: () => keyboardFlags,
|
|
98
|
+
applySnapshot(s: Readonly<ModeSnapshot>): void {
|
|
99
|
+
if (disposed) {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
assign(s);
|
|
103
|
+
},
|
|
104
|
+
snapshot(): ModeSnapshot {
|
|
105
|
+
return {
|
|
106
|
+
bracketedPaste,
|
|
107
|
+
applicationCursor,
|
|
108
|
+
mouseSGR,
|
|
109
|
+
focusReporting,
|
|
110
|
+
mouseMode,
|
|
111
|
+
applicationKeypad,
|
|
112
|
+
reverseVideo,
|
|
113
|
+
mousePixels,
|
|
114
|
+
keyboardFlags,
|
|
115
|
+
};
|
|
116
|
+
},
|
|
117
|
+
dispose(): void {
|
|
118
|
+
disposed = true;
|
|
119
|
+
assign(POWER_ON_MODES);
|
|
120
|
+
},
|
|
141
121
|
};
|
|
142
122
|
}
|
|
143
|
-
|
|
144
|
-
/** True when the server has DEC 2004 (bracketed paste) enabled. */
|
|
145
|
-
export function isBracketedPaste(): boolean {
|
|
146
|
-
return bracketedPaste;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
/** True when the server has DECCKM (application cursor keys) enabled. */
|
|
150
|
-
export function isApplicationCursor(): boolean {
|
|
151
|
-
return applicationCursor;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
/**
|
|
155
|
-
* Active mouse tracking mode (xterm DECSET): 0 = off, 1000 = normal,
|
|
156
|
-
* 1002 = button-event, 1003 = any-event.
|
|
157
|
-
*/
|
|
158
|
-
export function getMouseMode(): number {
|
|
159
|
-
return mouseMode;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
/** True when the server has DEC 1006 (SGR mouse encoding) enabled. */
|
|
163
|
-
export function isMouseSGR(): boolean {
|
|
164
|
-
return mouseSGR;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
/** True when the server has DEC 1016 (SGR-pixels mouse) enabled: mouse reports
|
|
168
|
-
* carry pixel coordinates instead of cell coordinates. */
|
|
169
|
-
export function isMousePixels(): boolean {
|
|
170
|
-
return mousePixels;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
/** True when the server has DEC 1004 (focus event reporting) enabled. */
|
|
174
|
-
export function isFocusReporting(): boolean {
|
|
175
|
-
return focusReporting;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
/** True when the server has DECKPAM (application keypad) enabled. */
|
|
179
|
-
export function isApplicationKeypad(): boolean {
|
|
180
|
-
return applicationKeypad;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
/** True when the server has DEC 5 (reverse video / DECSCNM) enabled. */
|
|
184
|
-
export function isReverseVideo(): boolean {
|
|
185
|
-
return reverseVideo;
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
/**
|
|
189
|
-
* Kitty keyboard progressive-enhancement flags currently in effect (bit0
|
|
190
|
-
* disambiguate, bit1 report-event-types, bit2 report-alternate-keys). 0 means
|
|
191
|
-
* the protocol is disabled and keys use legacy encoding. Read by keyboard.ts to
|
|
192
|
-
* choose between legacy and kitty CSI-u encoding.
|
|
193
|
-
*/
|
|
194
|
-
export function getKeyboardFlags(): number {
|
|
195
|
-
return keyboardFlags;
|
|
196
|
-
}
|