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}