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}