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
7mod command;
8mod command_history;
9
10pub use command::{
11    ColorPolicy, CommandPreview, CommandPreviewWarning, ConfigOverlay, ExecutionMode,
12    GlobalOptions, ImmutabilityPolicy, JjCommandSpec, OperationIntegrationPolicy,
13    OperationLoadPolicy, OutputPolicy, PagerPolicy, RefreshPlan, SafetyClass, WorkingCopyPolicy,
14};
15pub use command_history::{
16    CommandExecutionContext, CommandFamily, CommandHistory, CommandIdentity, CommandRecord,
17    CommandRecordFinish, CommandRecordId, CommandRecordStart, CommandResultSummary, CommandSource,
18    CommandTiming, ExitStatusSummary, GlobalOptionsSnapshot, OutputRetention, PendingCommandRecord,
19    SourceAction, SourceView, StreamSummary,
20};
21
22/// A rendered `jj` log view plus semantic records for navigation.
23///
24/// The `rendered` body is opaque terminal text produced by `jj`. Each [`LogEntry`] in `entries`
25/// must correspond to one commit row in that rendered text through [`LogEntry::rendered_line`].
26#[derive(Clone, Debug, Default, Eq, PartialEq)]
27pub struct LogSnapshot {
28    title: String,
29    rendered: String,
30    entries: Vec<LogEntry>,
31}
32
33impl LogSnapshot {
34    /// Creates a log snapshot from rendered terminal text and semantic entries.
35    ///
36    /// The caller is responsible for ensuring each entry's rendered line points at the matching
37    /// commit row in `rendered`.
38    #[must_use]
39    pub fn new(rendered: impl Into<String>, entries: Vec<LogEntry>) -> Self {
40        Self {
41            title: String::new(),
42            rendered: rendered.into(),
43            entries,
44        }
45    }
46
47    /// Sets the command context shown in the title bar.
48    #[must_use]
49    pub fn with_title(mut self, title: impl Into<String>) -> Self {
50        self.title = title.into();
51        self
52    }
53
54    /// Returns the human-readable command context for the current view.
55    #[must_use]
56    pub fn title(&self) -> &str {
57        &self.title
58    }
59
60    /// Returns the opaque output rendered by `jj`.
61    #[must_use]
62    pub fn rendered(&self) -> &str {
63        &self.rendered
64    }
65
66    /// Returns semantic records aligned to the rendered log body.
67    #[must_use]
68    pub fn entries(&self) -> &[LogEntry] {
69        &self.entries
70    }
71
72    /// Consumes the snapshot into its owned title, rendered body, and entries.
73    #[must_use]
74    pub fn into_parts(self) -> (String, String, Vec<LogEntry>) {
75        (self.title, self.rendered, self.entries)
76    }
77}
78
79/// A rendered `jj diff` view for one change.
80#[derive(Clone, Debug, Default, Eq, PartialEq)]
81pub struct DiffSnapshot {
82    title: String,
83    change_id: String,
84    rendered: String,
85    file_stats: Vec<DiffFileStat>,
86}
87
88impl DiffSnapshot {
89    /// Creates a diff snapshot from the target change and rendered terminal text.
90    #[must_use]
91    pub fn new(change_id: impl Into<String>, rendered: impl Into<String>) -> Self {
92        Self {
93            title: String::new(),
94            change_id: change_id.into(),
95            rendered: rendered.into(),
96            file_stats: Vec::new(),
97        }
98    }
99
100    /// Sets the command context shown in the title bar.
101    #[must_use]
102    pub fn with_title(mut self, title: impl Into<String>) -> Self {
103        self.title = title.into();
104        self
105    }
106
107    /// Sets per-file line counts for the rendered diff.
108    #[must_use]
109    pub fn with_file_stats(mut self, file_stats: Vec<DiffFileStat>) -> Self {
110        self.file_stats = file_stats;
111        self
112    }
113
114    /// Returns the human-readable command context for the current view.
115    #[must_use]
116    pub fn title(&self) -> &str {
117        &self.title
118    }
119
120    /// Returns the `jj` change identifier being inspected.
121    #[must_use]
122    pub fn change_id(&self) -> &str {
123        &self.change_id
124    }
125
126    /// Returns the opaque output rendered by `jj`.
127    #[must_use]
128    pub fn rendered(&self) -> &str {
129        &self.rendered
130    }
131
132    /// Returns per-file line counts for changed files.
133    #[must_use]
134    pub fn file_stats(&self) -> &[DiffFileStat] {
135        &self.file_stats
136    }
137
138    /// Consumes the snapshot into its owned title, target change, and rendered body.
139    #[must_use]
140    pub fn into_parts(self) -> (String, String, String, Vec<DiffFileStat>) {
141        (self.title, self.change_id, self.rendered, self.file_stats)
142    }
143}
144
145/// A rendered read-only `jj` inspection view.
146#[derive(Clone, Debug, Default, Eq, PartialEq)]
147pub struct InspectionSnapshot {
148    title: String,
149    target: String,
150    rendered: String,
151}
152
153impl InspectionSnapshot {
154    /// Creates an inspection snapshot from the target label and rendered terminal text.
155    #[must_use]
156    pub fn new(target: impl Into<String>, rendered: impl Into<String>) -> Self {
157        Self {
158            title: String::new(),
159            target: target.into(),
160            rendered: rendered.into(),
161        }
162    }
163
164    /// Sets the command context shown in the title bar.
165    #[must_use]
166    pub fn with_title(mut self, title: impl Into<String>) -> Self {
167        self.title = title.into();
168        self
169    }
170
171    /// Returns the human-readable command context for the current view.
172    #[must_use]
173    pub fn title(&self) -> &str {
174        &self.title
175    }
176
177    /// Returns the inspected target label.
178    #[must_use]
179    pub fn target(&self) -> &str {
180        &self.target
181    }
182
183    /// Returns the opaque output rendered by `jj`.
184    #[must_use]
185    pub fn rendered(&self) -> &str {
186        &self.rendered
187    }
188
189    /// Consumes the snapshot into its owned title, target, and rendered body.
190    #[must_use]
191    pub fn into_parts(self) -> (String, String, String) {
192        (self.title, self.target, self.rendered)
193    }
194}
195
196/// Per-file line counts for a rendered diff.
197#[derive(Clone, Debug, Default, Eq, PartialEq)]
198pub struct DiffFileStat {
199    path: String,
200    added: usize,
201    removed: usize,
202    rendered: String,
203}
204
205impl DiffFileStat {
206    /// Creates line-count stats for one displayed diff path.
207    #[must_use]
208    pub fn new(path: impl Into<String>, added: usize, removed: usize) -> Self {
209        Self {
210            path: path.into(),
211            added,
212            removed,
213            rendered: String::new(),
214        }
215    }
216
217    /// Sets the rendered `--stat` suffix for this path, including the separator.
218    #[must_use]
219    pub fn with_rendered(mut self, rendered: impl Into<String>) -> Self {
220        self.rendered = rendered.into();
221        self
222    }
223
224    /// Returns the displayed diff path.
225    #[must_use]
226    pub fn path(&self) -> &str {
227        &self.path
228    }
229
230    /// Returns the number of added lines.
231    #[must_use]
232    pub const fn added(&self) -> usize {
233        self.added
234    }
235
236    /// Returns the number of removed lines.
237    #[must_use]
238    pub const fn removed(&self) -> usize {
239        self.removed
240    }
241
242    /// Returns the rendered `--stat` suffix for this path.
243    #[must_use]
244    pub fn rendered(&self) -> &str {
245        &self.rendered
246    }
247}
248
249/// A semantic log item that the TUI can navigate without knowing how `jj` produced it.
250#[derive(Clone, Debug, Eq, PartialEq)]
251pub struct LogEntry {
252    change_id: String,
253    commit_id: String,
254    description: String,
255    details: String,
256    rendered_line: usize,
257}
258
259impl LogEntry {
260    /// Creates a log entry from `jj` change metadata.
261    ///
262    /// New entries default to rendered line `0`; callers that build a [`LogSnapshot`] should set
263    /// the aligned rendered line with [`Self::with_rendered_line`] while constructing entries.
264    #[must_use]
265    pub fn new(
266        change_id: impl Into<String>,
267        commit_id: impl Into<String>,
268        description: impl Into<String>,
269    ) -> Self {
270        Self {
271            change_id: change_id.into(),
272            commit_id: commit_id.into(),
273            description: description.into(),
274            details: String::new(),
275            rendered_line: 0,
276        }
277    }
278
279    /// Sets the template-derived detail text for inline expansion.
280    #[must_use]
281    pub fn with_details(mut self, details: impl Into<String>) -> Self {
282        self.details = details.into();
283        self
284    }
285
286    /// Sets the rendered line for this entry.
287    #[must_use]
288    pub const fn with_rendered_line(mut self, rendered_line: usize) -> Self {
289        self.rendered_line = rendered_line;
290        self
291    }
292
293    /// Returns the stable `jj` change identifier.
294    #[must_use]
295    pub fn change_id(&self) -> &str {
296        &self.change_id
297    }
298
299    /// Returns the commit identifier for follow-up inspection commands.
300    #[must_use]
301    pub fn commit_id(&self) -> &str {
302        &self.commit_id
303    }
304
305    /// Returns the full commit description.
306    #[must_use]
307    pub fn description(&self) -> &str {
308        &self.description
309    }
310
311    /// Returns template-derived detail text for inline expansion.
312    #[must_use]
313    pub fn details(&self) -> &str {
314        &self.details
315    }
316
317    /// Returns the zero-based line in the rendered `jj` output for this entry.
318    #[must_use]
319    pub const fn rendered_line(&self) -> usize {
320        self.rendered_line
321    }
322
323    /// Returns the first line of the commit description, or a placeholder for empty descriptions.
324    #[must_use]
325    pub fn summary(&self) -> &str {
326        let first_line = self.description.lines().next().unwrap_or_default();
327        if first_line.is_empty() {
328            "(no description set)"
329        } else {
330            first_line
331        }
332    }
333}