diff options
Diffstat (limited to 'crates/ide')
-rw-r--r-- | crates/ide/src/typing.rs | 163 |
1 files changed, 114 insertions, 49 deletions
diff --git a/crates/ide/src/typing.rs b/crates/ide/src/typing.rs index 391a8e867..9050853ce 100644 --- a/crates/ide/src/typing.rs +++ b/crates/ide/src/typing.rs | |||
@@ -22,12 +22,12 @@ use ide_db::{ | |||
22 | use syntax::{ | 22 | use syntax::{ |
23 | algo::find_node_at_offset, | 23 | algo::find_node_at_offset, |
24 | ast::{self, edit::IndentLevel, AstToken}, | 24 | ast::{self, edit::IndentLevel, AstToken}, |
25 | AstNode, SourceFile, | 25 | AstNode, Parse, SourceFile, |
26 | SyntaxKind::{FIELD_EXPR, METHOD_CALL_EXPR}, | 26 | SyntaxKind::{FIELD_EXPR, METHOD_CALL_EXPR}, |
27 | TextRange, TextSize, | 27 | TextRange, TextSize, |
28 | }; | 28 | }; |
29 | 29 | ||
30 | use text_edit::TextEdit; | 30 | use text_edit::{Indel, TextEdit}; |
31 | 31 | ||
32 | use crate::SourceChange; | 32 | use crate::SourceChange; |
33 | 33 | ||
@@ -59,18 +59,22 @@ pub(crate) fn on_char_typed( | |||
59 | char_typed: char, | 59 | char_typed: char, |
60 | ) -> Option<SourceChange> { | 60 | ) -> Option<SourceChange> { |
61 | assert!(TRIGGER_CHARS.contains(char_typed)); | 61 | assert!(TRIGGER_CHARS.contains(char_typed)); |
62 | let file = &db.parse(position.file_id).tree(); | 62 | let file = &db.parse(position.file_id); |
63 | assert_eq!(file.syntax().text().char_at(position.offset), Some(char_typed)); | 63 | assert_eq!(file.tree().syntax().text().char_at(position.offset), Some(char_typed)); |
64 | let edit = on_char_typed_inner(file, position.offset, char_typed)?; | 64 | let edit = on_char_typed_inner(file, position.offset, char_typed)?; |
65 | Some(SourceChange::from_text_edit(position.file_id, edit)) | 65 | Some(SourceChange::from_text_edit(position.file_id, edit)) |
66 | } | 66 | } |
67 | 67 | ||
68 | fn on_char_typed_inner(file: &SourceFile, offset: TextSize, char_typed: char) -> Option<TextEdit> { | 68 | fn on_char_typed_inner( |
69 | file: &Parse<SourceFile>, | ||
70 | offset: TextSize, | ||
71 | char_typed: char, | ||
72 | ) -> Option<TextEdit> { | ||
69 | assert!(TRIGGER_CHARS.contains(char_typed)); | 73 | assert!(TRIGGER_CHARS.contains(char_typed)); |
70 | match char_typed { | 74 | match char_typed { |
71 | '.' => on_dot_typed(file, offset), | 75 | '.' => on_dot_typed(&file.tree(), offset), |
72 | '=' => on_eq_typed(file, offset), | 76 | '=' => on_eq_typed(&file.tree(), offset), |
73 | '>' => on_arrow_typed(file, offset), | 77 | '>' => on_arrow_typed(&file.tree(), offset), |
74 | '{' => on_opening_brace_typed(file, offset), | 78 | '{' => on_opening_brace_typed(file, offset), |
75 | _ => unreachable!(), | 79 | _ => unreachable!(), |
76 | } | 80 | } |
@@ -78,23 +82,38 @@ fn on_char_typed_inner(file: &SourceFile, offset: TextSize, char_typed: char) -> | |||
78 | 82 | ||
79 | /// Inserts a closing `}` when the user types an opening `{`, wrapping an existing expression in a | 83 | /// Inserts a closing `}` when the user types an opening `{`, wrapping an existing expression in a |
80 | /// block. | 84 | /// block. |
81 | fn on_opening_brace_typed(file: &SourceFile, offset: TextSize) -> Option<TextEdit> { | 85 | fn on_opening_brace_typed(file: &Parse<SourceFile>, offset: TextSize) -> Option<TextEdit> { |
82 | stdx::always!(file.syntax().text().char_at(offset) == Some('{')); | 86 | stdx::always!(file.tree().syntax().text().char_at(offset) == Some('{')); |
83 | let brace_token = file.syntax().token_at_offset(offset).right_biased()?; | 87 | |
84 | let block = ast::BlockExpr::cast(brace_token.parent()?)?; | 88 | let brace_token = file.tree().syntax().token_at_offset(offset).right_biased()?; |
85 | 89 | ||
86 | // We expect a block expression enclosing exactly 1 preexisting expression. It can be parsed as | 90 | // Remove the `{` to get a better parse tree, and reparse |
87 | // either the trailing expr or an ExprStmt. | 91 | let file = file.reparse(&Indel::delete(brace_token.text_range())); |
88 | let offset = match block.statements().next() { | 92 | |
89 | Some(ast::Stmt::ExprStmt(it)) => { | 93 | let mut expr: ast::Expr = find_node_at_offset(file.tree().syntax(), offset)?; |
90 | // Use the expression span to place `}` before the `;` | 94 | if expr.syntax().text_range().start() != offset { |
91 | it.expr()?.syntax().text_range().end() | 95 | return None; |
96 | } | ||
97 | |||
98 | // Enclose the outermost expression starting at `offset` | ||
99 | while let Some(parent) = expr.syntax().parent() { | ||
100 | if parent.text_range().start() != expr.syntax().text_range().start() { | ||
101 | break; | ||
92 | } | 102 | } |
93 | None => block.tail_expr()?.syntax().text_range().end(), | ||
94 | _ => return None, | ||
95 | }; | ||
96 | 103 | ||
97 | Some(TextEdit::insert(offset, "}".to_string())) | 104 | match ast::Expr::cast(parent) { |
105 | Some(parent) => expr = parent, | ||
106 | None => break, | ||
107 | } | ||
108 | } | ||
109 | |||
110 | // If it's a statement in a block, we don't know how many statements should be included | ||
111 | if ast::ExprStmt::can_cast(expr.syntax().parent()?.kind()) { | ||
112 | return None; | ||
113 | } | ||
114 | |||
115 | // Insert `}` right after the expression. | ||
116 | Some(TextEdit::insert(expr.syntax().text_range().end() + TextSize::of("{"), "}".to_string())) | ||
98 | } | 117 | } |
99 | 118 | ||
100 | /// Returns an edit which should be applied after `=` was typed. Primarily, | 119 | /// Returns an edit which should be applied after `=` was typed. Primarily, |
@@ -175,7 +194,7 @@ mod tests { | |||
175 | let edit = TextEdit::insert(offset, char_typed.to_string()); | 194 | let edit = TextEdit::insert(offset, char_typed.to_string()); |
176 | edit.apply(&mut before); | 195 | edit.apply(&mut before); |
177 | let parse = SourceFile::parse(&before); | 196 | let parse = SourceFile::parse(&before); |
178 | on_char_typed_inner(&parse.tree(), offset, char_typed).map(|it| { | 197 | on_char_typed_inner(&parse, offset, char_typed).map(|it| { |
179 | it.apply(&mut before); | 198 | it.apply(&mut before); |
180 | before.to_string() | 199 | before.to_string() |
181 | }) | 200 | }) |
@@ -399,36 +418,82 @@ fn main() { | |||
399 | 418 | ||
400 | #[test] | 419 | #[test] |
401 | fn adds_closing_brace() { | 420 | fn adds_closing_brace() { |
402 | type_char('{', r"fn f() { match () { _ => $0() } }", r"fn f() { match () { _ => {()} } }"); | ||
403 | type_char('{', r"fn f() { $0(); }", r"fn f() { {()}; }"); | ||
404 | type_char('{', r"fn f() { let x = $0(); }", r"fn f() { let x = {()}; }"); | ||
405 | type_char( | 421 | type_char( |
406 | '{', | 422 | '{', |
407 | r" | 423 | r#" |
408 | const S: () = $0(); | 424 | fn f() { match () { _ => $0() } } |
409 | fn f() {} | 425 | "#, |
410 | ", | 426 | r#" |
411 | r" | 427 | fn f() { match () { _ => {()} } } |
412 | const S: () = {()}; | 428 | "#, |
413 | fn f() {} | ||
414 | ", | ||
415 | ); | 429 | ); |
416 | type_char( | 430 | type_char( |
417 | '{', | 431 | '{', |
418 | r" | 432 | r#" |
419 | fn f() { | 433 | fn f() { $0() } |
420 | match x { | 434 | "#, |
421 | 0 => $0(), | 435 | r#" |
422 | 1 => (), | 436 | fn f() { {()} } |
423 | } | 437 | "#, |
424 | }", | 438 | ); |
425 | r" | 439 | type_char( |
426 | fn f() { | 440 | '{', |
427 | match x { | 441 | r#" |
428 | 0 => {()}, | 442 | fn f() { let x = $0(); } |
429 | 1 => (), | 443 | "#, |
430 | } | 444 | r#" |
431 | }", | 445 | fn f() { let x = {()}; } |
446 | "#, | ||
447 | ); | ||
448 | type_char( | ||
449 | '{', | ||
450 | r#" | ||
451 | fn f() { let x = $0a.b(); } | ||
452 | "#, | ||
453 | r#" | ||
454 | fn f() { let x = {a.b()}; } | ||
455 | "#, | ||
456 | ); | ||
457 | type_char( | ||
458 | '{', | ||
459 | r#" | ||
460 | const S: () = $0(); | ||
461 | fn f() {} | ||
462 | "#, | ||
463 | r#" | ||
464 | const S: () = {()}; | ||
465 | fn f() {} | ||
466 | "#, | ||
467 | ); | ||
468 | type_char( | ||
469 | '{', | ||
470 | r#" | ||
471 | const S: () = $0a.b(); | ||
472 | fn f() {} | ||
473 | "#, | ||
474 | r#" | ||
475 | const S: () = {a.b()}; | ||
476 | fn f() {} | ||
477 | "#, | ||
478 | ); | ||
479 | type_char( | ||
480 | '{', | ||
481 | r#" | ||
482 | fn f() { | ||
483 | match x { | ||
484 | 0 => $0(), | ||
485 | 1 => (), | ||
486 | } | ||
487 | } | ||
488 | "#, | ||
489 | r#" | ||
490 | fn f() { | ||
491 | match x { | ||
492 | 0 => {()}, | ||
493 | 1 => (), | ||
494 | } | ||
495 | } | ||
496 | "#, | ||
432 | ); | 497 | ); |
433 | } | 498 | } |
434 | } | 499 | } |