import { type RawHotkey, normalizeKeyName,} from "@tanstack/react-hotkeys";import { type KeybindingCommand, type KeybindingShortcut, type KeybindingWhenNode, type ResolvedKeybindingsConfig,} from "@t3tools/contracts";import { isMacPlatform } from "./lib/utils"; export interface ShortcutEventLike { type?: string; key: string; metaKey: boolean; ctrlKey: boolean; shiftKey: boolean; altKey: boolean;} export interface ShortcutMatchContext { terminalFocus: boolean; terminalOpen: boolean; [key: string]: boolean;} interface ShortcutMatchOptions { platform?: string; context?: Partial<ShortcutMatchContext>;} function shortcutSignature(shortcut: KeybindingShortcut): string { return [ shortcut.key, shortcut.metaKey ? "1" : "0", shortcut.ctrlKey ? "1" : "0", shortcut.shiftKey ? "1" : "0", shortcut.altKey ? "1" : "0", shortcut.modKey ? "1" : "0", ].join("|");} const TERMINAL_WORD_BACKWARD = "\u001bb";const TERMINAL_WORD_FORWARD = "\u001bf";const TERMINAL_LINE_START = "\u0001";const TERMINAL_LINE_END = "\u0005"; function normalizeEventKey(key: string): string { const normalized = key.toLowerCase(); if (normalized === "esc") return "escape"; return normalized;} function matchesShortcut( event: ShortcutEventLike, shortcut: KeybindingShortcut, platform = navigator.platform,): boolean { const key = normalizeEventKey(event.key); if (key !== shortcut.key) return false; const useMetaForMod = isMacPlatform(platform); const expectedMeta = shortcut.metaKey || (shortcut.modKey && useMetaForMod); const expectedCtrl = shortcut.ctrlKey || (shortcut.modKey && !useMetaForMod); return ( event.metaKey === expectedMeta && event.ctrlKey === expectedCtrl && event.shiftKey === shortcut.shiftKey && event.altKey === shortcut.altKey );} function resolvePlatform(options: ShortcutMatchOptions | undefined): string { return options?.platform ?? navigator.platform;} function resolveContext(options: ShortcutMatchOptions | undefined): ShortcutMatchContext { return { terminalFocus: false, terminalOpen: false, ...options?.context, };} function evaluateWhenNode(node: KeybindingWhenNode, context: ShortcutMatchContext): boolean { switch (node.type) { case "identifier": if (node.name === "true") return true; if (node.name === "false") return false; return Boolean(context[node.name]); case "not": return !evaluateWhenNode(node.node, context); case "and": return evaluateWhenNode(node.left, context) && evaluateWhenNode(node.right, context); case "or": return evaluateWhenNode(node.left, context) || evaluateWhenNode(node.right, context); }} function matchesWhenClause( whenAst: KeybindingWhenNode | undefined, context: ShortcutMatchContext,): boolean { if (!whenAst) return true; return evaluateWhenNode(whenAst, context);} function matchesCommandShortcut( event: ShortcutEventLike, keybindings: ResolvedKeybindingsConfig, command: KeybindingCommand, options?: ShortcutMatchOptions,): boolean { return resolveShortcutCommand(event, keybindings, options) === command;} function resolveShortcutCommand( event: ShortcutEventLike, keybindings: ResolvedKeybindingsConfig, options?: ShortcutMatchOptions,): KeybindingCommand | null { const platform = resolvePlatform(options); const context = resolveContext(options); for (let index = keybindings.length - 1; index >= 0; index -= 1) { const binding = keybindings[index]; if (!binding) continue; if (!matchesWhenClause(binding.whenAst, context)) continue; if (!matchesShortcut(event, binding.shortcut, platform)) continue; return binding.command; } return null;} function formatShortcutKeyLabel(key: string): string { if (key === " ") return "Space"; if (key.length === 1) return key.toUpperCase(); if (key === "escape") return "Esc"; if (key === "arrowup") return "Up"; if (key === "arrowdown") return "Down"; if (key === "arrowleft") return "Left"; if (key === "arrowright") return "Right"; return key.slice(0, 1).toUpperCase() + key.slice(1);} export function formatShortcutLabel( shortcut: KeybindingShortcut, platform = navigator.platform,): string { const keyLabel = formatShortcutKeyLabel(shortcut.key); const useMetaForMod = isMacPlatform(platform); const showMeta = shortcut.metaKey || (shortcut.modKey && useMetaForMod); const showCtrl = shortcut.ctrlKey || (shortcut.modKey && !useMetaForMod); const showAlt = shortcut.altKey; const showShift = shortcut.shiftKey; if (useMetaForMod) { return `${showCtrl ? "\u2303" : ""}${showAlt ? "\u2325" : ""}${showShift ? "\u21e7" : ""}${showMeta ? "\u2318" : ""}${keyLabel}`; } const parts: string[] = []; if (showCtrl) parts.push("Ctrl"); if (showAlt) parts.push("Alt"); if (showShift) parts.push("Shift"); if (showMeta) parts.push("Meta"); parts.push(keyLabel); return parts.join("+");} export function shortcutLabelForCommand( keybindings: ResolvedKeybindingsConfig, command: KeybindingCommand, platform = navigator.platform,): string | null { for (let index = keybindings.length - 1; index >= 0; index -= 1) { const binding = keybindings[index]; if (!binding || binding.command !== command) continue; return formatShortcutLabel(binding.shortcut, platform); } return null;} export function shortcutToRawHotkey(shortcut: KeybindingShortcut): RawHotkey { return { key: normalizeKeyName(shortcut.key), ...(shortcut.modKey ? { mod: true } : {}), ...(shortcut.metaKey ? { meta: true } : {}), ...(shortcut.ctrlKey ? { ctrl: true } : {}), ...(shortcut.shiftKey ? { shift: true } : {}), ...(shortcut.altKey ? { alt: true } : {}), };} export function shortcutsForCommands( keybindings: ResolvedKeybindingsConfig, commands: readonly KeybindingCommand[],): RawHotkey[] { if (commands.length === 0 || keybindings.length === 0) { return []; } const commandSet = new Set(commands); const seen = new Set<string>(); const hotkeys: RawHotkey[] = []; for (const binding of keybindings) { if (!commandSet.has(binding.command)) continue; const signature = shortcutSignature(binding.shortcut); if (seen.has(signature)) continue; seen.add(signature); hotkeys.push(shortcutToRawHotkey(binding.shortcut)); } return hotkeys;} export function isTerminalToggleShortcut( event: ShortcutEventLike, keybindings: ResolvedKeybindingsConfig, options?: ShortcutMatchOptions,): boolean { return matchesCommandShortcut(event, keybindings, "terminal.toggle", options);} export function isTerminalSplitShortcut( event: ShortcutEventLike, keybindings: ResolvedKeybindingsConfig, options?: ShortcutMatchOptions,): boolean { return matchesCommandShortcut(event, keybindings, "terminal.split", options);} export function isTerminalNewShortcut( event: ShortcutEventLike, keybindings: ResolvedKeybindingsConfig, options?: ShortcutMatchOptions,): boolean { return matchesCommandShortcut(event, keybindings, "terminal.new", options);} export function isTerminalCloseShortcut( event: ShortcutEventLike, keybindings: ResolvedKeybindingsConfig, options?: ShortcutMatchOptions,): boolean { return matchesCommandShortcut(event, keybindings, "terminal.close", options);} export function isChatNewShortcut( event: ShortcutEventLike, keybindings: ResolvedKeybindingsConfig, options?: ShortcutMatchOptions,): boolean { return matchesCommandShortcut(event, keybindings, "chat.new", options);} export function isChatNewLocalShortcut( event: ShortcutEventLike, keybindings: ResolvedKeybindingsConfig, options?: ShortcutMatchOptions,): boolean { return matchesCommandShortcut(event, keybindings, "chat.newLocal", options);} export function isOpenFavoriteEditorShortcut( event: ShortcutEventLike, keybindings: ResolvedKeybindingsConfig, options?: ShortcutMatchOptions,): boolean { return matchesCommandShortcut(event, keybindings, "editor.openFavorite", options);} export function isTerminalClearShortcut( event: ShortcutEventLike, platform = navigator.platform,): boolean { if (event.type !== undefined && event.type !== "keydown") { return false; } const key = event.key.toLowerCase(); if (key === "l" && event.ctrlKey && !event.metaKey && !event.altKey && !event.shiftKey) { return true; } return ( isMacPlatform(platform) && key === "k" && event.metaKey && !event.ctrlKey && !event.altKey && !event.shiftKey );} export function terminalNavigationShortcutData( event: ShortcutEventLike, platform = navigator.platform,): string | null { if (event.type !== undefined && event.type !== "keydown") { return null; } if (event.shiftKey) return null; const key = normalizeEventKey(event.key); if (key !== "arrowleft" && key !== "arrowright") { return null; } const moveWord = key === "arrowleft" ? TERMINAL_WORD_BACKWARD : TERMINAL_WORD_FORWARD; const moveLine = key === "arrowleft" ? TERMINAL_LINE_START : TERMINAL_LINE_END; if (isMacPlatform(platform)) { if (event.altKey && !event.metaKey && !event.ctrlKey) { return moveWord; } if (event.metaKey && !event.altKey && !event.ctrlKey) { return moveLine; } return null; } if (event.ctrlKey && !event.metaKey && !event.altKey) { return moveWord; } if (event.altKey && !event.metaKey && !event.ctrlKey) { return moveWord; } return null;}
import {
type RawHotkey,
normalizeKeyName,
} from "@tanstack/react-hotkeys";
import {
type KeybindingCommand,
type KeybindingShortcut,
type KeybindingWhenNode,
type ResolvedKeybindingsConfig,
} from "@t3tools/contracts";
import { isMacPlatform } from "./lib/utils";
export interface ShortcutEventLike {
type?: string;
key: string;
metaKey: boolean;
ctrlKey: boolean;
shiftKey: boolean;
altKey: boolean;
}
export interface ShortcutMatchContext {
terminalFocus: boolean;
terminalOpen: boolean;
[key: string]: boolean;
}
interface ShortcutMatchOptions {
platform?: string;
context?: Partial<ShortcutMatchContext>;
}
function shortcutSignature(shortcut: KeybindingShortcut): string {
return [
shortcut.key,
shortcut.metaKey ? "1" : "0",
shortcut.ctrlKey ? "1" : "0",
shortcut.shiftKey ? "1" : "0",
shortcut.altKey ? "1" : "0",
shortcut.modKey ? "1" : "0",
].join("|");
}
const TERMINAL_WORD_BACKWARD = "\u001bb";
const TERMINAL_WORD_FORWARD = "\u001bf";
const TERMINAL_LINE_START = "\u0001";
const TERMINAL_LINE_END = "\u0005";
function normalizeEventKey(key: string): string {
const normalized = key.toLowerCase();
if (normalized === "esc") return "escape";
return normalized;
}
function matchesShortcut(
event: ShortcutEventLike,
shortcut: KeybindingShortcut,
platform = navigator.platform,
): boolean {
const key = normalizeEventKey(event.key);
if (key !== shortcut.key) return false;
const useMetaForMod = isMacPlatform(platform);
const expectedMeta = shortcut.metaKey || (shortcut.modKey && useMetaForMod);
const expectedCtrl = shortcut.ctrlKey || (shortcut.modKey && !useMetaForMod);
return (
event.metaKey === expectedMeta &&
event.ctrlKey === expectedCtrl &&
event.shiftKey === shortcut.shiftKey &&
event.altKey === shortcut.altKey
);
}
function resolvePlatform(options: ShortcutMatchOptions | undefined): string {
return options?.platform ?? navigator.platform;
}
function resolveContext(options: ShortcutMatchOptions | undefined): ShortcutMatchContext {
return {
terminalFocus: false,
terminalOpen: false,
...options?.context,
};
}
function evaluateWhenNode(node: KeybindingWhenNode, context: ShortcutMatchContext): boolean {
switch (node.type) {
case "identifier":
if (node.name === "true") return true;
if (node.name === "false") return false;
return Boolean(context[node.name]);
case "not":
return !evaluateWhenNode(node.node, context);
case "and":
return evaluateWhenNode(node.left, context) && evaluateWhenNode(node.right, context);
case "or":
return evaluateWhenNode(node.left, context) || evaluateWhenNode(node.right, context);
}
}
function matchesWhenClause(
whenAst: KeybindingWhenNode | undefined,
context: ShortcutMatchContext,
): boolean {
if (!whenAst) return true;
return evaluateWhenNode(whenAst, context);
}
function matchesCommandShortcut(
event: ShortcutEventLike,
keybindings: ResolvedKeybindingsConfig,
command: KeybindingCommand,
options?: ShortcutMatchOptions,
): boolean {
return resolveShortcutCommand(event, keybindings, options) === command;
}
function resolveShortcutCommand(
event: ShortcutEventLike,
keybindings: ResolvedKeybindingsConfig,
options?: ShortcutMatchOptions,
): KeybindingCommand | null {
const platform = resolvePlatform(options);
const context = resolveContext(options);
for (let index = keybindings.length - 1; index >= 0; index -= 1) {
const binding = keybindings[index];
if (!binding) continue;
if (!matchesWhenClause(binding.whenAst, context)) continue;
if (!matchesShortcut(event, binding.shortcut, platform)) continue;
return binding.command;
}
return null;
}
function formatShortcutKeyLabel(key: string): string {
if (key === " ") return "Space";
if (key.length === 1) return key.toUpperCase();
if (key === "escape") return "Esc";
if (key === "arrowup") return "Up";
if (key === "arrowdown") return "Down";
if (key === "arrowleft") return "Left";
if (key === "arrowright") return "Right";
return key.slice(0, 1).toUpperCase() + key.slice(1);
}
export function formatShortcutLabel(
shortcut: KeybindingShortcut,
platform = navigator.platform,
): string {
const keyLabel = formatShortcutKeyLabel(shortcut.key);
const useMetaForMod = isMacPlatform(platform);
const showMeta = shortcut.metaKey || (shortcut.modKey && useMetaForMod);
const showCtrl = shortcut.ctrlKey || (shortcut.modKey && !useMetaForMod);
const showAlt = shortcut.altKey;
const showShift = shortcut.shiftKey;
if (useMetaForMod) {
return `${showCtrl ? "\u2303" : ""}${showAlt ? "\u2325" : ""}${showShift ? "\u21e7" : ""}${showMeta ? "\u2318" : ""}${keyLabel}`;
}
const parts: string[] = [];
if (showCtrl) parts.push("Ctrl");
if (showAlt) parts.push("Alt");
if (showShift) parts.push("Shift");
if (showMeta) parts.push("Meta");
parts.push(keyLabel);
return parts.join("+");
}
export function shortcutLabelForCommand(
keybindings: ResolvedKeybindingsConfig,
command: KeybindingCommand,
platform = navigator.platform,
): string | null {
for (let index = keybindings.length - 1; index >= 0; index -= 1) {
const binding = keybindings[index];
if (!binding || binding.command !== command) continue;
return formatShortcutLabel(binding.shortcut, platform);
}
return null;
}
export function shortcutToRawHotkey(shortcut: KeybindingShortcut): RawHotkey {
return {
key: normalizeKeyName(shortcut.key),
...(shortcut.modKey ? { mod: true } : {}),
...(shortcut.metaKey ? { meta: true } : {}),
...(shortcut.ctrlKey ? { ctrl: true } : {}),
...(shortcut.shiftKey ? { shift: true } : {}),
...(shortcut.altKey ? { alt: true } : {}),
};
}
export function shortcutsForCommands(
keybindings: ResolvedKeybindingsConfig,
commands: readonly KeybindingCommand[],
): RawHotkey[] {
if (commands.length === 0 || keybindings.length === 0) {
return [];
}
const commandSet = new Set(commands);
const seen = new Set<string>();
const hotkeys: RawHotkey[] = [];
for (const binding of keybindings) {
if (!commandSet.has(binding.command)) continue;
const signature = shortcutSignature(binding.shortcut);
if (seen.has(signature)) continue;
seen.add(signature);
hotkeys.push(shortcutToRawHotkey(binding.shortcut));
}
return hotkeys;
}
export function isTerminalToggleShortcut(
event: ShortcutEventLike,
keybindings: ResolvedKeybindingsConfig,
options?: ShortcutMatchOptions,
): boolean {
return matchesCommandShortcut(event, keybindings, "terminal.toggle", options);
}
export function isTerminalSplitShortcut(
event: ShortcutEventLike,
keybindings: ResolvedKeybindingsConfig,
options?: ShortcutMatchOptions,
): boolean {
return matchesCommandShortcut(event, keybindings, "terminal.split", options);
}
export function isTerminalNewShortcut(
event: ShortcutEventLike,
keybindings: ResolvedKeybindingsConfig,
options?: ShortcutMatchOptions,
): boolean {
return matchesCommandShortcut(event, keybindings, "terminal.new", options);
}
export function isTerminalCloseShortcut(
event: ShortcutEventLike,
keybindings: ResolvedKeybindingsConfig,
options?: ShortcutMatchOptions,
): boolean {
return matchesCommandShortcut(event, keybindings, "terminal.close", options);
}
export function isChatNewShortcut(
event: ShortcutEventLike,
keybindings: ResolvedKeybindingsConfig,
options?: ShortcutMatchOptions,
): boolean {
return matchesCommandShortcut(event, keybindings, "chat.new", options);
}
export function isChatNewLocalShortcut(
event: ShortcutEventLike,
keybindings: ResolvedKeybindingsConfig,
options?: ShortcutMatchOptions,
): boolean {
return matchesCommandShortcut(event, keybindings, "chat.newLocal", options);
}
export function isOpenFavoriteEditorShortcut(
event: ShortcutEventLike,
keybindings: ResolvedKeybindingsConfig,
options?: ShortcutMatchOptions,
): boolean {
return matchesCommandShortcut(event, keybindings, "editor.openFavorite", options);
}
export function isTerminalClearShortcut(
event: ShortcutEventLike,
platform = navigator.platform,
): boolean {
if (event.type !== undefined && event.type !== "keydown") {
return false;
}
const key = event.key.toLowerCase();
if (key === "l" && event.ctrlKey && !event.metaKey && !event.altKey && !event.shiftKey) {
return true;
}
return (
isMacPlatform(platform) &&
key === "k" &&
event.metaKey &&
!event.ctrlKey &&
!event.altKey &&
!event.shiftKey
);
}
export function terminalNavigationShortcutData(
event: ShortcutEventLike,
platform = navigator.platform,
): string | null {
if (event.type !== undefined && event.type !== "keydown") {
return null;
}
if (event.shiftKey) return null;
const key = normalizeEventKey(event.key);
if (key !== "arrowleft" && key !== "arrowright") {
return null;
}
const moveWord = key === "arrowleft" ? TERMINAL_WORD_BACKWARD : TERMINAL_WORD_FORWARD;
const moveLine = key === "arrowleft" ? TERMINAL_LINE_START : TERMINAL_LINE_END;
if (isMacPlatform(platform)) {
if (event.altKey && !event.metaKey && !event.ctrlKey) {
return moveWord;
}
if (event.metaKey && !event.altKey && !event.ctrlKey) {
return moveLine;
}
return null;
}
if (event.ctrlKey && !event.metaKey && !event.altKey) {
return moveWord;
}
if (event.altKey && !event.metaKey && !event.ctrlKey) {
return moveWord;
}
return null;
}