Skip to main content

oak_rust/lsp/highlighter/
mod.rs

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