mirror of
https://github.com/openai/codex.git
synced 2026-10-29 19:08:43 +00:00
Merge branch 'agentydragon-23-interactive-container-command-affordance' into agentydragon
This commit is contained in:
@@ -499,7 +499,7 @@ impl<'a> App<'a> {
|
||||
widget.handle_shell_command_result(call_id, stdout, stderr, exit_code);
|
||||
self.app_event_tx.send(AppEvent::Redraw);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
terminal.clear()?;
|
||||
|
||||
@@ -25,6 +25,7 @@ const MIN_TEXTAREA_ROWS: usize = 1;
|
||||
const BORDER_LINES: u16 = 2;
|
||||
|
||||
/// Result returned when the user interacts with the text area.
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum InputResult {
|
||||
Submitted(String),
|
||||
None,
|
||||
@@ -39,6 +40,39 @@ pub(crate) struct ChatComposer<'a> {
|
||||
max_rows: usize,
|
||||
/// Last computed context-left percentage
|
||||
context_left_percent: f64,
|
||||
/// Whether the composer is in shell-command mode (Ctrl+M toggles).
|
||||
shell_mode: bool,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::app_event::AppEvent;
|
||||
use crate::app_event_sender::AppEventSender;
|
||||
use crate::slash_command::SlashCommand;
|
||||
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
|
||||
use std::sync::mpsc;
|
||||
|
||||
#[test]
|
||||
fn ctrl_m_dispatches_shell_command() {
|
||||
let (tx, rx) = mpsc::channel();
|
||||
let evt_tx = AppEventSender::new(tx);
|
||||
let mut composer = ChatComposer::new(true, evt_tx.clone(), 1);
|
||||
// Initial shell_mode should be false.
|
||||
assert!(!composer.shell_mode);
|
||||
// Simulate Ctrl+M key event.
|
||||
let key_event = KeyEvent::new(KeyCode::Char('m'), KeyModifiers::CONTROL);
|
||||
let (res, needs_redraw) = composer.handle_key_event(key_event);
|
||||
assert!(needs_redraw);
|
||||
assert_eq!(res, InputResult::None);
|
||||
// shell_mode should have toggled to true.
|
||||
assert!(composer.shell_mode);
|
||||
// Verify DispatchCommand(Shell) event was sent.
|
||||
match rx.recv().unwrap() {
|
||||
AppEvent::DispatchCommand(cmd) => assert_eq!(cmd, SlashCommand::Shell),
|
||||
other => panic!("Expected DispatchCommand(Shell), got {:?}", other),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ChatComposer<'_> {
|
||||
@@ -54,6 +88,7 @@ impl ChatComposer<'_> {
|
||||
history: ChatComposerHistory::new(),
|
||||
max_rows,
|
||||
context_left_percent: 100.0,
|
||||
shell_mode: false,
|
||||
};
|
||||
this.update_border(has_input_focus);
|
||||
this
|
||||
@@ -232,7 +267,8 @@ impl ChatComposer<'_> {
|
||||
(InputResult::None, true)
|
||||
}
|
||||
Input { key: Key::Char('m'), ctrl: true, alt: false, shift: false } => {
|
||||
// Launch shell-command prompt
|
||||
// Toggle shell-command mode and prompt/exit accordingly
|
||||
self.shell_mode = !self.shell_mode;
|
||||
self.app_event_tx.send(AppEvent::DispatchCommand(SlashCommand::Shell));
|
||||
(InputResult::None, true)
|
||||
}
|
||||
@@ -286,6 +322,12 @@ impl ChatComposer<'_> {
|
||||
self.textarea.lines().join("\n")
|
||||
}
|
||||
|
||||
/// Returns true if the composer is in shell-command mode.
|
||||
#[allow(dead_code)]
|
||||
pub fn is_shell_mode(&self) -> bool {
|
||||
self.shell_mode
|
||||
}
|
||||
|
||||
/// Synchronize `self.command_popup` with the current text in the
|
||||
/// textarea. This must be called after every modification that can change
|
||||
/// the text so the popup is shown/updated/hidden as appropriate.
|
||||
@@ -337,7 +379,15 @@ impl ChatComposer<'_> {
|
||||
border_style: Style,
|
||||
}
|
||||
|
||||
let bs = if has_focus {
|
||||
let bs = if self.shell_mode {
|
||||
BlockState {
|
||||
right_title: Line::from(
|
||||
"Shell mode – Enter to run | Ctrl+M to exit shell mode",
|
||||
)
|
||||
.alignment(Alignment::Right),
|
||||
border_style: Style::default().fg(Color::Red),
|
||||
}
|
||||
} else if has_focus {
|
||||
BlockState {
|
||||
right_title: Line::from("Enter to send | Ctrl+D to quit | Ctrl+J for newline")
|
||||
.alignment(Alignment::Right),
|
||||
|
||||
@@ -257,9 +257,9 @@ impl WidgetRef for &BottomPane<'_> {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
|
||||
use std::sync::mpsc;
|
||||
use crate::app_event::AppEvent;
|
||||
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
|
||||
|
||||
/// Construct a BottomPane with default parameters for testing.
|
||||
fn make_pane() -> BottomPane<'static> {
|
||||
@@ -303,6 +303,17 @@ mod tests {
|
||||
assert!(pane.active_view.as_mut().unwrap().should_hide_when_task_is_done());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ctrl_m_toggles_shell_mode() {
|
||||
let mut pane = make_pane();
|
||||
assert!(!pane.composer.is_shell_mode());
|
||||
let key = KeyEvent::new(KeyCode::Char('m'), KeyModifiers::CONTROL);
|
||||
pane.handle_key_event(key);
|
||||
assert!(pane.composer.is_shell_mode());
|
||||
pane.handle_key_event(key);
|
||||
assert!(!pane.composer.is_shell_mode());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn remove_status_indicator_after_task_complete() {
|
||||
let mut pane = make_pane();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crossterm::event::{Event as CrosstermEvent, KeyCode, KeyEvent};
|
||||
use crossterm::event::{Event as CrosstermEvent, KeyCode, KeyEvent, KeyModifiers};
|
||||
use ratatui::buffer::Buffer;
|
||||
use ratatui::layout::Rect;
|
||||
use ratatui::prelude::Widget;
|
||||
@@ -29,6 +29,12 @@ impl ShellCommandView {
|
||||
|
||||
impl<'a> BottomPaneView<'a> for ShellCommandView {
|
||||
fn handle_key_event(&mut self, pane: &mut BottomPane<'a>, key_event: KeyEvent) {
|
||||
// Exit shell prompt on Ctrl+M
|
||||
if let KeyEvent { code: KeyCode::Char('m'), modifiers: KeyModifiers::CONTROL, .. } = key_event {
|
||||
self.done = true;
|
||||
pane.request_redraw();
|
||||
return;
|
||||
}
|
||||
if self.done {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ impl SlashCommand {
|
||||
SlashCommand::MountRemove => "Remove a mount by container path.",
|
||||
SlashCommand::InspectEnv => "Inspect sandbox and container environment (mounts, permissions, network)",
|
||||
SlashCommand::Shell => "Run a shell command in the container.",
|
||||
SlashCommand::Quit => "Quit",
|
||||
SlashCommand::Quit => "Exit the application.",
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user