Skip to main content

diffs_rs/highlight/
inline.rs

1use std::ops::Range;
2
3use similar::{ChangeTag, TextDiff};
4use unicode_segmentation::UnicodeSegmentation;
5
6#[derive(Clone, Copy, PartialEq)]
7enum Kind {
8    Word,
9    Whitespace,
10    Symbol,
11}
12
13#[derive(Clone, Copy, Debug, PartialEq, Eq)]
14pub enum ChangeSide {
15    Old,
16    New,
17}
18
19fn tokens(text: &str) -> Vec<&str> {
20    let mut tokens = Vec::new();
21    let mut start = 0;
22    let mut previous = Kind::Symbol;
23    for (byte, grapheme) in text.grapheme_indices(true) {
24        let first = grapheme.chars().next().expect("nonempty grapheme");
25        let kind = if first.is_alphanumeric() || first == '_' {
26            Kind::Word
27        } else if first.is_whitespace() {
28            Kind::Whitespace
29        } else {
30            Kind::Symbol
31        };
32        if byte > start && (kind != previous || kind == Kind::Symbol) {
33            tokens.push(&text[start..byte]);
34            start = byte;
35        }
36        previous = kind;
37    }
38    if start < text.len() {
39        tokens.push(&text[start..]);
40    }
41    tokens
42}
43
44pub fn changed_spans(old: &str, new: &str, side: ChangeSide) -> Vec<Range<usize>> {
45    let diff = TextDiff::from_slices(&tokens(old), &tokens(new));
46    let (changed_tag, skipped_tag) = match side {
47        ChangeSide::Old => (ChangeTag::Delete, ChangeTag::Insert),
48        ChangeSide::New => (ChangeTag::Insert, ChangeTag::Delete),
49    };
50    let mut offset = 0;
51    let mut changed: Vec<Range<usize>> = Vec::new();
52    for part in diff.iter_all_changes() {
53        if part.tag() == skipped_tag {
54            continue;
55        }
56        let end = offset + part.value().len();
57        if part.tag() == changed_tag {
58            if let Some(last) = changed.last_mut().filter(|last| last.end == offset) {
59                last.end = end;
60            } else {
61                changed.push(offset..end);
62            }
63        }
64        offset = end;
65    }
66    changed
67}