Merge PR #8238: fix(tui): render platform-aware modifier labels on macOS (@roboomp)
This commit is contained in:
@@ -654,12 +654,52 @@ export class KeybindingsManager extends TuiKeybindingsManager {
|
||||
|
||||
/**
|
||||
* Key hint formatting utilities for UI labels.
|
||||
*
|
||||
* Modifier labels are platform-aware: macOS names the physical keys `Option`
|
||||
* (`alt`) and `Cmd` (`super`), so rendering `Alt`/`Super` there would name keys
|
||||
* absent from a Mac keyboard. Every other platform keeps `Alt`/`Super`.
|
||||
*/
|
||||
const MODIFIER_LABELS: Record<string, string> = {
|
||||
ctrl: "Ctrl",
|
||||
shift: "Shift",
|
||||
alt: "Alt",
|
||||
};
|
||||
|
||||
/**
|
||||
* Platform override for key-hint rendering; `undefined` resolves to the host
|
||||
* `process.platform`. Mirrors `setKittyProtocolActive` in the TUI keys module:
|
||||
* a single seam that keeps hint output deterministic in tests without mutating
|
||||
* the global `process.platform`.
|
||||
*/
|
||||
let keyHintPlatformOverride: NodeJS.Platform | undefined;
|
||||
|
||||
/** Pin the platform used to render modifier labels (test seam). */
|
||||
export function setKeyHintPlatform(platform: NodeJS.Platform | undefined): void {
|
||||
keyHintPlatformOverride = platform;
|
||||
}
|
||||
|
||||
/** Platform currently used for key-hint rendering. */
|
||||
export function keyHintPlatform(): NodeJS.Platform {
|
||||
return keyHintPlatformOverride ?? process.platform;
|
||||
}
|
||||
|
||||
type Modifier = "ctrl" | "shift" | "alt" | "super";
|
||||
|
||||
function isModifier(part: string): part is Modifier {
|
||||
return part === "ctrl" || part === "shift" || part === "alt" || part === "super";
|
||||
}
|
||||
|
||||
/**
|
||||
* Human label for a modifier, using each platform's own key names. `ctrl` and
|
||||
* `shift` are the same everywhere; `alt`/`super` become `Option`/`Cmd` on macOS.
|
||||
*/
|
||||
export function modifierLabel(mod: Modifier, platform: NodeJS.Platform = keyHintPlatform()): string {
|
||||
switch (mod) {
|
||||
case "ctrl":
|
||||
return "Ctrl";
|
||||
case "shift":
|
||||
return "Shift";
|
||||
case "alt":
|
||||
return platform === "darwin" ? "Option" : "Alt";
|
||||
case "super":
|
||||
return platform === "darwin" ? "Cmd" : "Super";
|
||||
}
|
||||
}
|
||||
|
||||
const KEY_LABELS: Record<string, string> = {
|
||||
esc: "Esc",
|
||||
@@ -680,10 +720,9 @@ const KEY_LABELS: Record<string, string> = {
|
||||
right: "Right",
|
||||
};
|
||||
|
||||
function formatKeyPart(part: string): string {
|
||||
function formatKeyPart(part: string, platform: NodeJS.Platform): string {
|
||||
const lower = part.toLowerCase();
|
||||
const modifier = MODIFIER_LABELS[lower];
|
||||
if (modifier) return modifier;
|
||||
if (isModifier(lower)) return modifierLabel(lower, platform);
|
||||
const label = KEY_LABELS[lower];
|
||||
if (label) return label;
|
||||
if (part.length === 1) return part.toUpperCase();
|
||||
@@ -691,7 +730,11 @@ function formatKeyPart(part: string): string {
|
||||
}
|
||||
|
||||
export function formatKeyHint(key: KeyId): string {
|
||||
return key.split("+").map(formatKeyPart).join("+");
|
||||
const platform = keyHintPlatform();
|
||||
return key
|
||||
.split("+")
|
||||
.map(part => formatKeyPart(part, platform))
|
||||
.join("+");
|
||||
}
|
||||
|
||||
export function formatKeyHints(keys: KeyId | KeyId[]): string {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { AppKeybinding, KeybindingsManager } from "../../config/keybindings";
|
||||
import { type AppKeybinding, type KeybindingsManager, keyHintPlatform, modifierLabel } from "../../config/keybindings";
|
||||
|
||||
export interface HotkeysMarkdownBindings {
|
||||
keybindings: Pick<KeybindingsManager, "getDisplayString">;
|
||||
@@ -9,21 +9,25 @@ function appKey(bindings: HotkeysMarkdownBindings, action: AppKeybinding): strin
|
||||
}
|
||||
|
||||
export function buildHotkeysMarkdown(bindings: HotkeysMarkdownBindings): string {
|
||||
const platform = keyHintPlatform();
|
||||
const isMac = platform === "darwin";
|
||||
const alt = modifierLabel("alt", platform);
|
||||
const cmd = modifierLabel("super", platform);
|
||||
return [
|
||||
"**Navigation**",
|
||||
"| Key | Action |",
|
||||
"|-----|--------|",
|
||||
"| `Arrow keys` | Move cursor / browse history (Up when empty) |",
|
||||
"| `Option+Left/Right` | Move by word |",
|
||||
"| `Ctrl+A` / `Home` / `Cmd+Left` | Start of line |",
|
||||
"| `Ctrl+E` / `End` / `Cmd+Right` | End of line |",
|
||||
`| \`${alt}+Left/Right\` | Move by word |`,
|
||||
isMac ? `| \`Ctrl+A\` / \`Home\` / \`${cmd}+Left\` | Start of line |` : "| `Ctrl+A` / `Home` | Start of line |",
|
||||
isMac ? `| \`Ctrl+E\` / \`End\` / \`${cmd}+Right\` | End of line |` : "| `Ctrl+E` / `End` | End of line |",
|
||||
"",
|
||||
"**Editing**",
|
||||
"| Key | Action |",
|
||||
"|-----|--------|",
|
||||
"| `Enter` | Send message |",
|
||||
"| `Shift+Enter` / `Alt+Enter` | New line |",
|
||||
"| `Ctrl+W` / `Option+Backspace` | Delete word backwards |",
|
||||
`| \`Shift+Enter\` / \`${alt}+Enter\` | New line |`,
|
||||
`| \`Ctrl+W\` / \`${alt}+Backspace\` | Delete word backwards |`,
|
||||
"| `Ctrl+U` | Delete to start of line |",
|
||||
"| `Ctrl+K` | Delete to end of line |",
|
||||
`| \`${appKey(bindings, "app.clipboard.copyLine")}\` | Copy current line |`,
|
||||
|
||||
Reference in New Issue
Block a user