1use serde_derive::{Deserialize, Serialize};
2use std::ops::Range;
3
4#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
5#[serde(rename_all = "snake_case")]
6pub struct Highlight {
7 pub kind: HighlightKind,
8 pub range: Range<usize>,
9}
10
11#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq, Eq)]
12#[serde(rename_all = "snake_case")]
13#[non_exhaustive]
14pub enum HighlightKind {
15 Value,
16}
17
18pub struct Highlighter {
19 quotation: char,
20}
21
22impl Default for Highlighter {
23 fn default() -> Self {
24 Self::new('`')
25 }
26}
27
28impl Highlighter {
29 pub fn new(quotation: char) -> Self {
30 Self { quotation }
31 }
32
33 pub fn highlight(&self, s: &str) -> (String, Vec<Highlight>) {
34 let mut plain = String::with_capacity(s.len());
35 let mut highlights = vec![];
36 let mut start: Option<usize> = None;
37 let mut escaped = false;
38 for c in s.chars() {
39 if escaped {
40 escaped = false;
41 plain.push(c);
42 } else if c == '\\' {
43 escaped = true;
44 } else if c == self.quotation {
45 if let Some(start) = start.take() {
46 highlights.push(Highlight {
47 kind: HighlightKind::Value,
48 range: start..plain.len(),
49 });
50 } else {
51 start = Some(plain.len());
52 }
53 } else {
54 plain.push(c);
55 }
56 }
57 (plain, highlights)
58 }
59}
60
61#[cfg(test)]
62mod tests {
63 use super::*;
64
65 #[test]
66 fn highlighter() {
67 let text = "`quick brown` 🦊 \\`jumps\\`\nover `the` lazy `dog`.";
68 let (plain, highlights) = Highlighter::default().highlight(text);
69 assert_eq!(plain, "quick brown 🦊 `jumps`\nover the lazy dog.");
70 assert_eq!(
71 highlights,
72 vec![
73 Highlight {
74 kind: HighlightKind::Value,
75 range: 0..11
76 },
77 Highlight {
78 kind: HighlightKind::Value,
79 range: 30..33
80 },
81 Highlight {
82 kind: HighlightKind::Value,
83 range: 39..42
84 }
85 ]
86 );
87 }
88}