diff options
Diffstat (limited to 'crates/ra_syntax/src/ast')
-rw-r--r-- | crates/ra_syntax/src/ast/make.rs | 11 | ||||
-rw-r--r-- | crates/ra_syntax/src/ast/tokens.rs | 372 |
2 files changed, 374 insertions, 9 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..3865729b8 100644 --- a/crates/ra_syntax/src/ast/tokens.rs +++ b/crates/ra_syntax/src/ast/tokens.rs | |||
@@ -1,8 +1,10 @@ | |||
1 | //! There are many AstNodes, but only a few tokens, so we hand-write them here. | 1 | //! There are many AstNodes, but only a few tokens, so we hand-write them here. |
2 | 2 | ||
3 | use std::convert::{TryFrom, TryInto}; | ||
4 | |||
3 | use crate::{ | 5 | use crate::{ |
4 | ast::{AstToken, Comment, RawString, String, Whitespace}, | 6 | ast::{AstToken, Comment, RawString, String, Whitespace}, |
5 | TextRange, TextUnit, | 7 | TextRange, TextSize, |
6 | }; | 8 | }; |
7 | 9 | ||
8 | impl Comment { | 10 | impl Comment { |
@@ -56,6 +58,9 @@ const COMMENT_PREFIX_TO_KIND: &[(&str, CommentKind)] = { | |||
56 | }; | 58 | }; |
57 | 59 | ||
58 | fn kind_by_prefix(text: &str) -> CommentKind { | 60 | fn kind_by_prefix(text: &str) -> CommentKind { |
61 | if text == "/**/" { | ||
62 | return CommentKind { shape: CommentShape::Block, doc: None }; | ||
63 | } | ||
59 | for (prefix, kind) in COMMENT_PREFIX_TO_KIND.iter() { | 64 | for (prefix, kind) in COMMENT_PREFIX_TO_KIND.iter() { |
60 | if text.starts_with(prefix) { | 65 | if text.starts_with(prefix) { |
61 | return *kind; | 66 | return *kind; |
@@ -94,14 +99,14 @@ impl QuoteOffsets { | |||
94 | return None; | 99 | return None; |
95 | } | 100 | } |
96 | 101 | ||
97 | let start = TextUnit::from(0); | 102 | let start = TextSize::from(0); |
98 | let left_quote = TextUnit::from_usize(left_quote) + TextUnit::of_char('"'); | 103 | let left_quote = TextSize::try_from(left_quote).unwrap() + TextSize::of('"'); |
99 | let right_quote = TextUnit::from_usize(right_quote); | 104 | let right_quote = TextSize::try_from(right_quote).unwrap(); |
100 | let end = TextUnit::of_str(literal); | 105 | let end = TextSize::of(literal); |
101 | 106 | ||
102 | let res = QuoteOffsets { | 107 | let res = QuoteOffsets { |
103 | quotes: [TextRange::from_to(start, left_quote), TextRange::from_to(right_quote, end)], | 108 | quotes: [TextRange::new(start, left_quote), TextRange::new(right_quote, end)], |
104 | contents: TextRange::from_to(left_quote, right_quote), | 109 | contents: TextRange::new(left_quote, right_quote), |
105 | }; | 110 | }; |
106 | Some(res) | 111 | Some(res) |
107 | } | 112 | } |
@@ -168,7 +173,358 @@ impl HasStringValue for RawString { | |||
168 | impl RawString { | 173 | impl RawString { |
169 | pub fn map_range_up(&self, range: TextRange) -> Option<TextRange> { | 174 | pub fn map_range_up(&self, range: TextRange) -> Option<TextRange> { |
170 | let contents_range = self.text_range_between_quotes()?; | 175 | let contents_range = self.text_range_between_quotes()?; |
171 | assert!(range.is_subrange(&TextRange::offset_len(0.into(), contents_range.len()))); | 176 | assert!(TextRange::up_to(contents_range.len()).contains_range(range)); |
172 | Some(range + contents_range.start()) | 177 | Some(range + contents_range.start()) |
173 | } | 178 | } |
174 | } | 179 | } |
180 | |||
181 | #[derive(Debug)] | ||
182 | pub enum FormatSpecifier { | ||
183 | Open, | ||
184 | Close, | ||
185 | Integer, | ||
186 | Identifier, | ||
187 | Colon, | ||
188 | Fill, | ||
189 | Align, | ||
190 | Sign, | ||
191 | NumberSign, | ||
192 | Zero, | ||
193 | DollarSign, | ||
194 | Dot, | ||
195 | Asterisk, | ||
196 | QuestionMark, | ||
197 | } | ||
198 | |||
199 | pub trait HasFormatSpecifier: AstToken { | ||
200 | fn char_ranges( | ||
201 | &self, | ||
202 | ) -> Option<Vec<(TextRange, Result<char, rustc_lexer::unescape::EscapeError>)>>; | ||
203 | |||
204 | fn lex_format_specifier<F>(&self, mut callback: F) | ||
205 | where | ||
206 | F: FnMut(TextRange, FormatSpecifier), | ||
207 | { | ||
208 | let char_ranges = if let Some(char_ranges) = self.char_ranges() { | ||
209 | char_ranges | ||
210 | } else { | ||
211 | return; | ||
212 | }; | ||
213 | let mut chars = char_ranges.iter().peekable(); | ||
214 | |||
215 | while let Some((range, first_char)) = chars.next() { | ||
216 | match first_char { | ||
217 | Ok('{') => { | ||
218 | // Format specifier, see syntax at https://doc.rust-lang.org/std/fmt/index.html#syntax | ||
219 | if let Some((_, Ok('{'))) = chars.peek() { | ||
220 | // Escaped format specifier, `{{` | ||
221 | chars.next(); | ||
222 | continue; | ||
223 | } | ||
224 | |||
225 | callback(*range, FormatSpecifier::Open); | ||
226 | |||
227 | // check for integer/identifier | ||
228 | match chars | ||
229 | .peek() | ||
230 | .and_then(|next| next.1.as_ref().ok()) | ||
231 | .copied() | ||
232 | .unwrap_or_default() | ||
233 | { | ||
234 | '0'..='9' => { | ||
235 | // integer | ||
236 | read_integer(&mut chars, &mut callback); | ||
237 | } | ||
238 | c if c == '_' || c.is_alphabetic() => { | ||
239 | // identifier | ||
240 | read_identifier(&mut chars, &mut callback); | ||
241 | } | ||
242 | _ => {} | ||
243 | } | ||
244 | |||
245 | if let Some((_, Ok(':'))) = chars.peek() { | ||
246 | skip_char_and_emit(&mut chars, FormatSpecifier::Colon, &mut callback); | ||
247 | |||
248 | // check for fill/align | ||
249 | let mut cloned = chars.clone().take(2); | ||
250 | let first = cloned | ||
251 | .next() | ||
252 | .and_then(|next| next.1.as_ref().ok()) | ||
253 | .copied() | ||
254 | .unwrap_or_default(); | ||
255 | let second = cloned | ||
256 | .next() | ||
257 | .and_then(|next| next.1.as_ref().ok()) | ||
258 | .copied() | ||
259 | .unwrap_or_default(); | ||
260 | match second { | ||
261 | '<' | '^' | '>' => { | ||
262 | // alignment specifier, first char specifies fillment | ||
263 | skip_char_and_emit( | ||
264 | &mut chars, | ||
265 | FormatSpecifier::Fill, | ||
266 | &mut callback, | ||
267 | ); | ||
268 | skip_char_and_emit( | ||
269 | &mut chars, | ||
270 | FormatSpecifier::Align, | ||
271 | &mut callback, | ||
272 | ); | ||
273 | } | ||
274 | _ => match first { | ||
275 | '<' | '^' | '>' => { | ||
276 | skip_char_and_emit( | ||
277 | &mut chars, | ||
278 | FormatSpecifier::Align, | ||
279 | &mut callback, | ||
280 | ); | ||
281 | } | ||
282 | _ => {} | ||
283 | }, | ||
284 | } | ||
285 | |||
286 | // check for sign | ||
287 | match chars | ||
288 | .peek() | ||
289 | .and_then(|next| next.1.as_ref().ok()) | ||
290 | .copied() | ||
291 | .unwrap_or_default() | ||
292 | { | ||
293 | '+' | '-' => { | ||
294 | skip_char_and_emit( | ||
295 | &mut chars, | ||
296 | FormatSpecifier::Sign, | ||
297 | &mut callback, | ||
298 | ); | ||
299 | } | ||
300 | _ => {} | ||
301 | } | ||
302 | |||
303 | // check for `#` | ||
304 | if let Some((_, Ok('#'))) = chars.peek() { | ||
305 | skip_char_and_emit( | ||
306 | &mut chars, | ||
307 | FormatSpecifier::NumberSign, | ||
308 | &mut callback, | ||
309 | ); | ||
310 | } | ||
311 | |||
312 | // check for `0` | ||
313 | let mut cloned = chars.clone().take(2); | ||
314 | let first = cloned.next().and_then(|next| next.1.as_ref().ok()).copied(); | ||
315 | let second = cloned.next().and_then(|next| next.1.as_ref().ok()).copied(); | ||
316 | |||
317 | if first == Some('0') && second != Some('$') { | ||
318 | skip_char_and_emit(&mut chars, FormatSpecifier::Zero, &mut callback); | ||
319 | } | ||
320 | |||
321 | // width | ||
322 | match chars | ||
323 | .peek() | ||
324 | .and_then(|next| next.1.as_ref().ok()) | ||
325 | .copied() | ||
326 | .unwrap_or_default() | ||
327 | { | ||
328 | '0'..='9' => { | ||
329 | read_integer(&mut chars, &mut callback); | ||
330 | if let Some((_, Ok('$'))) = chars.peek() { | ||
331 | skip_char_and_emit( | ||
332 | &mut chars, | ||
333 | FormatSpecifier::DollarSign, | ||
334 | &mut callback, | ||
335 | ); | ||
336 | } | ||
337 | } | ||
338 | c if c == '_' || c.is_alphabetic() => { | ||
339 | read_identifier(&mut chars, &mut callback); | ||
340 | if chars.peek().and_then(|next| next.1.as_ref().ok()).copied() | ||
341 | != Some('$') | ||
342 | { | ||
343 | continue; | ||
344 | } | ||
345 | skip_char_and_emit( | ||
346 | &mut chars, | ||
347 | FormatSpecifier::DollarSign, | ||
348 | &mut callback, | ||
349 | ); | ||
350 | } | ||
351 | _ => {} | ||
352 | } | ||
353 | |||
354 | // precision | ||
355 | if let Some((_, Ok('.'))) = chars.peek() { | ||
356 | skip_char_and_emit(&mut chars, FormatSpecifier::Dot, &mut callback); | ||
357 | |||
358 | match chars | ||
359 | .peek() | ||
360 | .and_then(|next| next.1.as_ref().ok()) | ||
361 | .copied() | ||
362 | .unwrap_or_default() | ||
363 | { | ||
364 | '*' => { | ||
365 | skip_char_and_emit( | ||
366 | &mut chars, | ||
367 | FormatSpecifier::Asterisk, | ||
368 | &mut callback, | ||
369 | ); | ||
370 | } | ||
371 | '0'..='9' => { | ||
372 | read_integer(&mut chars, &mut callback); | ||
373 | if let Some((_, Ok('$'))) = chars.peek() { | ||
374 | skip_char_and_emit( | ||
375 | &mut chars, | ||
376 | FormatSpecifier::DollarSign, | ||
377 | &mut callback, | ||
378 | ); | ||
379 | } | ||
380 | } | ||
381 | c if c == '_' || c.is_alphabetic() => { | ||
382 | read_identifier(&mut chars, &mut callback); | ||
383 | if chars.peek().and_then(|next| next.1.as_ref().ok()).copied() | ||
384 | != Some('$') | ||
385 | { | ||
386 | continue; | ||
387 | } | ||
388 | skip_char_and_emit( | ||
389 | &mut chars, | ||
390 | FormatSpecifier::DollarSign, | ||
391 | &mut callback, | ||
392 | ); | ||
393 | } | ||
394 | _ => { | ||
395 | continue; | ||
396 | } | ||
397 | } | ||
398 | } | ||
399 | |||
400 | // type | ||
401 | match chars | ||
402 | .peek() | ||
403 | .and_then(|next| next.1.as_ref().ok()) | ||
404 | .copied() | ||
405 | .unwrap_or_default() | ||
406 | { | ||
407 | '?' => { | ||
408 | skip_char_and_emit( | ||
409 | &mut chars, | ||
410 | FormatSpecifier::QuestionMark, | ||
411 | &mut callback, | ||
412 | ); | ||
413 | } | ||
414 | c if c == '_' || c.is_alphabetic() => { | ||
415 | read_identifier(&mut chars, &mut callback); | ||
416 | } | ||
417 | _ => {} | ||
418 | } | ||
419 | } | ||
420 | |||
421 | let mut cloned = chars.clone().take(2); | ||
422 | let first = cloned.next().and_then(|next| next.1.as_ref().ok()).copied(); | ||
423 | let second = cloned.next().and_then(|next| next.1.as_ref().ok()).copied(); | ||
424 | if first != Some('}') { | ||
425 | continue; | ||
426 | } | ||
427 | if second == Some('}') { | ||
428 | // Escaped format end specifier, `}}` | ||
429 | continue; | ||
430 | } | ||
431 | skip_char_and_emit(&mut chars, FormatSpecifier::Close, &mut callback); | ||
432 | } | ||
433 | _ => { | ||
434 | while let Some((_, Ok(next_char))) = chars.peek() { | ||
435 | match next_char { | ||
436 | '{' => break, | ||
437 | _ => {} | ||
438 | } | ||
439 | chars.next(); | ||
440 | } | ||
441 | } | ||
442 | }; | ||
443 | } | ||
444 | |||
445 | fn skip_char_and_emit<'a, I, F>( | ||
446 | chars: &mut std::iter::Peekable<I>, | ||
447 | emit: FormatSpecifier, | ||
448 | callback: &mut F, | ||
449 | ) where | ||
450 | I: Iterator<Item = &'a (TextRange, Result<char, rustc_lexer::unescape::EscapeError>)>, | ||
451 | F: FnMut(TextRange, FormatSpecifier), | ||
452 | { | ||
453 | let (range, _) = chars.next().unwrap(); | ||
454 | callback(*range, emit); | ||
455 | } | ||
456 | |||
457 | fn read_integer<'a, I, F>(chars: &mut std::iter::Peekable<I>, callback: &mut F) | ||
458 | where | ||
459 | I: Iterator<Item = &'a (TextRange, Result<char, rustc_lexer::unescape::EscapeError>)>, | ||
460 | F: FnMut(TextRange, FormatSpecifier), | ||
461 | { | ||
462 | let (mut range, c) = chars.next().unwrap(); | ||
463 | assert!(c.as_ref().unwrap().is_ascii_digit()); | ||
464 | while let Some((r, Ok(next_char))) = chars.peek() { | ||
465 | if next_char.is_ascii_digit() { | ||
466 | chars.next(); | ||
467 | range = range.cover(*r); | ||
468 | } else { | ||
469 | break; | ||
470 | } | ||
471 | } | ||
472 | callback(range, FormatSpecifier::Integer); | ||
473 | } | ||
474 | |||
475 | fn read_identifier<'a, I, F>(chars: &mut std::iter::Peekable<I>, callback: &mut F) | ||
476 | where | ||
477 | I: Iterator<Item = &'a (TextRange, Result<char, rustc_lexer::unescape::EscapeError>)>, | ||
478 | F: FnMut(TextRange, FormatSpecifier), | ||
479 | { | ||
480 | let (mut range, c) = chars.next().unwrap(); | ||
481 | assert!(c.as_ref().unwrap().is_alphabetic() || *c.as_ref().unwrap() == '_'); | ||
482 | while let Some((r, Ok(next_char))) = chars.peek() { | ||
483 | if *next_char == '_' || next_char.is_ascii_digit() || next_char.is_alphabetic() { | ||
484 | chars.next(); | ||
485 | range = range.cover(*r); | ||
486 | } else { | ||
487 | break; | ||
488 | } | ||
489 | } | ||
490 | callback(range, FormatSpecifier::Identifier); | ||
491 | } | ||
492 | } | ||
493 | } | ||
494 | |||
495 | impl HasFormatSpecifier for String { | ||
496 | fn char_ranges( | ||
497 | &self, | ||
498 | ) -> Option<Vec<(TextRange, Result<char, rustc_lexer::unescape::EscapeError>)>> { | ||
499 | let text = self.text().as_str(); | ||
500 | let text = &text[self.text_range_between_quotes()? - self.syntax().text_range().start()]; | ||
501 | let offset = self.text_range_between_quotes()?.start() - self.syntax().text_range().start(); | ||
502 | |||
503 | let mut res = Vec::with_capacity(text.len()); | ||
504 | rustc_lexer::unescape::unescape_str(text, &mut |range, unescaped_char| { | ||
505 | res.push(( | ||
506 | TextRange::new(range.start.try_into().unwrap(), range.end.try_into().unwrap()) | ||
507 | + offset, | ||
508 | unescaped_char, | ||
509 | )) | ||
510 | }); | ||
511 | |||
512 | Some(res) | ||
513 | } | ||
514 | } | ||
515 | |||
516 | impl HasFormatSpecifier for RawString { | ||
517 | fn char_ranges( | ||
518 | &self, | ||
519 | ) -> Option<Vec<(TextRange, Result<char, rustc_lexer::unescape::EscapeError>)>> { | ||
520 | let text = self.text().as_str(); | ||
521 | let text = &text[self.text_range_between_quotes()? - self.syntax().text_range().start()]; | ||
522 | let offset = self.text_range_between_quotes()?.start() - self.syntax().text_range().start(); | ||
523 | |||
524 | let mut res = Vec::with_capacity(text.len()); | ||
525 | for (idx, c) in text.char_indices() { | ||
526 | res.push((TextRange::at(idx.try_into().unwrap(), TextSize::of(c)) + offset, Ok(c))); | ||
527 | } | ||
528 | Some(res) | ||
529 | } | ||
530 | } | ||