From 43734ec5c9f5056ba53a4c9f88cffb830af4c444 Mon Sep 17 00:00:00 2001 From: Felipe Coury Date: Sat, 30 May 2026 17:54:15 -0300 Subject: [PATCH] feat(tui): polish cwd workspace summary --- codex-rs/tui/src/app/event_dispatch.rs | 82 ++++++++++++++++++-------- 1 file changed, 59 insertions(+), 23 deletions(-) diff --git a/codex-rs/tui/src/app/event_dispatch.rs b/codex-rs/tui/src/app/event_dispatch.rs index 6568caacf4..f9a0f686a0 100644 --- a/codex-rs/tui/src/app/event_dispatch.rs +++ b/codex-rs/tui/src/app/event_dispatch.rs @@ -7,28 +7,51 @@ use super::resize_reflow::trailing_run_start; use super::*; use crate::config_update::format_config_error; use crate::external_agent_config_migration_flow::ExternalAgentConfigMigrationFlowOutcome; +use crate::render::highlight::foreground_style_for_scopes; +use crate::style::accent_style; +use crate::style::table_separator_style; use codex_app_server_protocol::ThreadWorkspaceReadResponse; use codex_app_server_protocol::WorkspaceMutationOperation; #[cfg(target_os = "windows")] use codex_config::types::WindowsSandboxModeToml; +use ratatui::style::Styled; +use ratatui::text::Line; const SHUTDOWN_FIRST_EXIT_TIMEOUT: Duration = Duration::from_secs(/*secs*/ 2); -fn format_thread_workspace(response: &ThreadWorkspaceReadResponse) -> String { - let workspace_roots = if response.runtime_workspace_roots.is_empty() { - " (none)".to_string() - } else { - response - .runtime_workspace_roots - .iter() - .map(|root| format!(" {}", root.as_path().display())) - .collect::>() - .join("\n") - }; - format!( - "Working directory:\n {}\n\nWorkspace roots:\n{workspace_roots}", - response.cwd.as_path().display() - ) +fn thread_workspace_lines(response: &ThreadWorkspaceReadResponse) -> Vec> { + let accent_style = accent_style(); + let label_style = + foreground_style_for_scopes(&["entity.name.type", "support.type", "variable"]) + .unwrap_or(accent_style); + let separator_style = table_separator_style(); + let mut lines = vec![ + vec![ + "• ".set_style(separator_style), + "Workspace".set_style(accent_style), + ] + .into(), + vec![ + " cwd ".set_style(label_style), + response.cwd.as_path().display().to_string().into(), + ] + .into(), + ]; + + let mut roots = response.runtime_workspace_roots.iter(); + let first_root = roots + .next() + .map(|root| root.as_path().display().to_string()) + .unwrap_or_else(|| "(none)".to_string()); + lines.push(vec![" roots ".set_style(label_style), first_root.into()].into()); + lines.extend(roots.map(|root| { + vec![ + " ".set_style(separator_style), + root.as_path().display().to_string().into(), + ] + .into() + })); + lines } impl App { @@ -429,7 +452,7 @@ impl App { match app_server.thread_workspace_read(thread_id).await { Ok(response) => self .chat_widget - .add_info_message(format_thread_workspace(&response), /*hint*/ None), + .add_plain_history_lines(thread_workspace_lines(&response)), Err(err) => self .chat_widget .add_error_message(format!("Failed to read workspace: {err}")), @@ -2381,9 +2404,10 @@ mod tests { use super::*; use codex_utils_absolute_path::test_support::PathBufExt; use codex_utils_absolute_path::test_support::test_path_buf; + use pretty_assertions::assert_eq; #[test] - fn format_thread_workspace_lists_cwd_and_roots_snapshot() { + fn thread_workspace_lines_list_cwd_and_roots_snapshot() { let response = ThreadWorkspaceReadResponse { cwd: test_path_buf("/tmp/current").abs(), runtime_workspace_roots: vec![ @@ -2392,13 +2416,25 @@ mod tests { ], }; - insta::assert_snapshot!(format_thread_workspace(&response), @r" - Working directory: - /tmp/current + let lines = thread_workspace_lines(&response); + assert_eq!(lines[0].spans[1].style, accent_style()); + assert_eq!( + lines[1].spans[0].style, + foreground_style_for_scopes(&["entity.name.type", "support.type", "variable"]) + .unwrap_or_else(accent_style) + ); + assert_eq!(lines[0].spans[0].style, table_separator_style()); - Workspace roots: - /tmp/current - /tmp/shared + let text = lines + .into_iter() + .map(|line| line.to_string()) + .collect::>() + .join("\n"); + insta::assert_snapshot!(text, @r" + • Workspace + cwd /tmp/current + roots /tmp/current + /tmp/shared "); } }