Skip to main content

oak_rust/lsp/highlighter/
mod.rs

1#![doc = include_str!("readme.md")]
2
3/// Local definition of highlight kinds
4#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5pub enum HighlightKind {
6    /// Keywords
7    Keyword,
8    /// Strings
9    String,
10    /// Numbers
11    Number,
12    /// Comments
13    Comment,
14    /// Macros
15    Macro,
16    /// Identifiers
17    Identifier,
18}
19
20/// Highlighter trait
21pub trait Highlighter {
22    /// Highlights the given text
23    fn highlight(&self, text: &str) -> Vec<(usize, usize, HighlightKind)>;
24}
25
26/// Rust syntax highlighter
27///
28/// `RustHighlighter` implements the `Highlighter` trait, providing syntax highlighting for Rust code.
29pub struct RustHighlighter {
30    /// Whether to use parser-based highlighting for better accuracy
31    pub use_parser: bool,
32}
33
34impl Default for RustHighlighter {
35    fn default() -> Self {
36        Self { use_parser: false }
37    }
38}
39
40impl RustHighlighter {
41    /// Creates a new Rust highlighter instance
42    pub fn new() -> Self {
43        Self::default()
44    }
45
46    /// Creates a highlighter instance that uses a parser
47    pub fn with_parser() -> Self {
48        Self { use_parser: true }
49    }
50
51    /// Highlights Rust keywords
52    fn highlight_keywords(&self, text: &str) -> Vec<(usize, usize, HighlightKind)> {
53        let mut highlights = Vec::new();
54        let keywords = [
55            "as", "break", "const", "continue", "crate", "else", "enum", "extern", "false", "fn", "for", "if", "impl", "in", "let", "loop", "match", "mod", "move", "mut", "pub", "ref", "return", "self", "Self", "static", "struct", "super", "trait",
56            "true", "type", "unsafe", "use", "where", "while", "async", "await", "dyn", "abstract", "become", "box", "do", "final", "macro", "override", "priv", "typeof", "unsized", "virtual", "yield", "try", "union", "raw",
57        ];
58
59        for keyword in &keywords {
60            let mut start = 0;
61            while let Some(pos) = text[start..].find(keyword) {
62                let absolute_pos = start + pos;
63                let end_pos = absolute_pos + keyword.len();
64
65                // Ensure this is a full word
66                let is_word_boundary_before = absolute_pos == 0 || !text.chars().nth(absolute_pos - 1).unwrap_or(' ').is_alphanumeric();
67                let is_word_boundary_after = end_pos >= text.len() || !text.chars().nth(end_pos).unwrap_or(' ').is_alphanumeric();
68
69                if is_word_boundary_before && is_word_boundary_after {
70                    highlights.push((absolute_pos, end_pos, HighlightKind::Keyword))
71                }
72
73                start = absolute_pos + 1
74            }
75        }
76
77        highlights
78    }
79
80    /// Highlights string literals
81    fn highlight_strings(&self, text: &str) -> Vec<(usize, usize, HighlightKind)> {
82        let mut highlights = Vec::new();
83        let mut chars = text.char_indices().peekable();
84
85        while let Some((i, ch)) = chars.next() {
86            match ch {
87                '"' => {
88                    let start = i;
89                    let mut end = i + 1;
90                    let mut escaped = false;
91
92                    while let Some((j, next_ch)) = chars.next() {
93                        end = j + next_ch.len_utf8();
94                        if escaped {
95                            escaped = false;
96                        }
97                        else if next_ch == '\\' {
98                            escaped = true;
99                        }
100                        else if next_ch == '"' {
101                            break;
102                        }
103                    }
104
105                    highlights.push((start, end, HighlightKind::String))
106                }
107                '\'' => {
108                    let start = i;
109                    let mut end = i + 1;
110                    let mut escaped = false;
111
112                    while let Some((j, next_ch)) = chars.next() {
113                        end = j + next_ch.len_utf8();
114                        if escaped {
115                            escaped = false;
116                        }
117                        else if next_ch == '\\' {
118                            escaped = true;
119                        }
120                        else if next_ch == '\'' {
121                            break;
122                        }
123                    }
124
125                    highlights.push((start, end, HighlightKind::String))
126                }
127                _ => {}
128            }
129        }
130
131        highlights
132    }
133
134    /// Highlights number literals
135    fn highlight_numbers(&self, text: &str) -> Vec<(usize, usize, HighlightKind)> {
136        let mut highlights = Vec::new();
137        let mut chars = text.char_indices().peekable();
138
139        while let Some((i, ch)) = chars.next() {
140            if ch.is_ascii_digit() {
141                let start = i;
142                let mut end = i + 1;
143
144                // Continue reading number characters
145                while let Some(&(j, next_ch)) = chars.peek() {
146                    if next_ch.is_ascii_digit() || next_ch == '.' || next_ch == '_' {
147                        end = j + next_ch.len_utf8();
148                        chars.next();
149                    }
150                    else {
151                        break;
152                    }
153                }
154
155                highlights.push((start, end, HighlightKind::Number))
156            }
157        }
158
159        highlights
160    }
161
162    /// Highlights comments
163    fn highlight_comments(&self, text: &str) -> Vec<(usize, usize, HighlightKind)> {
164        let mut highlights = Vec::new();
165        let lines: Vec<&str> = text.lines().collect();
166        let mut pos = 0;
167
168        for line in lines {
169            if let Some(comment_start) = line.find("//") {
170                let start = pos + comment_start;
171                let end = pos + line.len();
172                highlights.push((start, end, HighlightKind::Comment))
173            }
174            pos += line.len() + 1; // +1 for newline
175        }
176
177        highlights
178    }
179}
180
181impl Highlighter for RustHighlighter {
182    fn highlight(&self, text: &str) -> Vec<(usize, usize, HighlightKind)> {
183        let mut highlights = Vec::new();
184
185        highlights.extend(self.highlight_keywords(text));
186        highlights.extend(self.highlight_strings(text));
187        highlights.extend(self.highlight_numbers(text));
188        highlights.extend(self.highlight_comments(text));
189
190        // Sort by position
191        highlights.sort_by_key(|&(start, _, _)| start);
192        highlights
193    }
194}