egui_code_editor/
highlighting.rs

1#[cfg(feature = "editor")]
2use super::Editor;
3
4use super::syntax::{Syntax, TokenType, QUOTES, SEPARATORS};
5use std::mem;
6
7#[derive(Default, Debug, PartialEq, PartialOrd, Eq, Ord)]
8/// Lexer and Token
9pub struct Token {
10    ty: TokenType,
11    buffer: String,
12}
13
14impl Token {
15    pub fn new<S: Into<String>>(ty: TokenType, buffer: S) -> Self {
16        Token {
17            ty,
18            buffer: buffer.into(),
19        }
20    }
21    pub fn ty(&self) -> TokenType {
22        self.ty
23    }
24    pub fn buffer(&self) -> &str {
25        &self.buffer
26    }
27
28    fn first(&mut self, c: char, syntax: &Syntax) -> Option<Self> {
29        self.buffer.push(c);
30        let mut token = None;
31        self.ty = match c {
32            c if c.is_whitespace() => {
33                self.ty = TokenType::Whitespace(c);
34                token = self.drain(self.ty);
35                TokenType::Whitespace(c)
36            }
37            c if syntax.is_keyword(c.to_string().as_str()) => TokenType::Keyword,
38            c if syntax.is_type(c.to_string().as_str()) => TokenType::Type,
39            c if syntax.is_special(c.to_string().as_str()) => TokenType::Special,
40            c if syntax.comment == c.to_string().as_str() => TokenType::Comment(false),
41            c if syntax.comment_multiline[0] == c.to_string().as_str() => TokenType::Comment(true),
42            _ => TokenType::from(c),
43        };
44        token
45    }
46
47    fn drain(&mut self, ty: TokenType) -> Option<Self> {
48        let mut token = None;
49        if !self.buffer().is_empty() {
50            token = Some(Token {
51                buffer: mem::take(&mut self.buffer),
52                ty: self.ty,
53            });
54        }
55        self.ty = ty;
56        token
57    }
58
59    fn push_drain(&mut self, c: char, ty: TokenType) -> Option<Self> {
60        self.buffer.push(c);
61        self.drain(ty)
62    }
63
64    fn drain_push(&mut self, c: char, ty: TokenType) -> Option<Self> {
65        let token = self.drain(self.ty);
66        self.buffer.push(c);
67        self.ty = ty;
68        token
69    }
70
71    #[cfg(feature = "egui")]
72    /// Syntax highlighting
73    pub fn highlight<T: Editor>(&mut self, editor: &T, text: &str) -> LayoutJob {
74        *self = Token::default();
75        let mut job = LayoutJob::default();
76        for c in text.chars() {
77            for token in self.automata(c, editor.syntax()) {
78                editor.append(&mut job, &token);
79            }
80        }
81        editor.append(&mut job, self);
82        job
83    }
84
85    /// Lexer
86    pub fn tokens(&mut self, syntax: &Syntax, text: &str) -> Vec<Self> {
87        let mut tokens: Vec<Self> = text
88            .chars()
89            .flat_map(|c| self.automata(c, syntax))
90            .collect();
91
92        if !self.buffer.is_empty() {
93            tokens.push(mem::take(self));
94        }
95        tokens
96    }
97
98    fn automata(&mut self, c: char, syntax: &Syntax) -> Vec<Self> {
99        use TokenType as Ty;
100        let mut tokens = vec![];
101        match (self.ty, Ty::from(c)) {
102            (Ty::Comment(false), Ty::Whitespace('\n')) => {
103                self.buffer.push(c);
104                let n = self.buffer.pop();
105                tokens.extend(self.drain(Ty::Whitespace(c)));
106                if let Some(n) = n {
107                    tokens.extend(self.push_drain(n, self.ty));
108                }
109            }
110            (Ty::Comment(false), _) => {
111                self.buffer.push(c);
112            }
113            (Ty::Comment(true), _) => {
114                self.buffer.push(c);
115                if self.buffer.ends_with(syntax.comment_multiline[1]) {
116                    tokens.extend(self.drain(Ty::Unknown));
117                }
118            }
119            (Ty::Literal | Ty::Punctuation(_), Ty::Whitespace(_)) => {
120                tokens.extend(self.drain(Ty::Whitespace(c)));
121                tokens.extend(self.first(c, syntax));
122            }
123            (Ty::Hyperlink, Ty::Whitespace(_)) => {
124                tokens.extend(self.drain(Ty::Whitespace(c)));
125                tokens.extend(self.first(c, syntax));
126            }
127            (Ty::Hyperlink, _) => {
128                self.buffer.push(c);
129            }
130            (Ty::Literal, _) => match c {
131                c if c == '(' => {
132                    self.ty = Ty::Function;
133                    tokens.extend(self.drain(Ty::Punctuation(c)));
134                    tokens.extend(self.push_drain(c, Ty::Unknown));
135                }
136                c if !c.is_alphanumeric() && !SEPARATORS.contains(&c) => {
137                    tokens.extend(self.drain(self.ty));
138                    self.buffer.push(c);
139                    self.ty = if QUOTES.contains(&c) {
140                        Ty::Str(c)
141                    } else {
142                        Ty::Punctuation(c)
143                    };
144                }
145                _ => {
146                    self.buffer.push(c);
147                    self.ty = {
148                        if self.buffer.starts_with(syntax.comment) {
149                            Ty::Comment(false)
150                        } else if self.buffer.starts_with(syntax.comment_multiline[0]) {
151                            Ty::Comment(true)
152                        } else if syntax.is_hyperlink(&self.buffer) {
153                            Ty::Hyperlink
154                        } else if syntax.is_keyword(&self.buffer) {
155                            Ty::Keyword
156                        } else if syntax.is_type(&self.buffer) {
157                            Ty::Type
158                        } else if syntax.is_special(&self.buffer) {
159                            Ty::Special
160                        } else {
161                            Ty::Literal
162                        }
163                    };
164                }
165            },
166            (Ty::Numeric(false), Ty::Punctuation('.')) => {
167                self.buffer.push(c);
168                self.ty = Ty::Numeric(true);
169            }
170            (Ty::Numeric(_), Ty::Numeric(_)) => {
171                self.buffer.push(c);
172            }
173            (Ty::Numeric(_), Ty::Literal) => {
174                tokens.extend(self.drain(self.ty));
175                self.buffer.push(c);
176            }
177            (Ty::Numeric(_), _) | (Ty::Punctuation(_), Ty::Literal | Ty::Numeric(_)) => {
178                tokens.extend(self.drain(self.ty));
179                tokens.extend(self.first(c, syntax));
180            }
181            (Ty::Punctuation(_), Ty::Str(_)) => {
182                tokens.extend(self.drain_push(c, Ty::Str(c)));
183            }
184            (Ty::Punctuation(_), _) => {
185                if !(syntax.comment.starts_with(&self.buffer)
186                    || syntax.comment_multiline[0].starts_with(&self.buffer))
187                {
188                    tokens.extend(self.drain(self.ty));
189                    tokens.extend(self.first(c, syntax));
190                } else {
191                    self.buffer.push(c);
192                    if self.buffer.starts_with(syntax.comment) {
193                        self.ty = Ty::Comment(false);
194                    } else if self.buffer.starts_with(syntax.comment_multiline[0]) {
195                        self.ty = Ty::Comment(true);
196                    } else if let Some(c) = self.buffer.pop() {
197                        tokens.extend(self.drain(Ty::Punctuation(c)));
198                        tokens.extend(self.first(c, syntax));
199                    }
200                }
201            }
202            (Ty::Str(q), _) => {
203                let control = self.buffer.ends_with('\\');
204                self.buffer.push(c);
205                if c == q && !control {
206                    tokens.extend(self.drain(Ty::Unknown));
207                }
208            }
209            (Ty::Whitespace(_) | Ty::Unknown, _) => {
210                tokens.extend(self.first(c, syntax));
211            }
212            // Keyword, Type, Special
213            (_reserved, Ty::Literal | Ty::Numeric(_)) => {
214                self.buffer.push(c);
215                self.ty = if syntax.is_keyword(&self.buffer) {
216                    Ty::Keyword
217                } else if syntax.is_type(&self.buffer) {
218                    Ty::Type
219                } else if syntax.is_special(&self.buffer) {
220                    Ty::Special
221                } else {
222                    Ty::Literal
223                };
224            }
225            (reserved, _) => {
226                self.ty = reserved;
227                tokens.extend(self.drain(self.ty));
228                tokens.extend(self.first(c, syntax));
229            }
230        }
231        tokens
232    }
233}
234
235#[cfg(feature = "egui")]
236use egui::text::LayoutJob;
237
238#[cfg(feature = "egui")]
239impl<T: Editor> egui::util::cache::ComputerMut<(&T, &str), LayoutJob> for Token {
240    fn compute(&mut self, (cache, text): (&T, &str)) -> LayoutJob {
241        self.highlight(cache, text)
242    }
243}
244
245#[cfg(feature = "egui")]
246pub type HighlightCache = egui::util::cache::FrameCache<LayoutJob, Token>;
247
248#[cfg(feature = "egui")]
249pub fn highlight<T: Editor>(ctx: &egui::Context, cache: &T, text: &str) -> LayoutJob {
250    ctx.memory_mut(|mem| mem.caches.cache::<HighlightCache>().get((cache, text)))
251}