aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_syntax
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_syntax')
-rw-r--r--crates/ra_syntax/src/ast/make.rs11
-rw-r--r--crates/ra_syntax/src/ast/tokens.rs359
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
296pub fn add_newlines(amount_of_newlines: usize, t: impl AstNode) -> ast::SourceFile { 296pub 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
301pub 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
306pub fn add_pub_crate_modifier(fn_def: ast::FnDef) -> ast::FnDef {
307 ast_from_text(&format!("pub(crate) {}", fn_def))
308}
309
301fn ast_from_text<N: AstNode>(text: &str) -> N { 310fn 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)]
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 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
490impl 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
513impl 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}