1use std::fmt;
8use std::time::Duration;
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub enum Chunk<'a> {
13 Equal(&'a str),
15 Delete(&'a str),
17 Insert(&'a str),
19}
20
21#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
23#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
24#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
25pub enum DiffAlgorithm {
26 #[default]
28 Myers,
29 Patience,
31 Histogram,
33}
34
35impl DiffAlgorithm {
36 pub(crate) fn similar(self) -> similar::Algorithm {
37 match self {
38 Self::Myers => similar::Algorithm::Myers,
39 Self::Patience => similar::Algorithm::Patience,
40 Self::Histogram => similar::Algorithm::Histogram,
41 }
42 }
43}
44
45#[derive(Debug, Clone)]
47pub struct DiffOptions<'a> {
48 pub context_lines: usize,
50 pub old_label: Option<&'a str>,
52 pub new_label: Option<&'a str>,
54 pub missing_newline_hint: bool,
56 pub algorithm: DiffAlgorithm,
58 pub timeout: Duration,
60 pub inline_timeout: Duration,
62}
63
64impl Default for DiffOptions<'_> {
65 fn default() -> Self {
66 Self {
67 context_lines: 3,
68 old_label: None,
69 new_label: None,
70 missing_newline_hint: true,
71 algorithm: DiffAlgorithm::Myers,
72 timeout: Duration::from_millis(200),
73 inline_timeout: Duration::from_millis(40),
74 }
75 }
76}
77
78#[derive(Debug, Clone, PartialEq, Eq)]
80#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
81pub struct DiffHunk {
82 pub old_start: usize,
84 pub old_lines: usize,
86 pub new_start: usize,
88 pub new_lines: usize,
90 pub lines: Vec<DiffLine>,
92}
93
94#[derive(Debug, Clone, Copy, PartialEq, Eq)]
96#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
97#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
98pub enum DiffLineKind {
99 Context,
101 Addition,
103 Deletion,
105}
106
107#[derive(Debug, Clone, PartialEq, Eq)]
109#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
110pub struct DiffLine {
111 pub kind: DiffLineKind,
113 pub old_line: Option<u32>,
115 pub new_line: Option<u32>,
117 pub text: String,
119}
120
121#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
123#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
124pub struct DiffStats {
125 pub additions: usize,
127 pub deletions: usize,
129 pub context: usize,
131 pub hunks: usize,
133 pub omitted_rows: usize,
135}
136
137#[derive(Debug, Clone, PartialEq, Eq)]
139#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
140pub struct DiffDocument {
141 pub hunks: Vec<DiffHunk>,
143 pub stats: DiffStats,
145 #[cfg_attr(feature = "serde", serde(default = "default_inline_timeout"))]
146 pub(crate) inline_timeout: Duration,
147}
148
149impl Default for DiffDocument {
150 fn default() -> Self {
151 Self {
152 hunks: Vec::new(),
153 stats: DiffStats::default(),
154 inline_timeout: default_inline_timeout(),
155 }
156 }
157}
158
159pub(crate) fn default_inline_timeout() -> Duration {
160 Duration::from_millis(40)
161}
162
163#[derive(Debug, Clone, PartialEq, Eq)]
165pub struct ParseDiffError {
166 message: &'static str,
167}
168
169impl ParseDiffError {
170 pub(crate) fn new(message: &'static str) -> Self {
171 Self { message }
172 }
173}
174
175impl fmt::Display for ParseDiffError {
176 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
177 formatter.write_str(self.message)
178 }
179}
180
181impl std::error::Error for ParseDiffError {}
182
183#[derive(Debug, Clone)]
185#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
186pub struct DiffBundle {
187 pub hunks: Vec<DiffHunk>,
189 pub formatted: String,
191 pub is_empty: bool,
193}
194#[derive(Debug, Clone, Copy, PartialEq, Eq)]
196#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
197pub struct IntralineRange {
198 pub start: usize,
200 pub end: usize,
202}
203
204pub type WordChangedRanges = Vec<(usize, usize)>;
206
207#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
209pub struct DiffChangeCounts {
210 pub additions: usize,
212 pub deletions: usize,
214}
215
216impl DiffChangeCounts {
217 #[must_use]
219 pub const fn total(self) -> usize {
220 self.additions + self.deletions
221 }
222}
223
224#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
226pub enum DiffDisplayKind {
227 Metadata,
229 HunkHeader,
231 Context,
233 Addition,
235 Deletion,
237}
238
239impl DiffDisplayKind {
240 #[must_use]
242 pub const fn is_diff(self) -> bool {
243 matches!(self, Self::Context | Self::Addition | Self::Deletion)
244 }
245}
246
247#[derive(Clone, Debug, Eq, PartialEq)]
249pub struct DiffDisplayLine {
250 pub kind: DiffDisplayKind,
252 pub old_line: Option<u32>,
254 pub new_line: Option<u32>,
256 pub text: String,
258 pub changed: WordChangedRanges,
260}
261
262impl DiffDisplayLine {
263 #[must_use]
265 pub fn body(kind: DiffDisplayKind, old_line: Option<u32>, new_line: Option<u32>, text: String) -> Self {
266 Self {
267 kind,
268 old_line,
269 new_line,
270 text,
271 changed: Vec::new(),
272 }
273 }
274
275 #[must_use]
277 pub const fn is_diff(&self) -> bool {
278 self.kind.is_diff()
279 }
280
281 #[must_use]
283 pub fn numbered_text(&self, line_number_width: usize) -> String {
284 match self.kind {
285 DiffDisplayKind::Metadata | DiffDisplayKind::HunkHeader => self.text.clone(),
286 DiffDisplayKind::Deletion => {
287 format!("-{:>width$} │ {}", self.old_line.unwrap_or_default(), self.text, width = line_number_width)
288 }
289 DiffDisplayKind::Addition => {
290 format!("+{:>width$} │ {}", self.new_line.unwrap_or_default(), self.text, width = line_number_width)
291 }
292 DiffDisplayKind::Context => format!(
293 " {:>width$} │ {}",
294 self.new_line.or(self.old_line).unwrap_or_default(),
295 self.text,
296 width = line_number_width
297 ),
298 }
299 }
300}
301pub const DIFF_MIN_SIDE_BY_SIDE_WIDTH: usize = 60;
306
307#[must_use]
312pub const fn diff_side_by_side_fits(available_width: Option<usize>) -> bool {
313 match available_width {
314 Some(width) => width >= DIFF_MIN_SIDE_BY_SIDE_WIDTH,
315 None => true,
316 }
317}
318
319pub const DIFF_MIN_BODY_WIDTH_WITH_GUTTER: usize = 20;
325
326#[must_use]
332pub const fn diff_gutter_width(line_number_width: usize) -> usize {
333 line_number_width.saturating_add(4)
334}
335
336#[must_use]
339pub const fn diff_gutter_fits(available_width: usize, line_number_width: usize) -> bool {
340 available_width >= diff_gutter_width(line_number_width).saturating_add(DIFF_MIN_BODY_WIDTH_WITH_GUTTER)
341}
342
343#[must_use]
350pub const fn diff_layout_width(available_width: usize, line_number_width: usize, show_gutter: bool) -> usize {
351 if show_gutter {
352 available_width
353 } else {
354 available_width.saturating_add(diff_gutter_width(line_number_width))
355 }
356}
357#[derive(Clone, Debug, Eq, PartialEq)]
359pub struct SideBySideRow {
360 pub left: Option<DiffDisplayLine>,
362 pub right: Option<DiffDisplayLine>,
364}
365
366impl SideBySideRow {
367 #[must_use]
369 pub fn is_full_width(&self) -> bool {
370 self.right.is_none()
371 && self
372 .left
373 .as_ref()
374 .is_some_and(|line| matches!(line.kind, DiffDisplayKind::HunkHeader | DiffDisplayKind::Metadata))
375 }
376}
377#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
379#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
380#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))]
381pub enum DiffLayout {
382 #[default]
384 Unified,
385 SideBySide,
387}
388
389#[derive(Debug, Clone, Copy, PartialEq, Eq)]
391pub struct LayoutOptions {
392 pub layout: DiffLayout,
394 pub width: usize,
396 pub max_rows: usize,
398 pub wrap: bool,
400 pub min_side_by_side_width: usize,
402}
403
404impl Default for LayoutOptions {
405 fn default() -> Self {
406 Self {
407 layout: DiffLayout::Unified,
408 width: 80,
409 max_rows: 2_000,
410 wrap: true,
411 min_side_by_side_width: DIFF_MIN_SIDE_BY_SIDE_WIDTH,
412 }
413 }
414}
415
416#[derive(Debug, Clone, Copy, PartialEq, Eq)]
418pub enum DiffRowKind {
419 Metadata,
421 HunkHeader,
423 Context,
425 Addition,
427 Deletion,
429 Omission,
431}
432
433#[derive(Debug, Clone, PartialEq, Eq)]
435pub struct DiffSegment {
436 pub text: String,
438 pub emphasized: bool,
440}
441
442#[derive(Debug, Clone, PartialEq, Eq)]
444pub struct DiffCell {
445 pub old_line: Option<u32>,
447 pub new_line: Option<u32>,
449 pub marker: char,
451 pub segments: Vec<DiffSegment>,
453}
454
455#[derive(Debug, Clone, PartialEq, Eq)]
457pub struct DiffRow {
458 pub kind: DiffRowKind,
460 pub marker: char,
462 pub hunk_index: Option<usize>,
464 pub continuation: bool,
466 pub left: Option<DiffCell>,
468 pub right: Option<DiffCell>,
470}