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