oak_purescript/lsp/highlighter/
mod.rs1#![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 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}