oak_purescript/lsp/highlighter/
mod.rs1#![doc = include_str!("readme.md")]
2#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5pub enum HighlightKind {
6 Keyword,
7 String,
8 Number,
9 Comment,
10 Identifier,
11}
12
13pub trait Highlighter {
15 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 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}