@mariozechner/pi-tui 0.57.0 → 0.57.1

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/keys.js CHANGED
@@ -460,20 +460,24 @@ function matchesKittySequence(data, expectedCodepoint, expectedModifier) {
460
460
  }
461
461
  return false;
462
462
  }
463
+ function parseModifyOtherKeysSequence(data) {
464
+ const match = data.match(/^\x1b\[27;(\d+);(\d+)~$/);
465
+ if (!match)
466
+ return null;
467
+ const modValue = parseInt(match[1], 10);
468
+ const codepoint = parseInt(match[2], 10);
469
+ return { codepoint, modifier: modValue - 1 };
470
+ }
463
471
  /**
464
472
  * Match xterm modifyOtherKeys format: CSI 27 ; modifiers ; keycode ~
465
473
  * This is used by terminals when Kitty protocol is not enabled.
466
474
  * Modifier values are 1-indexed: 2=shift, 3=alt, 5=ctrl, etc.
467
475
  */
468
476
  function matchesModifyOtherKeys(data, expectedKeycode, expectedModifier) {
469
- const match = data.match(/^\x1b\[27;(\d+);(\d+)~$/);
470
- if (!match)
477
+ const parsed = parseModifyOtherKeysSequence(data);
478
+ if (!parsed)
471
479
  return false;
472
- const modValue = parseInt(match[1], 10);
473
- const keycode = parseInt(match[2], 10);
474
- // Convert from 1-indexed xterm format to our 0-indexed format
475
- const actualMod = modValue - 1;
476
- return keycode === expectedKeycode && actualMod === expectedModifier;
480
+ return parsed.codepoint === expectedKeycode && parsed.modifier === expectedModifier;
477
481
  }
478
482
  // =============================================================================
479
483
  // Generic Key Matching
@@ -499,6 +503,28 @@ function rawCtrlChar(key) {
499
503
  }
500
504
  return null;
501
505
  }
506
+ function isDigitKey(key) {
507
+ return key >= "0" && key <= "9";
508
+ }
509
+ function matchesPrintableModifyOtherKeys(data, expectedKeycode, expectedModifier) {
510
+ if (expectedModifier === 0)
511
+ return false;
512
+ return matchesModifyOtherKeys(data, expectedKeycode, expectedModifier);
513
+ }
514
+ function formatKeyNameWithModifiers(keyName, modifier) {
515
+ const mods = [];
516
+ const effectiveMod = modifier & ~LOCK_MASK;
517
+ const supportedModifierMask = MODIFIERS.shift | MODIFIERS.ctrl | MODIFIERS.alt;
518
+ if ((effectiveMod & ~supportedModifierMask) !== 0)
519
+ return undefined;
520
+ if (effectiveMod & MODIFIERS.shift)
521
+ mods.push("shift");
522
+ if (effectiveMod & MODIFIERS.ctrl)
523
+ mods.push("ctrl");
524
+ if (effectiveMod & MODIFIERS.alt)
525
+ mods.push("alt");
526
+ return mods.length > 0 ? `${mods.join("+")}+${keyName}` : keyName;
527
+ }
502
528
  function parseKeyId(keyId) {
503
529
  const parts = keyId.toLowerCase().split("+");
504
530
  const key = parts[parts.length - 1];
@@ -611,7 +637,8 @@ export function matchesKey(data, keyId) {
611
637
  matchesKittySequence(data, CODEPOINTS.kpEnter, 0));
612
638
  }
613
639
  return (matchesKittySequence(data, CODEPOINTS.enter, modifier) ||
614
- matchesKittySequence(data, CODEPOINTS.kpEnter, modifier));
640
+ matchesKittySequence(data, CODEPOINTS.kpEnter, modifier) ||
641
+ matchesModifyOtherKeys(data, CODEPOINTS.enter, modifier));
615
642
  case "backspace":
616
643
  if (alt && !ctrl && !shift) {
617
644
  if (data === "\x1b\x7f" || data === "\x1b\b") {
@@ -765,16 +792,18 @@ export function matchesKey(data, keyId) {
765
792
  return matchesLegacySequence(data, LEGACY_KEY_SEQUENCES[functionKey]);
766
793
  }
767
794
  }
768
- // Handle single letter keys (a-z) and some symbols
769
- if (key.length === 1 && ((key >= "a" && key <= "z") || SYMBOL_KEYS.has(key))) {
795
+ // Handle single letter/digit keys and symbols
796
+ if (key.length === 1 && ((key >= "a" && key <= "z") || isDigitKey(key) || SYMBOL_KEYS.has(key))) {
770
797
  const codepoint = key.charCodeAt(0);
771
798
  const rawCtrl = rawCtrlChar(key);
799
+ const isLetter = key >= "a" && key <= "z";
800
+ const isDigit = isDigitKey(key);
772
801
  if (ctrl && alt && !shift && !_kittyProtocolActive && rawCtrl) {
773
802
  // Legacy: ctrl+alt+key is ESC followed by the control character
774
803
  return data === `\x1b${rawCtrl}`;
775
804
  }
776
- if (alt && !ctrl && !shift && !_kittyProtocolActive && key >= "a" && key <= "z") {
777
- // Legacy: alt+letter is ESC followed by the letter
805
+ if (alt && !ctrl && !shift && !_kittyProtocolActive && (isLetter || isDigit)) {
806
+ // Legacy: alt+letter/digit is ESC followed by the key
778
807
  if (data === `\x1b${key}`)
779
808
  return true;
780
809
  }
@@ -782,19 +811,23 @@ export function matchesKey(data, keyId) {
782
811
  // Legacy: ctrl+key sends the control character
783
812
  if (rawCtrl && data === rawCtrl)
784
813
  return true;
785
- return matchesKittySequence(data, codepoint, MODIFIERS.ctrl);
814
+ return (matchesKittySequence(data, codepoint, MODIFIERS.ctrl) ||
815
+ matchesPrintableModifyOtherKeys(data, codepoint, MODIFIERS.ctrl));
786
816
  }
787
817
  if (ctrl && shift && !alt) {
788
- return matchesKittySequence(data, codepoint, MODIFIERS.shift + MODIFIERS.ctrl);
818
+ return (matchesKittySequence(data, codepoint, MODIFIERS.shift + MODIFIERS.ctrl) ||
819
+ matchesPrintableModifyOtherKeys(data, codepoint, MODIFIERS.shift + MODIFIERS.ctrl));
789
820
  }
790
821
  if (shift && !ctrl && !alt) {
791
822
  // Legacy: shift+letter produces uppercase
792
- if (data === key.toUpperCase())
823
+ if (isLetter && data === key.toUpperCase())
793
824
  return true;
794
- return matchesKittySequence(data, codepoint, MODIFIERS.shift);
825
+ return (matchesKittySequence(data, codepoint, MODIFIERS.shift) ||
826
+ matchesPrintableModifyOtherKeys(data, codepoint, MODIFIERS.shift));
795
827
  }
796
828
  if (modifier !== 0) {
797
- return matchesKittySequence(data, codepoint, modifier);
829
+ return (matchesKittySequence(data, codepoint, modifier) ||
830
+ matchesPrintableModifyOtherKeys(data, codepoint, modifier));
798
831
  }
799
832
  // Check both raw char and Kitty sequence (needed for release events)
800
833
  return data === key || matchesKittySequence(data, codepoint, 0);
@@ -807,67 +840,65 @@ export function matchesKey(data, keyId) {
807
840
  * @param data - Raw input data from terminal
808
841
  * @returns Key identifier string (e.g., "ctrl+c") or undefined
809
842
  */
843
+ function formatParsedKey(codepoint, modifier, baseLayoutKey) {
844
+ // Use base layout key only when codepoint is not a recognized Latin
845
+ // letter (a-z), digit (0-9), or symbol (/, -, [, ;, etc.). For those,
846
+ // the codepoint is authoritative regardless of physical key position.
847
+ // This prevents remapped layouts (Dvorak, Colemak, xremap, etc.) from
848
+ // reporting the wrong key name based on the QWERTY physical position.
849
+ const isLatinLetter = codepoint >= 97 && codepoint <= 122; // a-z
850
+ const isDigit = codepoint >= 48 && codepoint <= 57; // 0-9
851
+ const isKnownSymbol = SYMBOL_KEYS.has(String.fromCharCode(codepoint));
852
+ const effectiveCodepoint = isLatinLetter || isDigit || isKnownSymbol ? codepoint : (baseLayoutKey ?? codepoint);
853
+ let keyName;
854
+ if (effectiveCodepoint === CODEPOINTS.escape)
855
+ keyName = "escape";
856
+ else if (effectiveCodepoint === CODEPOINTS.tab)
857
+ keyName = "tab";
858
+ else if (effectiveCodepoint === CODEPOINTS.enter || effectiveCodepoint === CODEPOINTS.kpEnter)
859
+ keyName = "enter";
860
+ else if (effectiveCodepoint === CODEPOINTS.space)
861
+ keyName = "space";
862
+ else if (effectiveCodepoint === CODEPOINTS.backspace)
863
+ keyName = "backspace";
864
+ else if (effectiveCodepoint === FUNCTIONAL_CODEPOINTS.delete)
865
+ keyName = "delete";
866
+ else if (effectiveCodepoint === FUNCTIONAL_CODEPOINTS.insert)
867
+ keyName = "insert";
868
+ else if (effectiveCodepoint === FUNCTIONAL_CODEPOINTS.home)
869
+ keyName = "home";
870
+ else if (effectiveCodepoint === FUNCTIONAL_CODEPOINTS.end)
871
+ keyName = "end";
872
+ else if (effectiveCodepoint === FUNCTIONAL_CODEPOINTS.pageUp)
873
+ keyName = "pageUp";
874
+ else if (effectiveCodepoint === FUNCTIONAL_CODEPOINTS.pageDown)
875
+ keyName = "pageDown";
876
+ else if (effectiveCodepoint === ARROW_CODEPOINTS.up)
877
+ keyName = "up";
878
+ else if (effectiveCodepoint === ARROW_CODEPOINTS.down)
879
+ keyName = "down";
880
+ else if (effectiveCodepoint === ARROW_CODEPOINTS.left)
881
+ keyName = "left";
882
+ else if (effectiveCodepoint === ARROW_CODEPOINTS.right)
883
+ keyName = "right";
884
+ else if (effectiveCodepoint >= 48 && effectiveCodepoint <= 57)
885
+ keyName = String.fromCharCode(effectiveCodepoint);
886
+ else if (effectiveCodepoint >= 97 && effectiveCodepoint <= 122)
887
+ keyName = String.fromCharCode(effectiveCodepoint);
888
+ else if (SYMBOL_KEYS.has(String.fromCharCode(effectiveCodepoint)))
889
+ keyName = String.fromCharCode(effectiveCodepoint);
890
+ if (!keyName)
891
+ return undefined;
892
+ return formatKeyNameWithModifiers(keyName, modifier);
893
+ }
810
894
  export function parseKey(data) {
811
895
  const kitty = parseKittySequence(data);
812
896
  if (kitty) {
813
- const { codepoint, baseLayoutKey, modifier } = kitty;
814
- const mods = [];
815
- const effectiveMod = modifier & ~LOCK_MASK;
816
- const supportedModifierMask = MODIFIERS.shift | MODIFIERS.ctrl | MODIFIERS.alt;
817
- if ((effectiveMod & ~supportedModifierMask) !== 0)
818
- return undefined;
819
- if (effectiveMod & MODIFIERS.shift)
820
- mods.push("shift");
821
- if (effectiveMod & MODIFIERS.ctrl)
822
- mods.push("ctrl");
823
- if (effectiveMod & MODIFIERS.alt)
824
- mods.push("alt");
825
- // Use base layout key only when codepoint is not a recognized Latin
826
- // letter (a-z) or symbol (/, -, [, ;, etc.). For those, the codepoint
827
- // is authoritative regardless of physical key position. This prevents
828
- // remapped layouts (Dvorak, Colemak, xremap, etc.) from reporting the
829
- // wrong key name based on the QWERTY physical position.
830
- const isLatinLetter = codepoint >= 97 && codepoint <= 122; // a-z
831
- const isKnownSymbol = SYMBOL_KEYS.has(String.fromCharCode(codepoint));
832
- const effectiveCodepoint = isLatinLetter || isKnownSymbol ? codepoint : (baseLayoutKey ?? codepoint);
833
- let keyName;
834
- if (effectiveCodepoint === CODEPOINTS.escape)
835
- keyName = "escape";
836
- else if (effectiveCodepoint === CODEPOINTS.tab)
837
- keyName = "tab";
838
- else if (effectiveCodepoint === CODEPOINTS.enter || effectiveCodepoint === CODEPOINTS.kpEnter)
839
- keyName = "enter";
840
- else if (effectiveCodepoint === CODEPOINTS.space)
841
- keyName = "space";
842
- else if (effectiveCodepoint === CODEPOINTS.backspace)
843
- keyName = "backspace";
844
- else if (effectiveCodepoint === FUNCTIONAL_CODEPOINTS.delete)
845
- keyName = "delete";
846
- else if (effectiveCodepoint === FUNCTIONAL_CODEPOINTS.insert)
847
- keyName = "insert";
848
- else if (effectiveCodepoint === FUNCTIONAL_CODEPOINTS.home)
849
- keyName = "home";
850
- else if (effectiveCodepoint === FUNCTIONAL_CODEPOINTS.end)
851
- keyName = "end";
852
- else if (effectiveCodepoint === FUNCTIONAL_CODEPOINTS.pageUp)
853
- keyName = "pageUp";
854
- else if (effectiveCodepoint === FUNCTIONAL_CODEPOINTS.pageDown)
855
- keyName = "pageDown";
856
- else if (effectiveCodepoint === ARROW_CODEPOINTS.up)
857
- keyName = "up";
858
- else if (effectiveCodepoint === ARROW_CODEPOINTS.down)
859
- keyName = "down";
860
- else if (effectiveCodepoint === ARROW_CODEPOINTS.left)
861
- keyName = "left";
862
- else if (effectiveCodepoint === ARROW_CODEPOINTS.right)
863
- keyName = "right";
864
- else if (effectiveCodepoint >= 97 && effectiveCodepoint <= 122)
865
- keyName = String.fromCharCode(effectiveCodepoint);
866
- else if (SYMBOL_KEYS.has(String.fromCharCode(effectiveCodepoint)))
867
- keyName = String.fromCharCode(effectiveCodepoint);
868
- if (keyName) {
869
- return mods.length > 0 ? `${mods.join("+")}+${keyName}` : keyName;
870
- }
897
+ return formatParsedKey(kitty.codepoint, kitty.modifier, kitty.baseLayoutKey);
898
+ }
899
+ const modifyOtherKeys = parseModifyOtherKeysSequence(data);
900
+ if (modifyOtherKeys) {
901
+ return formatParsedKey(modifyOtherKeys.codepoint, modifyOtherKeys.modifier);
871
902
  }
872
903
  // Mode-aware legacy sequences
873
904
  // When Kitty protocol is active, ambiguous sequences are interpreted as custom terminal mappings:
@@ -924,8 +955,8 @@ export function parseKey(data) {
924
955
  if (code >= 1 && code <= 26) {
925
956
  return `ctrl+alt+${String.fromCharCode(code + 96)}`;
926
957
  }
927
- // Legacy alt+letter (ESC followed by letter a-z)
928
- if (code >= 97 && code <= 122) {
958
+ // Legacy alt+letter/digit (ESC followed by the key)
959
+ if ((code >= 97 && code <= 122) || (code >= 48 && code <= 57)) {
929
960
  return `alt+${String.fromCharCode(code)}`;
930
961
  }
931
962
  }