1use crate::edit_control::ModifiableEdit;
3use std::collections::HashMap;
4
5pub struct EditPreviewBuilder {
7 show_diff: bool,
8 show_risk_assessment: bool,
9 show_line_numbers: bool,
10 syntax_highlighting: bool,
11 theme: PreviewTheme,
12}
13
14impl EditPreviewBuilder {
15 pub fn new() -> Self {
16 Self {
17 show_diff: true,
18 show_risk_assessment: true,
19 show_line_numbers: true,
20 syntax_highlighting: true,
21 theme: PreviewTheme::Default,
22 }
23 }
24
25 pub fn with_diff(mut self, show: bool) -> Self {
26 self.show_diff = show;
27 self
28 }
29
30 pub fn with_risk_assessment(mut self, show: bool) -> Self {
31 self.show_risk_assessment = show;
32 self
33 }
34
35 pub fn with_syntax_highlighting(mut self, enabled: bool) -> Self {
36 self.syntax_highlighting = enabled;
37 self
38 }
39
40 pub fn with_theme(mut self, theme: PreviewTheme) -> Self {
41 self.theme = theme;
42 self
43 }
44
45 pub fn build(self) -> EditPreview {
46 EditPreview::new(self)
47 }
48}
49
50impl Default for EditPreviewBuilder {
51 fn default() -> Self {
52 Self::new()
53 }
54}
55
56pub struct EditPreview {
57 config: EditPreviewBuilder,
58 syntax_highlighter: SyntaxHighlighter,
59 diff_generator: DiffGenerator,
60}
61
62impl EditPreview {
63 fn new(config: EditPreviewBuilder) -> Self {
64 Self {
65 config,
66 syntax_highlighter: SyntaxHighlighter::new(),
67 diff_generator: DiffGenerator::new(),
68 }
69 }
70
71 pub fn generate_preview(&self, edit: &ModifiableEdit) -> PreviewResult {
73 let mut sections = Vec::new();
74
75 sections.push(self.create_header_section(edit));
77
78 if self.config.show_risk_assessment {
80 if let Some(risk_section) = self.create_risk_section(edit) {
81 sections.push(risk_section);
82 }
83 }
84
85 sections.push(self.create_code_section(edit));
87
88 if self.config.show_diff && !edit.modifications.is_empty() {
90 sections.push(self.create_diff_section(edit));
91 }
92
93 if !edit.processing_metadata.validation_results.is_empty() {
95 sections.push(self.create_validation_section(edit));
96 }
97
98 PreviewResult { sections }
99 }
100
101 fn create_header_section(&self, edit: &ModifiableEdit) -> PreviewSection {
103 let confidence_bar = self.create_confidence_visualization(edit.get_effective_confidence());
104
105 PreviewSection {
106 title: "📋 Edit Summary".to_string(),
107 content: format!(
108 "File: {}\nLines: {}-{}\nReason: {}\nConfidence: {:.1}% {}\nApproval: {:?}",
109 edit.base_edit.file,
110 edit.base_edit.line_range.0,
111 edit.base_edit.line_range.1,
112 edit.base_edit.reason,
113 edit.get_effective_confidence() * 100.0,
114 confidence_bar,
115 edit.approval_state
116 ),
117 style: SectionStyle::Info,
118 }
119 }
120
121 fn create_risk_section(&self, edit: &ModifiableEdit) -> Option<PreviewSection> {
123 let risk_score = self.calculate_risk_score(edit);
125 let risk_level = match risk_score {
126 r if r >= 0.8 => ("🔴 HIGH RISK", SectionStyle::Error),
127 r if r >= 0.5 => ("🟡 MEDIUM RISK", SectionStyle::Warning),
128 _ => ("🟢 LOW RISK", SectionStyle::Success),
129 };
130
131 Some(PreviewSection {
132 title: "⚠️ Risk Assessment".to_string(),
133 content: format!(
134 "{}\nRisk Score: {:.1}%\nRecommendation: {}",
135 risk_level.0,
136 risk_score * 100.0,
137 self.get_risk_recommendation(risk_score)
138 ),
139 style: risk_level.1,
140 })
141 }
142
143 fn create_code_section(&self, edit: &ModifiableEdit) -> PreviewSection {
145 let final_code = edit.compute_final_code();
146 let highlighted_code = if self.config.syntax_highlighting {
147 self.syntax_highlighter
148 .highlight(&final_code, &edit.base_edit.file)
149 } else {
150 final_code
151 };
152
153 let numbered_lines = if self.config.show_line_numbers {
154 self.add_line_numbers(&highlighted_code, edit.base_edit.line_range.0)
155 } else {
156 highlighted_code
157 };
158
159 PreviewSection {
160 title: "📄 Code Preview".to_string(),
161 content: numbered_lines,
162 style: SectionStyle::Code,
163 }
164 }
165
166 fn create_diff_section(&self, edit: &ModifiableEdit) -> PreviewSection {
168 let diff = self
169 .diff_generator
170 .generate_diff(&edit.base_edit.new_code, &edit.compute_final_code());
171
172 PreviewSection {
173 title: "🔍 Changes".to_string(),
174 content: diff,
175 style: SectionStyle::Diff,
176 }
177 }
178
179 fn create_validation_section(&self, edit: &ModifiableEdit) -> PreviewSection {
180 let validation_summary = edit
181 .processing_metadata
182 .validation_results
183 .iter()
184 .map(|v| {
185 format!(
186 "{}: {} - {}",
187 v.validator_name,
188 if v.passed { "✅" } else { "❌" },
189 v.message
190 )
191 })
192 .collect::<Vec<_>>()
193 .join("\n");
194
195 PreviewSection {
196 title: "🔧 Validation Results".to_string(),
197 content: validation_summary,
198 style: SectionStyle::Info,
199 }
200 }
201
202 fn create_confidence_visualization(&self, confidence: f64) -> String {
204 let filled = (confidence * 10.0) as usize;
205 let bar: String = (0..10)
206 .map(|i| if i < filled { "█" } else { "░" })
207 .collect();
208 format!("[{}]", bar)
209 }
210
211 fn add_line_numbers(&self, content: &str, start_line: usize) -> String {
212 content
213 .lines()
214 .enumerate()
215 .map(|(i, line)| format!("{:4} | {}", start_line + i, line))
216 .collect::<Vec<_>>()
217 .join("\n")
218 }
219
220 fn calculate_risk_score(&self, edit: &ModifiableEdit) -> f64 {
221 let base_risk = 1.0 - edit.get_effective_confidence();
223 let modification_risk = edit.modifications.len() as f64 * 0.1;
224 (base_risk + modification_risk).min(1.0)
225 }
226
227 fn get_risk_recommendation(&self, risk_score: f64) -> String {
228 match risk_score {
229 r if r >= 0.8 => "Requires manual review and testing",
230 r if r >= 0.5 => "Consider additional validation",
231 _ => "Safe to apply with standard review",
232 }
233 .to_string()
234 }
235
236 pub fn render_to_string(&self, edit: &ModifiableEdit) -> String {
238 let preview = self.generate_preview(edit);
239 let mut output = Vec::new();
240
241 for section in preview.sections {
242 output.push(format!("═══ {} ═══", section.title));
243 output.push(section.content);
244 output.push("".to_string()); }
246
247 output.join("\n")
248 }
249}
250
251#[derive(Debug)]
253pub struct PreviewResult {
254 pub sections: Vec<PreviewSection>,
255}
256
257#[derive(Debug)]
258pub struct PreviewSection {
259 pub title: String,
260 pub content: String,
261 pub style: SectionStyle,
262}
263
264#[derive(Debug, Clone)]
265pub enum SectionStyle {
266 Info,
267 Warning,
268 Error,
269 Success,
270 Code,
271 Diff,
272}
273
274#[derive(Debug, Clone)]
275pub enum PreviewTheme {
276 Default,
277 Dark,
278 Light,
279}
280
281pub struct SyntaxHighlighter {
283 rules: HashMap<String, Vec<HighlightRule>>,
284}
285
286impl SyntaxHighlighter {
287 pub fn new() -> Self {
288 let mut rules = HashMap::new();
289
290 rules.insert(
292 ".rs".to_string(),
293 vec![
294 HighlightRule::new(
295 r"\b(fn|let|mut|pub|struct|enum|impl|trait|use|mod)\b",
296 "keyword",
297 ),
298 HighlightRule::new(r"//.*$", "comment"),
299 HighlightRule::new(r#""[^"]*""#, "string"),
300 HighlightRule::new(r"\b\d+\b", "number"),
301 HighlightRule::new(r"\b(String|Vec|HashMap|Option|Result)\b", "type"),
302 ],
303 );
304
305 rules.insert(
307 ".js".to_string(),
308 vec![
309 HighlightRule::new(
310 r"\b(function|var|let|const|if|else|for|while|return)\b",
311 "keyword",
312 ),
313 HighlightRule::new(r"//.*$", "comment"),
314 HighlightRule::new(r#""[^"]*""#, "string"),
315 ],
316 );
317
318 rules.insert(
320 ".py".to_string(),
321 vec![
322 HighlightRule::new(
323 r"\b(def|class|if|else|for|while|return|import|from)\b",
324 "keyword",
325 ),
326 HighlightRule::new(r"#.*$", "comment"),
327 HighlightRule::new(r#""[^"]*""#, "string"),
328 ],
329 );
330
331 Self { rules }
332 }
333
334 pub fn highlight(&self, code: &str, filename: &str) -> String {
335 let extension = std::path::Path::new(filename)
336 .extension()
337 .and_then(|ext| ext.to_str())
338 .map(|ext| format!(".{}", ext))
339 .unwrap_or_default();
340
341 if let Some(rules) = self.rules.get(&extension) {
342 self.apply_rules(code, rules)
343 } else {
344 code.to_string()
345 }
346 }
347
348 fn apply_rules(&self, code: &str, rules: &[HighlightRule]) -> String {
349 let mut result = code.to_string();
351 for rule in rules {
352 if rule.pattern.contains("fn") {
354 let styled_replacement = format!("[{}]fn[/{}] ", rule.style, rule.style);
355 result = result.replace("fn ", &styled_replacement);
356 }
357 }
358 result
359 }
360}
361
362#[derive(Debug)]
363struct HighlightRule {
364 pattern: String,
365 style: String,
366}
367
368impl HighlightRule {
369 fn new(pattern: &str, style: &str) -> Self {
370 Self {
371 pattern: pattern.to_string(),
372 style: style.to_string(),
373 }
374 }
375}
376
377pub struct DiffGenerator;
379
380impl DiffGenerator {
381 pub fn new() -> Self {
382 Self
383 }
384
385 pub fn generate_diff(&self, original: &str, modified: &str) -> String {
386 if original == modified {
387 return "No changes detected.".to_string();
388 }
389
390 let original_lines: Vec<&str> = original.lines().collect();
392 let modified_lines: Vec<&str> = modified.lines().collect();
393
394 let mut diff = Vec::new();
395 diff.push("--- original".to_string());
396 diff.push("+++ modified".to_string());
397
398 let max_lines = original_lines.len().max(modified_lines.len());
400 for i in 0..max_lines {
401 let orig_line = original_lines.get(i).unwrap_or(&"");
402 let mod_line = modified_lines.get(i).unwrap_or(&"");
403
404 if orig_line != mod_line {
405 if !orig_line.is_empty() {
406 diff.push(format!("-{}", orig_line));
407 }
408 if !mod_line.is_empty() {
409 diff.push(format!("+{}", mod_line));
410 }
411 } else if !orig_line.is_empty() {
412 diff.push(format!(" {}", orig_line));
413 }
414 }
415
416 diff.join("\n")
417 }
418}
419
420impl Default for DiffGenerator {
421 fn default() -> Self {
422 Self::new()
423 }
424}
425
426#[cfg(test)]
427mod tests {
428 use super::*;
429 use crate::agents::gpt4_agent::ProposedEdit;
430
431 fn create_test_edit() -> ModifiableEdit {
432 let proposed = ProposedEdit {
433 file: "test.rs".to_string(),
434 line_range: (10, 15),
435 new_code: "fn test() {\n println!(\"Hello\");\n}".to_string(),
436 reason: "Test function".to_string(),
437 confidence: 0.9,
438 };
439 ModifiableEdit::from_proposed_edit(proposed)
440 }
441
442 #[test]
443 fn test_preview_builder() {
444 let preview = EditPreviewBuilder::new()
445 .with_diff(true)
446 .with_risk_assessment(false)
447 .with_syntax_highlighting(true)
448 .build();
449
450 assert!(preview.config.show_diff);
451 assert!(!preview.config.show_risk_assessment);
452 assert!(preview.config.syntax_highlighting);
453 }
454
455 #[test]
456 fn test_preview_generation() {
457 let edit = create_test_edit();
458 let preview = EditPreviewBuilder::new().build();
459 let result = preview.generate_preview(&edit);
460
461 assert!(!result.sections.is_empty());
462 assert!(result
463 .sections
464 .iter()
465 .any(|s| s.title.contains("Edit Summary")));
466 assert!(result
467 .sections
468 .iter()
469 .any(|s| s.title.contains("Code Preview")));
470 }
471
472 #[test]
473 fn test_diff_generation() {
474 let generator = DiffGenerator::new();
475 let original = "line1\nline2\nline3";
476 let modified = "line1\nmodified_line2\nline3";
477
478 let diff = generator.generate_diff(original, modified);
479 assert!(diff.contains("-line2"));
480 assert!(diff.contains("+modified_line2"));
481 }
482
483 #[test]
484 fn test_confidence_visualization() {
485 let preview = EditPreviewBuilder::new().build();
486 let vis = preview.create_confidence_visualization(0.7);
487
488 assert!(vis.contains("█"));
489 assert!(vis.contains("░"));
490 assert!(vis.len() >= 12); }
494
495 #[test]
496 fn test_syntax_highlighter() {
497 let highlighter = SyntaxHighlighter::new();
498 let code = "fn main() {\n println!(\"Hello\");\n}";
499 let highlighted = highlighter.highlight(code, "test.rs");
500
501 assert!(highlighted.contains("[keyword]fn[/keyword]"));
503 assert!(highlighted.contains("main()"));
504 }
505
506 #[test]
507 fn test_render_to_string() {
508 let edit = create_test_edit();
509 let preview = EditPreviewBuilder::new().build();
510 let output = preview.render_to_string(&edit);
511
512 assert!(output.contains("═══"));
513 assert!(output.contains("Edit Summary"));
514 assert!(output.contains("Code Preview"));
515 }
516}