oak_purescript/highlighter/
mod.rs

1//! PureScript syntax highlighter
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq)]
4pub enum HighlightKind {
5    Keyword,
6    String,
7    Number,
8    Comment,
9    Identifier,
10}
11
12/// 高亮器 trait
13pub trait Highlighter {
14    /// 对给定的文本进行高亮处理
15    fn highlight(&self, text: &str) -> Vec<(usize, usize, HighlightKind)>;
16}
17
18pub struct PurescriptHighlighter {
19    pub use_parser: bool,
20}
21
22impl Default for PurescriptHighlighter {
23    fn default() -> Self {
24        Self { use_parser: false }
25    }
26}
27
28impl PurescriptHighlighter {
29    pub fn new() -> Self {
30        Self::default()
31    }
32
33    pub fn with_parser() -> Self {
34        Self { use_parser: true }
35    }
36
37    fn highlight_keywords(&self, text: &str) -> Vec<(usize, usize, HighlightKind)> {
38        let mut highlights = Vec::new();
39        let keywords = [
40            "ado", "as", "case", "class", "data", "derive", "do", "else", "false", "forall", "foreign", "hiding", "if", "import", "in", "infix", "infixl", "infixr", "instance", "let", "module", "newtype", "nominal", "of", "role", "then", "true", "type",
41            "where",
42        ];
43
44        for keyword in &keywords {
45            let mut start = 0;
46            while let Some(pos) = text[start..].find(keyword) {
47                let absolute_pos = start + pos;
48                let end_pos = absolute_pos + keyword.len();
49
50                let is_word_boundary_before = absolute_pos == 0 || !text.chars().nth(absolute_pos - 1).unwrap_or(' ').is_alphanumeric();
51                let is_word_boundary_after = end_pos >= text.len() || !text.chars().nth(end_pos).unwrap_or(' ').is_alphanumeric();
52
53                if is_word_boundary_before && is_word_boundary_after {
54                    highlights.push((absolute_pos, end_pos, HighlightKind::Keyword));
55                }
56
57                start = absolute_pos + 1;
58            }
59        }
60
61        highlights
62    }
63    fn highlight_strings(&self, text: &str) -> Vec<(usize, usize, HighlightKind)> {
64        let mut highlights = Vec::new();
65        let mut chars = text.char_indices().peekable();
66
67        while let Some((i, ch)) = chars.next() {
68            match ch {
69                '"' => {
70                    let start = i;
71                    let mut escaped = false;
72                    let mut found_end = false;
73
74                    while let Some((j, next_ch)) = chars.next() {
75                        if escaped {
76                            escaped = false;
77                        }
78                        else if next_ch == '\\' {
79                            escaped = true;
80                        }
81                        else if next_ch == '"' {
82                            highlights.push((start, j + 1, HighlightKind::String));
83                            found_end = true;
84                            break;
85                        }
86                    }
87                    if !found_end {
88                        highlights.push((start, text.len(), HighlightKind::String));
89                    }
90                }
91                _ => {}
92            }
93        }
94        highlights
95    }
96
97    fn highlight_numbers(&self, text: &str) -> Vec<(usize, usize, HighlightKind)> {
98        let mut highlights = Vec::new();
99        let mut start = None;
100
101        for (i, ch) in text.char_indices() {
102            if ch.is_ascii_digit() {
103                if start.is_none() {
104                    start = Some(i);
105                }
106            }
107            else {
108                if let Some(s) = start {
109                    highlights.push((s, i, HighlightKind::Number));
110                    start = None;
111                }
112            }
113        }
114        if let Some(s) = start {
115            highlights.push((s, text.len(), HighlightKind::Number));
116        }
117        highlights
118    }
119
120    fn highlight_comments(&self, text: &str) -> Vec<(usize, usize, HighlightKind)> {
121        let mut highlights = Vec::new();
122        let mut start = 0;
123        while let Some(pos) = text[start..].find("--") {
124            let absolute_pos = start + pos;
125            let end_pos = text[absolute_pos..].find('\n').map(|n| absolute_pos + n).unwrap_or(text.len());
126            highlights.push((absolute_pos, end_pos, HighlightKind::Comment));
127            start = end_pos;
128        }
129        // 块注释
130        let mut start = 0;
131        while let Some(pos) = text[start..].find("{-") {
132            let absolute_pos = start + pos;
133            let end_pos = text[absolute_pos..].find("-}").map(|n| absolute_pos + n + 2).unwrap_or(text.len());
134            highlights.push((absolute_pos, end_pos, HighlightKind::Comment));
135            start = end_pos;
136        }
137        highlights
138    }
139}
140
141impl Highlighter for PurescriptHighlighter {
142    fn highlight(&self, text: &str) -> Vec<(usize, usize, HighlightKind)> {
143        let mut highlights = self.highlight_keywords(text);
144        highlights.extend(self.highlight_strings(text));
145        highlights.extend(self.highlight_numbers(text));
146        highlights.extend(self.highlight_comments(text));
147        highlights.sort_by_key(|h| h.0);
148        highlights
149    }
150}