1#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7pub enum HighlightKind {
8 Keyword,
10 String,
12 Number,
14 Comment,
16 Macro,
18 Identifier,
20}
21
22pub trait Highlighter {
24 fn highlight(&self, text: &str) -> Vec<(usize, usize, HighlightKind)>;
26}
27
28pub struct RustHighlighter {
32 pub use_parser: bool,
34}
35
36impl Default for RustHighlighter {
37 fn default() -> Self {
38 Self { use_parser: false }
39 }
40}
41
42impl RustHighlighter {
43 pub fn new() -> Self {
45 Self::default()
46 }
47
48 pub fn with_parser() -> Self {
50 Self { use_parser: true }
51 }
52
53 fn highlight_keywords(&self, text: &str) -> Vec<(usize, usize, HighlightKind)> {
55 let mut highlights = Vec::new();
56 let keywords = [
57 "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",
58 "true", "type", "unsafe", "use", "where", "while", "async", "await", "dyn", "abstract", "become", "box", "do", "final", "macro", "override", "priv", "typeof", "unsized", "virtual", "yield", "try", "union", "raw",
59 ];
60
61 for keyword in &keywords {
62 let mut start = 0;
63 while let Some(pos) = text[start..].find(keyword) {
64 let absolute_pos = start + pos;
65 let end_pos = absolute_pos + keyword.len();
66
67 let is_word_boundary_before = absolute_pos == 0 || !text.chars().nth(absolute_pos - 1).unwrap_or(' ').is_alphanumeric();
69 let is_word_boundary_after = end_pos >= text.len() || !text.chars().nth(end_pos).unwrap_or(' ').is_alphanumeric();
70
71 if is_word_boundary_before && is_word_boundary_after {
72 highlights.push((absolute_pos, end_pos, HighlightKind::Keyword));
73 }
74
75 start = absolute_pos + 1;
76 }
77 }
78
79 highlights
80 }
81
82 fn highlight_strings(&self, text: &str) -> Vec<(usize, usize, HighlightKind)> {
84 let mut highlights = Vec::new();
85 let mut chars = text.char_indices().peekable();
86
87 while let Some((i, ch)) = chars.next() {
88 match ch {
89 '"' => {
90 let start = i;
91 let mut end = i + 1;
92 let mut escaped = false;
93
94 while let Some((j, next_ch)) = chars.next() {
95 end = j + next_ch.len_utf8();
96 if escaped {
97 escaped = false;
98 }
99 else if next_ch == '\\' {
100 escaped = true;
101 }
102 else if next_ch == '"' {
103 break;
104 }
105 }
106
107 highlights.push((start, end, HighlightKind::String));
108 }
109 '\'' => {
110 let start = i;
111 let mut end = i + 1;
112 let mut escaped = false;
113
114 while let Some((j, next_ch)) = chars.next() {
115 end = j + next_ch.len_utf8();
116 if escaped {
117 escaped = false;
118 }
119 else if next_ch == '\\' {
120 escaped = true;
121 }
122 else if next_ch == '\'' {
123 break;
124 }
125 }
126
127 highlights.push((start, end, HighlightKind::String));
128 }
129 _ => {}
130 }
131 }
132
133 highlights
134 }
135
136 fn highlight_numbers(&self, text: &str) -> Vec<(usize, usize, HighlightKind)> {
138 let mut highlights = Vec::new();
139 let mut chars = text.char_indices().peekable();
140
141 while let Some((i, ch)) = chars.next() {
142 if ch.is_ascii_digit() {
143 let start = i;
144 let mut end = i + 1;
145
146 while let Some(&(j, next_ch)) = chars.peek() {
148 if next_ch.is_ascii_digit() || next_ch == '.' || next_ch == '_' {
149 end = j + next_ch.len_utf8();
150 chars.next();
151 }
152 else {
153 break;
154 }
155 }
156
157 highlights.push((start, end, HighlightKind::Number));
158 }
159 }
160
161 highlights
162 }
163
164 fn highlight_comments(&self, text: &str) -> Vec<(usize, usize, HighlightKind)> {
166 let mut highlights = Vec::new();
167 let lines: Vec<&str> = text.lines().collect();
168 let mut pos = 0;
169
170 for line in lines {
171 if let Some(comment_start) = line.find("//") {
172 let start = pos + comment_start;
173 let end = pos + line.len();
174 highlights.push((start, end, HighlightKind::Comment));
175 }
176 pos += line.len() + 1; }
178
179 highlights
180 }
181}
182
183impl Highlighter for RustHighlighter {
184 fn highlight(&self, text: &str) -> Vec<(usize, usize, HighlightKind)> {
185 let mut highlights = Vec::new();
186
187 highlights.extend(self.highlight_keywords(text));
188 highlights.extend(self.highlight_strings(text));
189 highlights.extend(self.highlight_numbers(text));
190 highlights.extend(self.highlight_comments(text));
191
192 highlights.sort_by_key(|&(start, _, _)| start);
194 highlights
195 }
196}