@dungle-scrubs/tallow 0.9.3 → 0.9.6
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/dist/cli.js +7 -4
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +1 -1
- package/dist/config.js +1 -1
- package/dist/interactive-mode-patch.d.ts +24 -10
- package/dist/interactive-mode-patch.d.ts.map +1 -1
- package/dist/interactive-mode-patch.js +285 -148
- package/dist/interactive-mode-patch.js.map +1 -1
- package/dist/interactive-reset.d.ts +49 -0
- package/dist/interactive-reset.d.ts.map +1 -0
- package/dist/interactive-reset.js +40 -0
- package/dist/interactive-reset.js.map +1 -0
- package/dist/pi-tui-editor-patch.d.ts +10 -0
- package/dist/pi-tui-editor-patch.d.ts.map +1 -0
- package/dist/pi-tui-editor-patch.js +159 -0
- package/dist/pi-tui-editor-patch.js.map +1 -0
- package/dist/pi-tui-patch.d.ts +2 -0
- package/dist/pi-tui-patch.d.ts.map +1 -0
- package/dist/pi-tui-patch.js +563 -0
- package/dist/pi-tui-patch.js.map +1 -0
- package/dist/pi-tui-settings-list-patch.d.ts +11 -0
- package/dist/pi-tui-settings-list-patch.d.ts.map +1 -0
- package/dist/pi-tui-settings-list-patch.js +38 -0
- package/dist/pi-tui-settings-list-patch.js.map +1 -0
- package/dist/reset-diagnostics.d.ts +69 -0
- package/dist/reset-diagnostics.d.ts.map +1 -0
- package/dist/reset-diagnostics.js +41 -0
- package/dist/reset-diagnostics.js.map +1 -0
- package/dist/sdk.d.ts +5 -21
- package/dist/sdk.d.ts.map +1 -1
- package/dist/sdk.js +180 -149
- package/dist/sdk.js.map +1 -1
- package/dist/workspace-transition-interactive.d.ts +1 -0
- package/dist/workspace-transition-interactive.d.ts.map +1 -1
- package/dist/workspace-transition-interactive.js +7 -17
- package/dist/workspace-transition-interactive.js.map +1 -1
- package/extensions/__integration__/audit-findings.test.ts +6 -16
- package/extensions/__integration__/teams-runtime.test.ts +4 -1
- package/extensions/_icons/index.ts +2 -4
- package/extensions/_shared/__tests__/image-metadata.test.ts +33 -0
- package/extensions/_shared/__tests__/terminal-links.test.ts +18 -0
- package/extensions/_shared/image-metadata.ts +99 -0
- package/extensions/_shared/inline-preview.ts +1 -1
- package/extensions/_shared/pid-registry.ts +0 -1
- package/extensions/_shared/terminal-links.ts +22 -0
- package/extensions/ask-user-question-tool/index.ts +0 -3
- package/extensions/clear/__tests__/clear.test.ts +270 -3
- package/extensions/command-expansion/index.ts +1 -1
- package/extensions/context-files/index.ts +5 -1
- package/extensions/context-fork/__tests__/context-fork.test.ts +94 -1
- package/extensions/context-fork/extension.json +1 -1
- package/extensions/context-fork/index.ts +32 -0
- package/extensions/edit-tool-enhanced/index.ts +2 -1
- package/extensions/hooks/index.ts +33 -11
- package/extensions/loop/index.ts +14 -1
- package/extensions/lsp/index.ts +64 -13
- package/extensions/lsp/package.json +2 -2
- package/extensions/permissions/__tests__/permissions.test.ts +4 -4
- package/extensions/random-spinner/index.ts +7 -642
- package/extensions/read-tool-enhanced/index.ts +6 -8
- package/extensions/render-stabilizer/__tests__/render-stabilizer.test.ts +4 -5
- package/extensions/render-stabilizer/index.ts +6 -6
- package/extensions/show-system-prompt/__tests__/show-system-prompt.test.ts +1 -1
- package/extensions/slash-command-bridge/__tests__/slash-command-bridge.test.ts +26 -0
- package/extensions/slash-command-bridge/index.ts +14 -2
- package/extensions/subagent-tool/index.ts +1 -1
- package/extensions/subagent-tool/model-resolver.ts +274 -7
- package/extensions/tasks/__tests__/state-ui.test.ts +3 -3
- package/extensions/tasks/__tests__/widget-subagents.test.ts +2 -2
- package/extensions/tasks/commands/register-tasks-extension.ts +10 -10
- package/extensions/tasks/state/index.ts +1 -1
- package/extensions/tasks/ui/index.ts +2 -7
- package/extensions/teams-tool/tools/register-extension.ts +1 -3
- package/extensions/web-search-tool/index.ts +2 -1
- package/extensions/write-tool-enhanced/__tests__/write-tool-enhanced.test.ts +21 -6
- package/extensions/write-tool-enhanced/index.ts +2 -1
- package/node_modules/@mariozechner/pi-tui/README.md +56 -34
- package/node_modules/@mariozechner/pi-tui/dist/autocomplete.d.ts +18 -13
- package/node_modules/@mariozechner/pi-tui/dist/autocomplete.d.ts.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/autocomplete.js +182 -113
- package/node_modules/@mariozechner/pi-tui/dist/autocomplete.js.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/components/cancellable-loader.js +3 -3
- package/node_modules/@mariozechner/pi-tui/dist/components/cancellable-loader.js.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/components/editor.d.ts +45 -36
- package/node_modules/@mariozechner/pi-tui/dist/components/editor.d.ts.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/components/editor.js +489 -325
- package/node_modules/@mariozechner/pi-tui/dist/components/editor.js.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/components/image.d.ts +1 -99
- package/node_modules/@mariozechner/pi-tui/dist/components/image.d.ts.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/components/image.js +17 -192
- package/node_modules/@mariozechner/pi-tui/dist/components/image.js.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/components/input.d.ts.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/components/input.js +57 -60
- package/node_modules/@mariozechner/pi-tui/dist/components/input.js.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/components/loader.d.ts +2 -69
- package/node_modules/@mariozechner/pi-tui/dist/components/loader.d.ts.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/components/loader.js +5 -102
- package/node_modules/@mariozechner/pi-tui/dist/components/loader.js.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/components/markdown.d.ts.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/components/markdown.js +111 -53
- package/node_modules/@mariozechner/pi-tui/dist/components/markdown.js.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/components/select-list.d.ts +19 -1
- package/node_modules/@mariozechner/pi-tui/dist/components/select-list.d.ts.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/components/select-list.js +78 -67
- package/node_modules/@mariozechner/pi-tui/dist/components/select-list.js.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/components/settings-list.d.ts +0 -2
- package/node_modules/@mariozechner/pi-tui/dist/components/settings-list.d.ts.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/components/settings-list.js +12 -23
- package/node_modules/@mariozechner/pi-tui/dist/components/settings-list.js.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/index.d.ts +8 -10
- package/node_modules/@mariozechner/pi-tui/dist/index.d.ts.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/index.js +6 -9
- package/node_modules/@mariozechner/pi-tui/dist/index.js.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/keybindings.d.ts +108 -238
- package/node_modules/@mariozechner/pi-tui/dist/keybindings.d.ts.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/keybindings.js +108 -365
- package/node_modules/@mariozechner/pi-tui/dist/keybindings.js.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/keys.d.ts +33 -48
- package/node_modules/@mariozechner/pi-tui/dist/keys.d.ts.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/keys.js +239 -155
- package/node_modules/@mariozechner/pi-tui/dist/keys.js.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/terminal-image.d.ts +14 -94
- package/node_modules/@mariozechner/pi-tui/dist/terminal-image.d.ts.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/terminal-image.js +44 -186
- package/node_modules/@mariozechner/pi-tui/dist/terminal-image.js.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/terminal.d.ts +13 -58
- package/node_modules/@mariozechner/pi-tui/dist/terminal.d.ts.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/terminal.js +78 -111
- package/node_modules/@mariozechner/pi-tui/dist/terminal.js.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/tui.d.ts +24 -110
- package/node_modules/@mariozechner/pi-tui/dist/tui.d.ts.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/tui.js +188 -435
- package/node_modules/@mariozechner/pi-tui/dist/tui.js.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/utils.d.ts +0 -18
- package/node_modules/@mariozechner/pi-tui/dist/utils.d.ts.map +1 -1
- package/node_modules/@mariozechner/pi-tui/dist/utils.js +251 -119
- package/node_modules/@mariozechner/pi-tui/dist/utils.js.map +1 -1
- package/node_modules/@mariozechner/pi-tui/package.json +6 -6
- package/node_modules/@mariozechner/pi-tui/src/__tests__/__snapshots__/render.test.ts.snap +3 -40
- package/node_modules/@mariozechner/pi-tui/src/__tests__/image-component.test.ts +71 -81
- package/node_modules/@mariozechner/pi-tui/src/__tests__/render.test.ts +0 -33
- package/node_modules/@mariozechner/pi-tui/src/__tests__/terminal-image.test.ts +93 -334
- package/node_modules/@mariozechner/pi-tui/src/__tests__/tui-render-scheduling.test.ts +1 -1
- package/node_modules/@mariozechner/pi-tui/src/__tests__/utils.test.ts +11 -196
- package/node_modules/@mariozechner/pi-tui/src/autocomplete.ts +228 -142
- package/node_modules/@mariozechner/pi-tui/src/components/cancellable-loader.ts +3 -3
- package/node_modules/@mariozechner/pi-tui/src/components/editor.ts +624 -390
- package/node_modules/@mariozechner/pi-tui/src/components/image.ts +17 -227
- package/node_modules/@mariozechner/pi-tui/src/components/input.ts +71 -63
- package/node_modules/@mariozechner/pi-tui/src/components/loader.ts +5 -137
- package/node_modules/@mariozechner/pi-tui/src/components/markdown.ts +143 -52
- package/node_modules/@mariozechner/pi-tui/src/components/select-list.ts +136 -70
- package/node_modules/@mariozechner/pi-tui/src/components/settings-list.ts +11 -23
- package/node_modules/@mariozechner/pi-tui/src/index.ts +17 -36
- package/node_modules/@mariozechner/pi-tui/src/keybindings.ts +148 -421
- package/node_modules/@mariozechner/pi-tui/src/keys.ts +253 -181
- package/node_modules/@mariozechner/pi-tui/src/terminal-image.ts +51 -252
- package/node_modules/@mariozechner/pi-tui/src/terminal.ts +78 -133
- package/node_modules/@mariozechner/pi-tui/src/tui.ts +202 -478
- package/node_modules/@mariozechner/pi-tui/src/utils.ts +289 -125
- package/node_modules/@mariozechner/pi-tui/tsconfig.build.json +1 -0
- package/package.json +13 -13
- package/packages/tallow-tui/node_modules/@types/mime-types/README.md +8 -2
- package/packages/tallow-tui/node_modules/@types/mime-types/index.d.ts +6 -0
- package/packages/tallow-tui/node_modules/@types/mime-types/package.json +9 -3
- package/packages/tallow-tui/node_modules/get-east-asian-width/lookup-data.js +18 -0
- package/packages/tallow-tui/node_modules/get-east-asian-width/lookup.js +116 -384
- package/packages/tallow-tui/node_modules/get-east-asian-width/package.json +5 -4
- package/packages/tallow-tui/node_modules/get-east-asian-width/utilities.js +24 -0
- package/packages/tallow-tui/node_modules/marked/README.md +5 -4
- package/packages/tallow-tui/node_modules/marked/bin/main.js +10 -8
- package/packages/tallow-tui/node_modules/marked/bin/marked.js +2 -1
- package/packages/tallow-tui/node_modules/marked/lib/marked.d.ts +156 -125
- package/packages/tallow-tui/node_modules/marked/lib/marked.esm.js +67 -2179
- package/packages/tallow-tui/node_modules/marked/lib/marked.esm.js.map +3 -3
- package/packages/tallow-tui/node_modules/marked/lib/marked.umd.js +67 -2201
- package/packages/tallow-tui/node_modules/marked/lib/marked.umd.js.map +3 -3
- package/packages/tallow-tui/node_modules/marked/man/marked.1 +4 -2
- package/packages/tallow-tui/node_modules/marked/man/marked.1.md +2 -1
- package/packages/tallow-tui/node_modules/marked/package.json +26 -34
- package/runtime/model-metadata-overrides.ts +10 -1
- package/runtime/pid-schema.ts +26 -6
- package/skills/tallow-expert/SKILL.md +1 -3
- package/node_modules/@mariozechner/pi-tui/dist/border-styles.d.ts +0 -32
- package/node_modules/@mariozechner/pi-tui/dist/border-styles.d.ts.map +0 -1
- package/node_modules/@mariozechner/pi-tui/dist/border-styles.js +0 -46
- package/node_modules/@mariozechner/pi-tui/dist/border-styles.js.map +0 -1
- package/node_modules/@mariozechner/pi-tui/dist/components/bordered-box.d.ts +0 -52
- package/node_modules/@mariozechner/pi-tui/dist/components/bordered-box.d.ts.map +0 -1
- package/node_modules/@mariozechner/pi-tui/dist/components/bordered-box.js +0 -89
- package/node_modules/@mariozechner/pi-tui/dist/components/bordered-box.js.map +0 -1
- package/node_modules/@mariozechner/pi-tui/dist/test-utils/capability-env.d.ts +0 -14
- package/node_modules/@mariozechner/pi-tui/dist/test-utils/capability-env.d.ts.map +0 -1
- package/node_modules/@mariozechner/pi-tui/dist/test-utils/capability-env.js +0 -55
- package/node_modules/@mariozechner/pi-tui/dist/test-utils/capability-env.js.map +0 -1
- package/node_modules/@mariozechner/pi-tui/src/__tests__/editor-change-listener.test.ts +0 -121
- package/node_modules/@mariozechner/pi-tui/src/__tests__/editor-ghost-text.test.ts +0 -112
- package/node_modules/@mariozechner/pi-tui/src/__tests__/mouse-events.test.ts +0 -134
- package/node_modules/@mariozechner/pi-tui/src/__tests__/settings-list.test.ts +0 -49
- package/node_modules/@mariozechner/pi-tui/src/__tests__/tui-diff-regression.test.ts +0 -555
- package/node_modules/@mariozechner/pi-tui/src/border-styles.ts +0 -60
- package/node_modules/@mariozechner/pi-tui/src/components/bordered-box.ts +0 -113
- package/node_modules/@mariozechner/pi-tui/src/test-utils/capability-env.ts +0 -56
- package/packages/tallow-tui/node_modules/marked/lib/marked.cjs +0 -2211
- package/packages/tallow-tui/node_modules/marked/lib/marked.cjs.map +0 -7
- package/packages/tallow-tui/node_modules/marked/lib/marked.d.cts +0 -728
- package/packages/tallow-tui/node_modules/marked/marked.min.js +0 -69
|
@@ -71,6 +71,8 @@ type Letter =
|
|
|
71
71
|
| "y"
|
|
72
72
|
| "z";
|
|
73
73
|
|
|
74
|
+
type Digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9";
|
|
75
|
+
|
|
74
76
|
type SymbolKey =
|
|
75
77
|
| "`"
|
|
76
78
|
| "-"
|
|
@@ -136,29 +138,20 @@ type SpecialKey =
|
|
|
136
138
|
| "f11"
|
|
137
139
|
| "f12";
|
|
138
140
|
|
|
139
|
-
type BaseKey = Letter | SymbolKey | SpecialKey;
|
|
141
|
+
type BaseKey = Letter | Digit | SymbolKey | SpecialKey;
|
|
142
|
+
type ModifierName = "ctrl" | "shift" | "alt" | "super";
|
|
143
|
+
|
|
144
|
+
type ModifiedKeyId<Key extends string, RemainingModifiers extends ModifierName = ModifierName> = {
|
|
145
|
+
[M in RemainingModifiers]:
|
|
146
|
+
| `${M}+${Key}`
|
|
147
|
+
| `${M}+${ModifiedKeyId<Key, Exclude<RemainingModifiers, M>>}`;
|
|
148
|
+
}[RemainingModifiers];
|
|
140
149
|
|
|
141
150
|
/**
|
|
142
151
|
* Union type of all valid key identifiers.
|
|
143
152
|
* Provides autocomplete and catches typos at compile time.
|
|
144
153
|
*/
|
|
145
|
-
export type KeyId =
|
|
146
|
-
| BaseKey
|
|
147
|
-
| `ctrl+${BaseKey}`
|
|
148
|
-
| `shift+${BaseKey}`
|
|
149
|
-
| `alt+${BaseKey}`
|
|
150
|
-
| `ctrl+shift+${BaseKey}`
|
|
151
|
-
| `shift+ctrl+${BaseKey}`
|
|
152
|
-
| `ctrl+alt+${BaseKey}`
|
|
153
|
-
| `alt+ctrl+${BaseKey}`
|
|
154
|
-
| `shift+alt+${BaseKey}`
|
|
155
|
-
| `alt+shift+${BaseKey}`
|
|
156
|
-
| `ctrl+shift+alt+${BaseKey}`
|
|
157
|
-
| `ctrl+alt+shift+${BaseKey}`
|
|
158
|
-
| `shift+ctrl+alt+${BaseKey}`
|
|
159
|
-
| `shift+alt+ctrl+${BaseKey}`
|
|
160
|
-
| `alt+ctrl+shift+${BaseKey}`
|
|
161
|
-
| `alt+shift+ctrl+${BaseKey}`;
|
|
154
|
+
export type KeyId = BaseKey | ModifiedKeyId<BaseKey>;
|
|
162
155
|
|
|
163
156
|
/**
|
|
164
157
|
* Helper object for creating typed key identifiers with autocomplete.
|
|
@@ -166,8 +159,8 @@ export type KeyId =
|
|
|
166
159
|
* Usage:
|
|
167
160
|
* - Key.escape, Key.enter, Key.tab, etc. for special keys
|
|
168
161
|
* - Key.backtick, Key.comma, Key.period, etc. for symbol keys
|
|
169
|
-
* - Key.ctrl("c"), Key.alt("x") for single
|
|
170
|
-
* - Key.ctrlShift("p"), Key.ctrlAlt("x") for combined modifiers
|
|
162
|
+
* - Key.ctrl("c"), Key.alt("x"), Key.super("k") for single modifiers
|
|
163
|
+
* - Key.ctrlShift("p"), Key.ctrlAlt("x"), Key.ctrlSuper("k") for combined modifiers
|
|
171
164
|
*/
|
|
172
165
|
export const Key = {
|
|
173
166
|
// Special keys
|
|
@@ -239,6 +232,7 @@ export const Key = {
|
|
|
239
232
|
ctrl: <K extends BaseKey>(key: K): `ctrl+${K}` => `ctrl+${key}`,
|
|
240
233
|
shift: <K extends BaseKey>(key: K): `shift+${K}` => `shift+${key}`,
|
|
241
234
|
alt: <K extends BaseKey>(key: K): `alt+${K}` => `alt+${key}`,
|
|
235
|
+
super: <K extends BaseKey>(key: K): `super+${K}` => `super+${key}`,
|
|
242
236
|
|
|
243
237
|
// Combined modifiers
|
|
244
238
|
ctrlShift: <K extends BaseKey>(key: K): `ctrl+shift+${K}` => `ctrl+shift+${key}`,
|
|
@@ -247,9 +241,16 @@ export const Key = {
|
|
|
247
241
|
altCtrl: <K extends BaseKey>(key: K): `alt+ctrl+${K}` => `alt+ctrl+${key}`,
|
|
248
242
|
shiftAlt: <K extends BaseKey>(key: K): `shift+alt+${K}` => `shift+alt+${key}`,
|
|
249
243
|
altShift: <K extends BaseKey>(key: K): `alt+shift+${K}` => `alt+shift+${key}`,
|
|
244
|
+
ctrlSuper: <K extends BaseKey>(key: K): `ctrl+super+${K}` => `ctrl+super+${key}`,
|
|
245
|
+
superCtrl: <K extends BaseKey>(key: K): `super+ctrl+${K}` => `super+ctrl+${key}`,
|
|
246
|
+
shiftSuper: <K extends BaseKey>(key: K): `shift+super+${K}` => `shift+super+${key}`,
|
|
247
|
+
superShift: <K extends BaseKey>(key: K): `super+shift+${K}` => `super+shift+${key}`,
|
|
248
|
+
altSuper: <K extends BaseKey>(key: K): `alt+super+${K}` => `alt+super+${key}`,
|
|
249
|
+
superAlt: <K extends BaseKey>(key: K): `super+alt+${K}` => `super+alt+${key}`,
|
|
250
250
|
|
|
251
251
|
// Triple modifiers
|
|
252
252
|
ctrlShiftAlt: <K extends BaseKey>(key: K): `ctrl+shift+alt+${K}` => `ctrl+shift+alt+${key}`,
|
|
253
|
+
ctrlShiftSuper: <K extends BaseKey>(key: K): `ctrl+shift+super+${K}` => `ctrl+shift+super+${key}`,
|
|
253
254
|
} as const;
|
|
254
255
|
|
|
255
256
|
// =============================================================================
|
|
@@ -294,6 +295,7 @@ const MODIFIERS = {
|
|
|
294
295
|
shift: 1,
|
|
295
296
|
alt: 2,
|
|
296
297
|
ctrl: 4,
|
|
298
|
+
super: 8,
|
|
297
299
|
} as const;
|
|
298
300
|
|
|
299
301
|
const LOCK_MASK = 64 + 128; // Caps Lock + Num Lock
|
|
@@ -323,6 +325,40 @@ const FUNCTIONAL_CODEPOINTS = {
|
|
|
323
325
|
end: -15,
|
|
324
326
|
} as const;
|
|
325
327
|
|
|
328
|
+
const KITTY_FUNCTIONAL_KEY_EQUIVALENTS = new Map<number, number>([
|
|
329
|
+
[57399, 48], // KP_0 -> 0
|
|
330
|
+
[57400, 49], // KP_1 -> 1
|
|
331
|
+
[57401, 50], // KP_2 -> 2
|
|
332
|
+
[57402, 51], // KP_3 -> 3
|
|
333
|
+
[57403, 52], // KP_4 -> 4
|
|
334
|
+
[57404, 53], // KP_5 -> 5
|
|
335
|
+
[57405, 54], // KP_6 -> 6
|
|
336
|
+
[57406, 55], // KP_7 -> 7
|
|
337
|
+
[57407, 56], // KP_8 -> 8
|
|
338
|
+
[57408, 57], // KP_9 -> 9
|
|
339
|
+
[57409, 46], // KP_DECIMAL -> .
|
|
340
|
+
[57410, 47], // KP_DIVIDE -> /
|
|
341
|
+
[57411, 42], // KP_MULTIPLY -> *
|
|
342
|
+
[57412, 45], // KP_SUBTRACT -> -
|
|
343
|
+
[57413, 43], // KP_ADD -> +
|
|
344
|
+
[57415, 61], // KP_EQUAL -> =
|
|
345
|
+
[57416, 44], // KP_SEPARATOR -> ,
|
|
346
|
+
[57417, ARROW_CODEPOINTS.left],
|
|
347
|
+
[57418, ARROW_CODEPOINTS.right],
|
|
348
|
+
[57419, ARROW_CODEPOINTS.up],
|
|
349
|
+
[57420, ARROW_CODEPOINTS.down],
|
|
350
|
+
[57421, FUNCTIONAL_CODEPOINTS.pageUp],
|
|
351
|
+
[57422, FUNCTIONAL_CODEPOINTS.pageDown],
|
|
352
|
+
[57423, FUNCTIONAL_CODEPOINTS.home],
|
|
353
|
+
[57424, FUNCTIONAL_CODEPOINTS.end],
|
|
354
|
+
[57425, FUNCTIONAL_CODEPOINTS.insert],
|
|
355
|
+
[57426, FUNCTIONAL_CODEPOINTS.delete],
|
|
356
|
+
]);
|
|
357
|
+
|
|
358
|
+
function normalizeKittyFunctionalCodepoint(codepoint: number): number {
|
|
359
|
+
return KITTY_FUNCTIONAL_KEY_EQUIVALENTS.get(codepoint) ?? codepoint;
|
|
360
|
+
}
|
|
361
|
+
|
|
326
362
|
const LEGACY_KEY_SEQUENCES = {
|
|
327
363
|
up: ["\x1b[A", "\x1bOA"],
|
|
328
364
|
down: ["\x1b[B", "\x1bOB"],
|
|
@@ -475,16 +511,17 @@ interface ParsedKittySequence {
|
|
|
475
511
|
eventType: KeyEventType;
|
|
476
512
|
}
|
|
477
513
|
|
|
478
|
-
|
|
514
|
+
interface ParsedModifyOtherKeysSequence {
|
|
515
|
+
codepoint: number;
|
|
516
|
+
modifier: number;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
// Store the last parsed event type for isKeyRelease() to query
|
|
479
520
|
let _lastEventType: KeyEventType = "press";
|
|
480
521
|
|
|
481
522
|
/**
|
|
482
|
-
* Check if the
|
|
483
|
-
* Performs fast pattern matching on raw terminal escape sequences.
|
|
523
|
+
* Check if the last parsed key event was a key release.
|
|
484
524
|
* Only meaningful when Kitty keyboard protocol with flag 2 is active.
|
|
485
|
-
*
|
|
486
|
-
* @param data - Raw terminal input string
|
|
487
|
-
* @returns true if the data contains a key release escape sequence
|
|
488
525
|
*/
|
|
489
526
|
export function isKeyRelease(data: string): boolean {
|
|
490
527
|
// Don't treat bracketed paste content as key release, even if it contains
|
|
@@ -513,12 +550,8 @@ export function isKeyRelease(data: string): boolean {
|
|
|
513
550
|
}
|
|
514
551
|
|
|
515
552
|
/**
|
|
516
|
-
* Check if the
|
|
517
|
-
* Performs fast pattern matching on raw terminal escape sequences.
|
|
553
|
+
* Check if the last parsed key event was a key repeat.
|
|
518
554
|
* Only meaningful when Kitty keyboard protocol with flag 2 is active.
|
|
519
|
-
*
|
|
520
|
-
* @param data - Raw terminal input string
|
|
521
|
-
* @returns true if the data contains a key repeat escape sequence
|
|
522
555
|
*/
|
|
523
556
|
export function isKeyRepeat(data: string): boolean {
|
|
524
557
|
// Don't treat bracketed paste content as key repeat, even if it contains
|
|
@@ -631,8 +664,11 @@ function matchesKittySequence(
|
|
|
631
664
|
// Check if modifiers match
|
|
632
665
|
if (actualMod !== expectedMod) return false;
|
|
633
666
|
|
|
634
|
-
|
|
635
|
-
|
|
667
|
+
const normalizedCodepoint = normalizeKittyFunctionalCodepoint(parsed.codepoint);
|
|
668
|
+
const normalizedExpectedCodepoint = normalizeKittyFunctionalCodepoint(expectedCodepoint);
|
|
669
|
+
|
|
670
|
+
// Primary match: codepoint matches directly after normalizing functional keys
|
|
671
|
+
if (normalizedCodepoint === normalizedExpectedCodepoint) return true;
|
|
636
672
|
|
|
637
673
|
// Alternate match: use base layout key for non-Latin keyboard layouts.
|
|
638
674
|
// This allows Ctrl+С (Cyrillic) to match Ctrl+c (Latin) when terminal reports
|
|
@@ -647,7 +683,7 @@ function matchesKittySequence(
|
|
|
647
683
|
// (letter remapping) and Ctrl+/ could falsely match Ctrl+[ (symbol remapping)
|
|
648
684
|
// if the base layout key were always considered.
|
|
649
685
|
if (parsed.baseLayoutKey !== undefined && parsed.baseLayoutKey === expectedCodepoint) {
|
|
650
|
-
const cp =
|
|
686
|
+
const cp = normalizedCodepoint;
|
|
651
687
|
const isLatinLetter = cp >= 97 && cp <= 122; // a-z
|
|
652
688
|
const isKnownSymbol = SYMBOL_KEYS.has(String.fromCharCode(cp));
|
|
653
689
|
if (!isLatinLetter && !isKnownSymbol) return true;
|
|
@@ -656,6 +692,14 @@ function matchesKittySequence(
|
|
|
656
692
|
return false;
|
|
657
693
|
}
|
|
658
694
|
|
|
695
|
+
function parseModifyOtherKeysSequence(data: string): ParsedModifyOtherKeysSequence | null {
|
|
696
|
+
const match = data.match(/^\x1b\[27;(\d+);(\d+)~$/);
|
|
697
|
+
if (!match) return null;
|
|
698
|
+
const modValue = parseInt(match[1]!, 10);
|
|
699
|
+
const codepoint = parseInt(match[2]!, 10);
|
|
700
|
+
return { codepoint, modifier: modValue - 1 };
|
|
701
|
+
}
|
|
702
|
+
|
|
659
703
|
/**
|
|
660
704
|
* Match xterm modifyOtherKeys format: CSI 27 ; modifiers ; keycode ~
|
|
661
705
|
* This is used by terminals when Kitty protocol is not enabled.
|
|
@@ -666,21 +710,11 @@ function matchesModifyOtherKeys(
|
|
|
666
710
|
expectedKeycode: number,
|
|
667
711
|
expectedModifier: number
|
|
668
712
|
): boolean {
|
|
669
|
-
const
|
|
670
|
-
if (!
|
|
671
|
-
|
|
672
|
-
const keycode = parseInt(match[2]!, 10);
|
|
673
|
-
// Convert from 1-indexed xterm format to our 0-indexed format
|
|
674
|
-
const actualMod = modValue - 1;
|
|
675
|
-
return keycode === expectedKeycode && actualMod === expectedModifier;
|
|
713
|
+
const parsed = parseModifyOtherKeysSequence(data);
|
|
714
|
+
if (!parsed) return false;
|
|
715
|
+
return parsed.codepoint === expectedKeycode && parsed.modifier === expectedModifier;
|
|
676
716
|
}
|
|
677
717
|
|
|
678
|
-
/**
|
|
679
|
-
* Detect Windows Terminal by session environment variable.
|
|
680
|
-
* Excludes SSH sessions where WT_SESSION may leak from the host.
|
|
681
|
-
*
|
|
682
|
-
* @returns true when running directly inside Windows Terminal
|
|
683
|
-
*/
|
|
684
718
|
function isWindowsTerminalSession(): boolean {
|
|
685
719
|
return (
|
|
686
720
|
Boolean(process.env.WT_SESSION) &&
|
|
@@ -698,10 +732,6 @@ function isWindowsTerminalSession(): boolean {
|
|
|
698
732
|
*
|
|
699
733
|
* Prefer explicit Kitty / CSI-u / modifyOtherKeys sequences whenever they are
|
|
700
734
|
* available. Fall back to a Windows Terminal heuristic only for raw BS bytes.
|
|
701
|
-
*
|
|
702
|
-
* @param data - Raw terminal input byte
|
|
703
|
-
* @param expectedModifier - Modifier bitmask to match against
|
|
704
|
-
* @returns true if the data matches backspace with the given modifier
|
|
705
735
|
*/
|
|
706
736
|
function matchesRawBackspace(data: string, expectedModifier: number): boolean {
|
|
707
737
|
if (data === "\x7f") return expectedModifier === 0;
|
|
@@ -741,9 +771,34 @@ function rawCtrlChar(key: string): string | null {
|
|
|
741
771
|
return null;
|
|
742
772
|
}
|
|
743
773
|
|
|
774
|
+
function isDigitKey(key: string): boolean {
|
|
775
|
+
return key >= "0" && key <= "9";
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
function matchesPrintableModifyOtherKeys(
|
|
779
|
+
data: string,
|
|
780
|
+
expectedKeycode: number,
|
|
781
|
+
expectedModifier: number
|
|
782
|
+
): boolean {
|
|
783
|
+
if (expectedModifier === 0) return false;
|
|
784
|
+
return matchesModifyOtherKeys(data, expectedKeycode, expectedModifier);
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
function formatKeyNameWithModifiers(keyName: string, modifier: number): string | undefined {
|
|
788
|
+
const mods: string[] = [];
|
|
789
|
+
const effectiveMod = modifier & ~LOCK_MASK;
|
|
790
|
+
const supportedModifierMask = MODIFIERS.shift | MODIFIERS.ctrl | MODIFIERS.alt | MODIFIERS.super;
|
|
791
|
+
if ((effectiveMod & ~supportedModifierMask) !== 0) return undefined;
|
|
792
|
+
if (effectiveMod & MODIFIERS.shift) mods.push("shift");
|
|
793
|
+
if (effectiveMod & MODIFIERS.ctrl) mods.push("ctrl");
|
|
794
|
+
if (effectiveMod & MODIFIERS.alt) mods.push("alt");
|
|
795
|
+
if (effectiveMod & MODIFIERS.super) mods.push("super");
|
|
796
|
+
return mods.length > 0 ? `${mods.join("+")}+${keyName}` : keyName;
|
|
797
|
+
}
|
|
798
|
+
|
|
744
799
|
function parseKeyId(
|
|
745
800
|
keyId: string
|
|
746
|
-
): { key: string; ctrl: boolean; shift: boolean; alt: boolean } | null {
|
|
801
|
+
): { key: string; ctrl: boolean; shift: boolean; alt: boolean; super: boolean } | null {
|
|
747
802
|
const parts = keyId.toLowerCase().split("+");
|
|
748
803
|
const key = parts[parts.length - 1];
|
|
749
804
|
if (!key) return null;
|
|
@@ -752,6 +807,7 @@ function parseKeyId(
|
|
|
752
807
|
ctrl: parts.includes("ctrl"),
|
|
753
808
|
shift: parts.includes("shift"),
|
|
754
809
|
alt: parts.includes("alt"),
|
|
810
|
+
super: parts.includes("super"),
|
|
755
811
|
};
|
|
756
812
|
}
|
|
757
813
|
|
|
@@ -764,9 +820,10 @@ function parseKeyId(
|
|
|
764
820
|
* - Ctrl combinations: "ctrl+c", "ctrl+z", etc.
|
|
765
821
|
* - Shift combinations: "shift+tab", "shift+enter"
|
|
766
822
|
* - Alt combinations: "alt+enter", "alt+backspace"
|
|
767
|
-
* -
|
|
823
|
+
* - Super combinations: "super+k", "super+enter"
|
|
824
|
+
* - Combined modifiers: "shift+ctrl+p", "ctrl+alt+x", "ctrl+super+k"
|
|
768
825
|
*
|
|
769
|
-
* Use the Key helper for autocomplete: Key.ctrl("c"), Key.escape, Key.ctrlShift("p")
|
|
826
|
+
* Use the Key helper for autocomplete: Key.ctrl("c"), Key.escape, Key.ctrlShift("p"), Key.super("k")
|
|
770
827
|
*
|
|
771
828
|
* @param data - Raw input data from terminal
|
|
772
829
|
* @param keyId - Key identifier (e.g., "ctrl+c", "escape", Key.ctrl("c"))
|
|
@@ -775,11 +832,12 @@ export function matchesKey(data: string, keyId: KeyId): boolean {
|
|
|
775
832
|
const parsed = parseKeyId(keyId);
|
|
776
833
|
if (!parsed) return false;
|
|
777
834
|
|
|
778
|
-
const { key, ctrl, shift, alt } = parsed;
|
|
835
|
+
const { key, ctrl, shift, alt, super: superModifier } = parsed;
|
|
779
836
|
let modifier = 0;
|
|
780
837
|
if (shift) modifier |= MODIFIERS.shift;
|
|
781
838
|
if (alt) modifier |= MODIFIERS.alt;
|
|
782
839
|
if (ctrl) modifier |= MODIFIERS.ctrl;
|
|
840
|
+
if (superModifier) modifier |= MODIFIERS.super;
|
|
783
841
|
|
|
784
842
|
switch (key) {
|
|
785
843
|
case "escape":
|
|
@@ -793,10 +851,10 @@ export function matchesKey(data: string, keyId: KeyId): boolean {
|
|
|
793
851
|
|
|
794
852
|
case "space":
|
|
795
853
|
if (!_kittyProtocolActive) {
|
|
796
|
-
if (
|
|
854
|
+
if (modifier === MODIFIERS.ctrl && data === "\x00") {
|
|
797
855
|
return true;
|
|
798
856
|
}
|
|
799
|
-
if (
|
|
857
|
+
if (modifier === MODIFIERS.alt && data === "\x1b ") {
|
|
800
858
|
return true;
|
|
801
859
|
}
|
|
802
860
|
}
|
|
@@ -813,8 +871,12 @@ export function matchesKey(data: string, keyId: KeyId): boolean {
|
|
|
813
871
|
);
|
|
814
872
|
|
|
815
873
|
case "tab":
|
|
816
|
-
if (
|
|
817
|
-
return
|
|
874
|
+
if (modifier === MODIFIERS.shift) {
|
|
875
|
+
return (
|
|
876
|
+
data === "\x1b[Z" ||
|
|
877
|
+
matchesKittySequence(data, CODEPOINTS.tab, MODIFIERS.shift) ||
|
|
878
|
+
matchesModifyOtherKeys(data, CODEPOINTS.tab, MODIFIERS.shift)
|
|
879
|
+
);
|
|
818
880
|
}
|
|
819
881
|
if (modifier === 0) {
|
|
820
882
|
return data === "\t" || matchesKittySequence(data, CODEPOINTS.tab, 0);
|
|
@@ -826,7 +888,7 @@ export function matchesKey(data: string, keyId: KeyId): boolean {
|
|
|
826
888
|
|
|
827
889
|
case "enter":
|
|
828
890
|
case "return":
|
|
829
|
-
if (
|
|
891
|
+
if (modifier === MODIFIERS.shift) {
|
|
830
892
|
// CSI u sequences (standard Kitty protocol)
|
|
831
893
|
if (
|
|
832
894
|
matchesKittySequence(data, CODEPOINTS.enter, MODIFIERS.shift) ||
|
|
@@ -846,7 +908,7 @@ export function matchesKey(data: string, keyId: KeyId): boolean {
|
|
|
846
908
|
}
|
|
847
909
|
return false;
|
|
848
910
|
}
|
|
849
|
-
if (
|
|
911
|
+
if (modifier === MODIFIERS.alt) {
|
|
850
912
|
// CSI u sequences (standard Kitty protocol)
|
|
851
913
|
if (
|
|
852
914
|
matchesKittySequence(data, CODEPOINTS.enter, MODIFIERS.alt) ||
|
|
@@ -881,7 +943,7 @@ export function matchesKey(data: string, keyId: KeyId): boolean {
|
|
|
881
943
|
);
|
|
882
944
|
|
|
883
945
|
case "backspace":
|
|
884
|
-
if (
|
|
946
|
+
if (modifier === MODIFIERS.alt) {
|
|
885
947
|
if (data === "\x1b\x7f" || data === "\x1b\b") {
|
|
886
948
|
return true;
|
|
887
949
|
}
|
|
@@ -890,7 +952,7 @@ export function matchesKey(data: string, keyId: KeyId): boolean {
|
|
|
890
952
|
matchesModifyOtherKeys(data, CODEPOINTS.backspace, MODIFIERS.alt)
|
|
891
953
|
);
|
|
892
954
|
}
|
|
893
|
-
if (
|
|
955
|
+
if (modifier === MODIFIERS.ctrl) {
|
|
894
956
|
// Legacy raw 0x08 is ambiguous: it can be Ctrl+Backspace on Windows
|
|
895
957
|
// Terminal or plain Backspace on other terminals, while also
|
|
896
958
|
// overlapping with Ctrl+H.
|
|
@@ -991,7 +1053,7 @@ export function matchesKey(data: string, keyId: KeyId): boolean {
|
|
|
991
1053
|
return matchesKittySequence(data, FUNCTIONAL_CODEPOINTS.pageDown, modifier);
|
|
992
1054
|
|
|
993
1055
|
case "up":
|
|
994
|
-
if (
|
|
1056
|
+
if (modifier === MODIFIERS.alt) {
|
|
995
1057
|
return data === "\x1bp" || matchesKittySequence(data, ARROW_CODEPOINTS.up, MODIFIERS.alt);
|
|
996
1058
|
}
|
|
997
1059
|
if (modifier === 0) {
|
|
@@ -1006,7 +1068,7 @@ export function matchesKey(data: string, keyId: KeyId): boolean {
|
|
|
1006
1068
|
return matchesKittySequence(data, ARROW_CODEPOINTS.up, modifier);
|
|
1007
1069
|
|
|
1008
1070
|
case "down":
|
|
1009
|
-
if (
|
|
1071
|
+
if (modifier === MODIFIERS.alt) {
|
|
1010
1072
|
return data === "\x1bn" || matchesKittySequence(data, ARROW_CODEPOINTS.down, MODIFIERS.alt);
|
|
1011
1073
|
}
|
|
1012
1074
|
if (modifier === 0) {
|
|
@@ -1021,7 +1083,7 @@ export function matchesKey(data: string, keyId: KeyId): boolean {
|
|
|
1021
1083
|
return matchesKittySequence(data, ARROW_CODEPOINTS.down, modifier);
|
|
1022
1084
|
|
|
1023
1085
|
case "left":
|
|
1024
|
-
if (
|
|
1086
|
+
if (modifier === MODIFIERS.alt) {
|
|
1025
1087
|
return (
|
|
1026
1088
|
data === "\x1b[1;3D" ||
|
|
1027
1089
|
(!_kittyProtocolActive && data === "\x1bB") ||
|
|
@@ -1029,7 +1091,7 @@ export function matchesKey(data: string, keyId: KeyId): boolean {
|
|
|
1029
1091
|
matchesKittySequence(data, ARROW_CODEPOINTS.left, MODIFIERS.alt)
|
|
1030
1092
|
);
|
|
1031
1093
|
}
|
|
1032
|
-
if (
|
|
1094
|
+
if (modifier === MODIFIERS.ctrl) {
|
|
1033
1095
|
return (
|
|
1034
1096
|
data === "\x1b[1;5D" ||
|
|
1035
1097
|
matchesLegacyModifierSequence(data, "left", MODIFIERS.ctrl) ||
|
|
@@ -1048,7 +1110,7 @@ export function matchesKey(data: string, keyId: KeyId): boolean {
|
|
|
1048
1110
|
return matchesKittySequence(data, ARROW_CODEPOINTS.left, modifier);
|
|
1049
1111
|
|
|
1050
1112
|
case "right":
|
|
1051
|
-
if (
|
|
1113
|
+
if (modifier === MODIFIERS.alt) {
|
|
1052
1114
|
return (
|
|
1053
1115
|
data === "\x1b[1;3C" ||
|
|
1054
1116
|
(!_kittyProtocolActive && data === "\x1bF") ||
|
|
@@ -1056,7 +1118,7 @@ export function matchesKey(data: string, keyId: KeyId): boolean {
|
|
|
1056
1118
|
matchesKittySequence(data, ARROW_CODEPOINTS.right, MODIFIERS.alt)
|
|
1057
1119
|
);
|
|
1058
1120
|
}
|
|
1059
|
-
if (
|
|
1121
|
+
if (modifier === MODIFIERS.ctrl) {
|
|
1060
1122
|
return (
|
|
1061
1123
|
data === "\x1b[1;5C" ||
|
|
1062
1124
|
matchesLegacyModifierSequence(data, "right", MODIFIERS.ctrl) ||
|
|
@@ -1094,50 +1156,54 @@ export function matchesKey(data: string, keyId: KeyId): boolean {
|
|
|
1094
1156
|
}
|
|
1095
1157
|
}
|
|
1096
1158
|
|
|
1097
|
-
// Handle single letter keys
|
|
1098
|
-
if (key.length === 1 && ((key >= "a" && key <= "z") || SYMBOL_KEYS.has(key))) {
|
|
1159
|
+
// Handle single letter/digit keys and symbols
|
|
1160
|
+
if (key.length === 1 && ((key >= "a" && key <= "z") || isDigitKey(key) || SYMBOL_KEYS.has(key))) {
|
|
1099
1161
|
const codepoint = key.charCodeAt(0);
|
|
1100
1162
|
const rawCtrl = rawCtrlChar(key);
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1163
|
+
const isLetter = key >= "a" && key <= "z";
|
|
1164
|
+
const isDigit = isDigitKey(key);
|
|
1165
|
+
|
|
1166
|
+
if (modifier === MODIFIERS.ctrl + MODIFIERS.alt && !_kittyProtocolActive && rawCtrl) {
|
|
1167
|
+
// Legacy: ctrl+alt+key is ESC followed by the control character.
|
|
1168
|
+
// If that legacy form does not match, continue so CSI-u and
|
|
1169
|
+
// modifyOtherKeys sequences from tmux can still be recognized.
|
|
1170
|
+
if (data === `\x1b${rawCtrl}`) return true;
|
|
1105
1171
|
}
|
|
1106
1172
|
|
|
1107
|
-
if (
|
|
1108
|
-
// Legacy: alt+letter is ESC followed by the
|
|
1173
|
+
if (modifier === MODIFIERS.alt && !_kittyProtocolActive && (isLetter || isDigit)) {
|
|
1174
|
+
// Legacy: alt+letter/digit is ESC followed by the key
|
|
1109
1175
|
if (data === `\x1b${key}`) return true;
|
|
1110
1176
|
}
|
|
1111
1177
|
|
|
1112
|
-
if (
|
|
1178
|
+
if (modifier === MODIFIERS.ctrl) {
|
|
1113
1179
|
// Legacy: ctrl+key sends the control character
|
|
1114
1180
|
if (rawCtrl && data === rawCtrl) return true;
|
|
1115
1181
|
return (
|
|
1116
1182
|
matchesKittySequence(data, codepoint, MODIFIERS.ctrl) ||
|
|
1117
|
-
|
|
1183
|
+
matchesPrintableModifyOtherKeys(data, codepoint, MODIFIERS.ctrl)
|
|
1118
1184
|
);
|
|
1119
1185
|
}
|
|
1120
1186
|
|
|
1121
|
-
if (
|
|
1187
|
+
if (modifier === MODIFIERS.shift + MODIFIERS.ctrl) {
|
|
1122
1188
|
return (
|
|
1123
1189
|
matchesKittySequence(data, codepoint, MODIFIERS.shift + MODIFIERS.ctrl) ||
|
|
1124
|
-
|
|
1190
|
+
matchesPrintableModifyOtherKeys(data, codepoint, MODIFIERS.shift + MODIFIERS.ctrl)
|
|
1125
1191
|
);
|
|
1126
1192
|
}
|
|
1127
1193
|
|
|
1128
|
-
if (
|
|
1194
|
+
if (modifier === MODIFIERS.shift) {
|
|
1129
1195
|
// Legacy: shift+letter produces uppercase
|
|
1130
|
-
if (data === key.toUpperCase()) return true;
|
|
1196
|
+
if (isLetter && data === key.toUpperCase()) return true;
|
|
1131
1197
|
return (
|
|
1132
1198
|
matchesKittySequence(data, codepoint, MODIFIERS.shift) ||
|
|
1133
|
-
|
|
1199
|
+
matchesPrintableModifyOtherKeys(data, codepoint, MODIFIERS.shift)
|
|
1134
1200
|
);
|
|
1135
1201
|
}
|
|
1136
1202
|
|
|
1137
1203
|
if (modifier !== 0) {
|
|
1138
1204
|
return (
|
|
1139
1205
|
matchesKittySequence(data, codepoint, modifier) ||
|
|
1140
|
-
|
|
1206
|
+
matchesPrintableModifyOtherKeys(data, codepoint, modifier)
|
|
1141
1207
|
);
|
|
1142
1208
|
}
|
|
1143
1209
|
|
|
@@ -1154,51 +1220,63 @@ export function matchesKey(data: string, keyId: KeyId): boolean {
|
|
|
1154
1220
|
* @param data - Raw input data from terminal
|
|
1155
1221
|
* @returns Key identifier string (e.g., "ctrl+c") or undefined
|
|
1156
1222
|
*/
|
|
1223
|
+
function formatParsedKey(
|
|
1224
|
+
codepoint: number,
|
|
1225
|
+
modifier: number,
|
|
1226
|
+
baseLayoutKey?: number
|
|
1227
|
+
): string | undefined {
|
|
1228
|
+
const normalizedCodepoint = normalizeKittyFunctionalCodepoint(codepoint);
|
|
1229
|
+
|
|
1230
|
+
// Use base layout key only when codepoint is not a recognized Latin
|
|
1231
|
+
// letter (a-z), digit (0-9), or symbol (/, -, [, ;, etc.). For those,
|
|
1232
|
+
// the codepoint is authoritative regardless of physical key position.
|
|
1233
|
+
// This prevents remapped layouts (Dvorak, Colemak, xremap, etc.) from
|
|
1234
|
+
// reporting the wrong key name based on the QWERTY physical position.
|
|
1235
|
+
const isLatinLetter = normalizedCodepoint >= 97 && normalizedCodepoint <= 122; // a-z
|
|
1236
|
+
const isDigit = normalizedCodepoint >= 48 && normalizedCodepoint <= 57; // 0-9
|
|
1237
|
+
const isKnownSymbol = SYMBOL_KEYS.has(String.fromCharCode(normalizedCodepoint));
|
|
1238
|
+
const effectiveCodepoint =
|
|
1239
|
+
isLatinLetter || isDigit || isKnownSymbol
|
|
1240
|
+
? normalizedCodepoint
|
|
1241
|
+
: (baseLayoutKey ?? normalizedCodepoint);
|
|
1242
|
+
|
|
1243
|
+
let keyName: string | undefined;
|
|
1244
|
+
if (effectiveCodepoint === CODEPOINTS.escape) keyName = "escape";
|
|
1245
|
+
else if (effectiveCodepoint === CODEPOINTS.tab) keyName = "tab";
|
|
1246
|
+
else if (effectiveCodepoint === CODEPOINTS.enter || effectiveCodepoint === CODEPOINTS.kpEnter)
|
|
1247
|
+
keyName = "enter";
|
|
1248
|
+
else if (effectiveCodepoint === CODEPOINTS.space) keyName = "space";
|
|
1249
|
+
else if (effectiveCodepoint === CODEPOINTS.backspace) keyName = "backspace";
|
|
1250
|
+
else if (effectiveCodepoint === FUNCTIONAL_CODEPOINTS.delete) keyName = "delete";
|
|
1251
|
+
else if (effectiveCodepoint === FUNCTIONAL_CODEPOINTS.insert) keyName = "insert";
|
|
1252
|
+
else if (effectiveCodepoint === FUNCTIONAL_CODEPOINTS.home) keyName = "home";
|
|
1253
|
+
else if (effectiveCodepoint === FUNCTIONAL_CODEPOINTS.end) keyName = "end";
|
|
1254
|
+
else if (effectiveCodepoint === FUNCTIONAL_CODEPOINTS.pageUp) keyName = "pageUp";
|
|
1255
|
+
else if (effectiveCodepoint === FUNCTIONAL_CODEPOINTS.pageDown) keyName = "pageDown";
|
|
1256
|
+
else if (effectiveCodepoint === ARROW_CODEPOINTS.up) keyName = "up";
|
|
1257
|
+
else if (effectiveCodepoint === ARROW_CODEPOINTS.down) keyName = "down";
|
|
1258
|
+
else if (effectiveCodepoint === ARROW_CODEPOINTS.left) keyName = "left";
|
|
1259
|
+
else if (effectiveCodepoint === ARROW_CODEPOINTS.right) keyName = "right";
|
|
1260
|
+
else if (effectiveCodepoint >= 48 && effectiveCodepoint <= 57)
|
|
1261
|
+
keyName = String.fromCharCode(effectiveCodepoint);
|
|
1262
|
+
else if (effectiveCodepoint >= 97 && effectiveCodepoint <= 122)
|
|
1263
|
+
keyName = String.fromCharCode(effectiveCodepoint);
|
|
1264
|
+
else if (SYMBOL_KEYS.has(String.fromCharCode(effectiveCodepoint)))
|
|
1265
|
+
keyName = String.fromCharCode(effectiveCodepoint);
|
|
1266
|
+
|
|
1267
|
+
if (!keyName) return undefined;
|
|
1268
|
+
return formatKeyNameWithModifiers(keyName, modifier);
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1157
1271
|
export function parseKey(data: string): string | undefined {
|
|
1158
1272
|
const kitty = parseKittySequence(data);
|
|
1159
1273
|
if (kitty) {
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
// Use base layout key only when codepoint is not a recognized Latin
|
|
1168
|
-
// letter (a-z) or symbol (/, -, [, ;, etc.). For those, the codepoint
|
|
1169
|
-
// is authoritative regardless of physical key position. This prevents
|
|
1170
|
-
// remapped layouts (Dvorak, Colemak, xremap, etc.) from reporting the
|
|
1171
|
-
// wrong key name based on the QWERTY physical position.
|
|
1172
|
-
const isLatinLetter = codepoint >= 97 && codepoint <= 122; // a-z
|
|
1173
|
-
const isKnownSymbol = SYMBOL_KEYS.has(String.fromCharCode(codepoint));
|
|
1174
|
-
const effectiveCodepoint =
|
|
1175
|
-
isLatinLetter || isKnownSymbol ? codepoint : (baseLayoutKey ?? codepoint);
|
|
1176
|
-
|
|
1177
|
-
let keyName: string | undefined;
|
|
1178
|
-
if (effectiveCodepoint === CODEPOINTS.escape) keyName = "escape";
|
|
1179
|
-
else if (effectiveCodepoint === CODEPOINTS.tab) keyName = "tab";
|
|
1180
|
-
else if (effectiveCodepoint === CODEPOINTS.enter || effectiveCodepoint === CODEPOINTS.kpEnter)
|
|
1181
|
-
keyName = "enter";
|
|
1182
|
-
else if (effectiveCodepoint === CODEPOINTS.space) keyName = "space";
|
|
1183
|
-
else if (effectiveCodepoint === CODEPOINTS.backspace) keyName = "backspace";
|
|
1184
|
-
else if (effectiveCodepoint === FUNCTIONAL_CODEPOINTS.delete) keyName = "delete";
|
|
1185
|
-
else if (effectiveCodepoint === FUNCTIONAL_CODEPOINTS.insert) keyName = "insert";
|
|
1186
|
-
else if (effectiveCodepoint === FUNCTIONAL_CODEPOINTS.home) keyName = "home";
|
|
1187
|
-
else if (effectiveCodepoint === FUNCTIONAL_CODEPOINTS.end) keyName = "end";
|
|
1188
|
-
else if (effectiveCodepoint === FUNCTIONAL_CODEPOINTS.pageUp) keyName = "pageUp";
|
|
1189
|
-
else if (effectiveCodepoint === FUNCTIONAL_CODEPOINTS.pageDown) keyName = "pageDown";
|
|
1190
|
-
else if (effectiveCodepoint === ARROW_CODEPOINTS.up) keyName = "up";
|
|
1191
|
-
else if (effectiveCodepoint === ARROW_CODEPOINTS.down) keyName = "down";
|
|
1192
|
-
else if (effectiveCodepoint === ARROW_CODEPOINTS.left) keyName = "left";
|
|
1193
|
-
else if (effectiveCodepoint === ARROW_CODEPOINTS.right) keyName = "right";
|
|
1194
|
-
else if (effectiveCodepoint >= 97 && effectiveCodepoint <= 122)
|
|
1195
|
-
keyName = String.fromCharCode(effectiveCodepoint);
|
|
1196
|
-
else if (SYMBOL_KEYS.has(String.fromCharCode(effectiveCodepoint)))
|
|
1197
|
-
keyName = String.fromCharCode(effectiveCodepoint);
|
|
1198
|
-
|
|
1199
|
-
if (keyName) {
|
|
1200
|
-
return mods.length > 0 ? `${mods.join("+")}+${keyName}` : keyName;
|
|
1201
|
-
}
|
|
1274
|
+
return formatParsedKey(kitty.codepoint, kitty.modifier, kitty.baseLayoutKey);
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
const modifyOtherKeys = parseModifyOtherKeysSequence(data);
|
|
1278
|
+
if (modifyOtherKeys) {
|
|
1279
|
+
return formatParsedKey(modifyOtherKeys.codepoint, modifyOtherKeys.modifier);
|
|
1202
1280
|
}
|
|
1203
1281
|
|
|
1204
1282
|
// Mode-aware legacy sequences
|
|
@@ -1239,8 +1317,8 @@ export function parseKey(data: string): string | undefined {
|
|
|
1239
1317
|
if (code >= 1 && code <= 26) {
|
|
1240
1318
|
return `ctrl+alt+${String.fromCharCode(code + 96)}`;
|
|
1241
1319
|
}
|
|
1242
|
-
// Legacy alt+letter (ESC followed by
|
|
1243
|
-
if (code >= 97 && code <= 122) {
|
|
1320
|
+
// Legacy alt+letter/digit (ESC followed by the key)
|
|
1321
|
+
if ((code >= 97 && code <= 122) || (code >= 48 && code <= 57)) {
|
|
1244
1322
|
return `alt+${String.fromCharCode(code)}`;
|
|
1245
1323
|
}
|
|
1246
1324
|
}
|
|
@@ -1269,63 +1347,57 @@ export function parseKey(data: string): string | undefined {
|
|
|
1269
1347
|
}
|
|
1270
1348
|
|
|
1271
1349
|
// =============================================================================
|
|
1272
|
-
//
|
|
1350
|
+
// Kitty CSI-u Printable Decoding
|
|
1273
1351
|
// =============================================================================
|
|
1274
1352
|
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
* SGR format: \x1b[<button;column;row[Mm]
|
|
1278
|
-
* M = press, m = release
|
|
1279
|
-
*/
|
|
1280
|
-
export interface MouseEvent {
|
|
1281
|
-
/** Event type */
|
|
1282
|
-
type: "scroll-up" | "scroll-down" | "press" | "release" | "drag";
|
|
1283
|
-
/** 0=left, 1=middle, 2=right */
|
|
1284
|
-
button: number;
|
|
1285
|
-
/** 1-indexed column */
|
|
1286
|
-
x: number;
|
|
1287
|
-
/** 1-indexed row */
|
|
1288
|
-
y: number;
|
|
1289
|
-
}
|
|
1290
|
-
|
|
1291
|
-
/** SGR extended mouse format: \x1b[<button;x;y[Mm] */
|
|
1292
|
-
const SGR_MOUSE_RE = /^\x1b\[<(\d+);(\d+);(\d+)([Mm])$/;
|
|
1353
|
+
const KITTY_CSI_U_REGEX = /^\x1b\[(\d+)(?::(\d*))?(?::(\d+))?(?:;(\d+))?(?::(\d+))?u$/;
|
|
1354
|
+
const KITTY_PRINTABLE_ALLOWED_MODIFIERS = MODIFIERS.shift | LOCK_MASK;
|
|
1293
1355
|
|
|
1294
1356
|
/**
|
|
1295
|
-
*
|
|
1357
|
+
* Decode a Kitty CSI-u sequence into a printable character, if applicable.
|
|
1296
1358
|
*
|
|
1297
|
-
*
|
|
1298
|
-
*
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
const code = parseInt(match[1]!, 10);
|
|
1305
|
-
const x = parseInt(match[2]!, 10);
|
|
1306
|
-
const y = parseInt(match[3]!, 10);
|
|
1307
|
-
const isRelease = match[4] === "m";
|
|
1308
|
-
|
|
1309
|
-
// Scroll wheel: codes 64 (up) and 65 (down)
|
|
1310
|
-
if (code === 64) return { type: "scroll-up", button: 0, x, y };
|
|
1311
|
-
if (code === 65) return { type: "scroll-down", button: 0, x, y };
|
|
1312
|
-
|
|
1313
|
-
// Button number is in the low 2 bits
|
|
1314
|
-
const button = code & 0x03;
|
|
1315
|
-
|
|
1316
|
-
// Bit 5 (32) = motion/drag
|
|
1317
|
-
if (code & 32) return { type: "drag", button, x, y };
|
|
1318
|
-
|
|
1319
|
-
return { type: isRelease ? "release" : "press", button, x, y };
|
|
1320
|
-
}
|
|
1321
|
-
|
|
1322
|
-
/**
|
|
1323
|
-
* Fast check: is this data an SGR mouse event?
|
|
1324
|
-
* Avoids regex for non-mouse input.
|
|
1359
|
+
* When Kitty keyboard protocol flag 1 (disambiguate) is active, terminals send
|
|
1360
|
+
* CSI-u sequences for all keys, including plain printable characters. This
|
|
1361
|
+
* function extracts the printable character from such sequences.
|
|
1362
|
+
*
|
|
1363
|
+
* Only accepts plain or Shift-modified keys. Rejects Ctrl, Alt, and unsupported
|
|
1364
|
+
* modifier combinations (those are handled by keybinding matching instead).
|
|
1365
|
+
* Prefers the shifted keycode when Shift is held and a shifted key is reported.
|
|
1325
1366
|
*
|
|
1326
|
-
* @param data - Raw terminal
|
|
1327
|
-
* @returns
|
|
1367
|
+
* @param data - Raw input data from terminal
|
|
1368
|
+
* @returns The printable character, or undefined if not a printable CSI-u sequence
|
|
1328
1369
|
*/
|
|
1329
|
-
export function
|
|
1330
|
-
|
|
1370
|
+
export function decodeKittyPrintable(data: string): string | undefined {
|
|
1371
|
+
const match = data.match(KITTY_CSI_U_REGEX);
|
|
1372
|
+
if (!match) return undefined;
|
|
1373
|
+
|
|
1374
|
+
// CSI-u groups: <codepoint>[:<shifted>[:<base>]];<mod>[:<event>]u
|
|
1375
|
+
const codepoint = Number.parseInt(match[1] ?? "", 10);
|
|
1376
|
+
if (!Number.isFinite(codepoint)) return undefined;
|
|
1377
|
+
|
|
1378
|
+
const shiftedKey = match[2] && match[2].length > 0 ? Number.parseInt(match[2], 10) : undefined;
|
|
1379
|
+
const modValue = match[4] ? Number.parseInt(match[4], 10) : 1;
|
|
1380
|
+
// Modifiers are 1-indexed in CSI-u; normalize to our bitmask.
|
|
1381
|
+
const modifier = Number.isFinite(modValue) ? modValue - 1 : 0;
|
|
1382
|
+
|
|
1383
|
+
// Only accept printable CSI-u input for plain or Shift-modified text keys.
|
|
1384
|
+
// Reject unsupported modifier bits (e.g. Super/Meta) to avoid inserting
|
|
1385
|
+
// characters from modifier-only terminal events.
|
|
1386
|
+
if ((modifier & ~KITTY_PRINTABLE_ALLOWED_MODIFIERS) !== 0) return undefined;
|
|
1387
|
+
if (modifier & (MODIFIERS.alt | MODIFIERS.ctrl)) return undefined;
|
|
1388
|
+
|
|
1389
|
+
// Prefer the shifted keycode when Shift is held.
|
|
1390
|
+
let effectiveCodepoint = codepoint;
|
|
1391
|
+
if (modifier & MODIFIERS.shift && typeof shiftedKey === "number") {
|
|
1392
|
+
effectiveCodepoint = shiftedKey;
|
|
1393
|
+
}
|
|
1394
|
+
effectiveCodepoint = normalizeKittyFunctionalCodepoint(effectiveCodepoint);
|
|
1395
|
+
// Drop control characters or invalid codepoints.
|
|
1396
|
+
if (!Number.isFinite(effectiveCodepoint) || effectiveCodepoint < 32) return undefined;
|
|
1397
|
+
|
|
1398
|
+
try {
|
|
1399
|
+
return String.fromCodePoint(effectiveCodepoint);
|
|
1400
|
+
} catch {
|
|
1401
|
+
return undefined;
|
|
1402
|
+
}
|
|
1331
1403
|
}
|