aboutsummaryrefslogtreecommitdiff
path: root/crates/ide
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ide')
-rw-r--r--crates/ide/src/typing.rs163
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::{
22use syntax::{ 22use 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
30use text_edit::TextEdit; 30use text_edit::{Indel, TextEdit};
31 31
32use crate::SourceChange; 32use 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
68fn on_char_typed_inner(file: &SourceFile, offset: TextSize, char_typed: char) -> Option<TextEdit> { 68fn 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.
81fn on_opening_brace_typed(file: &SourceFile, offset: TextSize) -> Option<TextEdit> { 85fn 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(); 424fn f() { match () { _ => $0() } }
409 fn f() {} 425 "#,
410 ", 426 r#"
411 r" 427fn 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() { 433fn f() { $0() }
420 match x { 434 "#,
421 0 => $0(), 435 r#"
422 1 => (), 436fn f() { {()} }
423 } 437 "#,
424 }", 438 );
425 r" 439 type_char(
426 fn f() { 440 '{',
427 match x { 441 r#"
428 0 => {()}, 442fn f() { let x = $0(); }
429 1 => (), 443 "#,
430 } 444 r#"
431 }", 445fn f() { let x = {()}; }
446 "#,
447 );
448 type_char(
449 '{',
450 r#"
451fn f() { let x = $0a.b(); }
452 "#,
453 r#"
454fn f() { let x = {a.b()}; }
455 "#,
456 );
457 type_char(
458 '{',
459 r#"
460const S: () = $0();
461fn f() {}
462 "#,
463 r#"
464const S: () = {()};
465fn f() {}
466 "#,
467 );
468 type_char(
469 '{',
470 r#"
471const S: () = $0a.b();
472fn f() {}
473 "#,
474 r#"
475const S: () = {a.b()};
476fn f() {}
477 "#,
478 );
479 type_char(
480 '{',
481 r#"
482fn f() {
483 match x {
484 0 => $0(),
485 1 => (),
486 }
487}
488 "#,
489 r#"
490fn f() {
491 match x {
492 0 => {()},
493 1 => (),
494 }
495}
496 "#,
432 ); 497 );
433 } 498 }
434} 499}