use similar::{Algorithm, DiffTag, TextDiff, ChangeTag};
use crate::theme::Theme;
pub struct DiffLine {
pub left_num: Option<usize>,
pub left_text: Option<String>,
pub right_num: Option<usize>,
pub right_text: Option<String>,
pub left_diff: bool,
pub right_diff: bool,
}
pub fn compute_diff(text1: &str, text2: &str) -> Vec<DiffLine> {
if text1 == text2 {
return vec![];
}
let old_lines: Vec<&str> = text1.lines().collect();
let new_lines: Vec<&str> = text2.lines().collect();
let ops = similar::capture_diff_slices(Algorithm::Myers, &old_lines, &new_lines);
let theme = Theme::dracula();
let mut diff_lines = Vec::new();
for op in ops {
let (tag, old_range, new_range) = op.as_tag_tuple();
match tag {
DiffTag::Equal => {
for offset in 0..(old_range.end - old_range.start) {
let i = old_range.start + offset;
let j = new_range.start + offset;
diff_lines.push(DiffLine {
left_num: Some(i + 1),
left_text: Some(old_lines[i].to_string()),
right_num: Some(j + 1),
right_text: Some(new_lines[j].to_string()),
left_diff: false,
right_diff: false,
});
}
}
DiffTag::Delete => {
for i in old_range.start..old_range.end {
let original = old_lines[i];
let styled = apply_word_style(original, "", &theme);
diff_lines.push(DiffLine {
left_num: Some(i + 1),
left_text: Some(styled.left),
right_num: None,
right_text: None,
left_diff: false,
right_diff: false,
});
}
}
DiffTag::Insert => {
for j in new_range.start..new_range.end {
let added = new_lines[j];
let styled = apply_word_style("", added, &theme);
diff_lines.push(DiffLine {
left_num: None,
left_text: None,
right_num: Some(j + 1),
right_text: Some(styled.right),
left_diff: false,
right_diff: false,
});
}
}
DiffTag::Replace => {
let old_len = old_range.end - old_range.start;
let new_len = new_range.end - new_range.start;
let line_count = old_len.max(new_len);
for k in 0..line_count {
let i_opt = if k < old_len { Some(old_range.start + k) } else { None };
let j_opt = if k < new_len { Some(new_range.start + k) } else { None };
let left_line = i_opt.map(|i| old_lines[i]).unwrap_or("");
let right_line = j_opt.map(|j| new_lines[j]).unwrap_or("");
let styled = apply_word_style(left_line, right_line, &theme);
diff_lines.push(DiffLine {
left_num: i_opt.map(|i| i + 1),
left_text: Some(styled.left),
right_num: j_opt.map(|j| j + 1),
right_text: Some(styled.right),
left_diff: false,
right_diff: false,
});
}
}
}
}
diff_lines
}
struct StyledPair {
left: String,
right: String,
}
fn apply_word_style(left: &str, right: &str, theme: &Theme) -> StyledPair {
let word_diff = TextDiff::from_words(left, right);
let mut styled_left = String::new();
let mut styled_right = String::new();
for op in word_diff.ops() {
for change in word_diff.iter_changes(op) {
match change.tag() {
ChangeTag::Equal => {
styled_left.push_str(change.value());
styled_right.push_str(change.value());
}
ChangeTag::Delete => {
styled_left.push_str(&theme.deletion.apply_to(change.value()).to_string());
}
ChangeTag::Insert => {
styled_right.push_str(&theme.addition.apply_to(change.value()).to_string());
}
}
}
}
StyledPair { left: styled_left, right: styled_right }
}