Skip to main content

gwm/tui/state/
command_logs.rs

1//! Command Logs modal state (issue #226).
2//!
3//! The scroll cursor for the lazygit-style Command Logs overlay plus an
4//! owned snapshot of the [`crate::command_log`] global. The `App` calls
5//! [`CommandLogs::sync`] when the overlay opens (and on each tick while it
6//! is up) so the renderer paints from this owned state rather than locking
7//! the global mid-frame — the same boundary the modal-render tests inject
8//! through.
9//!
10//! Scroll mirrors the help overlay (`App::help_scroll` / `help_max_scroll`):
11//! the cursor lives here, but `max_scroll` / `max_x_scroll` are republished
12//! by the renderer each frame against the real viewport, since only the
13//! renderer knows both the content length and the inner modal height. The
14//! scroll methods clamp against whatever bound the last frame published.
15
16use crate::command_log::{self, CommandLogEntry};
17
18/// Owned state for the Command Logs overlay: the snapshotted entries and
19/// the (vertical + horizontal) scroll cursor with its renderer-published
20/// bounds.
21#[derive(Debug, Default)]
22pub struct CommandLogs {
23  /// Snapshot of the global command log, oldest-first (the renderer paints
24  /// it newest-first). Refreshed by [`Self::sync`].
25  pub entries: Vec<CommandLogEntry>,
26  /// Vertical scroll offset, in rows. Clamped to `max_scroll`.
27  pub scroll: u16,
28  /// Maximum vertical scroll offset, republished by the renderer each
29  /// frame as `content_rows.saturating_sub(viewport_rows)`.
30  pub max_scroll: u16,
31  /// Horizontal scroll offset, in columns. Clamped to `max_x_scroll`.
32  pub x_scroll: u16,
33  /// Maximum horizontal scroll offset, republished by the renderer.
34  pub max_x_scroll: u16,
35}
36
37impl CommandLogs {
38  /// An empty overlay at the origin.
39  pub fn new() -> Self {
40    Self::default()
41  }
42
43  /// Copy the global command log into [`Self::entries`]. Cheap clone of a
44  /// bounded ring (≤ `command_log::MAX_ENTRIES`); called on open and per
45  /// tick while the overlay is up so it tracks commands that finish (e.g.
46  /// an off-thread GitHub fetch) while the user is reading.
47  pub fn sync(&mut self) {
48    self.entries = command_log::snapshot();
49  }
50
51  /// Scroll down one row, never past the last line.
52  pub fn scroll_down(&mut self) {
53    self.scroll = (self.scroll + 1).min(self.max_scroll);
54  }
55
56  /// Scroll up one row, never above the top.
57  pub fn scroll_up(&mut self) {
58    self.scroll = self.scroll.saturating_sub(1);
59  }
60
61  /// Scroll right one column, never past the widest line.
62  pub fn scroll_right(&mut self) {
63    self.x_scroll = (self.x_scroll + 1).min(self.max_x_scroll);
64  }
65
66  /// Scroll left one column, never before the first.
67  pub fn scroll_left(&mut self) {
68    self.x_scroll = self.x_scroll.saturating_sub(1);
69  }
70
71  /// Jump to the first row (`g`).
72  pub fn scroll_to_top(&mut self) {
73    self.scroll = 0;
74  }
75
76  /// Jump to the last row (`G`).
77  pub fn scroll_to_bottom(&mut self) {
78    self.scroll = self.max_scroll;
79  }
80
81  /// Reset the scroll cursor to the origin, keeping the snapshotted
82  /// entries. Called when the overlay opens.
83  pub fn reset(&mut self) {
84    self.scroll = 0;
85    self.x_scroll = 0;
86  }
87}