feat(ui): dynamically generate all keybinding hints (#21346)

This commit is contained in:
Tommaso Sciortino
2026-03-06 18:34:26 +00:00
committed by GitHub
parent 4669148a4c
commit 6d607a5953
24 changed files with 424 additions and 293 deletions
@@ -11,6 +11,8 @@ import { useMemo } from 'react';
import type { HistoryItemToolGroup } from '../../types.js';
import { Checklist } from '../Checklist.js';
import type { ChecklistItemData } from '../ChecklistItem.js';
import { formatCommand } from '../../utils/keybindingUtils.js';
import { Command } from '../../../config/keyBindings.js';
export const TodoTray: React.FC = () => {
const uiState = useUIState();
@@ -55,7 +57,7 @@ export const TodoTray: React.FC = () => {
title="Todo"
items={checklistItems}
isExpanded={uiState.showFullTodos}
toggleHint="ctrl+t to toggle"
toggleHint={`${formatCommand(Command.SHOW_FULL_TODOS)} to toggle`}
/>
);
};
@@ -31,12 +31,6 @@ import { theme } from '../../semantic-colors.js';
import { useSettings } from '../../contexts/SettingsContext.js';
import { keyMatchers, Command } from '../../keyMatchers.js';
import { formatCommand } from '../../utils/keybindingUtils.js';
import {
REDIRECTION_WARNING_NOTE_LABEL,
REDIRECTION_WARNING_NOTE_TEXT,
REDIRECTION_WARNING_TIP_LABEL,
REDIRECTION_WARNING_TIP_TEXT,
} from '../../textConstants.js';
import { AskUserDialog } from '../AskUserDialog.js';
import { ExitPlanModeDialog } from '../ExitPlanModeDialog.js';
import { WarningMessage } from './WarningMessage.js';
@@ -57,6 +51,11 @@ export interface ToolConfirmationMessageProps {
terminalWidth: number;
}
const REDIRECTION_WARNING_NOTE_LABEL = 'Note: ';
const REDIRECTION_WARNING_NOTE_TEXT =
'Command contains redirection which can be undesirable.';
const REDIRECTION_WARNING_TIP_LABEL = 'Tip: '; // Padded to align with "Note: "
export const ToolConfirmationMessage: React.FC<
ToolConfirmationMessageProps
> = ({
@@ -503,12 +502,12 @@ export const ToolConfirmationMessage: React.FC<
if (containsRedirection) {
// Calculate lines needed for Note and Tip
const safeWidth = Math.max(terminalWidth, 1);
const tipText = `Toggle auto-edit (${formatCommand(Command.CYCLE_APPROVAL_MODE)}) to allow redirection in the future.`;
const noteLength =
REDIRECTION_WARNING_NOTE_LABEL.length +
REDIRECTION_WARNING_NOTE_TEXT.length;
const tipLength =
REDIRECTION_WARNING_TIP_LABEL.length +
REDIRECTION_WARNING_TIP_TEXT.length;
const tipLength = REDIRECTION_WARNING_TIP_LABEL.length + tipText.length;
const noteLines = Math.ceil(noteLength / safeWidth);
const tipLines = Math.ceil(tipLength / safeWidth);
@@ -534,7 +533,7 @@ export const ToolConfirmationMessage: React.FC<
<Box>
<Text color={theme.border.default}>
<Text bold>{REDIRECTION_WARNING_TIP_LABEL}</Text>
{REDIRECTION_WARNING_TIP_TEXT}
{tipText}
</Text>
</Box>
</>
@@ -2,7 +2,7 @@
exports[`<TodoTray /> (showFullTodos: false) > renders a todo list with long descriptions that wrap when full view is on 1`] = `
"──────────────────────────────────────────────────
Todo 1/2 completed (ctrl+t to toggle) » This i…
Todo 1/2 completed (Ctrl+T to toggle) » This i…
"
`;
@@ -14,25 +14,25 @@ exports[`<TodoTray /> (showFullTodos: false) > renders null when todo list is em
exports[`<TodoTray /> (showFullTodos: false) > renders the most recent todo list when multiple write_todos calls are in history 1`] = `
"────────────────────────────────────────────────────────────────────────────────────────────────────
Todo 0/2 completed (ctrl+t to toggle) » Newer Task 2
Todo 0/2 completed (Ctrl+T to toggle) » Newer Task 2
"
`;
exports[`<TodoTray /> (showFullTodos: false) > renders when todos exist and one is in progress 1`] = `
"────────────────────────────────────────────────────────────────────────────────────────────────────
Todo 1/3 completed (ctrl+t to toggle) » Task 2
Todo 1/3 completed (Ctrl+T to toggle) » Task 2
"
`;
exports[`<TodoTray /> (showFullTodos: false) > renders when todos exist but none are in progress 1`] = `
"────────────────────────────────────────────────────────────────────────────────────────────────────
Todo 1/2 completed (ctrl+t to toggle)
Todo 1/2 completed (Ctrl+T to toggle)
"
`;
exports[`<TodoTray /> (showFullTodos: true) > renders a todo list with long descriptions that wrap when full view is on 1`] = `
"──────────────────────────────────────────────────
Todo 1/2 completed (ctrl+t to toggle)
Todo 1/2 completed (Ctrl+T to toggle)
» This is a very long description for a pending
task that should wrap around multiple lines
@@ -44,7 +44,7 @@ exports[`<TodoTray /> (showFullTodos: true) > renders a todo list with long desc
exports[`<TodoTray /> (showFullTodos: true) > renders full list when all todos are inactive 1`] = `
"────────────────────────────────────────────────────────────────────────────────────────────────────
Todo 1/1 completed (ctrl+t to toggle)
Todo 1/1 completed (Ctrl+T to toggle)
✓ Task 1
✗ Task 2
@@ -57,7 +57,7 @@ exports[`<TodoTray /> (showFullTodos: true) > renders null when todo list is emp
exports[`<TodoTray /> (showFullTodos: true) > renders the most recent todo list when multiple write_todos calls are in history 1`] = `
"────────────────────────────────────────────────────────────────────────────────────────────────────
Todo 0/2 completed (ctrl+t to toggle)
Todo 0/2 completed (Ctrl+T to toggle)
☐ Newer Task 1
» Newer Task 2
@@ -66,7 +66,7 @@ exports[`<TodoTray /> (showFullTodos: true) > renders the most recent todo list
exports[`<TodoTray /> (showFullTodos: true) > renders when todos exist and one is in progress 1`] = `
"────────────────────────────────────────────────────────────────────────────────────────────────────
Todo 1/3 completed (ctrl+t to toggle)
Todo 1/3 completed (Ctrl+T to toggle)
☐ Pending Task
» Task 2
@@ -77,7 +77,7 @@ exports[`<TodoTray /> (showFullTodos: true) > renders when todos exist and one i
exports[`<TodoTray /> (showFullTodos: true) > renders when todos exist but none are in progress 1`] = `
"────────────────────────────────────────────────────────────────────────────────────────────────────
Todo 1/2 completed (ctrl+t to toggle)
Todo 1/2 completed (Ctrl+T to toggle)
☐ Pending Task
✗ In Progress Task