Skip to main content

semantic_diff/diff/
mod.rs

1mod parser;
2pub mod untracked;
3
4pub use parser::parse;
5
6/// Append synthetic diff for untracked files to a raw diff string, parse the
7/// combined result, and mark the untracked files in the returned DiffData.
8pub fn parse_with_untracked(raw_diff: &str) -> (DiffData, String) {
9    let untracked_paths = untracked::discover_untracked_files();
10    parse_with_untracked_paths(raw_diff, &untracked_paths)
11}
12
13/// Same as `parse_with_untracked` but accepts pre-discovered untracked paths.
14/// Returns `(diff_data, combined_raw_diff)`.
15pub fn parse_with_untracked_paths(raw_diff: &str, untracked_paths: &[String]) -> (DiffData, String) {
16    if untracked_paths.is_empty() {
17        let data = parse(raw_diff);
18        return (data, raw_diff.to_string());
19    }
20
21    let (untracked_diff, binary_untracked) =
22        untracked::generate_untracked_diff(untracked_paths);
23
24    let combined = if untracked_diff.is_empty() {
25        raw_diff.to_string()
26    } else {
27        format!("{raw_diff}{untracked_diff}")
28    };
29
30    let mut data = parse(&combined);
31
32    // Mark untracked files by matching their paths
33    let untracked_set: std::collections::HashSet<&str> =
34        untracked_paths.iter().map(|s| s.as_str()).collect();
35    for file in &mut data.files {
36        let path = file.target_file.trim_start_matches("b/");
37        if untracked_set.contains(path) {
38            file.is_untracked = true;
39        }
40    }
41
42    // Add binary untracked files to the binary list
43    data.binary_files.extend(binary_untracked);
44
45    (data, combined)
46}
47
48/// Top-level parsed diff result.
49#[derive(Debug, Clone)]
50pub struct DiffData {
51    pub files: Vec<DiffFile>,
52    pub binary_files: Vec<String>,
53}
54
55/// One changed file in the diff.
56#[derive(Debug, Clone)]
57pub struct DiffFile {
58    pub source_file: String,
59    pub target_file: String,
60    pub is_rename: bool,
61    pub is_untracked: bool,
62    pub hunks: Vec<Hunk>,
63    pub added_count: usize,
64    pub removed_count: usize,
65}
66
67/// One @@ hunk section within a file.
68#[derive(Debug, Clone)]
69pub struct Hunk {
70    pub header: String,
71    pub source_start: usize,
72    pub target_start: usize,
73    pub lines: Vec<DiffLine>,
74}
75
76/// A single line within a hunk.
77#[derive(Debug, Clone)]
78pub struct DiffLine {
79    pub line_type: LineType,
80    pub content: String,
81    /// Word-level inline diff segments, if computed.
82    pub inline_segments: Option<Vec<DiffSegment>>,
83}
84
85/// Whether a diff line is added, removed, or context.
86#[derive(Debug, Clone, PartialEq, Eq)]
87pub enum LineType {
88    Added,
89    Removed,
90    Context,
91}
92
93/// A segment of a line for word-level inline diff highlighting.
94#[derive(Debug, Clone)]
95pub struct DiffSegment {
96    pub tag: SegmentTag,
97    pub text: String,
98}
99
100/// Whether a segment is unchanged or changed in an inline diff.
101#[derive(Debug, Clone, PartialEq, Eq)]
102pub enum SegmentTag {
103    Equal,
104    Changed,
105}