ricecoder_refactoring/preview/
generator.rs1use crate::error::Result;
4use crate::impact::ImpactAnalyzer;
5use crate::types::{Refactoring, RefactoringPreview};
6
7pub struct PreviewGenerator;
9
10impl PreviewGenerator {
11 pub fn new() -> Self {
13 Self
14 }
15}
16
17impl Default for PreviewGenerator {
18 fn default() -> Self {
19 Self::new()
20 }
21}
22
23#[derive(Debug, Clone, PartialEq, Eq)]
25pub struct DiffHunk {
26 pub original_start: usize,
28 pub original_count: usize,
30 pub new_start: usize,
32 pub new_count: usize,
34 pub lines: Vec<String>,
36}
37
38#[derive(Debug, Clone)]
40pub struct UnifiedDiff {
41 pub original_path: String,
43 pub new_path: String,
45 pub hunks: Vec<DiffHunk>,
47}
48
49impl PreviewGenerator {
50 pub fn generate(refactoring: &Refactoring) -> Result<RefactoringPreview> {
52 let analyzer = ImpactAnalyzer::new();
53 let impact = analyzer.analyze(refactoring)?;
54
55 let changes = vec![];
58
59 Ok(RefactoringPreview {
60 changes,
61 impact,
62 estimated_time_seconds: 5,
63 })
64 }
65
66 pub fn generate_unified_diff(original: &str, new: &str) -> String {
71 let hunks = Self::compute_hunks(original, new);
72 Self::format_unified_diff("original", "new", &hunks)
73 }
74
75 pub fn generate_side_by_side_diff(original: &str, new: &str) -> String {
80 let original_lines: Vec<&str> = original.lines().collect();
81 let new_lines: Vec<&str> = new.lines().collect();
82
83 let mut result = String::new();
84 result.push_str("--- original\t\t+++ new\n");
85 result.push_str("---\n");
86
87 let max_lines = original_lines.len().max(new_lines.len());
88 for i in 0..max_lines {
89 let orig_line = original_lines.get(i).copied().unwrap_or("");
90 let new_line = new_lines.get(i).copied().unwrap_or("");
91
92 if orig_line == new_line {
93 result.push_str(&format!("{:<40} | {}\n", orig_line, new_line));
94 } else {
95 result.push_str(&format!("< {:<38} | > {}\n", orig_line, new_line));
96 }
97 }
98
99 result
100 }
101
102 fn compute_hunks(original: &str, new: &str) -> Vec<DiffHunk> {
107 let original_lines: Vec<&str> = original.lines().collect();
108 let new_lines: Vec<&str> = new.lines().collect();
109
110 let mut hunks = Vec::new();
111 let mut i = 0;
112 let mut j = 0;
113
114 while i < original_lines.len() || j < new_lines.len() {
115 while i < original_lines.len()
117 && j < new_lines.len()
118 && original_lines[i] == new_lines[j]
119 {
120 i += 1;
121 j += 1;
122 }
123
124 if i >= original_lines.len() && j >= new_lines.len() {
125 break;
126 }
127
128 let hunk_start_orig = i + 1; let hunk_start_new = j + 1; let mut hunk_lines = Vec::new();
132
133 let removed_start = i;
135 while i < original_lines.len() && (j >= new_lines.len() || original_lines[i] != new_lines[j]) {
136 hunk_lines.push(format!("-{}", original_lines[i]));
137 i += 1;
138 }
139
140 let added_start = j;
142 while j < new_lines.len() && (i >= original_lines.len() || original_lines[i] != new_lines[j]) {
143 hunk_lines.push(format!("+{}", new_lines[j]));
144 j += 1;
145 }
146
147 let context_count = 3;
149 let mut context_added = 0;
150 while context_added < context_count
151 && i < original_lines.len()
152 && j < new_lines.len()
153 && original_lines[i] == new_lines[j]
154 {
155 hunk_lines.push(format!(" {}", original_lines[i]));
156 i += 1;
157 j += 1;
158 context_added += 1;
159 }
160
161 let original_count = i - removed_start;
162 let new_count = j - added_start;
163
164 hunks.push(DiffHunk {
165 original_start: hunk_start_orig,
166 original_count,
167 new_start: hunk_start_new,
168 new_count,
169 lines: hunk_lines,
170 });
171 }
172
173 hunks
174 }
175
176 fn format_unified_diff(original_path: &str, new_path: &str, hunks: &[DiffHunk]) -> String {
178 let mut result = String::new();
179 result.push_str(&format!("--- {}\n", original_path));
180 result.push_str(&format!("+++ {}\n", new_path));
181
182 for hunk in hunks {
183 result.push_str(&format!(
184 "@@ -{},{} +{},{} @@\n",
185 hunk.original_start, hunk.original_count, hunk.new_start, hunk.new_count
186 ));
187
188 for line in &hunk.lines {
189 result.push_str(line);
190 result.push('\n');
191 }
192 }
193
194 result
195 }
196
197 pub fn generate_diff(original: &str, new: &str) -> String {
202 Self::generate_unified_diff(original, new)
203 }
204}
205
206#[cfg(test)]
207mod tests {
208 use super::*;
209 use crate::types::{RefactoringOptions, RefactoringTarget, RefactoringType};
210 use std::path::PathBuf;
211
212 #[test]
213 fn test_generate_preview() -> Result<()> {
214 let refactoring = Refactoring {
215 id: "test-refactoring".to_string(),
216 refactoring_type: RefactoringType::Rename,
217 target: RefactoringTarget {
218 file: PathBuf::from("src/main.rs"),
219 symbol: "old_name".to_string(),
220 range: None,
221 },
222 options: RefactoringOptions::default(),
223 };
224
225 let preview = PreviewGenerator::generate(&refactoring)?;
226 assert_eq!(preview.estimated_time_seconds, 5);
227
228 Ok(())
229 }
230
231 #[test]
232 fn test_generate_unified_diff_simple() {
233 let original = "fn old_name() {}\n";
234 let new = "fn new_name() {}\n";
235 let diff = PreviewGenerator::generate_unified_diff(original, new);
236
237 assert!(diff.contains("--- original"));
238 assert!(diff.contains("+++ new"));
239 assert!(diff.contains("-fn old_name() {}"));
240 assert!(diff.contains("+fn new_name() {}"));
241 }
242
243 #[test]
244 fn test_generate_unified_diff_multiline() {
245 let original = "line 1\nline 2\nline 3\n";
246 let new = "line 1\nmodified line 2\nline 3\n";
247 let diff = PreviewGenerator::generate_unified_diff(original, new);
248
249 assert!(diff.contains("--- original"));
250 assert!(diff.contains("+++ new"));
251 assert!(diff.contains("-line 2"));
252 assert!(diff.contains("+modified line 2"));
253 }
254
255 #[test]
256 fn test_generate_side_by_side_diff() {
257 let original = "fn old_name() {}";
258 let new = "fn new_name() {}";
259 let diff = PreviewGenerator::generate_side_by_side_diff(original, new);
260
261 assert!(diff.contains("--- original"));
262 assert!(diff.contains("+++ new"));
263 assert!(diff.contains("old_name"));
264 assert!(diff.contains("new_name"));
265 }
266
267 #[test]
268 fn test_generate_diff_identical() {
269 let content = "fn test() {}";
270 let diff = PreviewGenerator::generate_diff(content, content);
271
272 assert!(diff.contains("--- original"));
274 assert!(diff.contains("+++ new"));
275 }
276
277 #[test]
278 fn test_compute_hunks_simple() {
279 let original = "a\nb\nc\n";
280 let new = "a\nx\nc\n";
281 let hunks = PreviewGenerator::compute_hunks(original, new);
282
283 assert_eq!(hunks.len(), 1);
284 assert_eq!(hunks[0].original_start, 2);
285 assert_eq!(hunks[0].new_start, 2);
286 }
287
288 #[test]
289 fn test_compute_hunks_multiple_changes() {
290 let original = "a\nb\nc\nd\ne\n";
291 let new = "a\nx\nc\ny\ne\n";
292 let hunks = PreviewGenerator::compute_hunks(original, new);
293
294 assert!(hunks.len() >= 1);
296 }
297
298 #[test]
299 fn test_compute_hunks_empty_original() {
300 let original = "";
301 let new = "a\nb\nc\n";
302 let hunks = PreviewGenerator::compute_hunks(original, new);
303
304 assert!(!hunks.is_empty());
305 }
306
307 #[test]
308 fn test_compute_hunks_empty_new() {
309 let original = "a\nb\nc\n";
310 let new = "";
311 let hunks = PreviewGenerator::compute_hunks(original, new);
312
313 assert!(!hunks.is_empty());
314 }
315}