Skip to main content

jk_core/
lib.rs

1//! Shared log records used by `jk` integration and TUI crates.
2//!
3//! `jk-core` keeps the boundary between opaque `jj` rendering and semantic log state explicit. The
4//! rendered log body is still owned by `jj`; `jk` only keeps enough structured metadata to move by
5//! change, preserve selection across refresh, and insert inline details at the right rendered line.
6
7/// A rendered `jj` log view plus semantic records for navigation.
8///
9/// The `rendered` body is opaque terminal text produced by `jj`. Each [`LogEntry`] in `entries`
10/// must correspond to one commit row in that rendered text through [`LogEntry::rendered_line`].
11#[derive(Clone, Debug, Default, Eq, PartialEq)]
12pub struct LogSnapshot {
13    title: String,
14    rendered: String,
15    entries: Vec<LogEntry>,
16}
17
18impl LogSnapshot {
19    /// Creates a log snapshot from rendered terminal text and semantic entries.
20    ///
21    /// The caller is responsible for ensuring each entry's rendered line points at the matching
22    /// commit row in `rendered`.
23    #[must_use]
24    pub fn new(rendered: impl Into<String>, entries: Vec<LogEntry>) -> Self {
25        Self {
26            title: String::new(),
27            rendered: rendered.into(),
28            entries,
29        }
30    }
31
32    /// Sets the command context shown in the title bar.
33    #[must_use]
34    pub fn with_title(mut self, title: impl Into<String>) -> Self {
35        self.title = title.into();
36        self
37    }
38
39    /// Returns the human-readable command context for the current view.
40    #[must_use]
41    pub fn title(&self) -> &str {
42        &self.title
43    }
44
45    /// Returns the opaque output rendered by `jj`.
46    #[must_use]
47    pub fn rendered(&self) -> &str {
48        &self.rendered
49    }
50
51    /// Returns semantic records aligned to the rendered log body.
52    #[must_use]
53    pub fn entries(&self) -> &[LogEntry] {
54        &self.entries
55    }
56
57    /// Consumes the snapshot into its owned title, rendered body, and entries.
58    #[must_use]
59    pub fn into_parts(self) -> (String, String, Vec<LogEntry>) {
60        (self.title, self.rendered, self.entries)
61    }
62}
63
64/// A semantic log item that the TUI can navigate without knowing how `jj` produced it.
65#[derive(Clone, Debug, Eq, PartialEq)]
66pub struct LogEntry {
67    change_id: String,
68    commit_id: String,
69    description: String,
70    details: String,
71    rendered_line: usize,
72}
73
74impl LogEntry {
75    /// Creates a log entry from `jj` change metadata.
76    ///
77    /// New entries default to rendered line `0`; callers that build a [`LogSnapshot`] should set
78    /// the aligned rendered line with [`Self::with_rendered_line`] while constructing entries.
79    #[must_use]
80    pub fn new(
81        change_id: impl Into<String>,
82        commit_id: impl Into<String>,
83        description: impl Into<String>,
84    ) -> Self {
85        Self {
86            change_id: change_id.into(),
87            commit_id: commit_id.into(),
88            description: description.into(),
89            details: String::new(),
90            rendered_line: 0,
91        }
92    }
93
94    /// Sets the template-derived detail text for inline expansion.
95    #[must_use]
96    pub fn with_details(mut self, details: impl Into<String>) -> Self {
97        self.details = details.into();
98        self
99    }
100
101    /// Sets the rendered line for this entry.
102    #[must_use]
103    pub const fn with_rendered_line(mut self, rendered_line: usize) -> Self {
104        self.rendered_line = rendered_line;
105        self
106    }
107
108    /// Returns the stable `jj` change identifier.
109    #[must_use]
110    pub fn change_id(&self) -> &str {
111        &self.change_id
112    }
113
114    /// Returns the commit identifier for follow-up inspection commands.
115    #[must_use]
116    pub fn commit_id(&self) -> &str {
117        &self.commit_id
118    }
119
120    /// Returns the full commit description.
121    #[must_use]
122    pub fn description(&self) -> &str {
123        &self.description
124    }
125
126    /// Returns template-derived detail text for inline expansion.
127    #[must_use]
128    pub fn details(&self) -> &str {
129        &self.details
130    }
131
132    /// Returns the zero-based line in the rendered `jj` output for this entry.
133    #[must_use]
134    pub const fn rendered_line(&self) -> usize {
135        self.rendered_line
136    }
137
138    /// Returns the first line of the commit description, or a placeholder for empty descriptions.
139    #[must_use]
140    pub fn summary(&self) -> &str {
141        let first_line = self.description.lines().next().unwrap_or_default();
142        if first_line.is_empty() {
143            "(no description set)"
144        } else {
145            first_line
146        }
147    }
148}