Skip to main content

oak_purescript/lsp/highlighter/
mod.rs

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