diff options
Diffstat (limited to 'crates/ra_syntax/src')
-rw-r--r-- | crates/ra_syntax/src/ast/make.rs | 11 | ||||
-rw-r--r-- | crates/ra_syntax/src/ast/tokens.rs | 359 |
2 files changed, 369 insertions, 1 deletions
diff --git a/crates/ra_syntax/src/ast/make.rs b/crates/ra_syntax/src/ast/make.rs index 0f4a50be4..ee0f5cc40 100644 --- a/crates/ra_syntax/src/ast/make.rs +++ b/crates/ra_syntax/src/ast/make.rs | |||
@@ -293,11 +293,20 @@ pub fn fn_def( | |||
293 | ast_from_text(&format!("fn {}{}{} {}", fn_name, type_params, params, body)) | 293 | ast_from_text(&format!("fn {}{}{} {}", fn_name, type_params, params, body)) |
294 | } | 294 | } |
295 | 295 | ||
296 | pub fn add_newlines(amount_of_newlines: usize, t: impl AstNode) -> ast::SourceFile { | 296 | pub fn add_leading_newlines(amount_of_newlines: usize, t: impl AstNode) -> ast::SourceFile { |
297 | let newlines = "\n".repeat(amount_of_newlines); | 297 | let newlines = "\n".repeat(amount_of_newlines); |
298 | ast_from_text(&format!("{}{}", newlines, t.syntax())) | 298 | ast_from_text(&format!("{}{}", newlines, t.syntax())) |
299 | } | 299 | } |
300 | 300 | ||
301 | pub fn add_trailing_newlines(amount_of_newlines: usize, t: impl AstNode) -> ast::SourceFile { | ||
302 | let newlines = "\n".repeat(amount_of_newlines); | ||
303 | ast_from_text(&format!("{}{}", t.syntax(), newlines)) | ||
304 | } | ||
305 | |||
306 | pub fn add_pub_crate_modifier(fn_def: ast::FnDef) -> ast::FnDef { | ||
307 | ast_from_text(&format!("pub(crate) {}", fn_def)) | ||
308 | } | ||
309 | |||
301 | fn ast_from_text<N: AstNode>(text: &str) -> N { | 310 | fn ast_from_text<N: AstNode>(text: &str) -> N { |
302 | let parse = SourceFile::parse(text); | 311 | let parse = SourceFile::parse(text); |
303 | let node = parse.tree().syntax().descendants().find_map(N::cast).unwrap(); | 312 | let node = parse.tree().syntax().descendants().find_map(N::cast).unwrap(); |
diff --git a/crates/ra_syntax/src/ast/tokens.rs b/crates/ra_syntax/src/ast/tokens.rs index e8320b57e..aa34b682d 100644 --- a/crates/ra_syntax/src/ast/tokens.rs +++ b/crates/ra_syntax/src/ast/tokens.rs | |||
@@ -172,3 +172,362 @@ impl RawString { | |||
172 | Some(range + contents_range.start()) | 172 | Some(range + contents_range.start()) |
173 | } | 173 | } |
174 | } | 174 | } |
175 | |||
176 | #[derive(Debug)] | ||
177 | pub 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 | |||
194 | pub trait HasFormatSpecifier: AstToken { | ||
195 | fn char_ranges( | ||
196 | &self, | ||
197 | ) -> Option<Vec<(TextRange, Result<char, rustc_lexer::unescape::EscapeError>)>>; | ||
198 | |||
199 | fn lex_format_specifier<F>(&self, mut callback: F) | ||
200 | where | ||
201 | F: FnMut(TextRange, FormatSpecifier), | ||
202 | { | ||
203 | let char_ranges = if let Some(char_ranges) = self.char_ranges() { | ||
204 | char_ranges | ||
205 | } else { | ||
206 | return; | ||
207 | }; | ||
208 | let mut chars = char_ranges.iter().peekable(); | ||
209 | |||
210 | while let Some((range, first_char)) = chars.next() { | ||
211 | match first_char { | ||
212 | Ok('{') => { | ||
213 | // Format specifier, see syntax at https://doc.rust-lang.org/std/fmt/index.html#syntax | ||
214 | if let Some((_, Ok('{'))) = chars.peek() { | ||
215 | // Escaped format specifier, `{{` | ||
216 | chars.next(); | ||
217 | continue; | ||
218 | } | ||
219 | |||
220 | callback(*range, FormatSpecifier::Open); | ||
221 | |||
222 | // check for integer/identifier | ||
223 | match chars | ||
224 | .peek() | ||
225 | .and_then(|next| next.1.as_ref().ok()) | ||
226 | .copied() | ||
227 | .unwrap_or_default() | ||
228 | { | ||
229 | '0'..='9' => { | ||
230 | // integer | ||
231 | read_integer(&mut chars, &mut callback); | ||
232 | } | ||
233 | c if c == '_' || c.is_alphabetic() => { | ||
234 | // identifier | ||
235 | read_identifier(&mut chars, &mut callback); | ||
236 | } | ||
237 | _ => {} | ||
238 | } | ||
239 | |||
240 | if let Some((_, Ok(':'))) = chars.peek() { | ||
241 | skip_char_and_emit(&mut chars, FormatSpecifier::Colon, &mut callback); | ||
242 | |||
243 | // check for fill/align | ||
244 | let mut cloned = chars.clone().take(2); | ||
245 | let first = cloned | ||
246 | .next() | ||
247 | .and_then(|next| next.1.as_ref().ok()) | ||
248 | .copied() | ||
249 | .unwrap_or_default(); | ||
250 | let second = cloned | ||
251 | .next() | ||
252 | .and_then(|next| next.1.as_ref().ok()) | ||
253 | .copied() | ||
254 | .unwrap_or_default(); | ||
255 | match second { | ||
256 | '<' | '^' | '>' => { | ||
257 | // alignment specifier, first char specifies fillment | ||
258 | skip_char_and_emit( | ||
259 | &mut chars, | ||
260 | FormatSpecifier::Fill, | ||
261 | &mut callback, | ||
262 | ); | ||
263 | skip_char_and_emit( | ||
264 | &mut chars, | ||
265 | FormatSpecifier::Align, | ||
266 | &mut callback, | ||
267 | ); | ||
268 | } | ||
269 | _ => match first { | ||
270 | '<' | '^' | '>' => { | ||
271 | skip_char_and_emit( | ||
272 | &mut chars, | ||
273 | FormatSpecifier::Align, | ||
274 | &mut callback, | ||
275 | ); | ||
276 | } | ||
277 | _ => {} | ||
278 | }, | ||
279 | } | ||
280 | |||
281 | // check for sign | ||
282 | match chars | ||
283 | .peek() | ||
284 | .and_then(|next| next.1.as_ref().ok()) | ||
285 | .copied() | ||
286 | .unwrap_or_default() | ||
287 | { | ||
288 | '+' | '-' => { | ||
289 | skip_char_and_emit( | ||
290 | &mut chars, | ||
291 | FormatSpecifier::Sign, | ||
292 | &mut callback, | ||
293 | ); | ||
294 | } | ||
295 | _ => {} | ||
296 | } | ||
297 | |||
298 | // check for `#` | ||
299 | if let Some((_, Ok('#'))) = chars.peek() { | ||
300 | skip_char_and_emit( | ||
301 | &mut chars, | ||
302 | FormatSpecifier::NumberSign, | ||
303 | &mut callback, | ||
304 | ); | ||
305 | } | ||
306 | |||
307 | // check for `0` | ||
308 | let mut cloned = chars.clone().take(2); | ||
309 | let first = cloned.next().and_then(|next| next.1.as_ref().ok()).copied(); | ||
310 | let second = cloned.next().and_then(|next| next.1.as_ref().ok()).copied(); | ||
311 | |||
312 | if first == Some('0') && second != Some('$') { | ||
313 | skip_char_and_emit(&mut chars, FormatSpecifier::Zero, &mut callback); | ||
314 | } | ||
315 | |||
316 | // width | ||
317 | match chars | ||
318 | .peek() | ||
319 | .and_then(|next| next.1.as_ref().ok()) | ||
320 | .copied() | ||
321 | .unwrap_or_default() | ||
322 | { | ||
323 | '0'..='9' => { | ||
324 | read_integer(&mut chars, &mut callback); | ||
325 | if let Some((_, Ok('$'))) = chars.peek() { | ||
326 | skip_char_and_emit( | ||
327 | &mut chars, | ||
328 | FormatSpecifier::DollarSign, | ||
329 | &mut callback, | ||
330 | ); | ||
331 | } | ||
332 | } | ||
333 | c if c == '_' || c.is_alphabetic() => { | ||
334 | read_identifier(&mut chars, &mut callback); | ||
335 | if chars.peek().and_then(|next| next.1.as_ref().ok()).copied() | ||
336 | != Some('$') | ||
337 | { | ||
338 | continue; | ||
339 | } | ||
340 | skip_char_and_emit( | ||
341 | &mut chars, | ||
342 | FormatSpecifier::DollarSign, | ||
343 | &mut callback, | ||
344 | ); | ||
345 | } | ||
346 | _ => {} | ||
347 | } | ||
348 | |||
349 | // precision | ||
350 | if let Some((_, Ok('.'))) = chars.peek() { | ||
351 | skip_char_and_emit(&mut chars, FormatSpecifier::Dot, &mut callback); | ||
352 | |||
353 | match chars | ||
354 | .peek() | ||
355 | .and_then(|next| next.1.as_ref().ok()) | ||
356 | .copied() | ||
357 | .unwrap_or_default() | ||
358 | { | ||
359 | '*' => { | ||
360 | skip_char_and_emit( | ||
361 | &mut chars, | ||
362 | FormatSpecifier::Asterisk, | ||
363 | &mut callback, | ||
364 | ); | ||
365 | } | ||
366 | '0'..='9' => { | ||
367 | read_integer(&mut chars, &mut callback); | ||
368 | if let Some((_, Ok('$'))) = chars.peek() { | ||
369 | skip_char_and_emit( | ||
370 | &mut chars, | ||
371 | FormatSpecifier::DollarSign, | ||
372 | &mut callback, | ||
373 | ); | ||
374 | } | ||
375 | } | ||
376 | c if c == '_' || c.is_alphabetic() => { | ||
377 | read_identifier(&mut chars, &mut callback); | ||
378 | if chars.peek().and_then(|next| next.1.as_ref().ok()).copied() | ||
379 | != Some('$') | ||
380 | { | ||
381 | continue; | ||
382 | } | ||
383 | skip_char_and_emit( | ||
384 | &mut chars, | ||
385 | FormatSpecifier::DollarSign, | ||
386 | &mut callback, | ||
387 | ); | ||
388 | } | ||
389 | _ => { | ||
390 | continue; | ||
391 | } | ||
392 | } | ||
393 | } | ||
394 | |||
395 | // type | ||
396 | match chars | ||
397 | .peek() | ||
398 | .and_then(|next| next.1.as_ref().ok()) | ||
399 | .copied() | ||
400 | .unwrap_or_default() | ||
401 | { | ||
402 | '?' => { | ||
403 | skip_char_and_emit( | ||
404 | &mut chars, | ||
405 | FormatSpecifier::QuestionMark, | ||
406 | &mut callback, | ||
407 | ); | ||
408 | } | ||
409 | c if c == '_' || c.is_alphabetic() => { | ||
410 | read_identifier(&mut chars, &mut callback); | ||
411 | } | ||
412 | _ => {} | ||
413 | } | ||
414 | } | ||
415 | |||
416 | let mut cloned = chars.clone().take(2); | ||
417 | let first = cloned.next().and_then(|next| next.1.as_ref().ok()).copied(); | ||
418 | let second = cloned.next().and_then(|next| next.1.as_ref().ok()).copied(); | ||
419 | if first != Some('}') { | ||
420 | continue; | ||
421 | } | ||
422 | if second == Some('}') { | ||
423 | // Escaped format end specifier, `}}` | ||
424 | continue; | ||
425 | } | ||
426 | skip_char_and_emit(&mut chars, FormatSpecifier::Close, &mut callback); | ||
427 | } | ||
428 | _ => { | ||
429 | while let Some((_, Ok(next_char))) = chars.peek() { | ||
430 | match next_char { | ||
431 | '{' => break, | ||
432 | _ => {} | ||
433 | } | ||
434 | chars.next(); | ||
435 | } | ||
436 | } | ||
437 | }; | ||
438 | } | ||
439 | |||
440 | fn skip_char_and_emit<'a, I, F>( | ||
441 | chars: &mut std::iter::Peekable<I>, | ||
442 | emit: FormatSpecifier, | ||
443 | callback: &mut F, | ||
444 | ) where | ||
445 | I: Iterator<Item = &'a (TextRange, Result<char, rustc_lexer::unescape::EscapeError>)>, | ||
446 | F: FnMut(TextRange, FormatSpecifier), | ||
447 | { | ||
448 | let (range, _) = chars.next().unwrap(); | ||
449 | callback(*range, emit); | ||
450 | } | ||
451 | |||
452 | fn read_integer<'a, I, F>(chars: &mut std::iter::Peekable<I>, callback: &mut F) | ||
453 | where | ||
454 | I: Iterator<Item = &'a (TextRange, Result<char, rustc_lexer::unescape::EscapeError>)>, | ||
455 | F: FnMut(TextRange, FormatSpecifier), | ||
456 | { | ||
457 | let (mut range, c) = chars.next().unwrap(); | ||
458 | assert!(c.as_ref().unwrap().is_ascii_digit()); | ||
459 | while let Some((r, Ok(next_char))) = chars.peek() { | ||
460 | if next_char.is_ascii_digit() { | ||
461 | chars.next(); | ||
462 | range = range.extend_to(r); | ||
463 | } else { | ||
464 | break; | ||
465 | } | ||
466 | } | ||
467 | callback(range, FormatSpecifier::Integer); | ||
468 | } | ||
469 | |||
470 | fn read_identifier<'a, I, F>(chars: &mut std::iter::Peekable<I>, callback: &mut F) | ||
471 | where | ||
472 | I: Iterator<Item = &'a (TextRange, Result<char, rustc_lexer::unescape::EscapeError>)>, | ||
473 | F: FnMut(TextRange, FormatSpecifier), | ||
474 | { | ||
475 | let (mut range, c) = chars.next().unwrap(); | ||
476 | assert!(c.as_ref().unwrap().is_alphabetic() || *c.as_ref().unwrap() == '_'); | ||
477 | while let Some((r, Ok(next_char))) = chars.peek() { | ||
478 | if *next_char == '_' || next_char.is_ascii_digit() || next_char.is_alphabetic() { | ||
479 | chars.next(); | ||
480 | range = range.extend_to(r); | ||
481 | } else { | ||
482 | break; | ||
483 | } | ||
484 | } | ||
485 | callback(range, FormatSpecifier::Identifier); | ||
486 | } | ||
487 | } | ||
488 | } | ||
489 | |||
490 | impl HasFormatSpecifier for String { | ||
491 | fn char_ranges( | ||
492 | &self, | ||
493 | ) -> Option<Vec<(TextRange, Result<char, rustc_lexer::unescape::EscapeError>)>> { | ||
494 | let text = self.text().as_str(); | ||
495 | let text = &text[self.text_range_between_quotes()? - self.syntax().text_range().start()]; | ||
496 | let offset = self.text_range_between_quotes()?.start() - self.syntax().text_range().start(); | ||
497 | |||
498 | let mut res = Vec::with_capacity(text.len()); | ||
499 | rustc_lexer::unescape::unescape_str(text, &mut |range, unescaped_char| { | ||
500 | res.push(( | ||
501 | TextRange::from_to( | ||
502 | TextUnit::from_usize(range.start), | ||
503 | TextUnit::from_usize(range.end), | ||
504 | ) + offset, | ||
505 | unescaped_char, | ||
506 | )) | ||
507 | }); | ||
508 | |||
509 | Some(res) | ||
510 | } | ||
511 | } | ||
512 | |||
513 | impl HasFormatSpecifier for RawString { | ||
514 | fn char_ranges( | ||
515 | &self, | ||
516 | ) -> Option<Vec<(TextRange, Result<char, rustc_lexer::unescape::EscapeError>)>> { | ||
517 | let text = self.text().as_str(); | ||
518 | let text = &text[self.text_range_between_quotes()? - self.syntax().text_range().start()]; | ||
519 | let offset = self.text_range_between_quotes()?.start() - self.syntax().text_range().start(); | ||
520 | |||
521 | let mut res = Vec::with_capacity(text.len()); | ||
522 | for (idx, c) in text.char_indices() { | ||
523 | res.push(( | ||
524 | TextRange::from_to( | ||
525 | TextUnit::from_usize(idx), | ||
526 | TextUnit::from_usize(idx + c.len_utf8()), | ||
527 | ) + offset, | ||
528 | Ok(c), | ||
529 | )); | ||
530 | } | ||
531 | Some(res) | ||
532 | } | ||
533 | } | ||