add a timer to running exec commands (#2321)

sometimes i switch back to codex and i don't know how long a command has
been running.

<img width="744" height="462" alt="Screenshot 2025-08-14 at 3 30 07 PM"
src="https://github.com/user-attachments/assets/bd80947f-5a47-43e6-ad19-69c2995a2a29"
/>
This commit is contained in:
Jeremy Rose
2025-08-14 19:32:45 -04:00
committed by GitHub
parent 6a0f709cff
commit 235987843c

View File

@@ -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<String>,
pub(crate) parsed: Vec<ParsedCommand>,
pub(crate) output: Option<CommandOutput>,
start_time: Option<Instant>,
}
impl HistoryCell for ExecCell {
fn display_lines(&self) -> Vec<Line<'static>> {
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<String>,
parsed: Vec<ParsedCommand>,
) -> 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<String>,
parsed: Vec<ParsedCommand>,
output: CommandOutput,
) -> ExecCell {
new_exec_cell(command, parsed, Some(output))
}
fn new_exec_cell(
command: Vec<String>,
parsed: Vec<ParsedCommand>,
output: Option<CommandOutput>,
) -> 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<Instant>,
) -> Vec<Line<'static>> {
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<Instant>,
) -> Vec<Line<'static>> {
let mut lines: Vec<Line> = 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<Line> = 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<Instant>,
) -> Vec<Line<'static>> {
let mut lines: Vec<Line<'static>> = 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<Span> = 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<Span> = 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, " ");