ratatui_toolkit/widgets/code_diff/widget/constructors/
with_file.rs

1//! Builder method to add a file with its diff.
2
3use crate::widgets::code_diff::code_diff::CodeDiff;
4use crate::widgets::code_diff::diff_file_tree::FileStatus;
5use crate::widgets::code_diff::diff_hunk::DiffHunk;
6use crate::widgets::code_diff::diff_line::DiffLine;
7
8impl CodeDiff {
9    /// Adds a file with its diff to the widget.
10    ///
11    /// This is used for multi-file diffs. The file will appear in the sidebar
12    /// file tree with the specified status, and its diff content will be stored
13    /// for display when selected.
14    ///
15    /// # Arguments
16    ///
17    /// * `path` - The file path to display
18    /// * `status` - The file's modification status (Modified, Added, Deleted, Renamed)
19    /// * `diff_text` - The unified diff text for this file
20    ///
21    /// # Returns
22    ///
23    /// Self for method chaining
24    ///
25    /// # Example
26    ///
27    /// ```rust
28    /// use ratatui_toolkit::code_diff::{CodeDiff, DiffConfig};
29    /// use ratatui_toolkit::widgets::code_diff::diff_file_tree::FileStatus;
30    ///
31    /// let diff_text = r#"--- a/src/lib.rs
32    /// +++ b/src/lib.rs
33    /// @@ -1,3 +1,4 @@
34    ///  fn main() {
35    /// +    println!("Hello");
36    ///  }
37    /// "#;
38    ///
39    /// let widget = CodeDiff::new()
40    ///     .with_config(DiffConfig::new().sidebar_enabled(true))
41    ///     .with_file("src/lib.rs", FileStatus::Modified, diff_text);
42    /// ```
43    pub fn with_file(mut self, path: &str, status: FileStatus, diff_text: &str) -> Self {
44        // Parse the diff text into hunks
45        let hunks = parse_file_diff(diff_text);
46
47        // Add to file tree
48        self.file_tree.add_file(path, status);
49
50        // Store the hunks for this file
51        self.file_diffs.insert(path.to_string(), hunks.clone());
52
53        // If this is the first file, also set it as the current hunks
54        if self.file_path.is_none() {
55            self.file_path = Some(path.to_string());
56            self.hunks = hunks;
57        }
58
59        self
60    }
61}
62
63/// Parses unified diff format text into hunks (for a single file).
64fn parse_file_diff(diff_text: &str) -> Vec<DiffHunk> {
65    use crate::widgets::code_diff::diff_hunk::DiffHunk;
66
67    let mut hunks: Vec<DiffHunk> = Vec::new();
68    let mut current_hunk: Option<DiffHunk> = None;
69    let mut old_line_num: usize = 0;
70    let mut new_line_num: usize = 0;
71
72    for line in diff_text.lines() {
73        // Skip file headers
74        if line.starts_with("--- ") || line.starts_with("+++ ") {
75            continue;
76        }
77
78        // Parse hunk header
79        if line.starts_with("@@") {
80            // Save previous hunk if exists
81            if let Some(hunk) = current_hunk.take() {
82                hunks.push(hunk);
83            }
84
85            // Parse new hunk header
86            if let Some(hunk) = DiffHunk::from_header(line) {
87                old_line_num = hunk.old_start;
88                new_line_num = hunk.new_start;
89                current_hunk = Some(hunk);
90            }
91            continue;
92        }
93
94        // Parse diff lines within a hunk
95        if let Some(ref mut hunk) = current_hunk {
96            let diff_line = if let Some(content) = line.strip_prefix('+') {
97                let line = DiffLine::added(content, new_line_num);
98                new_line_num += 1;
99                Some(line)
100            } else if let Some(content) = line.strip_prefix('-') {
101                let line = DiffLine::removed(content, old_line_num);
102                old_line_num += 1;
103                Some(line)
104            } else if let Some(content) = line.strip_prefix(' ') {
105                let line = DiffLine::context(content, old_line_num, new_line_num);
106                old_line_num += 1;
107                new_line_num += 1;
108                Some(line)
109            } else if line.is_empty() {
110                // Empty line in diff (context line with no content)
111                let line = DiffLine::context("", old_line_num, new_line_num);
112                old_line_num += 1;
113                new_line_num += 1;
114                Some(line)
115            } else {
116                None
117            };
118
119            if let Some(diff_line) = diff_line {
120                hunk.add_line(diff_line);
121            }
122        }
123    }
124
125    // Don't forget the last hunk
126    if let Some(hunk) = current_hunk {
127        hunks.push(hunk);
128    }
129
130    hunks
131}