Tree changes disappeared when their loaded diff had no parent, range, or paths, unlike the clean worktree block. Separate pane rendering from focusability so an available empty Tree block remains visible, reports clean, and stays out of Tab navigation.
Unified · Split
| @@ -436,7 +436,8 @@ space first; changes blocks adapt within the remaining history width. · 435 unmodified lines | |||
| 436 | - Each block has a top border carrying its compact summary. Tree summaries show | 436 | - Each block has a top border carrying its compact summary. Tree summaries show |
| 437 | the selected short hash; worktree summaries distinguish staged and unstaged | 437 | the selected short hash; worktree summaries distinguish staged and unstaged |
| 438 | counts. Kind totals, total files when non-redundant, and non-zero line totals | 438 | counts. Kind totals, total files when non-redundant, and non-zero line totals |
| 439 | are color-coded. An empty enabled worktree says `Worktree clean` in green. | 439 | are color-coded. Empty enabled Tree and Worktree blocks remain visible, say |
| 440 | `clean` in green, and are not focusable. | ||
| 440 | - Tree paths preserve tree-diff order. Worktree paths show staged entries first in | 441 | - Tree paths preserve tree-diff order. Worktree paths show staged entries first in |
| 441 | green and unstaged/untracked/conflicted entries second in bright red, sorted by | 442 | green and unstaged/untracked/conflicted entries second in bright red, sorted by |
| 442 | raw path within each group. | 443 | raw path within each group. |
| @@ -290,8 +290,8 @@ pub(crate) fn draw_with_worktree( · 289 unmodified lines | |||
| 290 | } else { | 290 | } else { |
| 291 | None | 291 | None |
| 292 | }; | 292 | }; |
| 293 | let tree_visible = app.changes_visible() && tree_changes.is_some_and(Changes::is_visible); | 293 | let tree_shown = app.changes_visible() && tree_changes.is_some(); |
| 294 | let worktree_visible = | 294 | let worktree_shown = |
| 295 | app.changes_visible() && app.changes_mode == Some(ChangesMode::Both) && worktree_changes.is_some(); | 295 | app.changes_visible() && app.changes_mode == Some(ChangesMode::Both) && worktree_changes.is_some(); |
| 296 | let tree_summary = tree_changes.map(|changes| changes_summary(ChangePane::Tree, app, changes)); | 296 | let tree_summary = tree_changes.map(|changes| changes_summary(ChangePane::Tree, app, changes)); |
| 297 | let worktree_summary = worktree_changes.map(|changes| changes_summary(ChangePane::Worktree, app, changes)); | 297 | let worktree_summary = worktree_changes.map(|changes| changes_summary(ChangePane::Worktree, app, changes)); |
| @@ -310,13 +310,13 @@ pub(crate) fn draw_with_worktree( · 12 unmodified lines | |||
| 310 | let (changes_layout, changes_panes, _) = changes_pane_areas( | 310 | let (changes_layout, changes_panes, _) = changes_pane_areas( |
| 311 | body, | 311 | body, |
| 312 | frame.area().height / 2, | 312 | frame.area().height / 2, |
| 313 | tree_visible.then(|| { | 313 | tree_shown.then(|| { |
| 314 | ( | 314 | ( |
| 315 | pane_height(tree_changes.expect("visible tree changes exist")), | 315 | pane_height(tree_changes.expect("visible tree changes exist")), |
| 316 | tree_summary.as_ref().map_or(0, Line::width), | 316 | tree_summary.as_ref().map_or(0, Line::width), |
| 317 | ) | 317 | ) |
| 318 | }), | 318 | }), |
| 319 | worktree_visible.then(|| { | 319 | worktree_shown.then(|| { |
| 320 | ( | 320 | ( |
| 321 | pane_height(worktree_changes.expect("visible worktree changes exist")), | 321 | pane_height(worktree_changes.expect("visible worktree changes exist")), |
| 322 | worktree_summary.as_ref().map_or(0, Line::width), | 322 | worktree_summary.as_ref().map_or(0, Line::width), |
| @@ -406,7 +406,8 @@ pub(crate) fn draw_with_worktree( · 83 unmodified lines | |||
| 406 | changes_layout, | 406 | changes_layout, |
| 407 | changes_panes | 407 | changes_panes |
| 408 | .iter() | 408 | .iter() |
| 409 | .any(|pane| pane.pane == ChangePane::Tree && pane.outer.height > 0), | 409 | .any(|pane| pane.pane == ChangePane::Tree && pane.outer.height > 0) |
| 410 | && tree_changes.is_some_and(Changes::is_visible), | ||
| 410 | changes_panes | 411 | changes_panes |
| 411 | .iter() | 412 | .iter() |
| 412 | .any(|pane| pane.pane == ChangePane::Worktree && pane.outer.height > 0) | 413 | .any(|pane| pane.pane == ChangePane::Worktree && pane.outer.height > 0) |
| @@ -1436,7 +1437,13 @@ fn changes_summary(pane: ChangePane, app: &App, changes: &Changes) -> Line<'stat · 1023 unmodified lines | |||
| 1436 | }, | 1437 | }, |
| 1437 | |range| format!("{}..{}", range.base.to_hex_with_len(7), range.tip.to_hex_with_len(7)), | 1438 | |range| format!("{}..{}", range.base.to_hex_with_len(7), range.tip.to_hex_with_len(7)), |
| 1438 | ); | 1439 | ); |
| 1439 | vec![Span::raw(format!("─ Tree {label} ── "))] | 1440 | let mut spans = vec![Span::raw(format!("─ Tree {label} "))]; |
| 1441 | if changes.paths.is_empty() { | ||
| 1442 | spans.push(Span::styled("clean", color(Color::Green))); | ||
| 1443 | spans.push(Span::raw(" ")); | ||
| 1444 | } | ||
| 1445 | spans.push(Span::raw("── ")); | ||
| 1446 | spans | ||
| 1440 | } | 1447 | } |
| 1441 | ChangePane::Worktree if changes.paths.is_empty() => vec![ | 1448 | ChangePane::Worktree if changes.paths.is_empty() => vec![ |
| 1442 | Span::raw("─ Worktree "), | 1449 | Span::raw("─ Worktree "), |
| @@ -4771,6 +4778,25 @@ mod tests { · 3328 unmodified lines | |||
| 4771 | "a clean worktree has no empty aggregate" | 4778 | "a clean worktree has no empty aggregate" |
| 4772 | ); | 4779 | ); |
| 4773 | assert!(!app.worktree_changes_visible, "an empty block is not focusable"); | 4780 | assert!(!app.worktree_changes_visible, "an empty block is not focusable"); |
| 4781 | |||
| 4782 | terminal.draw(|frame| { | ||
| 4783 | super::draw_with_worktree( | ||
| 4784 | frame, | ||
| 4785 | &mut app, | ||
| 4786 | &Decorations::new(), | ||
| 4787 | &gix::mailmap::Snapshot::default(), | ||
| 4788 | None, | ||
| 4789 | Some(&Changes::default()), | ||
| 4790 | Some(&Changes::default()), | ||
| 4791 | ); | ||
| 4792 | })?; | ||
| 4793 | let (clean_y, clean_tree) = (0..8) | ||
| 4794 | .map(|y| (y, rendered_line(&terminal, y))) | ||
| 4795 | .find(|(_, line)| line.contains("Tree ------- clean")) | ||
| 4796 | .expect("an empty tree remains visible and says it is clean"); | ||
| 4797 | let clean_x = clean_tree.find("clean").expect("clean tree label") as u16; | ||
| 4798 | assert_eq!(terminal.backend().buffer()[(clean_x, clean_y)].fg, Color::Green); | ||
| 4799 | assert!(!app.tree_changes_visible, "an empty tree block is not focusable"); | ||
| 4774 | Ok(()) | 4800 | Ok(()) |
| 4775 | } | 4801 | } |
| 4776 | 4802 | ||