oak_purescript/highlighter/
mod.rs1#[derive(Debug, Clone, Copy, PartialEq, Eq)]
4pub enum HighlightKind {
5 Keyword,
6 String,
7 Number,
8 Comment,
9 Identifier,
10}
11
12pub trait Highlighter {
14 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 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}