algo::find_node_at_offset,
ast::{self, edit::IndentLevel, AstToken},
AstNode, Parse, SourceFile,
- SyntaxKind::{FIELD_EXPR, METHOD_CALL_EXPR},
+ SyntaxKind::{self, FIELD_EXPR, METHOD_CALL_EXPR},
TextRange, TextSize,
};
//
// - typing `let =` tries to smartly add `;` if `=` is followed by an existing expression
// - typing `.` in a chain method call auto-indents
+// - typing `{` in front of an expression inserts a closing `}` after the expression
//
// VS Code::
//
}
/// Inserts a closing `}` when the user types an opening `{`, wrapping an existing expression in a
-/// block.
+/// block, or a part of a `use` item.
fn on_opening_brace_typed(file: &Parse<SourceFile>, offset: TextSize) -> Option<TextEdit> {
if !stdx::always!(file.tree().syntax().text().char_at(offset) == Some('{')) {
return None;
}
let brace_token = file.tree().syntax().token_at_offset(offset).right_biased()?;
+ if brace_token.kind() != SyntaxKind::L_CURLY {
+ return None;
+ }
- // Remove the `{` to get a better parse tree, and reparse
- let file = file.reparse(&Indel::delete(brace_token.text_range()));
-
- let mut expr: ast::Expr = find_node_at_offset(file.tree().syntax(), offset)?;
- if expr.syntax().text_range().start() != offset {
+ // Remove the `{` to get a better parse tree, and reparse.
+ let range = brace_token.text_range();
+ if !stdx::always!(range.len() == TextSize::of('{')) {
return None;
}
+ let file = file.reparse(&Indel::delete(range));
- // Enclose the outermost expression starting at `offset`
- while let Some(parent) = expr.syntax().parent() {
- if parent.text_range().start() != expr.syntax().text_range().start() {
- break;
- }
+ if let Some(edit) = brace_expr(&file.tree(), offset) {
+ return Some(edit);
+ }
- match ast::Expr::cast(parent) {
- Some(parent) => expr = parent,
- None => break,
- }
+ if let Some(edit) = brace_use_path(&file.tree(), offset) {
+ return Some(edit);
}
- // If it's a statement in a block, we don't know how many statements should be included
- if ast::ExprStmt::can_cast(expr.syntax().parent()?.kind()) {
- return None;
+ return None;
+
+ fn brace_use_path(file: &SourceFile, offset: TextSize) -> Option<TextEdit> {
+ let segment: ast::PathSegment = find_node_at_offset(file.syntax(), offset)?;
+ if segment.syntax().text_range().start() != offset {
+ return None;
+ }
+
+ let tree: ast::UseTree = find_node_at_offset(file.syntax(), offset)?;
+
+ Some(TextEdit::insert(
+ tree.syntax().text_range().end() + TextSize::of("{"),
+ "}".to_string(),
+ ))
}
- // Insert `}` right after the expression.
- Some(TextEdit::insert(expr.syntax().text_range().end() + TextSize::of("{"), "}".to_string()))
+ fn brace_expr(file: &SourceFile, offset: TextSize) -> Option<TextEdit> {
+ let mut expr: ast::Expr = find_node_at_offset(file.syntax(), offset)?;
+ if expr.syntax().text_range().start() != offset {
+ return None;
+ }
+
+ // Enclose the outermost expression starting at `offset`
+ while let Some(parent) = expr.syntax().parent() {
+ if parent.text_range().start() != expr.syntax().text_range().start() {
+ break;
+ }
+
+ match ast::Expr::cast(parent) {
+ Some(parent) => expr = parent,
+ None => break,
+ }
+ }
+
+ // If it's a statement in a block, we don't know how many statements should be included
+ if ast::ExprStmt::can_cast(expr.syntax().parent()?.kind()) {
+ return None;
+ }
+
+ // Insert `}` right after the expression.
+ Some(TextEdit::insert(
+ expr.syntax().text_range().end() + TextSize::of("{"),
+ "}".to_string(),
+ ))
+ }
}
/// Returns an edit which should be applied after `=` was typed. Primarily,
assert_eq_text!(ra_fixture_after, &actual);
}
- fn type_char_noop(char_typed: char, before: &str) {
- let file_change = do_type_char(char_typed, before);
+ fn type_char_noop(char_typed: char, ra_fixture_before: &str) {
+ let file_change = do_type_char(char_typed, ra_fixture_before);
assert!(file_change.is_none())
}
// ");
type_char(
'=',
- r"
+ r#"
fn foo() {
let foo $0 1 + 1
}
-",
- r"
+"#,
+ r#"
fn foo() {
let foo = 1 + 1;
}
-",
+"#,
);
// do_check(r"
// fn foo() {
fn indents_new_chain_call() {
type_char(
'.',
- r"
- fn main() {
- xs.foo()
- $0
- }
- ",
- r"
- fn main() {
- xs.foo()
- .
- }
- ",
+ r#"
+fn main() {
+ xs.foo()
+ $0
+}
+ "#,
+ r#"
+fn main() {
+ xs.foo()
+ .
+}
+ "#,
);
type_char_noop(
'.',
- r"
- fn main() {
- xs.foo()
- $0
- }
- ",
+ r#"
+fn main() {
+ xs.foo()
+ $0
+}
+ "#,
)
}
type_char(
'.',
r"
- fn main() {
- xs.foo()
- $0;
- }
- ",
- r"
- fn main() {
- xs.foo()
- .;
- }
+fn main() {
+ xs.foo()
+ $0;
+}
",
+ r#"
+fn main() {
+ xs.foo()
+ .;
+}
+ "#,
);
type_char_noop(
'.',
- r"
- fn main() {
- xs.foo()
- $0;
- }
- ",
+ r#"
+fn main() {
+ xs.foo()
+ $0;
+}
+ "#,
)
}
fn indents_continued_chain_call() {
type_char(
'.',
- r"
- fn main() {
- xs.foo()
- .first()
- $0
- }
- ",
- r"
- fn main() {
- xs.foo()
- .first()
- .
- }
- ",
+ r#"
+fn main() {
+ xs.foo()
+ .first()
+ $0
+}
+ "#,
+ r#"
+fn main() {
+ xs.foo()
+ .first()
+ .
+}
+ "#,
);
type_char_noop(
'.',
- r"
- fn main() {
- xs.foo()
- .first()
- $0
- }
- ",
+ r#"
+fn main() {
+ xs.foo()
+ .first()
+ $0
+}
+ "#,
);
}
fn indents_middle_of_chain_call() {
type_char(
'.',
- r"
- fn source_impl() {
- let var = enum_defvariant_list().unwrap()
- $0
- .nth(92)
- .unwrap();
- }
- ",
- r"
- fn source_impl() {
- let var = enum_defvariant_list().unwrap()
- .
- .nth(92)
- .unwrap();
- }
- ",
+ r#"
+fn source_impl() {
+ let var = enum_defvariant_list().unwrap()
+ $0
+ .nth(92)
+ .unwrap();
+}
+ "#,
+ r#"
+fn source_impl() {
+ let var = enum_defvariant_list().unwrap()
+ .
+ .nth(92)
+ .unwrap();
+}
+ "#,
);
type_char_noop(
'.',
- r"
- fn source_impl() {
- let var = enum_defvariant_list().unwrap()
- $0
- .nth(92)
- .unwrap();
- }
- ",
+ r#"
+fn source_impl() {
+ let var = enum_defvariant_list().unwrap()
+ $0
+ .nth(92)
+ .unwrap();
+}
+ "#,
);
}
fn dont_indent_freestanding_dot() {
type_char_noop(
'.',
- r"
- fn main() {
- $0
- }
- ",
+ r#"
+fn main() {
+ $0
+}
+ "#,
);
type_char_noop(
'.',
- r"
- fn main() {
- $0
- }
- ",
+ r#"
+fn main() {
+$0
+}
+ "#,
);
}
#[test]
fn adds_space_after_return_type() {
- type_char('>', "fn foo() -$0{ 92 }", "fn foo() -> { 92 }")
+ type_char(
+ '>',
+ r#"
+fn foo() -$0{ 92 }
+"#,
+ r#"
+fn foo() -> { 92 }
+"#,
+ );
}
#[test]
- fn adds_closing_brace() {
+ fn adds_closing_brace_for_expr() {
type_char(
'{',
r#"
"#,
);
}
+
+ #[test]
+ fn noop_in_string_literal() {
+ // Regression test for #9351
+ type_char_noop(
+ '{',
+ r##"
+fn check_with(ra_fixture: &str, expect: Expect) {
+ let base = r#"
+enum E { T(), R$0, C }
+use self::E::X;
+const Z: E = E::C;
+mod m {}
+asdasdasdasdasdasda
+sdasdasdasdasdasda
+sdasdasdasdasd
+"#;
+ let actual = completion_list(&format!("{}\n{}", base, ra_fixture));
+ expect.assert_eq(&actual)
+}
+ "##,
+ );
+ }
+
+ #[test]
+ fn adds_closing_brace_for_use_tree() {
+ type_char(
+ '{',
+ r#"
+use some::$0Path;
+ "#,
+ r#"
+use some::{Path};
+ "#,
+ );
+ type_char(
+ '{',
+ r#"
+use some::{Path, $0Other};
+ "#,
+ r#"
+use some::{Path, {Other}};
+ "#,
+ );
+ type_char(
+ '{',
+ r#"
+use some::{$0Path, Other};
+ "#,
+ r#"
+use some::{{Path}, Other};
+ "#,
+ );
+ type_char(
+ '{',
+ r#"
+use some::path::$0to::Item;
+ "#,
+ r#"
+use some::path::{to::Item};
+ "#,
+ );
+ type_char(
+ '{',
+ r#"
+use some::$0path::to::Item;
+ "#,
+ r#"
+use some::{path::to::Item};
+ "#,
+ );
+ type_char(
+ '{',
+ r#"
+use $0some::path::to::Item;
+ "#,
+ r#"
+use {some::path::to::Item};
+ "#,
+ );
+ type_char(
+ '{',
+ r#"
+use some::path::$0to::{Item};
+ "#,
+ r#"
+use some::path::{to::{Item}};
+ "#,
+ );
+ type_char(
+ '{',
+ r#"
+use $0Thing as _;
+ "#,
+ r#"
+use {Thing as _};
+ "#,
+ );
+
+ type_char_noop(
+ '{',
+ r#"
+use some::pa$0th::to::Item;
+ "#,
+ );
+ }
}