chore(vendor): import pi 1.3.0
Imported as a linear vendor commit so forge rebases do not replay the upstream root tree. git-subtree-dir: third_party/pi git-subtree-split: 71023aec1546d6d677a65669c5665c0927ce0dd0
This commit is contained in:
92
third_party/pi/packages/tui/src/keys.ts
vendored
92
third_party/pi/packages/tui/src/keys.ts
vendored
@@ -289,6 +289,10 @@ const SYMBOL_KEYS = new Set([
|
||||
"?",
|
||||
]);
|
||||
|
||||
const SHIFTED_SYMBOL_KEYS = new Set("~!@#$%^&*()_+|{}:<>?");
|
||||
|
||||
const MODIFIER_NAMES = ["shift", "alt", "ctrl", "super"] as const;
|
||||
|
||||
const MODIFIERS = {
|
||||
shift: 1,
|
||||
alt: 2,
|
||||
@@ -365,6 +369,17 @@ function normalizeShiftedLetterIdentityCodepoint(codepoint: number, modifier: nu
|
||||
return codepoint;
|
||||
}
|
||||
|
||||
function getLogicalShiftedSymbolCandidate(
|
||||
codepoint: number | undefined,
|
||||
modifier: number,
|
||||
allowedSymbols: ReadonlySet<string>,
|
||||
): { codepoint: number; modifier: number } | undefined {
|
||||
if (codepoint === undefined || (modifier & MODIFIERS.shift) === 0) return undefined;
|
||||
const normalizedCodepoint = normalizeKittyFunctionalCodepoint(codepoint);
|
||||
if (!allowedSymbols.has(String.fromCharCode(normalizedCodepoint))) return undefined;
|
||||
return { codepoint: normalizedCodepoint, modifier: modifier & ~MODIFIERS.shift };
|
||||
}
|
||||
|
||||
const LEGACY_KEY_SEQUENCES = {
|
||||
up: ["\x1b[A", "\x1bOA"],
|
||||
down: ["\x1b[B", "\x1bOB"],
|
||||
@@ -656,9 +671,6 @@ function matchesKittySequence(data: string, expectedCodepoint: number, expectedM
|
||||
const actualMod = parsed.modifier & ~LOCK_MASK;
|
||||
const expectedMod = expectedModifier & ~LOCK_MASK;
|
||||
|
||||
// Check if modifiers match
|
||||
if (actualMod !== expectedMod) return false;
|
||||
|
||||
const normalizedCodepoint = normalizeShiftedLetterIdentityCodepoint(
|
||||
normalizeKittyFunctionalCodepoint(parsed.codepoint),
|
||||
parsed.modifier,
|
||||
@@ -668,8 +680,22 @@ function matchesKittySequence(data: string, expectedCodepoint: number, expectedM
|
||||
expectedModifier,
|
||||
);
|
||||
|
||||
// Primary match: codepoint matches directly after normalizing functional keys
|
||||
if (normalizedCodepoint === normalizedExpectedCodepoint) return true;
|
||||
// Physical match: primary codepoint and every reported modifier match.
|
||||
if (actualMod === expectedMod && normalizedCodepoint === normalizedExpectedCodepoint) return true;
|
||||
|
||||
// Logical match: Kitty reports symbols such as "+" as Shift plus the physical "=" key.
|
||||
// Bindings name the produced symbol, so consume that implicit Shift while preserving
|
||||
// Ctrl, Alt, and Super. The physical "shift+=" spelling remains valid through the match above.
|
||||
const logicalShiftedSymbol = getLogicalShiftedSymbolCandidate(parsed.shiftedKey, actualMod, SYMBOL_KEYS);
|
||||
if (
|
||||
logicalShiftedSymbol &&
|
||||
(logicalShiftedSymbol.modifier & ~LOCK_MASK) === expectedMod &&
|
||||
logicalShiftedSymbol.codepoint === normalizeKittyFunctionalCodepoint(expectedCodepoint)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (actualMod !== expectedMod) return false;
|
||||
|
||||
// Alternate match: use base layout key for non-Latin keyboard layouts.
|
||||
// This allows Ctrl+С (Cyrillic) to match Ctrl+c (Latin) when terminal reports
|
||||
@@ -764,13 +790,19 @@ function isDigitKey(key: string): boolean {
|
||||
}
|
||||
|
||||
function matchesPrintableModifyOtherKeys(data: string, expectedKeycode: number, expectedModifier: number): boolean {
|
||||
if (expectedModifier === 0) return false;
|
||||
const parsed = parseModifyOtherKeysSequence(data);
|
||||
if (!parsed || parsed.modifier !== expectedModifier) return false;
|
||||
return (
|
||||
if (!parsed) return false;
|
||||
const physicalMatch =
|
||||
parsed.modifier === expectedModifier &&
|
||||
normalizeShiftedLetterIdentityCodepoint(parsed.codepoint, parsed.modifier) ===
|
||||
normalizeShiftedLetterIdentityCodepoint(expectedKeycode, expectedModifier)
|
||||
normalizeShiftedLetterIdentityCodepoint(expectedKeycode, expectedModifier);
|
||||
if (physicalMatch) return true;
|
||||
const logicalShiftedSymbol = getLogicalShiftedSymbolCandidate(
|
||||
parsed.codepoint,
|
||||
parsed.modifier,
|
||||
SHIFTED_SYMBOL_KEYS,
|
||||
);
|
||||
return logicalShiftedSymbol?.modifier === expectedModifier && logicalShiftedSymbol.codepoint === expectedKeycode;
|
||||
}
|
||||
|
||||
function formatKeyNameWithModifiers(keyName: string, modifier: number): string | undefined {
|
||||
@@ -788,15 +820,22 @@ function formatKeyNameWithModifiers(keyName: string, modifier: number): string |
|
||||
function parseKeyId(
|
||||
keyId: string,
|
||||
): { key: string; ctrl: boolean; shift: boolean; alt: boolean; super: boolean } | null {
|
||||
const parts = keyId.toLowerCase().split("+");
|
||||
const key = parts[parts.length - 1];
|
||||
if (!key) return null;
|
||||
let key = keyId.toLowerCase();
|
||||
const modifiers = new Set<(typeof MODIFIER_NAMES)[number]>();
|
||||
while (true) {
|
||||
const modifier = MODIFIER_NAMES.find((name) => key.startsWith(`${name}+`));
|
||||
if (!modifier) break;
|
||||
if (modifiers.has(modifier)) return null;
|
||||
modifiers.add(modifier);
|
||||
key = key.slice(modifier.length + 1);
|
||||
}
|
||||
if (!key || (key.includes("+") && key !== "+")) return null;
|
||||
return {
|
||||
key,
|
||||
ctrl: parts.includes("ctrl"),
|
||||
shift: parts.includes("shift"),
|
||||
alt: parts.includes("alt"),
|
||||
super: parts.includes("super"),
|
||||
ctrl: modifiers.has("ctrl"),
|
||||
shift: modifiers.has("shift"),
|
||||
alt: modifiers.has("alt"),
|
||||
super: modifiers.has("super"),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1197,7 +1236,9 @@ export function matchesKey(data: string, keyId: KeyId): boolean {
|
||||
}
|
||||
|
||||
// Check both raw char and Kitty sequence (needed for release events)
|
||||
return data === key || matchesKittySequence(data, codepoint, 0);
|
||||
return (
|
||||
data === key || matchesKittySequence(data, codepoint, 0) || matchesPrintableModifyOtherKeys(data, codepoint, 0)
|
||||
);
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -1251,12 +1292,25 @@ function formatParsedKey(codepoint: number, modifier: number, baseLayoutKey?: nu
|
||||
export function parseKey(data: string): string | undefined {
|
||||
const kitty = parseKittySequence(data);
|
||||
if (kitty) {
|
||||
return formatParsedKey(kitty.codepoint, kitty.modifier, kitty.baseLayoutKey);
|
||||
const logicalShiftedSymbol = getLogicalShiftedSymbolCandidate(kitty.shiftedKey, kitty.modifier, SYMBOL_KEYS);
|
||||
return formatParsedKey(
|
||||
logicalShiftedSymbol?.codepoint ?? kitty.codepoint,
|
||||
logicalShiftedSymbol?.modifier ?? kitty.modifier,
|
||||
kitty.baseLayoutKey,
|
||||
);
|
||||
}
|
||||
|
||||
const modifyOtherKeys = parseModifyOtherKeysSequence(data);
|
||||
if (modifyOtherKeys) {
|
||||
return formatParsedKey(modifyOtherKeys.codepoint, modifyOtherKeys.modifier);
|
||||
const logicalShiftedSymbol = getLogicalShiftedSymbolCandidate(
|
||||
modifyOtherKeys.codepoint,
|
||||
modifyOtherKeys.modifier,
|
||||
SHIFTED_SYMBOL_KEYS,
|
||||
);
|
||||
return formatParsedKey(
|
||||
logicalShiftedSymbol?.codepoint ?? modifyOtherKeys.codepoint,
|
||||
logicalShiftedSymbol?.modifier ?? modifyOtherKeys.modifier,
|
||||
);
|
||||
}
|
||||
|
||||
// Mode-aware legacy sequences
|
||||
|
||||
Reference in New Issue
Block a user