From 235987843c3d6647c0819c1071f9b9f064673e9c Mon Sep 17 00:00:00 2001
From: Jeremy Rose <172423086+nornagon-openai@users.noreply.github.com>
Date: Thu, 14 Aug 2025 19:32:45 -0400
Subject: [PATCH] add a timer to running exec commands (#2321)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
sometimes i switch back to codex and i don't know how long a command has
been running.
---
codex-rs/tui/src/history_cell.rs | 84 ++++++++++++++++++++++----------
1 file changed, 59 insertions(+), 25 deletions(-)
diff --git a/codex-rs/tui/src/history_cell.rs b/codex-rs/tui/src/history_cell.rs
index 19fdfc4a..ba645a7e 100644
--- a/codex-rs/tui/src/history_cell.rs
+++ b/codex-rs/tui/src/history_cell.rs
@@ -35,6 +35,7 @@ use std::collections::HashMap;
use std::io::Cursor;
use std::path::PathBuf;
use std::time::Duration;
+use std::time::Instant;
use tracing::error;
#[derive(Clone)]
@@ -78,10 +79,16 @@ pub(crate) struct ExecCell {
pub(crate) command: Vec,
pub(crate) parsed: Vec,
pub(crate) output: Option,
+ start_time: Option,
}
impl HistoryCell for ExecCell {
fn display_lines(&self) -> Vec> {
- exec_command_lines(&self.command, &self.parsed, self.output.as_ref())
+ exec_command_lines(
+ &self.command,
+ &self.parsed,
+ self.output.as_ref(),
+ self.start_time,
+ )
}
}
@@ -199,49 +206,66 @@ pub(crate) fn new_active_exec_command(
command: Vec,
parsed: Vec,
) -> ExecCell {
- new_exec_cell(command, parsed, None)
+ ExecCell {
+ command,
+ parsed,
+ output: None,
+ start_time: Some(Instant::now()),
+ }
}
pub(crate) fn new_completed_exec_command(
command: Vec,
parsed: Vec,
output: CommandOutput,
-) -> ExecCell {
- new_exec_cell(command, parsed, Some(output))
-}
-
-fn new_exec_cell(
- command: Vec,
- parsed: Vec,
- output: Option,
) -> ExecCell {
ExecCell {
command,
parsed,
- output,
+ output: Some(output),
+ start_time: None,
}
}
+fn exec_duration(start: Instant) -> String {
+ format!("{}s", start.elapsed().as_secs())
+}
+
fn exec_command_lines(
command: &[String],
parsed: &[ParsedCommand],
output: Option<&CommandOutput>,
+ start_time: Option,
) -> Vec> {
match parsed.is_empty() {
- true => new_exec_command_generic(command, output),
- false => new_parsed_command(parsed, output),
+ true => new_exec_command_generic(command, output, start_time),
+ false => new_parsed_command(parsed, output, start_time),
}
}
-
fn new_parsed_command(
parsed_commands: &[ParsedCommand],
output: Option<&CommandOutput>,
+ start_time: Option,
) -> Vec> {
- let mut lines: Vec = vec![match output {
- None => Line::from("⚙︎ Working".magenta().bold()),
- Some(o) if o.exit_code == 0 => Line::from("✓ Completed".green().bold()),
- Some(o) => Line::from(format!("✗ Failed (exit {})", o.exit_code).red().bold()),
- }];
+ let mut lines: Vec = Vec::new();
+ match output {
+ None => {
+ let mut spans = vec!["⚙︎ Working".magenta().bold()];
+ if let Some(st) = start_time {
+ let dur = exec_duration(st);
+ spans.push(format!(" • {dur}").dim());
+ }
+ lines.push(Line::from(spans));
+ }
+ Some(o) if o.exit_code == 0 => {
+ lines.push(Line::from("✓ Completed".green().bold()));
+ }
+ Some(o) => {
+ lines.push(Line::from(
+ format!("✗ Failed (exit {})", o.exit_code).red().bold(),
+ ));
+ }
+ };
for (i, parsed) in parsed_commands.iter().enumerate() {
let text = match parsed {
@@ -282,17 +306,27 @@ fn new_parsed_command(
fn new_exec_command_generic(
command: &[String],
output: Option<&CommandOutput>,
+ start_time: Option,
) -> Vec> {
let mut lines: Vec> = Vec::new();
let command_escaped = strip_bash_lc_and_escape(command);
let mut cmd_lines = command_escaped.lines();
if let Some(first) = cmd_lines.next() {
- lines.push(Line::from(vec![
- "⚡ Running ".to_string().magenta(),
- first.to_string().into(),
- ]));
+ let mut spans: Vec = vec!["⚡ Running".magenta()];
+ if let Some(st) = start_time {
+ let dur = exec_duration(st);
+ spans.push(format!(" • {dur}").dim());
+ }
+ spans.push(" ".into());
+ spans.push(first.to_string().into());
+ lines.push(Line::from(spans));
} else {
- lines.push(Line::from("⚡ Running".to_string().magenta()));
+ let mut spans: Vec = vec!["⚡ Running".magenta()];
+ if let Some(st) = start_time {
+ let dur = exec_duration(st);
+ spans.push(format!(" • {dur}").dim());
+ }
+ lines.push(Line::from(spans));
}
for cont in cmd_lines {
lines.push(Line::from(cont.to_string()));
@@ -866,7 +900,7 @@ mod tests {
let parsed = vec![ParsedCommand::Unknown {
cmd: "printf 'foo\nbar'".to_string(),
}];
- let lines = exec_command_lines(&[], &parsed, None);
+ let lines = exec_command_lines(&[], &parsed, None, None);
assert!(lines.len() >= 3);
assert_eq!(lines[1].spans[0].content, " └ ");
assert_eq!(lines[2].spans[0].content, " ");