From 0e63b05507839e5ce980596fe5e7370b1fe73219 Mon Sep 17 00:00:00 2001 From: Shen Hao Date: Thu, 28 May 2026 10:13:21 +0800 Subject: [PATCH] =?UTF-8?q?fix(tui-v2):=20add=20missing=20keysym.py=20?= =?UTF-8?q?=E2=80=94=20tuiapp=5Fv2=20ImportError=20on=20fresh=20clone?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `tuiapp_v2.py:27` imports `fmt_key`, `fmt_keys` from `keysym`, but the module file was never tracked. PR #513 (tui v2/v3 fixes) is unusable on a fresh checkout without this. Adds the cross-platform shortcut formatter that turns binding strings into mac/Win-Linux key labels (e.g. `"ctrl+b"` → `"⌃B"` / `"Ctrl+B"`), honoring `GA_KEYSYM_STYLE`. --- frontends/keysym.py | 72 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 frontends/keysym.py diff --git a/frontends/keysym.py b/frontends/keysym.py new file mode 100644 index 00000000..3d931027 --- /dev/null +++ b/frontends/keysym.py @@ -0,0 +1,72 @@ +"""Cross-platform keyboard-shortcut display formatter. + +One job: turn a Textual binding string like ``"ctrl+b"`` into a human-facing +label such as ``"Ctrl+B"`` on Win/Linux or ``"⌃B"`` on macOS. + +Binding strings (the *physical* keys Textual captures) are NOT touched — +this module only formats labels for tips / footers / help panels. + +Override with env ``GA_KEYSYM_STYLE=auto|mac|ascii`` (default ``auto``). +""" +from __future__ import annotations + +import os +import sys + +_STYLE = os.environ.get("GA_KEYSYM_STYLE", "auto").lower() +IS_MAC = _STYLE == "mac" or (_STYLE != "ascii" and sys.platform == "darwin") + +# Modifier display per style. mac uses Apple HIG glyphs; others use words. +_MOD = { + "ctrl": "⌃" if IS_MAC else "Ctrl", + "shift": "⇧" if IS_MAC else "Shift", + "alt": "⌥" if IS_MAC else "Alt", + "meta": "⌘" if IS_MAC else "Alt", + "super": "⌘" if IS_MAC else "Win", + "cmd": "⌘" if IS_MAC else "Win", +} + +# Bare-key display. Arrows / slash are universal; rest mac-glyphs vs words. +_KEY = { + "enter": "⏎" if IS_MAC else "Enter", + "tab": "⇥" if IS_MAC else "Tab", + "escape": "⎋" if IS_MAC else "Esc", + "esc": "⎋" if IS_MAC else "Esc", + "backspace": "⌫" if IS_MAC else "Backspace", + "delete": "⌦" if IS_MAC else "Del", + "space": "␣" if IS_MAC else "Space", + "up": "↑", "down": "↓", "left": "←", "right": "→", + "slash": "/", "underscore": "_", +} + +# Joiner between modifier and key. mac concatenates (⌃B); others use '+'. +_JOIN = "" if IS_MAC else "+" + + +def fmt_key(combo: str) -> str: + """``"ctrl+b"`` → ``"⌃B"`` (mac) / ``"Ctrl+B"`` (Win/Linux). + + Unknown single-char keys are upper-cased (``"b"`` → ``"B"``); + multi-char names fall back to the original token unchanged. + """ + parts = [p.strip() for p in combo.lower().split("+") if p.strip()] + if not parts: + return combo + mods, key = parts[:-1], parts[-1] + key_disp = _KEY.get(key) or (key.upper() if len(key) == 1 else key) + mod_disp = [_MOD.get(m, m) for m in mods] + if not mod_disp: + return key_disp + return _JOIN.join(mod_disp) + _JOIN + key_disp + + +def fmt_keys(*combos: str, sep: str = " / ") -> str: + """Join multiple combos: ``fmt_keys("ctrl+j", "ctrl+enter")`` → + ``"Ctrl+J / Ctrl+Enter"`` or ``"⌃J / ⌃⏎"``.""" + return sep.join(fmt_key(c) for c in combos) + + +# Convenience constants for f-string templates. +CTRL = _MOD["ctrl"] +SHIFT = _MOD["shift"] +ALT = _MOD["alt"]