1use crate::cli::Output;
7use crate::error::{RecError, Result};
8use crate::models::Session;
9
10pub fn show_session(
23 session: &Session,
24 grep: Option<&str>,
25 json: bool,
26 output: &Output,
27) -> Result<()> {
28 if json {
29 let json_str = serde_json::to_string_pretty(session)
30 .map_err(|e| RecError::InvalidSession(format!("Failed to serialize session: {e}")))?;
31 println!("{json_str}");
32 return Ok(());
33 }
34
35 let re = match grep {
37 Some(pattern) => {
38 let compiled = regex::Regex::new(pattern).map_err(|e| {
39 RecError::InvalidSession(format!("Invalid grep pattern '{pattern}': {e}"))
40 })?;
41 Some(compiled)
42 }
43 None => None,
44 };
45
46 println!();
48 output.success(&format!("Session: {}", session.name()));
49 println!();
50 println!(" ID: {}", session.id());
51
52 if let Some(dt) = chrono::DateTime::from_timestamp(
54 session.header.started_at as i64,
55 ((session.header.started_at.fract()) * 1_000_000_000.0) as u32,
56 ) {
57 let local: chrono::DateTime<chrono::Local> = dt.into();
58 println!(" Date: {}", local.format("%Y-%m-%d %H:%M"));
59 }
60
61 println!(" Shell: {}", session.header.shell);
62 println!(" OS: {}", session.header.os);
63 println!(" Hostname: {}", session.header.hostname);
64
65 if let Some(pwd) = session.header.env.get("PWD") {
67 println!(" Directory: {pwd}");
68 }
69
70 if let Some(ref footer) = session.footer {
72 let duration_secs = footer.ended_at - session.header.started_at;
73 println!(" Duration: {}", format_duration(duration_secs));
74 println!(" Commands: {}", footer.command_count);
75 } else {
76 println!(
77 " Commands: {} (session still recording)",
78 session.commands.len()
79 );
80 }
81
82 if !session.header.tags.is_empty() {
84 println!(" Tags: {}", session.header.tags.join(", "));
85 }
86
87 println!();
89 println!(" {}", "-".repeat(60));
90 println!();
91
92 if session.commands.is_empty() {
94 output.info(" No commands recorded in this session.");
95 return Ok(());
96 }
97
98 let mut shown_count = 0;
99 for cmd in &session.commands {
100 if let Some(ref re) = re {
102 if !re.is_match(&cmd.command) {
103 continue;
104 }
105 }
106
107 shown_count += 1;
108 let index = cmd.index + 1; let cwd_str = cmd.cwd.display().to_string();
110
111 let exit_str = match cmd.exit_code {
113 Some(0) => output.style_success("exit:0"),
114 Some(code) => output.style_error(&format!("exit:{code}")),
115 None => "running".to_string(),
116 };
117
118 let dur_str = match cmd.duration_ms {
120 Some(ms) if ms >= 1000 => format!("{:.1}s", ms as f64 / 1000.0),
121 Some(ms) => format!("{ms}ms"),
122 None => String::new(),
123 };
124
125 println!(
126 " {:>3}. {} [{}] {} {}",
127 index, cmd.command, cwd_str, exit_str, dur_str
128 );
129 }
130
131 if re.is_some() {
132 println!();
133 output.info(&format!(
134 " Showing {} of {} commands (filtered by grep)",
135 shown_count,
136 session.commands.len()
137 ));
138 }
139
140 println!();
141 Ok(())
142}
143
144fn format_duration(seconds: f64) -> String {
146 let total_secs = seconds as u64;
147 let hours = total_secs / 3600;
148 let minutes = (total_secs % 3600) / 60;
149 let secs = total_secs % 60;
150
151 if hours > 0 {
152 format!("{hours}h {minutes}m {secs}s")
153 } else if minutes > 0 {
154 format!("{minutes}m {secs}s")
155 } else {
156 format!("{secs}s")
157 }
158}