|
1 | 1 | //! The stacked, collapsible dashboard. |
2 | 2 |
|
3 | 3 | use std::collections::HashSet; |
4 | | -use std::time::{Duration, Instant}; |
| 4 | +use std::time::{Duration, Instant, UNIX_EPOCH}; |
5 | 5 |
|
6 | 6 | use ratatui::Frame; |
7 | 7 | use ratatui::layout::Rect; |
8 | 8 | use ratatui::style::{Modifier, Style}; |
9 | 9 | use ratatui::text::{Line, Span}; |
10 | 10 | use ratatui::widgets::{Block, BorderType, Paragraph}; |
11 | 11 |
|
12 | | -use super::fmt::{clock, rel_age, truncate_left}; |
| 12 | +use super::fmt::{clock, truncate_left, width}; |
13 | 13 | use super::layout::{Density, SectionId, SectionReq, Slot, allocate}; |
14 | 14 | use super::theme::Theme; |
15 | | -use super::{leak_marker, line_lr, now_secs, spans_width, truncate_spans}; |
| 15 | +use super::{age_of, leak_marker, line_lr, spans_width, truncate_spans}; |
16 | 16 | use crate::activity::{ActivityKind, merged}; |
17 | 17 | use crate::app::{Action, App, NavItem, ScrollTarget, Target}; |
18 | 18 | use crate::model::{Change, Commit, Counts, Snapshot}; |
19 | 19 |
|
20 | 20 | /// Most activity rows kept in the section. |
21 | 21 | const ACTIVITY_ROWS: usize = 100; |
22 | 22 | const METER_CELLS: usize = 5; |
| 23 | +/// Path cells a change row keeps before it gives up its age. |
| 24 | +const MIN_PATH: usize = 20; |
| 25 | +/// Cells set aside for a change's age: a space and up to `12mo`. |
| 26 | +const AGE_RESERVE: usize = 5; |
23 | 27 |
|
24 | 28 | struct Row { |
25 | 29 | line: Line<'static>, |
@@ -253,7 +257,7 @@ fn pad_right(mut spans: Vec<Span<'static>>) -> Vec<Span<'static>> { |
253 | 257 |
|
254 | 258 | fn title_spans(s: &Section, expanded: bool, theme: &Theme) -> Vec<Span<'static>> { |
255 | 259 | let mut spans = vec![ |
256 | | - Span::styled(if expanded { "▾ " } else { "▸ " }, theme.dim), |
| 260 | + Span::styled(if expanded { "▼ " } else { "▶ " }, theme.dim), |
257 | 261 | Span::styled(s.id.title(), Style::new().add_modifier(Modifier::BOLD)), |
258 | 262 | ]; |
259 | 263 | if let Some(n) = s.count { |
@@ -435,12 +439,28 @@ fn changes(snap: &Snapshot, app: &App, theme: &Theme, w: usize, d: Density) -> S |
435 | 439 | .filter(|l| l.source.oid().is_none()) |
436 | 440 | .map(|l| l.path.as_str()) |
437 | 441 | .collect(); |
| 442 | + // The age goes before the squares do: only while the path keeps room. |
| 443 | + let fixed = 3 + stats_w + if d.stats { 1 + METER_CELLS } else { 0 } + 2; |
| 444 | + let path_room = w.saturating_sub(fixed + 1); |
| 445 | + let ages: Vec<Option<String>> = if d.ages && path_room >= MIN_PATH + AGE_RESERVE { |
| 446 | + snap.changes |
| 447 | + .iter() |
| 448 | + .map(|c| { |
| 449 | + let t = c.modified?.duration_since(UNIX_EPOCH).ok()?; |
| 450 | + Some(age_of(app, t.as_secs() as i64)) |
| 451 | + }) |
| 452 | + .collect() |
| 453 | + } else { |
| 454 | + Vec::new() |
| 455 | + }; |
| 456 | + let age_w = ages.iter().flatten().map(|a| width(a)).max(); |
438 | 457 | let ttl = Duration::from_secs(app.pulse_secs); |
439 | 458 | let now = Instant::now(); |
440 | 459 | let mut rows: Vec<Row> = snap |
441 | 460 | .changes |
442 | 461 | .iter() |
443 | | - .map(|c| { |
| 462 | + .enumerate() |
| 463 | + .map(|(i, c)| { |
444 | 464 | let mut left = status_spans(c, theme); |
445 | 465 | left.push(Span::raw(" ")); |
446 | 466 | let mut right = Vec::new(); |
@@ -470,6 +490,10 @@ fn changes(snap: &Snapshot, app: &App, theme: &Theme, w: usize, d: Density) -> S |
470 | 490 | }; |
471 | 491 | right.push(Span::raw(" ")); |
472 | 492 | right.push(pulse); |
| 493 | + if let Some(age_w) = age_w { |
| 494 | + let age = ages[i].as_deref().unwrap_or(""); |
| 495 | + right.push(Span::styled(format!(" {age:>age_w$}"), theme.dim)); |
| 496 | + } |
473 | 497 | let display = match &c.orig_path { |
474 | 498 | Some(orig) => format!("{orig} → {}", c.path), |
475 | 499 | None => c.path.clone(), |
@@ -543,7 +567,7 @@ fn activity(snap: &Snapshot, app: &App, theme: &Theme, w: usize, d: Density) -> |
543 | 567 |
|
544 | 568 | fn age_span(time: i64, app: &App, theme: &Theme, d: Density) -> Vec<Span<'static>> { |
545 | 569 | if d.ages { |
546 | | - vec![Span::styled(rel_age(now_secs(app) - time), theme.dim)] |
| 570 | + vec![Span::styled(age_of(app, time), theme.dim)] |
547 | 571 | } else { |
548 | 572 | Vec::new() |
549 | 573 | } |
|
0 commit comments