oli_server/tools/fs/
diff.rs

1use anyhow::Result;
2use std::fmt::Write;
3
4/// Represents a line in a diff
5#[derive(Debug)]
6pub enum DiffLine {
7    Added(String),
8    Removed(String),
9    Context(String),
10}
11
12/// Utilities for generating and formatting diffs between text
13pub struct DiffTools;
14
15impl DiffTools {
16    /// Generate a diff between two strings
17    pub fn generate_diff(old_text: &str, new_text: &str) -> Vec<DiffLine> {
18        let old_lines: Vec<&str> = old_text.lines().collect();
19        let new_lines: Vec<&str> = new_text.lines().collect();
20
21        // Simple line-by-line diff implementation
22        let mut diff = Vec::new();
23        let mut i = 0;
24        let mut j = 0;
25
26        while i < old_lines.len() || j < new_lines.len() {
27            if i < old_lines.len() && j < new_lines.len() && old_lines[i] == new_lines[j] {
28                // Line is the same
29                diff.push(DiffLine::Context(old_lines[i].to_string()));
30                i += 1;
31                j += 1;
32            } else {
33                // Find next matching line
34                let mut found_match = false;
35
36                // Look ahead in new_lines
37                for look_ahead in 0..3 {
38                    if i < old_lines.len()
39                        && j + look_ahead < new_lines.len()
40                        && old_lines[i] == new_lines[j + look_ahead]
41                    {
42                        // Found a matching line in new_lines, add the added lines before it
43                        for k in 0..look_ahead {
44                            diff.push(DiffLine::Added(new_lines[j + k].to_string()));
45                        }
46                        j += look_ahead;
47                        found_match = true;
48                        break;
49                    }
50                }
51
52                // Look ahead in old_lines if no match found
53                if !found_match {
54                    for look_ahead in 0..3 {
55                        if i + look_ahead < old_lines.len()
56                            && j < new_lines.len()
57                            && old_lines[i + look_ahead] == new_lines[j]
58                        {
59                            // Found a matching line in old_lines, add the removed lines before it
60                            for k in 0..look_ahead {
61                                diff.push(DiffLine::Removed(old_lines[i + k].to_string()));
62                            }
63                            i += look_ahead;
64                            found_match = true;
65                            break;
66                        }
67                    }
68                }
69
70                // If no match found, add one line as difference
71                if !found_match {
72                    if i < old_lines.len() {
73                        diff.push(DiffLine::Removed(old_lines[i].to_string()));
74                        i += 1;
75                    }
76                    if j < new_lines.len() {
77                        diff.push(DiffLine::Added(new_lines[j].to_string()));
78                        j += 1;
79                    }
80                }
81            }
82        }
83
84        diff
85    }
86
87    /// Format diff as a string with line numbers and colors
88    pub fn format_diff(diff: &[DiffLine], file_path: &str) -> Result<String> {
89        let mut output = String::new();
90        let mut line_number = 0;
91        let mut adds = 0;
92        let mut removes = 0;
93
94        // Count additions and removals first
95        for line in diff {
96            match line {
97                DiffLine::Added(_) => adds += 1,
98                DiffLine::Removed(_) => removes += 1,
99                _ => {}
100            }
101        }
102
103        // Add header
104        writeln!(
105            output,
106            "  ⎿  Updated {} with {} addition{} and {} removal{}",
107            file_path,
108            adds,
109            if adds == 1 { "" } else { "s" },
110            removes,
111            if removes == 1 { "" } else { "s" }
112        )?;
113
114        // Only show the diff if there are changes
115        if adds > 0 || removes > 0 {
116            // Add the diff content with line numbers and colored indicators
117            for line in diff {
118                match line {
119                    DiffLine::Context(text) => {
120                        line_number += 1;
121                        writeln!(output, "     {:3}  {}", line_number, text)?;
122                    }
123                    DiffLine::Added(text) => {
124                        line_number += 1;
125                        // Use ANSI colors to show additions in light green
126                        writeln!(output, "     \x1b[92m{:3}+ {}\x1b[0m", line_number, text)?;
127                    }
128                    DiffLine::Removed(text) => {
129                        // For removed lines, use a darker red color
130                        // Don't increment line number for removed lines
131                        writeln!(output, "     \x1b[91m{:3}- {}\x1b[0m", line_number, text)?;
132                    }
133                }
134            }
135        }
136
137        Ok(output)
138    }
139}