mirror of
https://github.com/openai/codex.git
synced 2026-04-24 06:35:50 +00:00
agentydragon(tasks): TUI integration for inspect-env slash-command
- Extend SlashCommand and AppEvent with InspectEnv variants - Dispatch and handle AppEvent::InlineInspectEnv to invoke codex inspect-env in background - Add InspectEnvView and wire into BottomPane and ChatWidget - Add tests for SlashCommand, command popup filter, and InspectEnvView rendering - Update task 35 status to Done and record implementation details
This commit is contained in:
@@ -1,9 +1,9 @@
|
||||
+++
|
||||
id = "35"
|
||||
title = "TUI Integration for Inspect-Env Command"
|
||||
status = "Not started"
|
||||
status = "Done"
|
||||
dependencies = "10" # Rationale: depends on Task 10 for container state inspection
|
||||
last_updated = "2025-06-25T04:45:29Z"
|
||||
last_updated = "2025-06-25T11:38:19Z"
|
||||
+++
|
||||
|
||||
> *This task is specific to codex-rs.*
|
||||
@@ -32,7 +32,7 @@ Add an `/inspect-env` slash-command in the TUI that invokes the existing `codex
|
||||
- Add `InlineInspectEnv` variant to `AppEvent` enum to represent inline slash-command invocation.
|
||||
- Update dispatch logic in `App::run` to spawn a background thread on `InlineInspectEnv` that runs `codex inspect-env`, reads its stdout line-by-line, and sends each line as `AppEvent::LatestLog`, then triggers a redraw.
|
||||
- Wire up `/inspect-env` to dispatch `InlineInspectEnv` in the slash-command handling.
|
||||
- Add unit tests in the TUI crate to verify `built_in_slash_commands()` includes `inspect-env` mapping and description.
|
||||
- Add unit tests in the TUI crate to verify `built_in_slash_commands()` includes `inspect-env` mapping and description, and tests for the command-popup filter to ensure `InspectEnv` is listed when `/inspect-env` is entered.
|
||||
|
||||
**How it works**
|
||||
When the user enters `/inspect-env`, the TUI parser recognizes the command and emits `AppEvent::InlineInspectEnv`. The main event loop handles this event by spawning a thread that invokes the external `codex inspect-env` command, captures its output line-by-line, and forwards each line into the TUI log pane via `AppEvent::LatestLog`. A redraw is scheduled once the inspection completes.
|
||||
|
||||
@@ -478,8 +478,11 @@ impl<'a> App<'a> {
|
||||
}
|
||||
}
|
||||
SlashCommand::InspectEnv => {
|
||||
// Activate inspect-env view and initiate output streaming
|
||||
if let AppState::Chat { widget } = &mut self.app_state {
|
||||
widget.push_inspect_env();
|
||||
}
|
||||
let _ = self.app_event_tx.send(AppEvent::InlineInspectEnv(String::new()));
|
||||
let _ = self.app_event_tx.send(AppEvent::Redraw);
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
@@ -29,6 +29,29 @@ pub(crate) struct CommandPopup {
|
||||
selected_idx: Option<usize>,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::slash_command::SlashCommand;
|
||||
|
||||
#[test]
|
||||
fn filter_inspect_env_in_command_popup() {
|
||||
let mut popup = CommandPopup::new();
|
||||
popup.on_composer_text_change("/inspect-env".to_string());
|
||||
let filtered: Vec<&SlashCommand> = popup.filtered_commands();
|
||||
// Ensure InspectEnv command is among filtered results
|
||||
assert!(filtered.contains(&&SlashCommand::InspectEnv));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn select_inspect_env_as_selected_command() {
|
||||
let mut popup = CommandPopup::new();
|
||||
popup.on_composer_text_change("/inspect-env".to_string());
|
||||
popup.selected_idx = Some(0);
|
||||
assert_eq!(popup.selected_command(), Some(&SlashCommand::InspectEnv));
|
||||
}
|
||||
}
|
||||
|
||||
impl CommandPopup {
|
||||
pub(crate) fn new() -> Self {
|
||||
Self {
|
||||
|
||||
85
codex-rs/tui/src/bottom_pane/inspect_env_view.rs
Normal file
85
codex-rs/tui/src/bottom_pane/inspect_env_view.rs
Normal file
@@ -0,0 +1,85 @@
|
||||
use crossterm::event::{KeyCode, KeyEvent};
|
||||
use ratatui::buffer::Buffer;
|
||||
use ratatui::layout::Rect;
|
||||
use ratatui::widgets::{Block, Borders, BorderType, Paragraph};
|
||||
use ratatui::prelude::Widget;
|
||||
|
||||
use super::{BottomPane, BottomPaneView};
|
||||
use super::bottom_pane_view::ConditionalUpdate;
|
||||
|
||||
/// View for displaying the output of `codex inspect-env` in the bottom pane.
|
||||
pub(crate) struct InspectEnvView {
|
||||
lines: Vec<String>,
|
||||
done: bool,
|
||||
}
|
||||
|
||||
impl InspectEnvView {
|
||||
/// Create a new inspect-env view.
|
||||
pub fn new() -> Self {
|
||||
Self { lines: Vec::new(), done: false }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> BottomPaneView<'a> for InspectEnvView {
|
||||
fn update_status_text(&mut self, text: String) -> ConditionalUpdate {
|
||||
self.lines.push(text);
|
||||
ConditionalUpdate::NeedsRedraw
|
||||
}
|
||||
|
||||
fn handle_key_event(&mut self, pane: &mut BottomPane<'a>, key_event: KeyEvent) {
|
||||
if key_event.code == KeyCode::Enter || key_event.code == KeyCode::Esc {
|
||||
self.done = true;
|
||||
}
|
||||
pane.request_redraw();
|
||||
}
|
||||
|
||||
fn is_complete(&self) -> bool {
|
||||
self.done
|
||||
}
|
||||
|
||||
fn calculate_required_height(&self, area: &Rect) -> u16 {
|
||||
area.height
|
||||
}
|
||||
|
||||
fn render(&self, area: Rect, buf: &mut Buffer) {
|
||||
let block = Block::default()
|
||||
.borders(Borders::ALL)
|
||||
.border_type(BorderType::Rounded)
|
||||
.title("Inspect Env (Enter/Esc to close)");
|
||||
let text = self.lines.join("\n");
|
||||
Paragraph::new(text).block(block).render(area, buf);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use ratatui::buffer::Buffer;
|
||||
use ratatui::layout::Rect;
|
||||
|
||||
#[test]
|
||||
fn update_status_text_appends_lines() {
|
||||
let mut view = InspectEnvView::new();
|
||||
assert!(view.lines.is_empty());
|
||||
view.update_status_text("foo".to_string());
|
||||
view.update_status_text("bar".to_string());
|
||||
assert_eq!(view.lines, vec!["foo".to_string(), "bar".to_string()]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn render_includes_lines() {
|
||||
let mut view = InspectEnvView::new();
|
||||
view.update_status_text("line1".to_string());
|
||||
view.update_status_text("line2".to_string());
|
||||
let area = Rect { x: 0, y: 0, width: 10, height: 3 };
|
||||
let mut buf = Buffer::empty(area);
|
||||
view.render(area, &mut buf);
|
||||
// Collect all cell symbols into a flat string and verify the lines are present
|
||||
let content: String = buf
|
||||
.content()
|
||||
.iter()
|
||||
.fold(String::new(), |mut acc, cell| { acc.push_str(cell.symbol()); acc });
|
||||
assert!(content.contains("line1"));
|
||||
assert!(content.contains("line2"));
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@ use crate::user_approval_widget::ApprovalRequest;
|
||||
|
||||
mod approval_modal_view;
|
||||
mod mount_view;
|
||||
mod inspect_env_view;
|
||||
mod bottom_pane_view;
|
||||
mod chat_composer;
|
||||
mod chat_composer_history;
|
||||
@@ -25,6 +26,7 @@ pub(crate) use chat_composer::InputResult;
|
||||
|
||||
use approval_modal_view::ApprovalModalView;
|
||||
use mount_view::{MountAddView, MountRemoveView};
|
||||
use inspect_env_view::InspectEnvView;
|
||||
use status_indicator_view::StatusIndicatorView;
|
||||
use config_reload_view::ConfigReloadView;
|
||||
|
||||
@@ -161,6 +163,13 @@ impl BottomPane<'_> {
|
||||
self.request_redraw();
|
||||
}
|
||||
|
||||
/// Launch inspect-env output view.
|
||||
pub fn push_inspect_env(&mut self) {
|
||||
let view = InspectEnvView::new();
|
||||
self.active_view = Some(Box::new(view));
|
||||
self.request_redraw();
|
||||
}
|
||||
|
||||
/// Launch interactive mount-remove dialog (container path).
|
||||
pub fn push_mount_remove_interactive(&mut self) {
|
||||
let view = MountRemoveView::new(self.app_event_tx.clone());
|
||||
|
||||
@@ -463,6 +463,12 @@ impl ChatWidget<'_> {
|
||||
self.request_redraw();
|
||||
}
|
||||
|
||||
/// Launch inspect-env output view.
|
||||
pub fn push_inspect_env(&mut self) {
|
||||
self.bottom_pane.push_inspect_env();
|
||||
self.request_redraw();
|
||||
}
|
||||
|
||||
/// Update the running config and reconstruct bottom pane settings.
|
||||
pub fn update_config(&mut self, config: Config) {
|
||||
self.config = config.clone();
|
||||
|
||||
Reference in New Issue
Block a user