@oh-my-pi/pi-tui 1.337.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 +654 -0
- package/package.json +45 -0
- package/src/autocomplete.ts +575 -0
- package/src/components/box.ts +134 -0
- package/src/components/cancellable-loader.ts +39 -0
- package/src/components/editor.ts +1342 -0
- package/src/components/image.ts +87 -0
- package/src/components/input.ts +344 -0
- package/src/components/loader.ts +55 -0
- package/src/components/markdown.ts +646 -0
- package/src/components/select-list.ts +184 -0
- package/src/components/settings-list.ts +188 -0
- package/src/components/spacer.ts +28 -0
- package/src/components/tab-bar.ts +140 -0
- package/src/components/text.ts +106 -0
- package/src/components/truncated-text.ts +65 -0
- package/src/index.ts +91 -0
- package/src/keys.ts +560 -0
- package/src/terminal-image.ts +340 -0
- package/src/terminal.ts +163 -0
- package/src/tui.ts +353 -0
- package/src/utils.ts +712 -0
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { Component } from "../tui.js";
|
|
2
|
+
import { truncateToWidth, visibleWidth } from "../utils.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Text component that truncates to fit viewport width
|
|
6
|
+
*/
|
|
7
|
+
export class TruncatedText implements Component {
|
|
8
|
+
private text: string;
|
|
9
|
+
private paddingX: number;
|
|
10
|
+
private paddingY: number;
|
|
11
|
+
|
|
12
|
+
constructor(text: string, paddingX: number = 0, paddingY: number = 0) {
|
|
13
|
+
this.text = text;
|
|
14
|
+
this.paddingX = paddingX;
|
|
15
|
+
this.paddingY = paddingY;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
invalidate(): void {
|
|
19
|
+
// No cached state to invalidate currently
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
render(width: number): string[] {
|
|
23
|
+
const result: string[] = [];
|
|
24
|
+
|
|
25
|
+
// Empty line padded to width
|
|
26
|
+
const emptyLine = " ".repeat(width);
|
|
27
|
+
|
|
28
|
+
// Add vertical padding above
|
|
29
|
+
for (let i = 0; i < this.paddingY; i++) {
|
|
30
|
+
result.push(emptyLine);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Calculate available width after horizontal padding
|
|
34
|
+
const availableWidth = Math.max(1, width - this.paddingX * 2);
|
|
35
|
+
|
|
36
|
+
// Take only the first line (stop at newline)
|
|
37
|
+
let singleLineText = this.text;
|
|
38
|
+
const newlineIndex = this.text.indexOf("\n");
|
|
39
|
+
if (newlineIndex !== -1) {
|
|
40
|
+
singleLineText = this.text.substring(0, newlineIndex);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Truncate text if needed (accounting for ANSI codes)
|
|
44
|
+
const displayText = truncateToWidth(singleLineText, availableWidth);
|
|
45
|
+
|
|
46
|
+
// Add horizontal padding
|
|
47
|
+
const leftPadding = " ".repeat(this.paddingX);
|
|
48
|
+
const rightPadding = " ".repeat(this.paddingX);
|
|
49
|
+
const lineWithPadding = leftPadding + displayText + rightPadding;
|
|
50
|
+
|
|
51
|
+
// Pad line to exactly width characters
|
|
52
|
+
const lineVisibleWidth = visibleWidth(lineWithPadding);
|
|
53
|
+
const paddingNeeded = Math.max(0, width - lineVisibleWidth);
|
|
54
|
+
const finalLine = lineWithPadding + " ".repeat(paddingNeeded);
|
|
55
|
+
|
|
56
|
+
result.push(finalLine);
|
|
57
|
+
|
|
58
|
+
// Add vertical padding below
|
|
59
|
+
for (let i = 0; i < this.paddingY; i++) {
|
|
60
|
+
result.push(emptyLine);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return result;
|
|
64
|
+
}
|
|
65
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
// Core TUI interfaces and classes
|
|
2
|
+
|
|
3
|
+
// Autocomplete support
|
|
4
|
+
export {
|
|
5
|
+
type AutocompleteItem,
|
|
6
|
+
type AutocompleteProvider,
|
|
7
|
+
CombinedAutocompleteProvider,
|
|
8
|
+
type SlashCommand,
|
|
9
|
+
} from "./autocomplete.js";
|
|
10
|
+
// Components
|
|
11
|
+
export { Box } from "./components/box.js";
|
|
12
|
+
export { CancellableLoader } from "./components/cancellable-loader.js";
|
|
13
|
+
export { Editor, type EditorTheme } from "./components/editor.js";
|
|
14
|
+
export { Image, type ImageOptions, type ImageTheme } from "./components/image.js";
|
|
15
|
+
export { Input } from "./components/input.js";
|
|
16
|
+
export { Loader } from "./components/loader.js";
|
|
17
|
+
export { type DefaultTextStyle, Markdown, type MarkdownTheme } from "./components/markdown.js";
|
|
18
|
+
export { type SelectItem, SelectList, type SelectListTheme } from "./components/select-list.js";
|
|
19
|
+
export { type SettingItem, SettingsList, type SettingsListTheme } from "./components/settings-list.js";
|
|
20
|
+
export { Spacer } from "./components/spacer.js";
|
|
21
|
+
export { type Tab, TabBar, type TabBarTheme } from "./components/tab-bar.js";
|
|
22
|
+
export { Text } from "./components/text.js";
|
|
23
|
+
export { TruncatedText } from "./components/truncated-text.js";
|
|
24
|
+
// Kitty keyboard protocol helpers
|
|
25
|
+
export {
|
|
26
|
+
isAltBackspace,
|
|
27
|
+
isAltEnter,
|
|
28
|
+
isAltLeft,
|
|
29
|
+
isAltRight,
|
|
30
|
+
isArrowDown,
|
|
31
|
+
isArrowLeft,
|
|
32
|
+
isArrowRight,
|
|
33
|
+
isArrowUp,
|
|
34
|
+
isBackspace,
|
|
35
|
+
isCtrlA,
|
|
36
|
+
isCtrlC,
|
|
37
|
+
isCtrlD,
|
|
38
|
+
isCtrlE,
|
|
39
|
+
isCtrlG,
|
|
40
|
+
isCtrlK,
|
|
41
|
+
isCtrlL,
|
|
42
|
+
isCtrlLeft,
|
|
43
|
+
isCtrlO,
|
|
44
|
+
isCtrlP,
|
|
45
|
+
isCtrlRight,
|
|
46
|
+
isCtrlT,
|
|
47
|
+
isCtrlU,
|
|
48
|
+
isCtrlV,
|
|
49
|
+
isCtrlW,
|
|
50
|
+
isCtrlZ,
|
|
51
|
+
isDelete,
|
|
52
|
+
isEnd,
|
|
53
|
+
isEnter,
|
|
54
|
+
isEscape,
|
|
55
|
+
isHome,
|
|
56
|
+
isShiftCtrlD,
|
|
57
|
+
isShiftCtrlO,
|
|
58
|
+
isShiftCtrlP,
|
|
59
|
+
isShiftEnter,
|
|
60
|
+
isShiftTab,
|
|
61
|
+
isTab,
|
|
62
|
+
Keys,
|
|
63
|
+
} from "./keys.js";
|
|
64
|
+
// Terminal interface and implementations
|
|
65
|
+
export { emergencyTerminalRestore, ProcessTerminal, type Terminal } from "./terminal.js";
|
|
66
|
+
// Terminal image support
|
|
67
|
+
export {
|
|
68
|
+
type CellDimensions,
|
|
69
|
+
calculateImageRows,
|
|
70
|
+
detectCapabilities,
|
|
71
|
+
encodeITerm2,
|
|
72
|
+
encodeKitty,
|
|
73
|
+
getCapabilities,
|
|
74
|
+
getCellDimensions,
|
|
75
|
+
getGifDimensions,
|
|
76
|
+
getImageDimensions,
|
|
77
|
+
getJpegDimensions,
|
|
78
|
+
getPngDimensions,
|
|
79
|
+
getWebpDimensions,
|
|
80
|
+
type ImageDimensions,
|
|
81
|
+
type ImageProtocol,
|
|
82
|
+
type ImageRenderOptions,
|
|
83
|
+
imageFallback,
|
|
84
|
+
renderImage,
|
|
85
|
+
resetCapabilitiesCache,
|
|
86
|
+
setCellDimensions,
|
|
87
|
+
type TerminalCapabilities,
|
|
88
|
+
} from "./terminal-image.js";
|
|
89
|
+
export { type Component, Container, TUI } from "./tui.js";
|
|
90
|
+
// Utilities
|
|
91
|
+
export { truncateToWidth, visibleWidth, wrapTextWithAnsi } from "./utils.js";
|
package/src/keys.ts
ADDED
|
@@ -0,0 +1,560 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kitty keyboard protocol key sequence helpers.
|
|
3
|
+
*
|
|
4
|
+
* The Kitty keyboard protocol sends enhanced escape sequences in the format:
|
|
5
|
+
* \x1b[<codepoint>;<modifier>u
|
|
6
|
+
*
|
|
7
|
+
* Modifier bits (before adding 1 for transmission):
|
|
8
|
+
* - Shift: 1 (value 2)
|
|
9
|
+
* - Alt: 2 (value 3)
|
|
10
|
+
* - Ctrl: 4 (value 5)
|
|
11
|
+
* - Super: 8 (value 9)
|
|
12
|
+
* - Hyper: 16
|
|
13
|
+
* - Meta: 32
|
|
14
|
+
* - Caps_Lock: 64
|
|
15
|
+
* - Num_Lock: 128
|
|
16
|
+
*
|
|
17
|
+
* See: https://sw.kovidgoyal.net/kitty/keyboard-protocol/
|
|
18
|
+
*
|
|
19
|
+
* NOTE: Some terminals (e.g., Ghostty on Linux) include lock key states
|
|
20
|
+
* (Caps Lock, Num Lock) in the modifier field. We mask these out when
|
|
21
|
+
* checking for key combinations since they shouldn't affect behavior.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
// Common codepoints
|
|
25
|
+
const CODEPOINTS = {
|
|
26
|
+
// Letters (lowercase ASCII)
|
|
27
|
+
a: 97,
|
|
28
|
+
c: 99,
|
|
29
|
+
d: 100,
|
|
30
|
+
e: 101,
|
|
31
|
+
g: 103,
|
|
32
|
+
k: 107,
|
|
33
|
+
l: 108,
|
|
34
|
+
o: 111,
|
|
35
|
+
p: 112,
|
|
36
|
+
t: 116,
|
|
37
|
+
u: 117,
|
|
38
|
+
v: 118,
|
|
39
|
+
w: 119,
|
|
40
|
+
z: 122,
|
|
41
|
+
|
|
42
|
+
// Special keys
|
|
43
|
+
escape: 27,
|
|
44
|
+
tab: 9,
|
|
45
|
+
enter: 13,
|
|
46
|
+
backspace: 127,
|
|
47
|
+
} as const;
|
|
48
|
+
|
|
49
|
+
// Lock key bits to ignore when matching (Caps Lock + Num Lock)
|
|
50
|
+
const LOCK_MASK = 64 + 128; // 192
|
|
51
|
+
|
|
52
|
+
// Modifier bits (before adding 1)
|
|
53
|
+
const MODIFIERS = {
|
|
54
|
+
shift: 1,
|
|
55
|
+
alt: 2,
|
|
56
|
+
ctrl: 4,
|
|
57
|
+
super: 8,
|
|
58
|
+
} as const;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Build a Kitty keyboard protocol sequence for a key with modifier.
|
|
62
|
+
*/
|
|
63
|
+
function kittySequence(codepoint: number, modifier: number): string {
|
|
64
|
+
return `\x1b[${codepoint};${modifier + 1}u`;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Parsed Kitty keyboard protocol sequence.
|
|
69
|
+
*/
|
|
70
|
+
interface ParsedKittySequence {
|
|
71
|
+
codepoint: number;
|
|
72
|
+
modifier: number; // Actual modifier bits (after subtracting 1)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Parse a Kitty keyboard protocol sequence.
|
|
77
|
+
* Handles formats:
|
|
78
|
+
* - \x1b[<codepoint>u (no modifier)
|
|
79
|
+
* - \x1b[<codepoint>;<modifier>u (with modifier)
|
|
80
|
+
* - \x1b[1;<modifier>A/B/C/D (arrow keys with modifier)
|
|
81
|
+
*
|
|
82
|
+
* Returns null if not a valid Kitty sequence.
|
|
83
|
+
*/
|
|
84
|
+
// Virtual codepoints for functional keys (negative to avoid conflicts)
|
|
85
|
+
const FUNCTIONAL_CODEPOINTS = {
|
|
86
|
+
delete: -10,
|
|
87
|
+
insert: -11,
|
|
88
|
+
pageUp: -12,
|
|
89
|
+
pageDown: -13,
|
|
90
|
+
home: -14,
|
|
91
|
+
end: -15,
|
|
92
|
+
} as const;
|
|
93
|
+
|
|
94
|
+
function parseKittySequence(data: string): ParsedKittySequence | null {
|
|
95
|
+
// Match CSI u format: \x1b[<num>u or \x1b[<num>;<mod>u
|
|
96
|
+
const csiUMatch = data.match(/^\x1b\[(\d+)(?:;(\d+))?u$/);
|
|
97
|
+
if (csiUMatch) {
|
|
98
|
+
const codepoint = parseInt(csiUMatch[1]!, 10);
|
|
99
|
+
const modValue = csiUMatch[2] ? parseInt(csiUMatch[2], 10) : 1;
|
|
100
|
+
return { codepoint, modifier: modValue - 1 };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Match arrow keys with modifier: \x1b[1;<mod>A/B/C/D
|
|
104
|
+
const arrowMatch = data.match(/^\x1b\[1;(\d+)([ABCD])$/);
|
|
105
|
+
if (arrowMatch) {
|
|
106
|
+
const modValue = parseInt(arrowMatch[1]!, 10);
|
|
107
|
+
// Map arrow letters to virtual codepoints for easier matching
|
|
108
|
+
const arrowCodes: Record<string, number> = { A: -1, B: -2, C: -3, D: -4 };
|
|
109
|
+
const codepoint = arrowCodes[arrowMatch[2]!]!;
|
|
110
|
+
return { codepoint, modifier: modValue - 1 };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Match functional keys with ~ terminator: \x1b[<num>~ or \x1b[<num>;<mod>~
|
|
114
|
+
// DELETE=3, INSERT=2, PAGEUP=5, PAGEDOWN=6, etc.
|
|
115
|
+
const funcMatch = data.match(/^\x1b\[(\d+)(?:;(\d+))?~$/);
|
|
116
|
+
if (funcMatch) {
|
|
117
|
+
const keyNum = parseInt(funcMatch[1]!, 10);
|
|
118
|
+
const modValue = funcMatch[2] ? parseInt(funcMatch[2], 10) : 1;
|
|
119
|
+
// Map functional key numbers to virtual codepoints
|
|
120
|
+
const funcCodes: Record<number, number> = {
|
|
121
|
+
2: FUNCTIONAL_CODEPOINTS.insert,
|
|
122
|
+
3: FUNCTIONAL_CODEPOINTS.delete,
|
|
123
|
+
5: FUNCTIONAL_CODEPOINTS.pageUp,
|
|
124
|
+
6: FUNCTIONAL_CODEPOINTS.pageDown,
|
|
125
|
+
7: FUNCTIONAL_CODEPOINTS.home, // Alternative home
|
|
126
|
+
8: FUNCTIONAL_CODEPOINTS.end, // Alternative end
|
|
127
|
+
};
|
|
128
|
+
const codepoint = funcCodes[keyNum];
|
|
129
|
+
if (codepoint !== undefined) {
|
|
130
|
+
return { codepoint, modifier: modValue - 1 };
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Match Home/End with modifier: \x1b[1;<mod>H/F
|
|
135
|
+
const homeEndMatch = data.match(/^\x1b\[1;(\d+)([HF])$/);
|
|
136
|
+
if (homeEndMatch) {
|
|
137
|
+
const modValue = parseInt(homeEndMatch[1]!, 10);
|
|
138
|
+
const codepoint = homeEndMatch[2] === "H" ? FUNCTIONAL_CODEPOINTS.home : FUNCTIONAL_CODEPOINTS.end;
|
|
139
|
+
return { codepoint, modifier: modValue - 1 };
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Check if a Kitty sequence matches the expected codepoint and modifier,
|
|
147
|
+
* ignoring lock key bits (Caps Lock, Num Lock).
|
|
148
|
+
*/
|
|
149
|
+
function matchesKittySequence(data: string, expectedCodepoint: number, expectedModifier: number): boolean {
|
|
150
|
+
const parsed = parseKittySequence(data);
|
|
151
|
+
if (!parsed) return false;
|
|
152
|
+
|
|
153
|
+
// Mask out lock bits from both sides for comparison
|
|
154
|
+
const actualMod = parsed.modifier & ~LOCK_MASK;
|
|
155
|
+
const expectedMod = expectedModifier & ~LOCK_MASK;
|
|
156
|
+
|
|
157
|
+
return parsed.codepoint === expectedCodepoint && actualMod === expectedMod;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Pre-built sequences for common key combinations
|
|
161
|
+
export const Keys = {
|
|
162
|
+
// Ctrl+<letter> combinations
|
|
163
|
+
CTRL_A: kittySequence(CODEPOINTS.a, MODIFIERS.ctrl),
|
|
164
|
+
CTRL_C: kittySequence(CODEPOINTS.c, MODIFIERS.ctrl),
|
|
165
|
+
CTRL_D: kittySequence(CODEPOINTS.d, MODIFIERS.ctrl),
|
|
166
|
+
CTRL_E: kittySequence(CODEPOINTS.e, MODIFIERS.ctrl),
|
|
167
|
+
CTRL_G: kittySequence(CODEPOINTS.g, MODIFIERS.ctrl),
|
|
168
|
+
CTRL_K: kittySequence(CODEPOINTS.k, MODIFIERS.ctrl),
|
|
169
|
+
CTRL_L: kittySequence(CODEPOINTS.l, MODIFIERS.ctrl),
|
|
170
|
+
CTRL_O: kittySequence(CODEPOINTS.o, MODIFIERS.ctrl),
|
|
171
|
+
CTRL_P: kittySequence(CODEPOINTS.p, MODIFIERS.ctrl),
|
|
172
|
+
CTRL_T: kittySequence(CODEPOINTS.t, MODIFIERS.ctrl),
|
|
173
|
+
CTRL_U: kittySequence(CODEPOINTS.u, MODIFIERS.ctrl),
|
|
174
|
+
CTRL_V: kittySequence(CODEPOINTS.v, MODIFIERS.ctrl),
|
|
175
|
+
CTRL_W: kittySequence(CODEPOINTS.w, MODIFIERS.ctrl),
|
|
176
|
+
CTRL_Z: kittySequence(CODEPOINTS.z, MODIFIERS.ctrl),
|
|
177
|
+
|
|
178
|
+
// Enter combinations
|
|
179
|
+
SHIFT_ENTER: kittySequence(CODEPOINTS.enter, MODIFIERS.shift),
|
|
180
|
+
ALT_ENTER: kittySequence(CODEPOINTS.enter, MODIFIERS.alt),
|
|
181
|
+
CTRL_ENTER: kittySequence(CODEPOINTS.enter, MODIFIERS.ctrl),
|
|
182
|
+
|
|
183
|
+
// Tab combinations
|
|
184
|
+
SHIFT_TAB: kittySequence(CODEPOINTS.tab, MODIFIERS.shift),
|
|
185
|
+
|
|
186
|
+
// Backspace combinations
|
|
187
|
+
ALT_BACKSPACE: kittySequence(CODEPOINTS.backspace, MODIFIERS.alt),
|
|
188
|
+
} as const;
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Check if input matches a Kitty protocol Ctrl+<key> sequence.
|
|
192
|
+
* Ignores lock key bits (Caps Lock, Num Lock).
|
|
193
|
+
* @param data - The input data to check
|
|
194
|
+
* @param key - Single lowercase letter (e.g., 'c' for Ctrl+C)
|
|
195
|
+
*/
|
|
196
|
+
export function isKittyCtrl(data: string, key: string): boolean {
|
|
197
|
+
if (key.length !== 1) return false;
|
|
198
|
+
const codepoint = key.charCodeAt(0);
|
|
199
|
+
// Check exact match first (fast path)
|
|
200
|
+
if (data === kittySequence(codepoint, MODIFIERS.ctrl)) return true;
|
|
201
|
+
// Check with lock bits masked out
|
|
202
|
+
return matchesKittySequence(data, codepoint, MODIFIERS.ctrl);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Check if input matches a Kitty protocol key sequence with specific modifier.
|
|
207
|
+
* Ignores lock key bits (Caps Lock, Num Lock).
|
|
208
|
+
* @param data - The input data to check
|
|
209
|
+
* @param codepoint - ASCII codepoint of the key
|
|
210
|
+
* @param modifier - Modifier value (use MODIFIERS constants)
|
|
211
|
+
*/
|
|
212
|
+
export function isKittyKey(data: string, codepoint: number, modifier: number): boolean {
|
|
213
|
+
// Check exact match first (fast path)
|
|
214
|
+
if (data === kittySequence(codepoint, modifier)) return true;
|
|
215
|
+
// Check with lock bits masked out
|
|
216
|
+
return matchesKittySequence(data, codepoint, modifier);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Raw control character codes
|
|
220
|
+
const RAW = {
|
|
221
|
+
CTRL_A: "\x01",
|
|
222
|
+
CTRL_C: "\x03",
|
|
223
|
+
CTRL_D: "\x04",
|
|
224
|
+
CTRL_E: "\x05",
|
|
225
|
+
CTRL_G: "\x07",
|
|
226
|
+
CTRL_K: "\x0b",
|
|
227
|
+
CTRL_L: "\x0c",
|
|
228
|
+
CTRL_O: "\x0f",
|
|
229
|
+
CTRL_P: "\x10",
|
|
230
|
+
CTRL_T: "\x14",
|
|
231
|
+
CTRL_U: "\x15",
|
|
232
|
+
CTRL_V: "\x16",
|
|
233
|
+
CTRL_W: "\x17",
|
|
234
|
+
CTRL_Z: "\x1a",
|
|
235
|
+
ALT_BACKSPACE: "\x1b\x7f",
|
|
236
|
+
SHIFT_TAB: "\x1b[Z",
|
|
237
|
+
} as const;
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Check if input matches Ctrl+A (raw byte or Kitty protocol).
|
|
241
|
+
* Ignores lock key bits.
|
|
242
|
+
*/
|
|
243
|
+
export function isCtrlA(data: string): boolean {
|
|
244
|
+
return data === RAW.CTRL_A || data === Keys.CTRL_A || matchesKittySequence(data, CODEPOINTS.a, MODIFIERS.ctrl);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Check if input matches Ctrl+C (raw byte or Kitty protocol).
|
|
249
|
+
* Ignores lock key bits.
|
|
250
|
+
*/
|
|
251
|
+
export function isCtrlC(data: string): boolean {
|
|
252
|
+
return data === RAW.CTRL_C || data === Keys.CTRL_C || matchesKittySequence(data, CODEPOINTS.c, MODIFIERS.ctrl);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Check if input matches Ctrl+D (raw byte or Kitty protocol).
|
|
257
|
+
* Ignores lock key bits.
|
|
258
|
+
*/
|
|
259
|
+
export function isCtrlD(data: string): boolean {
|
|
260
|
+
return data === RAW.CTRL_D || data === Keys.CTRL_D || matchesKittySequence(data, CODEPOINTS.d, MODIFIERS.ctrl);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Check if input matches Ctrl+E (raw byte or Kitty protocol).
|
|
265
|
+
* Ignores lock key bits.
|
|
266
|
+
*/
|
|
267
|
+
export function isCtrlE(data: string): boolean {
|
|
268
|
+
return data === RAW.CTRL_E || data === Keys.CTRL_E || matchesKittySequence(data, CODEPOINTS.e, MODIFIERS.ctrl);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Check if input matches Ctrl+G (raw byte or Kitty protocol).
|
|
273
|
+
* Ignores lock key bits.
|
|
274
|
+
*/
|
|
275
|
+
export function isCtrlG(data: string): boolean {
|
|
276
|
+
return data === RAW.CTRL_G || data === Keys.CTRL_G || matchesKittySequence(data, CODEPOINTS.g, MODIFIERS.ctrl);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Check if input matches Ctrl+K (raw byte or Kitty protocol).
|
|
281
|
+
* Ignores lock key bits.
|
|
282
|
+
* Also checks if first byte is 0x0b for compatibility with terminals
|
|
283
|
+
* that may send trailing bytes.
|
|
284
|
+
*/
|
|
285
|
+
export function isCtrlK(data: string): boolean {
|
|
286
|
+
return (
|
|
287
|
+
data === RAW.CTRL_K ||
|
|
288
|
+
(data.length > 0 && data.charCodeAt(0) === 0x0b) ||
|
|
289
|
+
data === Keys.CTRL_K ||
|
|
290
|
+
matchesKittySequence(data, CODEPOINTS.k, MODIFIERS.ctrl)
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Check if input matches Ctrl+L (raw byte or Kitty protocol).
|
|
296
|
+
* Ignores lock key bits.
|
|
297
|
+
*/
|
|
298
|
+
export function isCtrlL(data: string): boolean {
|
|
299
|
+
return data === RAW.CTRL_L || data === Keys.CTRL_L || matchesKittySequence(data, CODEPOINTS.l, MODIFIERS.ctrl);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Check if input matches Ctrl+O (raw byte or Kitty protocol).
|
|
304
|
+
* Ignores lock key bits.
|
|
305
|
+
*/
|
|
306
|
+
export function isCtrlO(data: string): boolean {
|
|
307
|
+
return data === RAW.CTRL_O || data === Keys.CTRL_O || matchesKittySequence(data, CODEPOINTS.o, MODIFIERS.ctrl);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Check if input matches Shift+Ctrl+O (Kitty protocol only).
|
|
312
|
+
* Ignores lock key bits.
|
|
313
|
+
*/
|
|
314
|
+
export function isShiftCtrlO(data: string): boolean {
|
|
315
|
+
return matchesKittySequence(data, CODEPOINTS.o, MODIFIERS.shift + MODIFIERS.ctrl);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Check if input matches Ctrl+P (raw byte or Kitty protocol).
|
|
320
|
+
* Ignores lock key bits.
|
|
321
|
+
*/
|
|
322
|
+
export function isCtrlP(data: string): boolean {
|
|
323
|
+
return data === RAW.CTRL_P || data === Keys.CTRL_P || matchesKittySequence(data, CODEPOINTS.p, MODIFIERS.ctrl);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Check if input matches Shift+Ctrl+P (Kitty protocol only).
|
|
328
|
+
* Ignores lock key bits.
|
|
329
|
+
*/
|
|
330
|
+
export function isShiftCtrlP(data: string): boolean {
|
|
331
|
+
return matchesKittySequence(data, CODEPOINTS.p, MODIFIERS.shift + MODIFIERS.ctrl);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Check if input matches Shift+Ctrl+D (Kitty protocol only, for debug).
|
|
336
|
+
* Ignores lock key bits.
|
|
337
|
+
*/
|
|
338
|
+
export function isShiftCtrlD(data: string): boolean {
|
|
339
|
+
return matchesKittySequence(data, CODEPOINTS.d, MODIFIERS.shift + MODIFIERS.ctrl);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Check if input matches Ctrl+T (raw byte or Kitty protocol).
|
|
344
|
+
* Ignores lock key bits.
|
|
345
|
+
*/
|
|
346
|
+
export function isCtrlT(data: string): boolean {
|
|
347
|
+
return data === RAW.CTRL_T || data === Keys.CTRL_T || matchesKittySequence(data, CODEPOINTS.t, MODIFIERS.ctrl);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Check if input matches Ctrl+U (raw byte or Kitty protocol).
|
|
352
|
+
* Ignores lock key bits.
|
|
353
|
+
*/
|
|
354
|
+
export function isCtrlU(data: string): boolean {
|
|
355
|
+
return data === RAW.CTRL_U || data === Keys.CTRL_U || matchesKittySequence(data, CODEPOINTS.u, MODIFIERS.ctrl);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Check if input matches Ctrl+V (raw byte or Kitty protocol).
|
|
360
|
+
* Ignores lock key bits.
|
|
361
|
+
* Note: In most terminals, Ctrl+V triggers paste which sends bracketed paste markers,
|
|
362
|
+
* but raw mode can intercept the key before the terminal processes it.
|
|
363
|
+
*/
|
|
364
|
+
export function isCtrlV(data: string): boolean {
|
|
365
|
+
return data === RAW.CTRL_V || data === Keys.CTRL_V || matchesKittySequence(data, CODEPOINTS.v, MODIFIERS.ctrl);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Check if input matches Ctrl+W (raw byte or Kitty protocol).
|
|
370
|
+
* Ignores lock key bits.
|
|
371
|
+
*/
|
|
372
|
+
export function isCtrlW(data: string): boolean {
|
|
373
|
+
return data === RAW.CTRL_W || data === Keys.CTRL_W || matchesKittySequence(data, CODEPOINTS.w, MODIFIERS.ctrl);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Check if input matches Ctrl+Z (raw byte or Kitty protocol).
|
|
378
|
+
* Ignores lock key bits.
|
|
379
|
+
*/
|
|
380
|
+
export function isCtrlZ(data: string): boolean {
|
|
381
|
+
return data === RAW.CTRL_Z || data === Keys.CTRL_Z || matchesKittySequence(data, CODEPOINTS.z, MODIFIERS.ctrl);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Check if input matches Alt+Backspace (legacy or Kitty protocol).
|
|
386
|
+
* Ignores lock key bits.
|
|
387
|
+
*/
|
|
388
|
+
export function isAltBackspace(data: string): boolean {
|
|
389
|
+
return (
|
|
390
|
+
data === RAW.ALT_BACKSPACE ||
|
|
391
|
+
data === Keys.ALT_BACKSPACE ||
|
|
392
|
+
matchesKittySequence(data, CODEPOINTS.backspace, MODIFIERS.alt)
|
|
393
|
+
);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* Check if input matches Shift+Tab (legacy or Kitty protocol).
|
|
398
|
+
* Ignores lock key bits.
|
|
399
|
+
*/
|
|
400
|
+
export function isShiftTab(data: string): boolean {
|
|
401
|
+
return (
|
|
402
|
+
data === RAW.SHIFT_TAB || data === Keys.SHIFT_TAB || matchesKittySequence(data, CODEPOINTS.tab, MODIFIERS.shift)
|
|
403
|
+
);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* Check if input matches the Escape key (raw byte or Kitty protocol).
|
|
408
|
+
* Raw: \x1b (single byte)
|
|
409
|
+
* Kitty: \x1b[27u (codepoint 27 = escape)
|
|
410
|
+
* Ignores lock key bits.
|
|
411
|
+
*/
|
|
412
|
+
export function isEscape(data: string): boolean {
|
|
413
|
+
return data === "\x1b" || data === `\x1b[${CODEPOINTS.escape}u` || matchesKittySequence(data, CODEPOINTS.escape, 0);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// Arrow key virtual codepoints (negative to avoid conflicts with real codepoints)
|
|
417
|
+
const ARROW_CODEPOINTS = {
|
|
418
|
+
up: -1,
|
|
419
|
+
down: -2,
|
|
420
|
+
right: -3,
|
|
421
|
+
left: -4,
|
|
422
|
+
} as const;
|
|
423
|
+
|
|
424
|
+
/**
|
|
425
|
+
* Check if input matches Arrow Up key.
|
|
426
|
+
* Handles both legacy (\x1b[A) and Kitty protocol with modifiers.
|
|
427
|
+
*/
|
|
428
|
+
export function isArrowUp(data: string): boolean {
|
|
429
|
+
return data === "\x1b[A" || matchesKittySequence(data, ARROW_CODEPOINTS.up, 0);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* Check if input matches Arrow Down key.
|
|
434
|
+
* Handles both legacy (\x1b[B) and Kitty protocol with modifiers.
|
|
435
|
+
*/
|
|
436
|
+
export function isArrowDown(data: string): boolean {
|
|
437
|
+
return data === "\x1b[B" || matchesKittySequence(data, ARROW_CODEPOINTS.down, 0);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/**
|
|
441
|
+
* Check if input matches Arrow Right key.
|
|
442
|
+
* Handles both legacy (\x1b[C) and Kitty protocol with modifiers.
|
|
443
|
+
*/
|
|
444
|
+
export function isArrowRight(data: string): boolean {
|
|
445
|
+
return data === "\x1b[C" || matchesKittySequence(data, ARROW_CODEPOINTS.right, 0);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
/**
|
|
449
|
+
* Check if input matches Arrow Left key.
|
|
450
|
+
* Handles both legacy (\x1b[D) and Kitty protocol with modifiers.
|
|
451
|
+
*/
|
|
452
|
+
export function isArrowLeft(data: string): boolean {
|
|
453
|
+
return data === "\x1b[D" || matchesKittySequence(data, ARROW_CODEPOINTS.left, 0);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* Check if input matches plain Tab key (no modifiers).
|
|
458
|
+
* Handles both legacy (\t) and Kitty protocol.
|
|
459
|
+
*/
|
|
460
|
+
export function isTab(data: string): boolean {
|
|
461
|
+
return data === "\t" || matchesKittySequence(data, CODEPOINTS.tab, 0);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/**
|
|
465
|
+
* Check if input matches plain Enter/Return key (no modifiers).
|
|
466
|
+
* Handles both legacy (\r) and Kitty protocol.
|
|
467
|
+
*/
|
|
468
|
+
export function isEnter(data: string): boolean {
|
|
469
|
+
return data === "\r" || matchesKittySequence(data, CODEPOINTS.enter, 0);
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
/**
|
|
473
|
+
* Check if input matches plain Backspace key (no modifiers).
|
|
474
|
+
* Handles both legacy (\x7f, \x08) and Kitty protocol.
|
|
475
|
+
*/
|
|
476
|
+
export function isBackspace(data: string): boolean {
|
|
477
|
+
return data === "\x7f" || data === "\x08" || matchesKittySequence(data, CODEPOINTS.backspace, 0);
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
/**
|
|
481
|
+
* Check if input matches Shift+Enter.
|
|
482
|
+
* Ignores lock key bits.
|
|
483
|
+
*/
|
|
484
|
+
export function isShiftEnter(data: string): boolean {
|
|
485
|
+
return data === Keys.SHIFT_ENTER || matchesKittySequence(data, CODEPOINTS.enter, MODIFIERS.shift);
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
/**
|
|
489
|
+
* Check if input matches Alt+Enter.
|
|
490
|
+
* Ignores lock key bits.
|
|
491
|
+
*/
|
|
492
|
+
export function isAltEnter(data: string): boolean {
|
|
493
|
+
return data === Keys.ALT_ENTER || data === "\x1b\r" || matchesKittySequence(data, CODEPOINTS.enter, MODIFIERS.alt);
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
/**
|
|
497
|
+
* Check if input matches Option/Alt+Left (word navigation).
|
|
498
|
+
* Handles multiple formats including Kitty protocol.
|
|
499
|
+
*/
|
|
500
|
+
export function isAltLeft(data: string): boolean {
|
|
501
|
+
return data === "\x1b[1;3D" || data === "\x1bb" || matchesKittySequence(data, ARROW_CODEPOINTS.left, MODIFIERS.alt);
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
/**
|
|
505
|
+
* Check if input matches Option/Alt+Right (word navigation).
|
|
506
|
+
* Handles multiple formats including Kitty protocol.
|
|
507
|
+
*/
|
|
508
|
+
export function isAltRight(data: string): boolean {
|
|
509
|
+
return data === "\x1b[1;3C" || data === "\x1bf" || matchesKittySequence(data, ARROW_CODEPOINTS.right, MODIFIERS.alt);
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
/**
|
|
513
|
+
* Check if input matches Ctrl+Left (word navigation).
|
|
514
|
+
* Handles multiple formats including Kitty protocol.
|
|
515
|
+
*/
|
|
516
|
+
export function isCtrlLeft(data: string): boolean {
|
|
517
|
+
return data === "\x1b[1;5D" || matchesKittySequence(data, ARROW_CODEPOINTS.left, MODIFIERS.ctrl);
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
/**
|
|
521
|
+
* Check if input matches Ctrl+Right (word navigation).
|
|
522
|
+
* Handles multiple formats including Kitty protocol.
|
|
523
|
+
*/
|
|
524
|
+
export function isCtrlRight(data: string): boolean {
|
|
525
|
+
return data === "\x1b[1;5C" || matchesKittySequence(data, ARROW_CODEPOINTS.right, MODIFIERS.ctrl);
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
/**
|
|
529
|
+
* Check if input matches Home key.
|
|
530
|
+
* Handles legacy formats and Kitty protocol with lock key modifiers.
|
|
531
|
+
*/
|
|
532
|
+
export function isHome(data: string): boolean {
|
|
533
|
+
return (
|
|
534
|
+
data === "\x1b[H" ||
|
|
535
|
+
data === "\x1b[1~" ||
|
|
536
|
+
data === "\x1b[7~" ||
|
|
537
|
+
matchesKittySequence(data, FUNCTIONAL_CODEPOINTS.home, 0)
|
|
538
|
+
);
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
/**
|
|
542
|
+
* Check if input matches End key.
|
|
543
|
+
* Handles legacy formats and Kitty protocol with lock key modifiers.
|
|
544
|
+
*/
|
|
545
|
+
export function isEnd(data: string): boolean {
|
|
546
|
+
return (
|
|
547
|
+
data === "\x1b[F" ||
|
|
548
|
+
data === "\x1b[4~" ||
|
|
549
|
+
data === "\x1b[8~" ||
|
|
550
|
+
matchesKittySequence(data, FUNCTIONAL_CODEPOINTS.end, 0)
|
|
551
|
+
);
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
/**
|
|
555
|
+
* Check if input matches Delete key (forward delete).
|
|
556
|
+
* Handles legacy format and Kitty protocol with lock key modifiers.
|
|
557
|
+
*/
|
|
558
|
+
export function isDelete(data: string): boolean {
|
|
559
|
+
return data === "\x1b[3~" || matchesKittySequence(data, FUNCTIONAL_CODEPOINTS.delete, 0);
|
|
560
|
+
}
|