mirror of
https://github.com/openai/codex.git
synced 2026-10-21 05:22:21 +00:00
Prefer Shift-arrow hints for queued messages and questions (#47618)
## Why Queued-message edit hints could vary by terminal instead of showing the first configured shortcut. Use the keymap consistently for queued messages and async question navigation. ## What changed - Make `Shift+Left` the primary shortcut for advancing through questions and editing the last queued message, and `Shift+Right` for navigating back toward the composer. Keep `Alt+Up` and `Alt+Down` as alternatives. - Remove terminal-specific hint selection and derive hints from the configured keymap, including custom chords and unbound actions. - Update question footers, queued-input previews, and keymap picker snapshots to reflect the preferred shortcuts. ## Testing Extend tests to cover both default shortcut pairs, configured shortcut ordering, custom chord hints, and hiding the queued-message edit hint when the action is unbound. GitOrigin-RevId: e0f60860ef1e81c71ca0fc32369b8cbd12582b47
This commit is contained in:
+1
-1
@@ -4,7 +4,7 @@ expression: "normalize_snapshot_paths(render_bottom_popup(&app.chat_widget, 80))
|
||||
---
|
||||
• Queued follow-up inputs
|
||||
↳ keep queued
|
||||
⌥+↑ edit last queued message
|
||||
shift+← edit last queued message
|
||||
|
||||
› keep draft
|
||||
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@ expression: "format!(\"{}\\n{}\",\nlines_to_single_string(&app.transcript_cells.
|
||||
|
||||
• Queued follow-up inputs
|
||||
↳ queued before disconnect
|
||||
⌥+↑ edit last queued message
|
||||
shift+← edit last queued message
|
||||
|
||||
› keep this draft!
|
||||
|
||||
|
||||
@@ -67,7 +67,6 @@ pub(crate) struct AsyncQuestions {
|
||||
pub(crate) delivery_enabled: bool,
|
||||
pub(crate) submission: Option<QuestionSubmission>,
|
||||
visible_options: std::cell::Cell<(usize, usize)>,
|
||||
pub(crate) next_hint: Option<crate::key_hint::ShortcutHint>,
|
||||
keymap: RuntimeKeymap,
|
||||
// Ignore autorepeat from the number key that opened Other.
|
||||
other_selector: Option<KeyCode>,
|
||||
@@ -103,7 +102,6 @@ impl AsyncQuestions {
|
||||
delivery_enabled: true,
|
||||
submission: None,
|
||||
visible_options: std::cell::Cell::new((0, 0)),
|
||||
next_hint: None,
|
||||
keymap,
|
||||
other_selector: None,
|
||||
composer,
|
||||
|
||||
@@ -209,7 +209,7 @@ impl AsyncQuestions {
|
||||
None
|
||||
};
|
||||
if let Some(label) = next
|
||||
&& let Some(key) = self.next_hint
|
||||
&& let Some(key) = chat_hint("edit_queued_message")
|
||||
{
|
||||
tips.push(crate::footer_hint::shortcut(&key.display_label(), label));
|
||||
}
|
||||
|
||||
+2
-1
@@ -16,4 +16,5 @@ expression: buffer_text(&buffer)
|
||||
long draft
|
||||
|
||||
|
||||
enter submit ctrl+] skip ⌥+↓ main prompt
|
||||
enter submit ctrl+] skip
|
||||
shift+→ main prompt
|
||||
|
||||
+1
-1
@@ -12,4 +12,4 @@ expression: buffer_text(&buffer)
|
||||
reverse-i-search: e enter accept · esc
|
||||
|
||||
enter submit ctrl+] skip option 13/13
|
||||
⌥+↓ prev question
|
||||
shift+→ prev question
|
||||
|
||||
+2
-1
@@ -9,4 +9,5 @@ expression: buffer_text(&buffer)
|
||||
› 1. Other
|
||||
2. Other (write an answer)
|
||||
|
||||
enter submit ctrl+] skip ⌥+↓ prev question
|
||||
enter submit ctrl+] skip
|
||||
shift+→ prev question
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@ Full height:
|
||||
|
||||
enter submit
|
||||
ctrl+] skip
|
||||
⌥+↓ main prompt
|
||||
shift+→ main prompt
|
||||
|
||||
|
||||
Clipped:
|
||||
|
||||
+1
-1
@@ -12,4 +12,4 @@ expression: buffer_text(&buffer)
|
||||
rows
|
||||
|
||||
f9 submit ctrl+] skip option 1/13
|
||||
⌥+↓ prev question
|
||||
shift+→ prev question
|
||||
|
||||
+1
-1
@@ -11,4 +11,4 @@ expression: buffer_text(&buffer)
|
||||
2. Other
|
||||
|
||||
enter submit ctrl+] skip
|
||||
⌥+↓ main prompt
|
||||
shift+→ main prompt
|
||||
|
||||
+1
-1
@@ -13,4 +13,4 @@ expression: buffer_text(&buffer)
|
||||
rows
|
||||
|
||||
enter submit ctrl+] skip
|
||||
⌥+↓ main prompt
|
||||
shift+→ main prompt
|
||||
|
||||
+1
-1
@@ -10,4 +10,4 @@ expression: buffer_text(&clipped)
|
||||
multiple rows
|
||||
|
||||
enter submit ctrl+] skip
|
||||
option 2/3 ⌥+↓ main prompt
|
||||
option 2/3 shift+→ main prompt
|
||||
|
||||
@@ -17,7 +17,6 @@ fn editor() -> AsyncQuestions {
|
||||
/*disable_paste_burst*/ true,
|
||||
RuntimeKeymap::defaults(),
|
||||
);
|
||||
editor.next_hint = Some(crate::key_hint::alt(KeyCode::Up).into());
|
||||
editor.append(
|
||||
"message",
|
||||
&[
|
||||
@@ -446,7 +445,7 @@ fn existing_cross_context_keymaps_load_without_misleading_submit_hints() {
|
||||
let mut editor = editor();
|
||||
editor.navigate(/*forward*/ true);
|
||||
editor.set_keymap(&keymap);
|
||||
insta::allow_duplicates! { insta::assert_snapshot!(editor.footer_lines(/*width*/ 100, /*option_tip*/ None)[0].to_string(), @"ctrl+] skip ⌥+↓ prev question"); }
|
||||
insta::allow_duplicates! { insta::assert_snapshot!(editor.footer_lines(/*width*/ 100, /*option_tip*/ None)[0].to_string(), @"ctrl+] skip shift+→ prev question"); }
|
||||
editor.handle_key_event(KeyEvent::from(KeyCode::F(12)));
|
||||
assert!(editor.submission.is_none());
|
||||
}
|
||||
|
||||
@@ -510,6 +510,9 @@ impl BottomPane {
|
||||
if let Some(questions) = &mut self.questions {
|
||||
questions.set_keymap(keymap);
|
||||
}
|
||||
// Show the first shortcut from the same keymap ChatWidget uses to handle queued edits.
|
||||
self.pending_input_preview
|
||||
.set_edit_binding(keymap.primary_hint(KeymapContext::Chat, "edit_queued_message"));
|
||||
let interrupt_binding = keymap.primary_hint(KeymapContext::Chat, "interrupt_turn");
|
||||
self.pending_input_preview
|
||||
.set_interrupt_binding(interrupt_binding);
|
||||
@@ -605,19 +608,6 @@ impl BottomPane {
|
||||
self.request_redraw();
|
||||
}
|
||||
|
||||
/// Update the key hint shown next to queued messages so it matches the
|
||||
/// binding that `ChatWidget` actually listens for.
|
||||
pub(crate) fn set_queued_message_edit_binding(
|
||||
&mut self,
|
||||
binding: Option<crate::key_hint::ShortcutHint>,
|
||||
) {
|
||||
self.pending_input_preview.set_edit_binding(binding);
|
||||
if let Some(questions) = &mut self.questions {
|
||||
questions.next_hint = binding;
|
||||
}
|
||||
self.request_redraw();
|
||||
}
|
||||
|
||||
pub(crate) fn set_vim_enabled(&mut self, enabled: bool) {
|
||||
self.composer.set_vim_enabled(enabled);
|
||||
if let Some(questions) = &mut self.questions {
|
||||
|
||||
@@ -17,15 +17,13 @@ use crate::wrapping::adaptive_wrap_lines;
|
||||
/// steers explain that they will be submitted after the next tool/result
|
||||
/// boundary unless the user invokes the interrupt binding to send them
|
||||
/// immediately. The edit hint at the bottom only appears when there are actual
|
||||
/// queued user inputs to pop back into the composer. Because some terminals
|
||||
/// intercept certain modifier-key combinations, the displayed binding is
|
||||
/// queued user inputs to pop back into the composer. The displayed binding is
|
||||
/// configurable via [`set_edit_binding`](Self::set_edit_binding).
|
||||
pub(crate) struct PendingInputPreview {
|
||||
pub pending_steers: Vec<String>,
|
||||
pub rejected_steers: Vec<String>,
|
||||
pub queued_messages: Vec<String>,
|
||||
/// Key combination rendered in the hint line. Defaults to Alt+Up but may
|
||||
/// be overridden for terminals where that chord is unavailable.
|
||||
/// Key combination rendered in the hint line. Defaults to Shift+Left.
|
||||
pub(super) edit_binding: Option<key_hint::ShortcutHint>,
|
||||
/// Key combination rendered for immediately interrupting and sending steers.
|
||||
interrupt_binding: Option<key_hint::ShortcutHint>,
|
||||
@@ -44,7 +42,7 @@ impl PendingInputPreview {
|
||||
pending_steers: Vec::new(),
|
||||
rejected_steers: Vec::new(),
|
||||
queued_messages: Vec::new(),
|
||||
edit_binding: Some(key_hint::alt(KeyCode::Up).into()),
|
||||
edit_binding: Some(key_hint::shift(KeyCode::Left).into()),
|
||||
interrupt_binding: Some(key_hint::plain(KeyCode::Esc).into()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,6 @@ impl BottomPane {
|
||||
self.keymap.clone(),
|
||||
);
|
||||
questions.set_vim_enabled(self.composer.is_vim_enabled());
|
||||
questions.next_hint = self.pending_input_preview.edit_binding;
|
||||
Box::new(questions)
|
||||
})
|
||||
}
|
||||
|
||||
+3
-3
@@ -10,7 +10,7 @@ Buffer {
|
||||
" a message ",
|
||||
" with many ",
|
||||
" … ",
|
||||
" ⌥+↑ edit last queued message ",
|
||||
" shift+← edit last queued message ",
|
||||
],
|
||||
styles: [
|
||||
x: 0, y: 0, fg: Reset, bg: Reset, underline: Reset, modifier: DIM,
|
||||
@@ -25,7 +25,7 @@ Buffer {
|
||||
x: 0, y: 4, fg: Reset, bg: Reset, underline: Reset, modifier: DIM | ITALIC,
|
||||
x: 5, y: 4, fg: Reset, bg: Reset, underline: Reset, modifier: NONE,
|
||||
x: 4, y: 5, fg: Reset, bg: Reset, underline: Reset, modifier: BOLD,
|
||||
x: 7, y: 5, fg: Reset, bg: Reset, underline: Reset, modifier: DIM,
|
||||
x: 32, y: 5, fg: Reset, bg: Reset, underline: Reset, modifier: NONE,
|
||||
x: 11, y: 5, fg: Reset, bg: Reset, underline: Reset, modifier: DIM,
|
||||
x: 36, y: 5, fg: Reset, bg: Reset, underline: Reset, modifier: NONE,
|
||||
]
|
||||
}
|
||||
|
||||
+3
-3
@@ -10,7 +10,7 @@ Buffer {
|
||||
" ↳ This is another message ",
|
||||
" ↳ This is a third message ",
|
||||
" ↳ This is a fourth message ",
|
||||
" ⌥+↑ edit last queued message ",
|
||||
" shift+← edit last queued message ",
|
||||
],
|
||||
styles: [
|
||||
x: 0, y: 0, fg: Reset, bg: Reset, underline: Reset, modifier: DIM,
|
||||
@@ -28,7 +28,7 @@ Buffer {
|
||||
x: 4, y: 4, fg: Reset, bg: Reset, underline: Reset, modifier: DIM | ITALIC,
|
||||
x: 28, y: 4, fg: Reset, bg: Reset, underline: Reset, modifier: NONE,
|
||||
x: 4, y: 5, fg: Reset, bg: Reset, underline: Reset, modifier: BOLD,
|
||||
x: 7, y: 5, fg: Reset, bg: Reset, underline: Reset, modifier: DIM,
|
||||
x: 32, y: 5, fg: Reset, bg: Reset, underline: Reset, modifier: NONE,
|
||||
x: 11, y: 5, fg: Reset, bg: Reset, underline: Reset, modifier: DIM,
|
||||
x: 36, y: 5, fg: Reset, bg: Reset, underline: Reset, modifier: NONE,
|
||||
]
|
||||
}
|
||||
|
||||
+3
-3
@@ -7,7 +7,7 @@ Buffer {
|
||||
content: [
|
||||
"• Queued follow-up inputs ",
|
||||
" ↳ Hello, world! ",
|
||||
" ⌥+↑ edit last queued message ",
|
||||
" shift+← edit last queued message ",
|
||||
],
|
||||
styles: [
|
||||
x: 0, y: 0, fg: Reset, bg: Reset, underline: Reset, modifier: DIM,
|
||||
@@ -16,7 +16,7 @@ Buffer {
|
||||
x: 4, y: 1, fg: Reset, bg: Reset, underline: Reset, modifier: DIM | ITALIC,
|
||||
x: 17, y: 1, fg: Reset, bg: Reset, underline: Reset, modifier: NONE,
|
||||
x: 4, y: 2, fg: Reset, bg: Reset, underline: Reset, modifier: BOLD,
|
||||
x: 7, y: 2, fg: Reset, bg: Reset, underline: Reset, modifier: DIM,
|
||||
x: 32, y: 2, fg: Reset, bg: Reset, underline: Reset, modifier: NONE,
|
||||
x: 11, y: 2, fg: Reset, bg: Reset, underline: Reset, modifier: DIM,
|
||||
x: 36, y: 2, fg: Reset, bg: Reset, underline: Reset, modifier: NONE,
|
||||
]
|
||||
}
|
||||
|
||||
+3
-3
@@ -15,7 +15,7 @@ Buffer {
|
||||
" ",
|
||||
"• Queued follow-up inputs ",
|
||||
" ↳ Queued follow-up question ",
|
||||
" ⌥+↑ edit last queued message ",
|
||||
" shift+← edit last queued message ",
|
||||
],
|
||||
styles: [
|
||||
x: 0, y: 0, fg: Reset, bg: Reset, underline: Reset, modifier: DIM,
|
||||
@@ -38,7 +38,7 @@ Buffer {
|
||||
x: 4, y: 9, fg: Reset, bg: Reset, underline: Reset, modifier: DIM | ITALIC,
|
||||
x: 29, y: 9, fg: Reset, bg: Reset, underline: Reset, modifier: NONE,
|
||||
x: 4, y: 10, fg: Reset, bg: Reset, underline: Reset, modifier: BOLD,
|
||||
x: 7, y: 10, fg: Reset, bg: Reset, underline: Reset, modifier: DIM,
|
||||
x: 32, y: 10, fg: Reset, bg: Reset, underline: Reset, modifier: NONE,
|
||||
x: 11, y: 10, fg: Reset, bg: Reset, underline: Reset, modifier: DIM,
|
||||
x: 36, y: 10, fg: Reset, bg: Reset, underline: Reset, modifier: NONE,
|
||||
]
|
||||
}
|
||||
|
||||
+3
-3
@@ -8,7 +8,7 @@ Buffer {
|
||||
"• Queued follow-up inputs ",
|
||||
" ↳ Hello, world! ",
|
||||
" ↳ This is another message ",
|
||||
" ⌥+↑ edit last queued message ",
|
||||
" shift+← edit last queued message ",
|
||||
],
|
||||
styles: [
|
||||
x: 0, y: 0, fg: Reset, bg: Reset, underline: Reset, modifier: DIM,
|
||||
@@ -20,7 +20,7 @@ Buffer {
|
||||
x: 4, y: 2, fg: Reset, bg: Reset, underline: Reset, modifier: DIM | ITALIC,
|
||||
x: 27, y: 2, fg: Reset, bg: Reset, underline: Reset, modifier: NONE,
|
||||
x: 4, y: 3, fg: Reset, bg: Reset, underline: Reset, modifier: BOLD,
|
||||
x: 7, y: 3, fg: Reset, bg: Reset, underline: Reset, modifier: DIM,
|
||||
x: 32, y: 3, fg: Reset, bg: Reset, underline: Reset, modifier: NONE,
|
||||
x: 11, y: 3, fg: Reset, bg: Reset, underline: Reset, modifier: DIM,
|
||||
x: 36, y: 3, fg: Reset, bg: Reset, underline: Reset, modifier: NONE,
|
||||
]
|
||||
}
|
||||
|
||||
+3
-3
@@ -9,7 +9,7 @@ Buffer {
|
||||
" ↳ This is a longer message that should",
|
||||
" be wrapped ",
|
||||
" ↳ This is another message ",
|
||||
" ⌥+↑ edit last queued message ",
|
||||
" shift+← edit last queued message ",
|
||||
],
|
||||
styles: [
|
||||
x: 0, y: 0, fg: Reset, bg: Reset, underline: Reset, modifier: DIM,
|
||||
@@ -23,7 +23,7 @@ Buffer {
|
||||
x: 4, y: 3, fg: Reset, bg: Reset, underline: Reset, modifier: DIM | ITALIC,
|
||||
x: 27, y: 3, fg: Reset, bg: Reset, underline: Reset, modifier: NONE,
|
||||
x: 4, y: 4, fg: Reset, bg: Reset, underline: Reset, modifier: BOLD,
|
||||
x: 7, y: 4, fg: Reset, bg: Reset, underline: Reset, modifier: DIM,
|
||||
x: 32, y: 4, fg: Reset, bg: Reset, underline: Reset, modifier: NONE,
|
||||
x: 11, y: 4, fg: Reset, bg: Reset, underline: Reset, modifier: DIM,
|
||||
x: 36, y: 4, fg: Reset, bg: Reset, underline: Reset, modifier: NONE,
|
||||
]
|
||||
}
|
||||
|
||||
+1
-1
@@ -3,4 +3,4 @@ source: tui/src/bottom_pane/questions_tests.rs
|
||||
expression: "pane.question_summary(now).unwrap().iter().map(ToString::to_string).collect::<Vec<_>>().join(\"\\n\")"
|
||||
---
|
||||
? 2 questions · 15s
|
||||
⌥+↑ to answer
|
||||
shift+← to answer
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ expression: "render_snapshot(&pane, area)"
|
||||
---
|
||||
• Queued follow-up inputs
|
||||
↳ Queued follow-up question
|
||||
⌥+↑ edit last queued message
|
||||
shift+← edit last queued message
|
||||
|
||||
› Ask Codex to do anything
|
||||
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@ expression: "render_snapshot(&pane, area)"
|
||||
|
||||
• Queued follow-up inputs
|
||||
↳ Queued follow-up question
|
||||
⌥+↑ edit last queued message
|
||||
shift+← edit last queued message
|
||||
|
||||
› Ask Codex to do anything
|
||||
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@ expression: "render_snapshot(&pane, area)"
|
||||
|
||||
• Queued follow-up inputs
|
||||
↳ Queued follow-up question
|
||||
⌥+↑ edit last queued message
|
||||
shift+← edit last queued message
|
||||
|
||||
› Ask Codex to do anything
|
||||
|
||||
|
||||
@@ -150,10 +150,6 @@ use codex_protocol::plan_tool::StepStatus as UpdatePlanItemStatus;
|
||||
use codex_protocol::request_permissions::RequestPermissionsEvent;
|
||||
use codex_protocol::user_input::ByteRange;
|
||||
use codex_protocol::user_input::TextElement;
|
||||
use codex_terminal_detection::Multiplexer;
|
||||
use codex_terminal_detection::TerminalInfo;
|
||||
use codex_terminal_detection::TerminalName;
|
||||
use codex_terminal_detection::terminal_info;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use codex_utils_path_uri::PathUri;
|
||||
use crossterm::event::KeyCode;
|
||||
@@ -188,62 +184,6 @@ const TUI_STUB_MESSAGE: &str = "Not available in TUI yet.";
|
||||
const PARENT_OWNED_INPUT_MESSAGE: &str =
|
||||
"This sub-agent is controlled by its parent. Direct input is disabled.";
|
||||
|
||||
/// Choose the keybinding used to edit the most-recently queued message.
|
||||
///
|
||||
/// Apple Terminal, Warp, and VSCode integrated terminals intercept or silently
|
||||
/// swallow Alt+Up, and tmux does not reliably pass that chord through. We fall
|
||||
/// back to Shift+Left for those environments while keeping the more discoverable
|
||||
/// Alt+Up everywhere else.
|
||||
///
|
||||
/// The match is exhaustive so that adding a new `TerminalName` variant forces
|
||||
/// an explicit decision about which binding that terminal should use.
|
||||
fn queued_message_edit_binding_for_terminal(terminal_info: TerminalInfo) -> KeyBinding {
|
||||
if matches!(
|
||||
terminal_info.multiplexer.as_ref(),
|
||||
Some(Multiplexer::Tmux { .. })
|
||||
) {
|
||||
return key_hint::shift(KeyCode::Left);
|
||||
}
|
||||
|
||||
match terminal_info.name {
|
||||
TerminalName::AppleTerminal | TerminalName::WarpTerminal | TerminalName::VsCode => {
|
||||
key_hint::shift(KeyCode::Left)
|
||||
}
|
||||
TerminalName::Ghostty
|
||||
| TerminalName::Iterm2
|
||||
| TerminalName::WezTerm
|
||||
| TerminalName::Kitty
|
||||
| TerminalName::Alacritty
|
||||
| TerminalName::Konsole
|
||||
| TerminalName::GnomeTerminal
|
||||
| TerminalName::Vte
|
||||
| TerminalName::WindowsTerminal
|
||||
| TerminalName::Dumb
|
||||
| TerminalName::Unknown => key_hint::alt(KeyCode::Up),
|
||||
}
|
||||
}
|
||||
|
||||
fn queued_message_edit_hint_binding(
|
||||
keymap: &RuntimeKeymap,
|
||||
terminal_info: TerminalInfo,
|
||||
) -> Option<crate::key_hint::ShortcutHint> {
|
||||
let configured = keymap.primary_hint(crate::keymap::KeymapContext::Chat, "edit_queued_message");
|
||||
if matches!(
|
||||
configured,
|
||||
Some(crate::key_hint::ShortcutHint::Chord { .. })
|
||||
) {
|
||||
return configured;
|
||||
}
|
||||
|
||||
let terminal_binding = queued_message_edit_binding_for_terminal(terminal_info);
|
||||
keymap
|
||||
.chat
|
||||
.edit_queued_message
|
||||
.contains(&terminal_binding)
|
||||
.then_some(crate::key_hint::ShortcutHint::Single(terminal_binding))
|
||||
.or(configured)
|
||||
}
|
||||
|
||||
fn normalize_thread_name(name: &str) -> Option<String> {
|
||||
let trimmed = name.trim();
|
||||
(!trimmed.is_empty()).then(|| trimmed.to_string())
|
||||
@@ -744,10 +684,6 @@ pub(crate) struct ChatWidget {
|
||||
/// Main chat-surface bindings resolved from `tui.keymap.chat`.
|
||||
chat_keymap: ChatKeymap,
|
||||
permission_shortcut_pending: bool,
|
||||
/// Keybinding to show for popping the most-recently queued message back
|
||||
/// into the composer. This may differ from the first configured binding
|
||||
/// when the default set includes a terminal-specific fallback.
|
||||
queued_message_edit_hint_binding: Option<crate::key_hint::ShortcutHint>,
|
||||
// Pending notification to show when unfocused on next Draw
|
||||
pending_notification: Option<Notification>,
|
||||
/// When `Some`, the user has pressed a quit shortcut and the second press
|
||||
|
||||
@@ -72,7 +72,6 @@ impl ChatWidget {
|
||||
&header_model,
|
||||
&model_catalog.try_list_models().unwrap_or_default(),
|
||||
);
|
||||
let current_terminal_info = terminal_info();
|
||||
let runtime_keymap = RuntimeKeymap::from_config(&local_settings.tui.keymap).ok();
|
||||
let default_keymap = RuntimeKeymap::defaults();
|
||||
let copy_last_response_binding = runtime_keymap
|
||||
@@ -83,10 +82,6 @@ impl ChatWidget {
|
||||
.as_ref()
|
||||
.map(|keymap| keymap.chat.clone())
|
||||
.unwrap_or_else(|| default_keymap.chat.clone());
|
||||
let queued_message_edit_hint_binding = queued_message_edit_hint_binding(
|
||||
runtime_keymap.as_ref().unwrap_or(&default_keymap),
|
||||
current_terminal_info,
|
||||
);
|
||||
let pet_http_client = codex_http_client::RouteAwareClientPool::new(
|
||||
config.http_client_factory(),
|
||||
codex_http_client::ClientRouteClass::Other,
|
||||
@@ -240,7 +235,6 @@ impl ChatWidget {
|
||||
safety_buffering_source: UserMessageSource::Prompt,
|
||||
chat_keymap,
|
||||
permission_shortcut_pending: false,
|
||||
queued_message_edit_hint_binding,
|
||||
show_welcome_banner: is_first_run,
|
||||
startup_tooltip_override,
|
||||
suppress_session_configured_redraw: false,
|
||||
@@ -309,9 +303,6 @@ impl ChatWidget {
|
||||
.bottom_pane
|
||||
.set_voice_command_enabled(/*enabled*/ false);
|
||||
widget.sync_mentions_v2_enabled();
|
||||
widget
|
||||
.bottom_pane
|
||||
.set_queued_message_edit_binding(widget.queued_message_edit_hint_binding);
|
||||
widget.update_collaboration_mode_indicator();
|
||||
|
||||
widget
|
||||
|
||||
@@ -12,10 +12,8 @@
|
||||
//! appear to accept a remap while some handlers still respond to the old keys.
|
||||
|
||||
use codex_config::types::TuiKeymap;
|
||||
use codex_terminal_detection::terminal_info;
|
||||
|
||||
use super::ChatWidget;
|
||||
use super::queued_message_edit_hint_binding;
|
||||
use crate::app_event::KeymapCaptureMode;
|
||||
use crate::app_event::KeymapEditIntent;
|
||||
use crate::keymap::RuntimeKeymap;
|
||||
@@ -173,10 +171,6 @@ impl ChatWidget {
|
||||
self.local_settings.tui.keymap = keymap_config;
|
||||
self.copy_last_response_binding = runtime_keymap.app.copy.clone();
|
||||
self.chat_keymap = runtime_keymap.chat.clone();
|
||||
self.queued_message_edit_hint_binding =
|
||||
queued_message_edit_hint_binding(runtime_keymap, terminal_info());
|
||||
self.bottom_pane
|
||||
.set_queued_message_edit_binding(self.queued_message_edit_hint_binding);
|
||||
self.bottom_pane.set_keymap_bindings(runtime_keymap);
|
||||
self.update_realtime_footer();
|
||||
self.request_redraw();
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@ expression: "normalize_snapshot_paths(render_bottom_popup(&chat, 80))"
|
||||
|
||||
• Queued follow-up inputs
|
||||
↳ [Pasted Content 1006 chars]
|
||||
⌥+↑ edit last queued message
|
||||
shift+← edit last queued message
|
||||
|
||||
› Ask Codex to do anything
|
||||
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@ expression: "normalize_snapshot_paths(render_bottom_popup(&chat, 80)).replace('
|
||||
|
||||
• Queued follow-up inputs
|
||||
↳ x
|
||||
⌥+↑ edit last queued message
|
||||
shift+← edit last queued message
|
||||
|
||||
› describe
|
||||
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ expression: "normalize_snapshot_paths(render_bottom_popup(&chat, 80))"
|
||||
---
|
||||
• Queued follow-up inputs
|
||||
↳ Preparing images: describe
|
||||
⌥+↑ edit last queued message
|
||||
shift+← edit last queued message
|
||||
|
||||
› x
|
||||
|
||||
|
||||
@@ -159,9 +159,6 @@ pub(super) use codex_protocol::plan_tool::StepStatus;
|
||||
pub(super) use codex_protocol::plan_tool::UpdatePlanArgs;
|
||||
pub(super) use codex_protocol::request_permissions::RequestPermissionProfile;
|
||||
pub(super) use codex_protocol::user_input::TextElement;
|
||||
pub(super) use codex_terminal_detection::Multiplexer;
|
||||
pub(super) use codex_terminal_detection::TerminalInfo;
|
||||
pub(super) use codex_terminal_detection::TerminalName;
|
||||
pub(super) use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
pub(super) use codex_utils_approval_presets::builtin_approval_presets;
|
||||
pub(super) use codex_utils_path_uri::LegacyAppPathString;
|
||||
|
||||
@@ -1924,54 +1924,42 @@ async fn restore_thread_input_state_applies_running_state_policy() {
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn alt_up_edits_most_recent_queued_message() {
|
||||
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
|
||||
chat.chat_keymap.edit_queued_message = vec![crate::key_hint::alt(KeyCode::Up)];
|
||||
chat.queued_message_edit_hint_binding = Some(crate::key_hint::alt(KeyCode::Up).into());
|
||||
chat.bottom_pane
|
||||
.set_queued_message_edit_binding(chat.queued_message_edit_hint_binding);
|
||||
async fn default_shortcuts_edit_most_recent_queued_message() {
|
||||
for key in [
|
||||
KeyEvent::new(KeyCode::Left, KeyModifiers::SHIFT),
|
||||
KeyEvent::new(KeyCode::Up, KeyModifiers::ALT),
|
||||
] {
|
||||
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
|
||||
chat.bottom_pane.set_task_running(/*running*/ true);
|
||||
for message in ["first queued", "second queued"] {
|
||||
chat.input_queue
|
||||
.queued_user_messages
|
||||
.push_back(UserMessage::from(message.to_string()).into());
|
||||
}
|
||||
chat.refresh_pending_input_preview();
|
||||
|
||||
// Simulate a running task so messages would normally be queued.
|
||||
chat.bottom_pane.set_task_running(/*running*/ true);
|
||||
chat.handle_key_event(key);
|
||||
|
||||
// Seed two queued messages.
|
||||
chat.input_queue
|
||||
.queued_user_messages
|
||||
.push_back(UserMessage::from("first queued".to_string()).into());
|
||||
chat.input_queue
|
||||
.queued_user_messages
|
||||
.push_back(UserMessage::from("second queued".to_string()).into());
|
||||
chat.refresh_pending_input_preview();
|
||||
|
||||
// Press Alt+Up to edit the most recent (last) queued message.
|
||||
chat.handle_key_event(KeyEvent::new(KeyCode::Up, KeyModifiers::ALT));
|
||||
|
||||
// Composer should now contain the last queued message.
|
||||
assert_eq!(
|
||||
chat.bottom_pane.composer_text(),
|
||||
"second queued".to_string()
|
||||
);
|
||||
// And the queue should now contain only the remaining (older) item.
|
||||
assert_eq!(chat.input_queue.queued_user_messages.len(), 1);
|
||||
assert_eq!(
|
||||
chat.input_queue.queued_user_messages.front().unwrap().text,
|
||||
"first queued"
|
||||
);
|
||||
assert_eq!(chat.bottom_pane.composer_text(), "second queued");
|
||||
assert_eq!(chat.queued_user_message_texts(), vec!["first queued"]);
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn unbound_queued_message_edit_does_not_fall_back_to_alt_up() {
|
||||
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
|
||||
chat.chat_keymap.edit_queued_message = Vec::new();
|
||||
chat.queued_message_edit_hint_binding = None;
|
||||
chat.bottom_pane
|
||||
.set_queued_message_edit_binding(chat.queued_message_edit_hint_binding);
|
||||
let mut config = codex_config::types::TuiKeymap::default();
|
||||
config.chat.edit_queued_message = Some(codex_config::types::KeybindingsSpec::Many(Vec::new()));
|
||||
let keymap = RuntimeKeymap::from_config(&config).expect("valid unbound queued edit");
|
||||
chat.apply_keymap_update(config, &keymap);
|
||||
chat.bottom_pane.set_task_running(/*running*/ true);
|
||||
chat.input_queue
|
||||
.queued_user_messages
|
||||
.push_back(UserMessage::from("queued".to_string()).into());
|
||||
chat.refresh_pending_input_preview();
|
||||
|
||||
assert!(!render_bottom_popup(&chat, /*width*/ 100).contains("edit last queued message"));
|
||||
chat.handle_key_event(KeyEvent::new(KeyCode::Left, KeyModifiers::SHIFT));
|
||||
chat.handle_key_event(KeyEvent::new(KeyCode::Up, KeyModifiers::ALT));
|
||||
|
||||
assert!(chat.bottom_pane.composer_text().is_empty());
|
||||
@@ -1979,141 +1967,31 @@ async fn unbound_queued_message_edit_does_not_fall_back_to_alt_up() {
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn shift_left_edits_most_recent_queued_message_in_apple_terminal() {
|
||||
assert_shift_left_edits_most_recent_queued_message_for_terminal(TerminalInfo {
|
||||
name: TerminalName::AppleTerminal,
|
||||
term_program: None,
|
||||
version: None,
|
||||
term: None,
|
||||
multiplexer: None,
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn shift_left_edits_most_recent_queued_message_in_warp_terminal() {
|
||||
assert_shift_left_edits_most_recent_queued_message_for_terminal(TerminalInfo {
|
||||
name: TerminalName::WarpTerminal,
|
||||
term_program: None,
|
||||
version: None,
|
||||
term: None,
|
||||
multiplexer: None,
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn shift_left_edits_most_recent_queued_message_in_vscode_terminal() {
|
||||
assert_shift_left_edits_most_recent_queued_message_for_terminal(TerminalInfo {
|
||||
name: TerminalName::VsCode,
|
||||
term_program: None,
|
||||
version: None,
|
||||
term: None,
|
||||
multiplexer: None,
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn shift_left_edits_most_recent_queued_message_in_tmux() {
|
||||
assert_shift_left_edits_most_recent_queued_message_for_terminal(TerminalInfo {
|
||||
name: TerminalName::Iterm2,
|
||||
term_program: None,
|
||||
version: None,
|
||||
term: None,
|
||||
multiplexer: Some(Multiplexer::Tmux { version: None }),
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn queued_message_edit_hint_displays_configured_chords() {
|
||||
async fn queued_message_edit_hint_displays_configured_chords() {
|
||||
use codex_config::types::KeybindingSpec;
|
||||
use codex_config::types::KeybindingsSpec;
|
||||
use codex_config::types::TuiKeymap;
|
||||
|
||||
let terminal_info = || TerminalInfo {
|
||||
name: TerminalName::Iterm2,
|
||||
term_program: None,
|
||||
version: None,
|
||||
term: None,
|
||||
multiplexer: None,
|
||||
};
|
||||
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
|
||||
let mut config = TuiKeymap::default();
|
||||
config.chat.edit_queued_message = Some(KeybindingsSpec::One(KeybindingSpec(
|
||||
"ctrl-x up".to_string(),
|
||||
)));
|
||||
let keymap = RuntimeKeymap::from_config(&config).expect("valid queued edit chord");
|
||||
chat.apply_keymap_update(config, &keymap);
|
||||
chat.input_queue
|
||||
.queued_user_messages
|
||||
.push_back(UserMessage::from("queued".to_string()).into());
|
||||
chat.refresh_pending_input_preview();
|
||||
|
||||
assert_eq!(
|
||||
queued_message_edit_hint_binding(&keymap, terminal_info()),
|
||||
Some(crate::key_hint::ShortcutHint::Chord {
|
||||
prefix: crate::key_hint::ctrl(KeyCode::Char('x')),
|
||||
completion: crate::key_hint::plain(KeyCode::Up),
|
||||
})
|
||||
);
|
||||
|
||||
let default_keymap = RuntimeKeymap::defaults();
|
||||
assert_eq!(
|
||||
queued_message_edit_hint_binding(&default_keymap, terminal_info()),
|
||||
Some(crate::key_hint::ShortcutHint::Single(crate::key_hint::alt(
|
||||
KeyCode::Up,
|
||||
)))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn queued_message_edit_binding_mapping_covers_special_terminals_and_tmux() {
|
||||
assert_eq!(
|
||||
queued_message_edit_binding_for_terminal(TerminalInfo {
|
||||
name: TerminalName::AppleTerminal,
|
||||
term_program: None,
|
||||
version: None,
|
||||
term: None,
|
||||
multiplexer: None,
|
||||
}),
|
||||
crate::key_hint::shift(KeyCode::Left)
|
||||
);
|
||||
assert_eq!(
|
||||
queued_message_edit_binding_for_terminal(TerminalInfo {
|
||||
name: TerminalName::WarpTerminal,
|
||||
term_program: None,
|
||||
version: None,
|
||||
term: None,
|
||||
multiplexer: None,
|
||||
}),
|
||||
crate::key_hint::shift(KeyCode::Left)
|
||||
);
|
||||
assert_eq!(
|
||||
queued_message_edit_binding_for_terminal(TerminalInfo {
|
||||
name: TerminalName::VsCode,
|
||||
term_program: None,
|
||||
version: None,
|
||||
term: None,
|
||||
multiplexer: None,
|
||||
}),
|
||||
crate::key_hint::shift(KeyCode::Left)
|
||||
);
|
||||
assert_eq!(
|
||||
queued_message_edit_binding_for_terminal(TerminalInfo {
|
||||
name: TerminalName::Iterm2,
|
||||
term_program: None,
|
||||
version: None,
|
||||
term: None,
|
||||
multiplexer: Some(Multiplexer::Tmux { version: None }),
|
||||
}),
|
||||
crate::key_hint::shift(KeyCode::Left)
|
||||
);
|
||||
assert_eq!(
|
||||
queued_message_edit_binding_for_terminal(TerminalInfo {
|
||||
name: TerminalName::Iterm2,
|
||||
term_program: None,
|
||||
version: None,
|
||||
term: None,
|
||||
multiplexer: None,
|
||||
}),
|
||||
crate::key_hint::alt(KeyCode::Up)
|
||||
);
|
||||
let hint = crate::key_hint::ShortcutHint::Chord {
|
||||
prefix: crate::key_hint::ctrl(KeyCode::Char('x')),
|
||||
completion: crate::key_hint::plain(KeyCode::Up),
|
||||
};
|
||||
assert!(render_bottom_popup(&chat, /*width*/ 100).contains(&format!(
|
||||
"{} edit last queued message",
|
||||
hint.display_label()
|
||||
)));
|
||||
}
|
||||
|
||||
/// Pressing Up to recall the most recent history entry and immediately queuing
|
||||
|
||||
@@ -2,7 +2,6 @@ use super::*;
|
||||
use codex_app_server_protocol::ImageGenerationItem;
|
||||
use codex_app_server_protocol::PluginAvailability;
|
||||
use codex_utils_absolute_path::test_support::PathExt;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
pub(super) async fn test_config() -> Config {
|
||||
// Start from the built-in defaults so tests do not inherit host/system config.
|
||||
@@ -1311,43 +1310,6 @@ pub(super) fn get_available_model(chat: &ChatWidget, model: &str) -> ModelPreset
|
||||
.unwrap_or_else(|| panic!("{model} preset not found"))
|
||||
}
|
||||
|
||||
pub(super) async fn assert_shift_left_edits_most_recent_queued_message_for_terminal(
|
||||
terminal_info: TerminalInfo,
|
||||
) {
|
||||
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
|
||||
chat.queued_message_edit_hint_binding =
|
||||
Some(queued_message_edit_binding_for_terminal(terminal_info).into());
|
||||
chat.bottom_pane
|
||||
.set_queued_message_edit_binding(chat.queued_message_edit_hint_binding);
|
||||
|
||||
// Simulate a running task so messages would normally be queued.
|
||||
chat.bottom_pane.set_task_running(/*running*/ true);
|
||||
|
||||
// Seed two queued messages.
|
||||
chat.input_queue
|
||||
.queued_user_messages
|
||||
.push_back(UserMessage::from("first queued".to_string()).into());
|
||||
chat.input_queue
|
||||
.queued_user_messages
|
||||
.push_back(UserMessage::from("second queued".to_string()).into());
|
||||
chat.refresh_pending_input_preview();
|
||||
|
||||
// Press Shift+Left to edit the most recent (last) queued message.
|
||||
chat.handle_key_event(KeyEvent::new(KeyCode::Left, KeyModifiers::SHIFT));
|
||||
|
||||
// Composer should now contain the last queued message.
|
||||
assert_eq!(
|
||||
chat.bottom_pane.composer_text(),
|
||||
"second queued".to_string()
|
||||
);
|
||||
// And the queue should now contain only the remaining (older) item.
|
||||
assert_eq!(chat.input_queue.queued_user_messages.len(), 1);
|
||||
assert_eq!(
|
||||
chat.input_queue.queued_user_messages.front().unwrap().text,
|
||||
"first queued"
|
||||
);
|
||||
}
|
||||
|
||||
pub(super) fn render_bottom_first_row(chat: &ChatWidget, width: u16) -> String {
|
||||
let height = chat.desired_height(width);
|
||||
let area = Rect::new(0, 0, width, height);
|
||||
|
||||
@@ -865,21 +865,93 @@ async fn questions_and_queued_messages_share_the_resolved_shortcut() {
|
||||
.queued_user_messages
|
||||
.push_back(UserMessage::from("queued".to_string()).into());
|
||||
chat.refresh_pending_input_preview();
|
||||
for binding in [key_hint::shift(KeyCode::Left), key_hint::alt(KeyCode::Up)] {
|
||||
chat.bottom_pane
|
||||
.set_queued_message_edit_binding(Some(binding.into()));
|
||||
let hint = binding.display_label();
|
||||
assert!(render_bottom_popup(&chat, /*width*/ 100).contains(&hint));
|
||||
chat.add_async_questions(&hint, &questions());
|
||||
assert!(render_bottom_popup(&chat, /*width*/ 100).contains(&format!("{hint} to answer")));
|
||||
let (key, modifiers) = binding.parts();
|
||||
chat.handle_key_event(KeyEvent::new(key, modifiers));
|
||||
insta::assert_snapshot!(
|
||||
format!("question_queue_hint_{}", key),
|
||||
render_bottom_popup(&chat, /*width*/ 100)
|
||||
);
|
||||
chat.handle_key_event(KeyEvent::from(KeyCode::Esc));
|
||||
let forward_hint = key_hint::shift(KeyCode::Left).display_label();
|
||||
let backward_hint = key_hint::shift(KeyCode::Right).display_label();
|
||||
assert!(
|
||||
render_bottom_popup(&chat, /*width*/ 100)
|
||||
.contains(&format!("{forward_hint} edit last queued message"))
|
||||
);
|
||||
chat.add_async_questions("message", &questions());
|
||||
assert!(
|
||||
render_bottom_popup(&chat, /*width*/ 100).contains(&format!("{forward_hint} to answer"))
|
||||
);
|
||||
|
||||
for (forward, backward) in [
|
||||
(
|
||||
KeyEvent::new(KeyCode::Left, KeyModifiers::SHIFT),
|
||||
KeyEvent::new(KeyCode::Right, KeyModifiers::SHIFT),
|
||||
),
|
||||
(
|
||||
KeyEvent::new(KeyCode::Up, KeyModifiers::ALT),
|
||||
KeyEvent::new(KeyCode::Down, KeyModifiers::ALT),
|
||||
),
|
||||
] {
|
||||
chat.handle_key_event(forward);
|
||||
let rendered = render_bottom_popup(&chat, /*width*/ 100);
|
||||
assert!(rendered.contains(&format!("{backward_hint} main prompt")));
|
||||
assert!(rendered.contains(&format!("{forward_hint} next question")));
|
||||
if forward.code == KeyCode::Left {
|
||||
insta::assert_snapshot!(
|
||||
"question_queue_hint_Left",
|
||||
render_bottom_popup(&chat, /*width*/ 100)
|
||||
);
|
||||
}
|
||||
chat.handle_key_event(forward);
|
||||
let rendered = render_bottom_popup(&chat, /*width*/ 100);
|
||||
assert!(rendered.contains(&format!("{backward_hint} prev question")));
|
||||
assert!(rendered.contains(&format!("{forward_hint} queued messages")));
|
||||
chat.handle_key_event(backward);
|
||||
assert!(render_bottom_popup(&chat, /*width*/ 100).contains("Which way?"));
|
||||
chat.handle_key_event(backward);
|
||||
assert!(!chat.bottom_pane.questions.as_ref().unwrap().expanded);
|
||||
}
|
||||
|
||||
let config = toml::from_str(
|
||||
r#"
|
||||
[chat]
|
||||
edit_queued_message = ["alt-up", "shift-left"]
|
||||
prompt_stack_back = ["alt-down", "shift-right"]
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
let keymap = RuntimeKeymap::from_config(&config).expect("valid alternate shortcuts");
|
||||
chat.apply_keymap_update(config, &keymap);
|
||||
chat.add_async_questions("remapped", &questions());
|
||||
let forward_hint = key_hint::alt(KeyCode::Up).display_label();
|
||||
assert!(
|
||||
render_bottom_popup(&chat, /*width*/ 100).contains(&format!("{forward_hint} to answer"))
|
||||
);
|
||||
chat.handle_key_event(KeyEvent::new(KeyCode::Up, KeyModifiers::ALT));
|
||||
insta::assert_snapshot!(
|
||||
"question_queue_hint_Up",
|
||||
render_bottom_popup(&chat, /*width*/ 100)
|
||||
);
|
||||
|
||||
let config = toml::from_str(
|
||||
r#"
|
||||
[chat]
|
||||
edit_queued_message = ["ctrl-x up", "shift-left"]
|
||||
prompt_stack_back = ["ctrl-x down", "shift-right"]
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
let keymap = RuntimeKeymap::from_config(&config).expect("valid configured chords");
|
||||
chat.apply_keymap_update(config, &keymap);
|
||||
let hint = |completion| {
|
||||
crate::key_hint::ShortcutHint::Chord {
|
||||
prefix: key_hint::ctrl(KeyCode::Char('x')),
|
||||
completion: key_hint::plain(completion),
|
||||
}
|
||||
.display_label()
|
||||
};
|
||||
let rendered = render_bottom_popup(&chat, /*width*/ 100);
|
||||
assert!(rendered.contains(&format!("{} main prompt", hint(KeyCode::Down))));
|
||||
assert!(rendered.contains(&format!("{} next question", hint(KeyCode::Up))));
|
||||
chat.handle_key_event(KeyEvent::new(KeyCode::Right, KeyModifiers::SHIFT));
|
||||
assert!(
|
||||
render_bottom_popup(&chat, /*width*/ 100)
|
||||
.contains(&format!("{} to answer", hint(KeyCode::Up)))
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
||||
+1
-2
@@ -1,6 +1,5 @@
|
||||
---
|
||||
source: tui/src/chatwidget/tests/questions_tests.rs
|
||||
assertion_line: 937
|
||||
expression: "render_bottom_popup(&chat, 80)"
|
||||
---
|
||||
• Queued follow-up inputs
|
||||
@@ -9,4 +8,4 @@ expression: "render_bottom_popup(&chat, 80)"
|
||||
|
||||
My draft for the second question
|
||||
|
||||
enter submit ctrl+] skip ⌥+↓ main prompt
|
||||
enter submit ctrl+] skip shift+→ main prompt
|
||||
|
||||
+1
-1
@@ -10,4 +10,4 @@ expression: "render_bottom_popup(&chat, 80)"
|
||||
|
||||
Type your answer
|
||||
|
||||
enter submit ctrl+] skip ⌥+↓ main prompt ⌥+↑ next question
|
||||
enter submit ctrl+] skip shift+→ main prompt shift+← next question
|
||||
|
||||
+1
-1
@@ -12,4 +12,4 @@ expression: "render_bottom_popup(&chat, 80)"
|
||||
› 1. Next
|
||||
2. Other
|
||||
|
||||
enter submit ctrl+] skip ⌥+↓ prev question ⌥+↑ queued messages
|
||||
enter submit ctrl+] skip shift+→ prev question shift+← queued messages
|
||||
|
||||
+1
-1
@@ -14,4 +14,4 @@ expression: "render_bottom_popup(&chat, 80)"
|
||||
xxxxxxxxxxxxxxxx but do not deploy
|
||||
2. Other
|
||||
|
||||
enter submit ctrl+] skip ⌥+↓ main prompt
|
||||
enter submit ctrl+] skip shift+→ main prompt
|
||||
|
||||
+1
-1
@@ -8,4 +8,4 @@ expression: "render_bottom_popup(&chat, 80)"
|
||||
|
||||
Type your answer
|
||||
|
||||
enter submit ctrl+] skip ⌥+↓ main prompt
|
||||
enter submit ctrl+] skip shift+→ main prompt
|
||||
|
||||
+1
-1
@@ -11,4 +11,4 @@ expression: "render_bottom_popup(&chat, 100)"
|
||||
|
||||
Type your answer
|
||||
|
||||
enter submit ctrl+] skip ⌥+↓ main prompt shift+← next question
|
||||
enter submit ctrl+] skip shift+→ main prompt shift+← next question
|
||||
|
||||
+2
-2
@@ -11,7 +11,7 @@ NORMAL
|
||||
› 2. alpha beta
|
||||
alpha gamma
|
||||
Vim: Normal
|
||||
enter submit ctrl+] skip ⌥+↓ main prompt
|
||||
enter submit ctrl+] skip shift+→ main prompt
|
||||
|
||||
SEARCH
|
||||
• Queued follow-up inputs
|
||||
@@ -23,4 +23,4 @@ SEARCH
|
||||
alpha gamma
|
||||
/gamma
|
||||
Vim: Normal
|
||||
enter submit ctrl+] skip ⌥+↓ main prompt
|
||||
enter submit ctrl+] skip shift+→ main prompt
|
||||
|
||||
+2
-2
@@ -10,7 +10,7 @@ NORMAL
|
||||
alpha beta
|
||||
alpha gamma
|
||||
Vim: Normal
|
||||
enter submit ctrl+] skip ⌥+↓ main prompt
|
||||
enter submit ctrl+] skip shift+→ main prompt
|
||||
|
||||
SEARCH
|
||||
• Queued follow-up inputs
|
||||
@@ -21,4 +21,4 @@ SEARCH
|
||||
alpha gamma
|
||||
/gamma
|
||||
Vim: Normal
|
||||
enter submit ctrl+] skip ⌥+↓ main prompt
|
||||
enter submit ctrl+] skip shift+→ main prompt
|
||||
|
||||
+1
-1
@@ -11,4 +11,4 @@ OPEN
|
||||
ONLY QUESTIONS
|
||||
• Queued follow-up inputs
|
||||
? 2 questions
|
||||
⌥+↑ to answer
|
||||
shift+← to answer
|
||||
|
||||
+1
-2
@@ -1,6 +1,5 @@
|
||||
---
|
||||
source: tui/src/chatwidget/tests/questions_tests.rs
|
||||
assertion_line: 51
|
||||
expression: "render_bottom_popup(&chat, 80)"
|
||||
---
|
||||
• Queued follow-up inputs
|
||||
@@ -12,4 +11,4 @@ expression: "render_bottom_popup(&chat, 80)"
|
||||
|
||||
Type your answer
|
||||
|
||||
enter submit ctrl+] skip ⌥+↓ main prompt ⌥+↑ queued messages
|
||||
enter submit ctrl+] skip shift+→ main prompt shift+← queued messages
|
||||
|
||||
+1
-1
@@ -10,4 +10,4 @@ expression: rendered
|
||||
|
||||
A typed answer
|
||||
|
||||
enter submit ctrl+] skip ⌥+↓ main prompt
|
||||
enter submit ctrl+] skip shift+→ main prompt
|
||||
|
||||
@@ -142,7 +142,7 @@ pub(crate) struct ChatKeymap {
|
||||
pub(crate) previous_permission_mode: Vec<KeyBinding>,
|
||||
/// Switch to the next available permission mode.
|
||||
pub(crate) next_permission_mode: Vec<KeyBinding>,
|
||||
/// Move up through async questions, then edit the most recently queued message.
|
||||
/// Move forward through async questions, then edit the most recently queued message.
|
||||
pub(crate) edit_queued_message: Vec<KeyBinding>,
|
||||
/// Move back through async questions toward the composer.
|
||||
pub(crate) prompt_stack_back: Vec<KeyBinding>,
|
||||
@@ -1672,8 +1672,8 @@ impl RuntimeKeymap {
|
||||
],
|
||||
previous_permission_mode: default_bindings![],
|
||||
next_permission_mode: default_bindings![],
|
||||
edit_queued_message: default_bindings![alt(KeyCode::Up), shift(KeyCode::Left)],
|
||||
prompt_stack_back: default_bindings![alt(KeyCode::Down), shift(KeyCode::Right)],
|
||||
edit_queued_message: default_bindings![shift(KeyCode::Left), alt(KeyCode::Up)],
|
||||
prompt_stack_back: default_bindings![shift(KeyCode::Right), alt(KeyCode::Down)],
|
||||
skip_question: default_bindings![ctrl(KeyCode::Char(']'))],
|
||||
},
|
||||
composer: ComposerKeymap {
|
||||
@@ -2944,7 +2944,7 @@ mod tests {
|
||||
);
|
||||
assert_eq!(
|
||||
runtime.chat.edit_queued_message,
|
||||
vec![key_hint::alt(KeyCode::Up), key_hint::shift(KeyCode::Left)]
|
||||
vec![key_hint::shift(KeyCode::Left), key_hint::alt(KeyCode::Up)]
|
||||
);
|
||||
assert_eq!(
|
||||
runtime.composer.history_search_previous,
|
||||
|
||||
@@ -102,7 +102,7 @@ pub(super) const KEYMAP_ACTIONS: &[KeymapActionDescriptor] = &[
|
||||
action("chat", "Chat", "increase_reasoning_effort", "Increase reasoning effort."),
|
||||
action("chat", "Chat", "previous_permission_mode", "Switch to the previous available permission mode."),
|
||||
action("chat", "Chat", "next_permission_mode", "Switch to the next available permission mode."),
|
||||
action("chat", "Chat", "edit_queued_message", "Move up through questions, then edit the last queued message."),
|
||||
action("chat", "Chat", "edit_queued_message", "Move forward through questions, then edit the last queued message."),
|
||||
action("chat", "Chat", "prompt_stack_back", "Move back through questions toward the composer."),
|
||||
action("chat", "Chat", "skip_question", "Skip the focused question."),
|
||||
action("chat", "Chat", "toggle_voice", "Start or stop a voice conversation."),
|
||||
|
||||
+2
-2
@@ -26,8 +26,8 @@ expression: "render_picker(params, 120)"
|
||||
Chat Increase Reasoning Effort alt-., shift-up
|
||||
Chat - Previous Permission Mode unbound
|
||||
Chat - Next Permission Mode unbound
|
||||
Chat Edit Queued Message alt-up, shift-left
|
||||
Chat Prompt Stack Back alt-down, shift-right
|
||||
Chat Edit Queued Message shift-left, alt-up
|
||||
Chat Prompt Stack Back shift-right, alt-down
|
||||
Chat Skip Question ctrl-]
|
||||
Chat Toggle Voice f8
|
||||
Chat Toggle Voice Mute ctrl-x
|
||||
|
||||
+2
-2
@@ -27,8 +27,8 @@ expression: "render_picker(params, 120)"
|
||||
Chat Increase Reasoning Effort alt-., shift-up
|
||||
Chat - Previous Permission Mode unbound
|
||||
Chat - Next Permission Mode unbound
|
||||
Chat Edit Queued Message alt-up, shift-left
|
||||
Chat Prompt Stack Back alt-down, shift-right
|
||||
Chat Edit Queued Message shift-left, alt-up
|
||||
Chat Prompt Stack Back shift-right, alt-down
|
||||
Chat Skip Question ctrl-]
|
||||
Chat Toggle Voice f8
|
||||
Chat Toggle Voice Mute ctrl-x
|
||||
|
||||
+2
-2
@@ -26,8 +26,8 @@ expression: "render_picker(params, 120)"
|
||||
Chat Increase Reasoning Effort alt-., shift-up
|
||||
Chat - Previous Permission Mode unbound
|
||||
Chat - Next Permission Mode unbound
|
||||
Chat Edit Queued Message alt-up, shift-left
|
||||
Chat Prompt Stack Back alt-down, shift-right
|
||||
Chat Edit Queued Message shift-left, alt-up
|
||||
Chat Prompt Stack Back shift-right, alt-down
|
||||
Chat Skip Question ctrl-]
|
||||
Chat Toggle Voice f8
|
||||
Chat - Toggle Voice Mute unbound
|
||||
|
||||
+2
-2
@@ -26,8 +26,8 @@ expression: "render_picker(params, 78)"
|
||||
Chat Increase Reasoning Effort alt-., shift-up
|
||||
Chat - Previous Permission Mode unbound
|
||||
Chat - Next Permission Mode unbound
|
||||
Chat Edit Queued Message alt-up, shift-left
|
||||
Chat Prompt Stack Back alt-down, shift-right
|
||||
Chat Edit Queued Message shift-left, alt-up
|
||||
Chat Prompt Stack Back shift-right, alt-down
|
||||
Chat Skip Question ctrl-]
|
||||
Chat Toggle Voice f8
|
||||
Chat Toggle Voice Mute ctrl-x
|
||||
|
||||
@@ -26,8 +26,8 @@ expression: "render_picker(params, 120)"
|
||||
Chat Increase Reasoning Effort alt-., shift-up
|
||||
Chat - Previous Permission Mode unbound
|
||||
Chat - Next Permission Mode unbound
|
||||
Chat Edit Queued Message alt-up, shift-left
|
||||
Chat Prompt Stack Back alt-down, shift-right
|
||||
Chat Edit Queued Message shift-left, alt-up
|
||||
Chat Prompt Stack Back shift-right, alt-down
|
||||
Chat Skip Question ctrl-]
|
||||
Chat Toggle Voice f8
|
||||
Chat Toggle Voice Mute ctrl-x
|
||||
|
||||
+3
-3
@@ -5,14 +5,14 @@ expression: descriptions
|
||||
|
||||
Edit Shortcut
|
||||
Edit Queued Message · Chat
|
||||
Current alt-up, shift-left · Default keymap
|
||||
Current shift-left, alt-up · Default keymap
|
||||
Config `tui.keymap.chat.edit_queued_message`
|
||||
Move up through questions, then edit the last queued message.
|
||||
Move forward through questions, then edit the last queued message.
|
||||
|
||||
|
||||
Edit Shortcut
|
||||
Prompt Stack Back · Chat
|
||||
Current alt-down, shift-right · Default keymap
|
||||
Current shift-right, alt-down · Default keymap
|
||||
Config `tui.keymap.chat.prompt_stack_back`
|
||||
Move back through questions toward the composer.
|
||||
|
||||
|
||||
+2
-2
@@ -26,8 +26,8 @@ expression: "render_picker(params, 120)"
|
||||
Chat Increase Reasoning Effort alt-., shift-up
|
||||
Chat - Previous Permission Mode unbound
|
||||
Chat - Next Permission Mode unbound
|
||||
Chat Edit Queued Message alt-up, shift-left
|
||||
Chat Prompt Stack Back alt-down, shift-right
|
||||
Chat Edit Queued Message shift-left, alt-up
|
||||
Chat Prompt Stack Back shift-right, alt-down
|
||||
› Chat Skip Question ctrl-]
|
||||
Chat Toggle Voice f8
|
||||
Chat Toggle Voice Mute ctrl-x
|
||||
|
||||
Reference in New Issue
Block a user