-
Notifications
You must be signed in to change notification settings - Fork 230
[feature] Add keybinding conflict detection for useKeymap #3754
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -166,48 +166,96 @@ export function stripAnsiControl(str: string): string { | |
| return out; | ||
| } | ||
|
|
||
| // ── Clipboard ─────────────────────────────────────── | ||
| // ── Clipboard ────────────────────────────────────────── | ||
|
|
||
| const TMUX_DCS_START = '\x1bPtmux;'; | ||
| const TMUX_DCS_END = '\x1b\\'; | ||
| const SCREEN_DCS_START = '\x1bP'; | ||
| const SCREEN_DCS_END = '\x1b\\'; | ||
|
|
||
| /** | ||
| * Wraps an OSC escape sequence for passthrough through tmux or GNU screen. | ||
| * Without this, multiplexers intercept and drop OSC 52 sequences instead | ||
| * of forwarding them to the underlying terminal. | ||
| */ | ||
| function wrapForMultiplexer(sequence: string): string { | ||
| if (process.env.TMUX) { | ||
| // tmux requires ESC bytes inside the payload to be doubled | ||
| const escaped = sequence.replace(/\x1b/g, '\x1b\x1b'); | ||
| return `${TMUX_DCS_START}${escaped}${TMUX_DCS_END}`; | ||
| } | ||
| if (process.env.STY) { | ||
| // GNU screen: wrap in a Device Control String | ||
| return `${SCREEN_DCS_START}${sequence}${SCREEN_DCS_END}`; | ||
| } | ||
| return sequence; | ||
| } | ||
|
|
||
| /** | ||
| * Write text to the system clipboard via OSC 52. | ||
| * Supported by: xterm, iTerm2, Kitty, WezTerm, Alacritty, Windows Terminal. | ||
| * Automatically wraps the sequence for tmux/screen passthrough when detected. | ||
| * @param text Plain text to copy to clipboard | ||
| * @param stdout Target stream (default: process.stdout) | ||
| */ | ||
| export function writeClipboard(text: string, stdout: NodeJS.WriteStream = process.stdout): void { | ||
| const encoded = Buffer.from(text, 'utf8').toString('base64'); | ||
| stdout.write(`${OSC}52;c;${encoded}\x07`); | ||
| const sequence = `${OSC}52;c;${encoded}\x07`; | ||
| stdout.write(wrapForMultiplexer(sequence)); | ||
| } | ||
|
|
||
| export interface ReadClipboardOptions { | ||
| /** Milliseconds to wait for a terminal response before rejecting (default: 1000) */ | ||
| timeoutMs?: number; | ||
| } | ||
|
|
||
| /** | ||
| * Read text from the system clipboard via OSC 52 query. | ||
| * Rejects if the terminal doesn't respond within `timeoutMs` (e.g. the | ||
| * terminal doesn't support OSC 52 queries), preventing an indefinite hang. | ||
| */ | ||
| export function readClipboard( | ||
| stdin: NodeJS.ReadStream = process.stdin, | ||
| stdout: NodeJS.WriteStream = process.stdout | ||
| stdout: NodeJS.WriteStream = process.stdout, | ||
| options: ReadClipboardOptions = {} | ||
| ): Promise<string> { | ||
| const timeoutMs = options.timeoutMs ?? 1000; | ||
|
|
||
| return new Promise((resolve, reject) => { | ||
| let settled = false; | ||
|
|
||
| const cleanup = () => { | ||
| stdin.off('data', handler); | ||
| clearTimeout(timer); | ||
| }; | ||
|
|
||
| const timer = setTimeout(() => { | ||
| if (settled) return; | ||
| settled = true; | ||
| cleanup(); | ||
| reject(new Error('readClipboard timed out: terminal did not respond to OSC 52 query')); | ||
| }, timeoutMs); | ||
|
|
||
| const handler = (data: Buffer) => { | ||
| const str = data.toString('utf8'); | ||
|
|
||
| const match = str.match(/\x1b\]52;c;([^\x07]+)\x07/); | ||
|
|
||
| if (!match) return; | ||
|
Comment on lines
239
to
242
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '--- target structure ---'
ast-grep outline packages/core/src/utils/ansi.ts
printf '%s\n' '--- target implementation ---'
sed -n '150,285p' packages/core/src/utils/ansi.ts
printf '%s\n' '--- relevant tests and usages ---'
rg -n -C 4 'readClipboard|OSC 52|clipboard|52;c' packages --glob '*.{ts,tsx}' --glob '*.{test,spec}.{ts,tsx}' || trueRepository: Karanjot786/TermUI Length of output: 32531 🏁 Script executed: #!/bin/bash
set -eu
node - <<'JS'
const response = '\x1b]52;c;aGVsbG8=\x07';
const chunks = [
response.slice(0, 7),
response.slice(7, 12),
response.slice(12),
];
let resolved;
let rejected;
for (const chunk of chunks) {
const str = Buffer.from(chunk).toString('utf8');
const match = str.match(/\x1b\]52;c;([^\x07]+)\x07/);
if (!match) continue;
resolved = Buffer.from(match[1], 'base64').toString('utf8');
}
console.log(JSON.stringify({
chunks,
resultWithCurrentPerChunkParsing: resolved ?? 'no match',
expected: 'hello',
reproducesFailure: resolved === undefined,
}));
let accumulated = '';
let accumulatedResult;
for (const chunk of chunks) {
accumulated += Buffer.from(chunk).toString('utf8');
const match = accumulated.match(/\x1b\]52;c;([^\x07]+)\x07/);
if (match) {
accumulatedResult = Buffer.from(match[1], 'base64').toString('utf8');
}
}
console.log(JSON.stringify({
resultWithAccumulation: accumulatedResult ?? 'no match',
accumulationFixesCase: accumulatedResult === 'hello',
}));
JS
printf '%s\n' '--- clipboard tests ---'
sed -n '1,215p' packages/core/src/utils/ansi.test.tsRepository: Karanjot786/TermUI Length of output: 7877 Accumulate fragmented OSC 52 responses. When 🤖 Prompt for AI Agents |
||
|
|
||
| stdin.off('data', handler); | ||
|
|
||
| if (settled) return; | ||
| settled = true; | ||
| cleanup(); | ||
| try { | ||
| resolve( | ||
| Buffer.from(match[1], 'base64').toString('utf8') | ||
| ); | ||
| resolve(Buffer.from(match[1], 'base64').toString('utf8')); | ||
| } catch (err) { | ||
| reject(err); | ||
| } | ||
| }; | ||
|
|
||
| stdin.on('data', handler); | ||
|
|
||
| stdout.write(`${OSC}52;c;?\x07`); | ||
| stdout.write(wrapForMultiplexer(`${OSC}52;c;?\x07`)); | ||
| }); | ||
| } | ||
|
|
||
| export const clipboard = { | ||
| write: writeClipboard, | ||
| read: readClipboard, | ||
| }; | ||
| }; | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -50,6 +50,7 @@ export interface Fiber { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // ── Portal tracking ── | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** Widgets created via createPortal and their target, for proper teardown */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| portalChildren?: Array<{ widgets: Widget[]; target: Widget }>; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| componentName?: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| interface HookState { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -332,11 +333,23 @@ export interface KeyBinding { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * ]); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * ``` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // ── Module-level registry: which fibers currently bind which key ── | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const _keymapOwners = new Map<string, Set<Fiber>>(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| function keymapLabel(key: string): string { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const [k, ctrl, alt, shift] = key.split('|'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const parts: string[] = []; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (ctrl === 'true') parts.push('ctrl'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (alt === 'true') parts.push('alt'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (shift === 'true') parts.push('shift'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| parts.push(k); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return parts.join('+'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export function useKeymap(bindings: KeyBinding[]): void { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const fiber = currentFiber(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const idx = fiber.hookIndex++; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Dev-mode conflict detection on every render (moved outside the init block) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (process.env.NODE_ENV !== 'production') { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const seen = new Map<string, KeyBinding>(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for (const b of bindings) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -346,14 +359,52 @@ export function useKeymap(bindings: KeyBinding[]): void { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| seen.set(key, b); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const name = fiber.componentName ?? 'anon'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const prevKeys: Set<string> = (fiber as any)._keymapKeys ?? new Set(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const nextKeys = new Set(seen.keys()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for (const key of prevKeys) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!nextKeys.has(key)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| _keymapOwners.get(key)?.delete(fiber); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+367
to
+370
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win Delete owner entries when their set becomes empty. These paths remove the fiber but retain an empty Proposed fix const _keymapOwners = new Map<string, Set<Fiber>>();
+function removeKeymapOwner(key: string, fiber: Fiber): void {
+ const owners = _keymapOwners.get(key);
+ if (!owners) return;
+ owners.delete(fiber);
+ if (owners.size === 0) _keymapOwners.delete(key);
+}
+
- _keymapOwners.get(key)?.delete(fiber);
+ removeKeymapOwner(key, fiber);Also applies to: 394-399 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for (const key of nextKeys) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let owners = _keymapOwners.get(key); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!owners) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| owners = new Set(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| _keymapOwners.set(key, owners); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const others = [...owners].filter((f) => f !== fiber); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (others.length > 0) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const otherNames = others.map((f) => `<${f.componentName ?? 'anon'}>`).join(', '); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| console.warn( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| `[TermUI] Keymap conflict: "${keymapLabel(key)}" is bound in both ${otherNames} and <${name}>. ` + | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| `The most recently mounted binding takes priority.` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+379
to
+385
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
# Locate input dispatch and keymap conflict handling.
ast-grep outline packages/jsx/src --items all
rg -n -C 8 -P --type ts '(\.onInput\b|onInput\s*=|onInput\s*\(|dispatch.*[Ii]nput|handle.*[Ii]nput)' packages/jsx/src
rg -n -C 8 --type ts 'useKeymap|Keymap conflict|most recently mounted' packages/jsx/srcRepository: Karanjot786/TermUI Length of output: 50376 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '%s\n' '--- useKeymap and input collection ---'
sed -n '295,430p' packages/jsx/src/hooks.ts
sed -n '720,755p' packages/jsx/src/hooks.ts
printf '%s\n' '--- render dispatch ---'
sed -n '112,145p' packages/jsx/src/render.ts
printf '%s\n' '--- keymap-related tests and call sites ---'
rg -n -C 6 --type ts 'useKeymap|Keymap conflict|collectInputHandlers|most recently mounted' packages/jsx/src packages -g '*.test.ts' -g '*.test.tsx'Repository: Karanjot786/TermUI Length of output: 16235 🏁 Script executed: #!/bin/bash
set -euo pipefail
python3 - <<'PY'
from pathlib import Path
import re
hooks = Path("packages/jsx/src/hooks.ts").read_text()
render = Path("packages/jsx/src/render.ts").read_text()
use_keymap = re.search(
r"export function useKeymap\(.*?\n\}\n/\*\*\n \* useInsertBefore",
hooks,
re.S,
)
collect = re.search(
r"export function collectInputHandlers\(.*?\n\}\n\n// ── Async Data Hook",
hooks,
re.S,
)
dispatch = re.search(
r"for \(const handler of collectInputHandlers\(rootInstance\.fiber\)\) \{\s*"
r"handler\(event\);\s*\}",
render,
re.S,
)
assert use_keymap and collect and dispatch
use_keymap = use_keymap.group()
collect = collect.group()
print("dispatches every collected handler:", bool(dispatch))
print("collects the current fiber handler:", "handlers.push(fiber.onInput)" in collect)
print("collects nested handlers:", "collectInputHandlers(entry.fiber)" in collect)
print("useKeymap invokes a matching action:", "b.action();" in use_keymap)
print("useKeymap returns only from its own handler:", bool(re.search(r"b\.action\(\);\s*return;", use_keymap)))
print("useKeymap has no cross-handler stop:", "stopPropagation" not in use_keymap and "return false" not in use_keymap)
PYRepository: Karanjot786/TermUI Length of output: 403 Align keymap conflict warnings with input dispatch.
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| owners.add(fiber); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (fiber as any)._keymapKeys = nextKeys; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!(fiber as any)._keymapCleanupRegistered) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (fiber as any)._keymapCleanupRegistered = true; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fiber.cleanups.push(() => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const keys: Set<string> = (fiber as any)._keymapKeys ?? new Set(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+364
to
+395
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win Replace untyped Fiber keymap metadata. Lines 364, 390, 392, and 395 use As per coding guidelines, “No Proposed fix export interface Fiber {
+ _keymapKeys?: Set<string>;
+ _keymapCleanupRegistered?: boolean;
componentName?: string;
}
-const prevKeys: Set<string> = (fiber as any)._keymapKeys ?? new Set();
+const prevKeys = fiber._keymapKeys ?? new Set<string>();
-(fiber as any)._keymapKeys = nextKeys;
+fiber._keymapKeys = nextKeys;
-if (!(fiber as any)._keymapCleanupRegistered) {
- (fiber as any)._keymapCleanupRegistered = true;
+if (!fiber._keymapCleanupRegistered) {
+ fiber._keymapCleanupRegistered = true;
fiber.cleanups.push(() => {
- const keys: Set<string> = (fiber as any)._keymapKeys ?? new Set();
+ const keys = fiber._keymapKeys ?? new Set<string>();📝 Committable suggestion
Suggested change
🤖 Prompt for AI AgentsSource: Coding guidelines |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for (const key of keys) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| _keymapOwners.get(key)?.delete(fiber); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (idx >= fiber.hooks.length) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fiber.hooks.push({ value: bindings }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fiber.hooks[idx].value = bindings; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fiber.onInput = (event: KeyEvent) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const currentBindings: KeyBinding[] = fiber.hooks[idx].value; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for (const b of currentBindings) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -369,8 +420,6 @@ export function useKeymap(bindings: KeyBinding[]): void { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * useInsertBefore — register a persistent line above the inline viewport. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * The line is added when the component mounts and removed on unmount or when | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
Repository: Karanjot786/TermUI
Length of output: 3833
🏁 Script executed:
Repository: Karanjot786/TermUI
Length of output: 8400
🏁 Script executed:
Repository: Karanjot786/TermUI
Length of output: 329
Handle OSC 52 query write failures
If
stdout.write()emits anerror, the process can terminate and thestdinlistener remains attached. Add a temporarystdouterror listener, remove it incleanup(), and reject with the write error. Catch synchronousstdout.write()throws as well.🤖 Prompt for AI Agents