diffs_rs/highlight/
inline.rs1use 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}