ratatui_toolkit/widgets/code_diff/widget/constructors/
from_unified_diff.rs1use std::collections::HashMap;
2
3use crate::primitives::resizable_split::ResizableSplit;
4use crate::services::theme::AppTheme;
5use crate::widgets::code_diff::code_diff::CodeDiff;
6use crate::widgets::code_diff::diff_config::DiffConfig;
7use crate::widgets::code_diff::diff_file_tree::DiffFileTree;
8use crate::widgets::code_diff::diff_hunk::DiffHunk;
9use crate::widgets::code_diff::diff_line::DiffLine;
10
11impl CodeDiff {
12 pub fn from_unified_diff(diff_text: &str) -> Self {
43 let (file_path, hunks) = parse_unified_diff(diff_text);
44 let config = DiffConfig::new();
45
46 let mut sidebar_split = ResizableSplit::new(config.sidebar_default_width);
48 sidebar_split.min_percent = config.sidebar_min_width;
49 sidebar_split.max_percent = config.sidebar_max_width;
50
51 Self {
52 file_path,
53 hunks,
54 scroll_offset: 0,
55 file_tree: DiffFileTree::new(),
56 file_diffs: HashMap::new(),
57 show_sidebar: config.sidebar_enabled,
58 sidebar_split,
59 sidebar_focused: true,
60 config,
61 theme: AppTheme::default(),
62 }
63 }
64}
65
66fn parse_unified_diff(diff_text: &str) -> (Option<String>, Vec<DiffHunk>) {
76 let mut file_path: Option<String> = None;
77 let mut hunks: Vec<DiffHunk> = Vec::new();
78 let mut current_hunk: Option<DiffHunk> = None;
79 let mut old_line_num: usize = 0;
80 let mut new_line_num: usize = 0;
81
82 for line in diff_text.lines() {
83 if line.starts_with("--- ") {
85 let path = line
87 .strip_prefix("--- ")
88 .and_then(|p| p.strip_prefix("a/"))
89 .unwrap_or_else(|| line.strip_prefix("--- ").unwrap_or(""));
90 if file_path.is_none() && !path.is_empty() {
91 file_path = Some(path.to_string());
92 }
93 continue;
94 }
95
96 if line.starts_with("+++ ") {
97 continue;
99 }
100
101 if line.starts_with("@@") {
103 if let Some(hunk) = current_hunk.take() {
105 hunks.push(hunk);
106 }
107
108 if let Some(hunk) = DiffHunk::from_header(line) {
110 old_line_num = hunk.old_start;
111 new_line_num = hunk.new_start;
112 current_hunk = Some(hunk);
113 }
114 continue;
115 }
116
117 if let Some(ref mut hunk) = current_hunk {
119 let diff_line = if let Some(content) = line.strip_prefix('+') {
120 let line = DiffLine::added(content, new_line_num);
121 new_line_num += 1;
122 Some(line)
123 } else if let Some(content) = line.strip_prefix('-') {
124 let line = DiffLine::removed(content, old_line_num);
125 old_line_num += 1;
126 Some(line)
127 } else if let Some(content) = line.strip_prefix(' ') {
128 let line = DiffLine::context(content, old_line_num, new_line_num);
129 old_line_num += 1;
130 new_line_num += 1;
131 Some(line)
132 } else if line.is_empty() {
133 let line = DiffLine::context("", old_line_num, new_line_num);
135 old_line_num += 1;
136 new_line_num += 1;
137 Some(line)
138 } else {
139 None
140 };
141
142 if let Some(diff_line) = diff_line {
143 hunk.add_line(diff_line);
144 }
145 }
146 }
147
148 if let Some(hunk) = current_hunk {
150 hunks.push(hunk);
151 }
152
153 (file_path, hunks)
154}