aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_syntax/src
diff options
context:
space:
mode:
authorLeander Tentrup <[email protected]>2020-04-17 08:37:18 +0100
committerLeander Tentrup <[email protected]>2020-04-20 10:19:15 +0100
commitac798e1f7cfbc6d27c87bb28e3f1d5b6801796aa (patch)
tree958ccd22ae5e75047b8189f69cf5878eeb9611ea /crates/ra_syntax/src
parent29a846464b63259f5152d61a5520bffcc2cb8703 (diff)
Implement syntax highlighting for format strings
Detailed changes: 1) Implement a lexer for string literals that divides the string in format specifier `{}` including the format specifier modifier. 2) Adapt syntax highlighting to add ranges for the detected sequences. 3) Add a test case for the format string syntax highlighting.
Diffstat (limited to 'crates/ra_syntax/src')
-rw-r--r--crates/ra_syntax/src/ast/tokens.rs324
1 files changed, 324 insertions, 0 deletions
diff --git a/crates/ra_syntax/src/ast/tokens.rs b/crates/ra_syntax/src/ast/tokens.rs
index e8320b57e..ec3b4e553 100644
--- a/crates/ra_syntax/src/ast/tokens.rs
+++ b/crates/ra_syntax/src/ast/tokens.rs
@@ -172,3 +172,327 @@ impl RawString {
172 Some(range + contents_range.start()) 172 Some(range + contents_range.start())
173 } 173 }
174} 174}
175
176#[derive(Debug)]
177pub enum FormatSpecifier {
178 Open,
179 Close,
180 Integer,
181 Identifier,
182 Colon,
183 Fill,
184 Align,
185 Sign,
186 NumberSign,
187 Zero,
188 DollarSign,
189 Dot,
190 Asterisk,
191 QuestionMark,
192}
193
194pub trait HasFormatSpecifier: AstToken {
195 fn lex_format_specifier<F>(&self, callback: &mut F)
196 where
197 F: FnMut(TextRange, FormatSpecifier),
198 {
199 let src = self.text().as_str();
200 let initial_len = src.len();
201 let mut chars = src.chars();
202
203 while let Some(first_char) = chars.next() {
204 match first_char {
205 '{' => {
206 // Format specifier, see syntax at https://doc.rust-lang.org/std/fmt/index.html#syntax
207 if chars.clone().next() == Some('{') {
208 // Escaped format specifier, `{{`
209 chars.next();
210 continue;
211 }
212
213 let start = initial_len - chars.as_str().len() - first_char.len_utf8();
214 let end = initial_len - chars.as_str().len();
215 callback(
216 TextRange::from_to(TextUnit::from_usize(start), TextUnit::from_usize(end)),
217 FormatSpecifier::Open,
218 );
219
220 let next_char = if let Some(c) = chars.clone().next() {
221 c
222 } else {
223 break;
224 };
225
226 // check for integer/identifier
227 match next_char {
228 '0'..='9' => {
229 // integer
230 read_integer(&mut chars, initial_len, callback);
231 }
232 'a'..='z' | 'A'..='Z' | '_' => {
233 // identifier
234 read_identifier(&mut chars, initial_len, callback);
235 }
236 _ => {}
237 }
238
239 if chars.clone().next() == Some(':') {
240 skip_char_and_emit(
241 &mut chars,
242 initial_len,
243 FormatSpecifier::Colon,
244 callback,
245 );
246
247 // check for fill/align
248 let mut cloned = chars.clone().take(2);
249 let first = cloned.next().unwrap_or_default();
250 let second = cloned.next().unwrap_or_default();
251 match second {
252 '<' | '^' | '>' => {
253 // alignment specifier, first char specifies fillment
254 skip_char_and_emit(
255 &mut chars,
256 initial_len,
257 FormatSpecifier::Fill,
258 callback,
259 );
260 skip_char_and_emit(
261 &mut chars,
262 initial_len,
263 FormatSpecifier::Align,
264 callback,
265 );
266 }
267 _ => match first {
268 '<' | '^' | '>' => {
269 skip_char_and_emit(
270 &mut chars,
271 initial_len,
272 FormatSpecifier::Align,
273 callback,
274 );
275 }
276 _ => {}
277 },
278 }
279
280 // check for sign
281 match chars.clone().next().unwrap_or_default() {
282 '+' | '-' => {
283 skip_char_and_emit(
284 &mut chars,
285 initial_len,
286 FormatSpecifier::Sign,
287 callback,
288 );
289 }
290 _ => {}
291 }
292
293 // check for `#`
294 if let Some('#') = chars.clone().next() {
295 skip_char_and_emit(
296 &mut chars,
297 initial_len,
298 FormatSpecifier::NumberSign,
299 callback,
300 );
301 }
302
303 // check for `0`
304 let mut cloned = chars.clone().take(2);
305 let first = cloned.next();
306 let second = cloned.next();
307
308 if first == Some('0') && second != Some('$') {
309 skip_char_and_emit(
310 &mut chars,
311 initial_len,
312 FormatSpecifier::Zero,
313 callback,
314 );
315 }
316
317 // width
318 match chars.clone().next().unwrap_or_default() {
319 '0'..='9' => {
320 read_integer(&mut chars, initial_len, callback);
321 if chars.clone().next() == Some('$') {
322 skip_char_and_emit(
323 &mut chars,
324 initial_len,
325 FormatSpecifier::DollarSign,
326 callback,
327 );
328 }
329 }
330 'a'..='z' | 'A'..='Z' | '_' => {
331 read_identifier(&mut chars, initial_len, callback);
332 if chars.clone().next() != Some('$') {
333 continue;
334 }
335 skip_char_and_emit(
336 &mut chars,
337 initial_len,
338 FormatSpecifier::DollarSign,
339 callback,
340 );
341 }
342 _ => {}
343 }
344
345 // precision
346 if chars.clone().next() == Some('.') {
347 skip_char_and_emit(
348 &mut chars,
349 initial_len,
350 FormatSpecifier::Dot,
351 callback,
352 );
353
354 match chars.clone().next().unwrap_or_default() {
355 '*' => {
356 skip_char_and_emit(
357 &mut chars,
358 initial_len,
359 FormatSpecifier::Asterisk,
360 callback,
361 );
362 }
363 '0'..='9' => {
364 read_integer(&mut chars, initial_len, callback);
365 if chars.clone().next() == Some('$') {
366 skip_char_and_emit(
367 &mut chars,
368 initial_len,
369 FormatSpecifier::DollarSign,
370 callback,
371 );
372 }
373 }
374 'a'..='z' | 'A'..='Z' | '_' => {
375 read_identifier(&mut chars, initial_len, callback);
376 if chars.clone().next() != Some('$') {
377 continue;
378 }
379 skip_char_and_emit(
380 &mut chars,
381 initial_len,
382 FormatSpecifier::DollarSign,
383 callback,
384 );
385 }
386 _ => {
387 continue;
388 }
389 }
390 }
391
392 // type
393 match chars.clone().next().unwrap_or_default() {
394 '?' => {
395 skip_char_and_emit(
396 &mut chars,
397 initial_len,
398 FormatSpecifier::QuestionMark,
399 callback,
400 );
401 }
402 'a'..='z' | 'A'..='Z' | '_' => {
403 read_identifier(&mut chars, initial_len, callback);
404 }
405 _ => {}
406 }
407 }
408
409 let mut cloned = chars.clone().take(2);
410 let first = cloned.next();
411 let second = cloned.next();
412 if first != Some('}') {
413 continue;
414 }
415 if second == Some('}') {
416 // Escaped format end specifier, `}}`
417 continue;
418 }
419 skip_char_and_emit(&mut chars, initial_len, FormatSpecifier::Close, callback);
420 }
421 _ => {
422 while let Some(next_char) = chars.clone().next() {
423 match next_char {
424 '{' => break,
425 _ => {}
426 }
427 chars.next();
428 }
429 }
430 };
431 }
432
433 fn skip_char_and_emit<F>(
434 chars: &mut std::str::Chars,
435 initial_len: usize,
436 emit: FormatSpecifier,
437 callback: &mut F,
438 ) where
439 F: FnMut(TextRange, FormatSpecifier),
440 {
441 let start = initial_len - chars.as_str().len();
442 chars.next();
443 let end = initial_len - chars.as_str().len();
444 callback(
445 TextRange::from_to(TextUnit::from_usize(start), TextUnit::from_usize(end)),
446 emit,
447 );
448 }
449
450 fn read_integer<F>(chars: &mut std::str::Chars, initial_len: usize, callback: &mut F)
451 where
452 F: FnMut(TextRange, FormatSpecifier),
453 {
454 let start = initial_len - chars.as_str().len();
455 chars.next();
456 while let Some(next_char) = chars.clone().next() {
457 match next_char {
458 '0'..='9' => {
459 chars.next();
460 }
461 _ => {
462 break;
463 }
464 }
465 }
466 let end = initial_len - chars.as_str().len();
467 callback(
468 TextRange::from_to(TextUnit::from_usize(start), TextUnit::from_usize(end)),
469 FormatSpecifier::Integer,
470 );
471 }
472 fn read_identifier<F>(chars: &mut std::str::Chars, initial_len: usize, callback: &mut F)
473 where
474 F: FnMut(TextRange, FormatSpecifier),
475 {
476 let start = initial_len - chars.as_str().len();
477 chars.next();
478 while let Some(next_char) = chars.clone().next() {
479 match next_char {
480 'a'..='z' | 'A'..='Z' | '0'..='9' | '_' => {
481 chars.next();
482 }
483 _ => {
484 break;
485 }
486 }
487 }
488 let end = initial_len - chars.as_str().len();
489 callback(
490 TextRange::from_to(TextUnit::from_usize(start), TextUnit::from_usize(end)),
491 FormatSpecifier::Identifier,
492 );
493 }
494 }
495}
496
497impl HasFormatSpecifier for String {}
498impl HasFormatSpecifier for RawString {}