egui_code_editor/
highlighting.rs1#[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)]
8pub 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 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 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 (_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}