Skip to main content

oak_purescript/lsp/highlighter/
mod.rs

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