Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 72 additions & 0 deletions frontends/keysym.py
Original file line number Diff line number Diff line change
@@ -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"]