import { useMemo, useState } from 'react'; import type { CommandInfo } from '../../adapters/types'; import { useI18n } from '../../i18n'; import { useFilterInput } from '../../hooks/useFilterInput'; import styles from './HelpDialog.module.css'; type HelpTab = 'general' | 'commands' | 'custom-commands'; interface HelpDialogProps { commands: readonly CommandInfo[]; } const TABS: Array<{ id: HelpTab; labelKey: string }> = [ { id: 'general', labelKey: 'help.tab.general' }, { id: 'commands', labelKey: 'help.tab.commands' }, { id: 'custom-commands', labelKey: 'help.tab.custom' }, ]; const BUILT_IN_COMMANDS = new Set([ 'about', 'agents', 'approval-mode', 'arena', 'auth', 'branch', 'btw', 'bug', 'clear', 'compress', 'context', 'copy', 'release', 'diff', 'directory', 'docs', 'doctor', 'dream', 'editor', 'export', 'extensions', 'forget', 'goal', 'help', 'hooks', 'ide', 'init', 'insight', 'language', 'lsp', 'mcp', 'memory', 'model', 'new', 'permissions', 'plan', 'quit', 'recap', 'remember', 'rename', 'reset', 'restore', 'resume', 'rewind', 'settings', 'setup-github', 'skills', 'stats', 'status', 'statusline', 'summary', 'tasks', 'terminal-setup', 'theme', 'tools', 'trust', 'vim', ]); const GENERAL_SHORTCUTS: Array<[string, string]> = [ ['@', 'help.shortcut.addContext'], ['!', 'help.shortcut.shell'], ['/', 'help.shortcut.commandMenu'], ['Tab', 'help.shortcut.completion'], ['Esc', 'help.shortcut.cancel'], ['Ctrl+J', 'help.shortcut.newline'], ['Ctrl+L', 'help.shortcut.clear'], ['Ctrl+Y', 'help.shortcut.retry'], ['Shift+Tab', 'help.shortcut.approvals'], ['Alt+Left/Right', 'help.shortcut.altWords'], ['Up/Down', 'help.shortcut.history'], ]; function commandSignature(command: CommandInfo): string { return [`/${command.name}`, command.argumentHint].filter(Boolean).join(' '); } function isCustomCommand(command: CommandInfo): boolean { return !BUILT_IN_COMMANDS.has(command.name); } function commandMeta( command: CommandInfo, t: ReturnType['t'], ): string { return isCustomCommand(command) ? t('help.commandMeta.custom') : t('help.commandMeta.builtIn'); } function filterCommands( commands: readonly CommandInfo[], tab: HelpTab, query: string, ): CommandInfo[] { const normalized = query.trim().toLowerCase(); return commands .filter((command) => command.name && command.description !== undefined) .filter((command) => { if (tab === 'commands') return !isCustomCommand(command); if (tab === 'custom-commands') return isCustomCommand(command); return true; }) .filter((command) => { if (!normalized) return true; return ( command.name.toLowerCase().includes(normalized) || (command.description ?? '').toLowerCase().includes(normalized) || (command.argumentHint ?? '').toLowerCase().includes(normalized) ); }) .sort((a, b) => a.name.localeCompare(b.name)); } function GeneralHelp() { const { t } = useI18n(); return (
{GENERAL_SHORTCUTS.map(([key, description]) => (
{t(description)} {key}
))}
); } function CommandsHelp({ commands, tab, query, }: { commands: readonly CommandInfo[]; tab: HelpTab; query: string; }) { const { t } = useI18n(); const [expandedCommand, setExpandedCommand] = useState(null); const visibleCommands = useMemo( () => filterCommands(commands, tab, query), [commands, query, tab], ); if (visibleCommands.length === 0) { return (
{tab === 'custom-commands' ? t('help.emptyCustom') : t('help.empty')}
); } return (
{visibleCommands.map((command) => { const expanded = expandedCommand === command.name; return (
{expanded && (
{command.description && (
{command.description}
)} {!!command.subcommands?.length && (
{t('help.subcommands')}: {command.subcommands.join(', ')}
)}
)}
); })}
); } export function HelpDialog({ commands }: HelpDialogProps) { const { t } = useI18n(); const [activeTab, setActiveTab] = useState('general'); const { filterValue: query, inputProps } = useFilterInput(); const showSearch = activeTab !== 'general'; return (
{TABS.map((tab) => ( ))}
{showSearch && ( )}
{activeTab === 'general' ? ( ) : ( )}
); }